commit 18a1b09dc904483105be0d7a83e53867274131c2 Author: 一瞥 Date: Sun Feb 19 16:13:57 2023 +0800 init repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a25cfe --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Created by .ignore support plugin (hsz.mobi) +out/ +target/ +.idea/ +*.log +*.gz +*.iml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3676686 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2020-2030 liuh(ov_001@163.com) https://www.quafer.cn + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba6f391 --- /dev/null +++ b/README.md @@ -0,0 +1,196 @@ +

logo

+

简洁优雅 · 稳定高效 | 宁静致远 · 精益求精

+

quafer Engine 基于 Spring Boot 2.X, 是 quafer Cloud 微服务架构内核核心组件库,可用于任何 Spring Boot 工程

+ +--- + +

+ Spring Authorization Server 0.4.0 + Spring Boot 2.7.8 + Spring Cloud 2021.0.5 + Spring Cloud Alibaba 2021.0.4.0 + Nacos 2.2.0 +

+

+ Version 2.7.8.0 + Java 8,11,17 + License Apache 2.0 + 码匠君 + Github star + Github fork + Gitee star + Gitee fork +

+

+ Github 仓库   |   + Gitee 仓库   |   + v3.0.0   |   + 文档 +

+ +## 说明 + +自11月24日,Spring Boot 3.0 以及 Spring Cloud 2022.0.0、Spring Cloud Tencent 等全新版本发布,整个Java 社区也步入的 Java 17 和 Spring Boot 3 的新时代。紧跟 Java 技术和 Spring 社区的发展,让更多质量更好、性能更优的新特性服务于实际的开发工作,quafer Cloud 也同步进行升级及适配,开发了全新的 3.0 版本。 + +基于 Spring Authorization Server 1.0.0、Spring Boot 3.0.1、Spring Cloud 2022.0.0、Spring Cloud Tencent 1.8.2-2022.0.0、Spring Cloud Alibaba 2022.0.0.0-RC1、Nacos 2.2.1-RC 等主流技术栈开发的 quafer Cloud v3.0.1 已经正式发布,关注请移步 [v3.0.0 分支](https://gitee.com/dromara/quafer-cloud/tree/3.0.0/) + +## 特点 + +1. 严格遵照“单一职责”原则,进行各个模块的划分和代码拆解。 +2. 严格遵循 Spring Boot 编码规则和命名规则。 +3. 大多数模块均支持 @EnableXXX注解 和 starter,让 Spring Bean 的注入顺序更加可控。 +4. 模块化设计思想,通过 Bean 注入、以及丰富的自定义 @ConditionalXXX 注解,让模块的添加和删除更加灵活。 +5. 各模块既可以综合在一起使用,也可以在其它 Spring Boot 工程中独立使用。 + +## 优点 + +很多朋友不理解这样做的好处,明明很多代码都可以放在一起,为什么要拆分出这么多包、拆这么细? + +这样做主要有以下优势: + +1. 虽然模块看似很多,但是每个模块职责单一、代码清晰,更有利于聚焦和定位问题。 +2. 通过对微服务架构的“庖丁解牛”,初学者不再需要在代码的海洋里“遨游”,通过针对性地了解各个模块,以点带面快速掌握微服务架构整体结构。 +3. 模块间的依赖极大的降低,想要替换为 `Spring Authorization Server`,影响到的代码和范围将会很小。该工程也是使用 `Spring Authorization Server` 的前序工作 +4. 每个模块均是最小化依赖第三包,规避依赖包过度依赖,特别是 starter 过多依赖,导致不可预知、难以调试、不好修改等问题。 +5. 降低微服务系统代码量,独立组件可提前编译并上传至Maven仓库,降低工程代码编译耗时,改进 CICD 效率。 + +## 工程结构 + +``` +quafer-engine +├── dependencies -- 工程Maven顶级依赖,统一控制版本和依赖 +├── documents -- 需要放置的文档位置 +├ └── readme -- README 相关素材放置目录 +├── engine-access -- 外部登录接入模块 +├ ├── access-core -- 外部登录通用代码组件 +├ ├── access-sdk-all -- 外部登录集成组件 +├ ├── access-sdk-justauth -- JustAuth登录组件 +├ ├── access-sdk-wxapp -- 微信小程序登录组件 +├ ├── access-sdk-wxmpp -- 微信公众号登录组件 +├ └── access-spring-boot-starter -- 外部登录 模块统一 Starter +├── engine-assistant -- 核心通用代码包 +├ ├── assistant-core -- 核心通用代码组件 +├ └── assistant-spring-boot-starter -- Assistant 模块统一 Starter +├── engine-cache -- 缓存模块 +├ ├── cache-core -- 缓存通用代码组件 +├ ├── cache-sdk-caffeine -- Caffeine 缓存配置相关代码组件模块 +├ ├── cache-sdk-jetcache -- JetCache 组件相关代码模块 +├ ├── cache-sdk-redis -- Caffeine 缓存配置相关代码组件模块 +├ ├── cache-sdk-redisson -- Redisson 组件相关代码模块 +├ └── cache-spring-boot-starter -- Cache 模块统一 Starter +├── engine-captcha -- 验证码模块 +├ ├── captcha-core -- 验证码共性通用代码 +├ ├── captcha-sdk-behavior -- 行为验证码组件(包括拼图滑块、文字点选) +├ ├── captcha-sdk-graphic -- 传统图形验证码组件(包括算数类型、中文类型、字母类型、GIF类型) +├ ├── captcha-sdk-hutool -- Hutool验证码组件(包括圆圈干扰、扭曲干扰、线段干扰) +├ └── captcha-spring-boot-starter -- Captcha 模块统一 Starter +├── engine-data -- 数据访问模块 +├ ├── data-core -- 数据访问共性通用代码 +├ ├── data-sdk-jpa -- JPA 及Hibernate 组件相关代码模块 +├ ├── data-sdk-mybatis-plus -- MybatisPlus 组件相关代码模块 +├ ├── data-sdk-p6spy -- P6spy 组件相关代码模块 +├ └── data-spring-boot-starter -- Data 模块统一 Starter +├── engine-event -- Spring 事件模块 +├ ├── event-core -- 事件组件共性代码模块 +├ ├── event-message-spring-boot-starter -- Kafka Message 统一 Starter +├ ├── event-pay-spring-boot-starter -- 支付事件统一 Starter +├ └── event-security-spring-boot-starter --安全事件统一 Starter +├── engine-facility -- 微服务基础设施模块 +├ ├── facility-core -- 基础设施共性通用代码 +├ ├── facility-sdk-log -- 微服务日志中心组件模块 +├ ├── facility-sdk-sentinel -- Sentinel 组件模块 +├ └── facility-spring-boot-starter -- Facility 模块统一 Starter +├── engine-message -- 消息模块 +├ ├── message-core -- 消息共性通用代码 +├ ├── message-sdk-mailing -- 站内消息、私信通用代码模块 +├ ├── message-sdk-websocket -- 基于 WebSocket 的消息代码模块 +├ └── message-spring-boot-starter -- Message 模块统一 Starter +├── engine-nosql -- Nosql 数据库接入管理模块 +├ ├── nosql-core -- nosql基础共性通用代码 +├ ├── nosql-sdk-couchdb -- Couchdb Nosql 数据库接入管理组件模块 +├ └── nosql-sdk-influxdb -- Influxdb 时序数据库接入管理组件模块 +├── engine-oauth2 -- OAuth2 认证模块 +├ ├── oauth2-core -- OAuth2 共性通用代码模块 +├ ├── oauth2-sdk-authentication -- Spring Authorization Server 认证逻辑模块 +├ ├── oauth2-sdk-authentication-server -- Spring Authorization Server 认证服务器管理基础模块 +├ ├── oauth2-sdk-authorization -- Spring Authorization Server 授权逻辑处理模块 +├ ├── oauth2-sdk-compliance -- Spring Authorization Server 应用安全合规支撑组件模块 +├ └── oauth2-sdk-data-jpa -- 基于 Spring Data JPA 封装的 Spring Authorization Server 数据访问代码模块 +├── engine-oss -- 对象存储模块 +├ ├── oss-core -- 对象存储共性通用代码 +├ ├── oss-sdk-minio -- Minio 组件模块 +├ └── oss-spring-boot-starter -- Oss 模块统一 Starter +├── engine-pay -- 支付模块 +├ ├── pay-core -- 支付共性通用代码 +├ ├── pay-sdk-alipay -- 支付宝支付组件模块 +├ ├── pay-sdk-all -- 支付方式整合组件模块 +├ ├── pay-sdk-wxpay -- 微信支付组件模块 +├ └── pay-spring-boot-starter -- Pay 模块统一 Starter +├── engine-rest -- 服务Rest接口模块 +├ ├── rest-core -- 服务Rest接口共性通用代码 +├ ├── rest-sdk-protect -- 前后端数据加密、接口幂等、防刷、Xss和SQL注入Rest API 防护组件模块 +├ └── rest-spring-boot-starter -- Rest 模块统一 Starter(包括通用CRUD代码) +├── engine-sms -- 短信接入模块 +├ ├── sms-core -- 短信共性通用代码模块 +├ ├── sms-sdk-aliyun -- 阿里云短信发送组件模块 +├ ├── sms-sdk-all -- 短信整合组件模块 +├ ├── sms-sdk-chinamobile -- 移动短信发送组件模块 +├ ├── sms-sdk-huawei -- 华为短信发送组件模块 +├ ├── sms-sdk-jd -- 京东短信发送组件模块 +├ ├── sms-sdk-netease -- 网易短信发送组件模块 +├ ├── sms-sdk-qiniu -- 七牛短信发送组件模块 +├ ├── sms-sdk-tencent -- 腾讯短信发送组件模块 +├ ├── sms-sdk-upyun -- 又拍短信发送组件模块 +├ ├── sms-sdk-yunpian -- 云片短信发送组件模块 +├ └── sms-spring-boot-starter -- SMS 模块统一 Starter +├── engine-web -- Web处理模块 +├ ├── web-core -- Web 应用共性通用代码模块组件 +├ ├── web-sdk-rest -- Web 应用基础支撑模块组件 +├ ├── web-sdk-scan -- 接口权限扫描组件模块 +└── └── web-spring-boot-starter -- Web 模块统一 Starter +``` + +## 阅读顺序 + +### 一、关联性阅读 + +部分组件存在关联和组合性,建议按照以下顺序阅读和了解代码: + +1. engine-assistant +2. engine-cache +3. engine-data +4. engine-web +5. engine-protect +6. engine-rest +7. engine-oauth2 +8. engine-facility +9. engine-event +10. engine-message + +### 二、独立性阅读 + +部分组件都是相对独立的,组件间的关联性非常弱。可分开独立阅读和了解代码: + +* engine-captcha +* engine-oss +* engine-pay +* engine-temporal +* engine-websocket + +## 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + +## 交流反馈 + +- 欢迎提交[ISSUS](https://gitee.com/dromara/quafer-cloud/issues) ,请写清楚问题的具体原因,重现步骤和环境 + +## 关联项目 + +- quafer 主工程地址:[https://gitee.com/dromara/quafer-cloud](https://gitee.com/dromara/quafer-cloud) +- quafer 单体版示例工程地址:[https://gitee.com/quafer/quafer-cloud-athena](https://gitee.com/quafer/quafer-cloud-athena) +- quafer 前端工程地址:[https://gitee.com/quafer/quafer-cloud-ui](https://gitee.com/quafer/quafer-cloud-ui) + diff --git a/dependencies/pom.xml b/dependencies/pom.xml new file mode 100644 index 0000000..8e6bc14 --- /dev/null +++ b/dependencies/pom.xml @@ -0,0 +1,1143 @@ + + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.8 + + + + com.liuhung.engine + dependencies + 2.7.8.0 + pom + + 基于 Spring Authorization Server 的 quafer Cloud Dependencies + https://git.liuhung.com/gz/quafer-cloud + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + Copyright (c) 2020-2030 liuh(码匠君) 'ov_001@163.com.com'. + + quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + See the License for the specific language governing permissions and + limitations under the License. + + + + + + + + + + + 1.8 + @ + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + ${project.version} + + 2.7.10 + 2.7.8 + 2021.0.5 + 2021.0.4.0 + 0.4.0 + + + 3.2.1 + 0.40.3 + 1.5.3.Final + 3.2.1 + 3.8.7 + 3.8.7 + 3.2.0 + + + 1.7.2 + 1.72 + 4.4 + 1.10.0 + 1.9.4 + 2.11.0 + 31.1-jre + + + 3.19.1 + 3.9.1 + 1.6.14 + 7.2 + 8.13.0 + 8.5.1 + + + + 1.2.83 + 2.0.23 + 2.7.3 + 2.2.0 + 2.14.2 + + + 5.8.11 + 4.0.0 + 4.4.8.B + 1.16.5 + 3.0.3 + + + 3.0.5 + 7.18.0 + 3.5.3.1 + 3.5.3.1 + 3.5.11 + 2.3 + + + + 4.6.3 + 2.0.23 + 3.1.681 + 1.3.3 + 7.12.1 + 1.2.7 + + 4.35.37.ALL + + 4.16.13 + 3.6.6 + 3.16.0 + + + 3.8.8.Final + + 2.10.0 + 4.10.0 + + 1.70 + 1.70 + 0.1.5 + 2.1.3 + 42.5.1 + 2.19.0 + 1.33 + 2.14.1 + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + pom + import + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring-cloud-alibaba-dependencies.version} + pom + import + + + de.codecentric + spring-boot-admin-starter-server + ${spring-boot-admin.version} + + + org.springframework.security + spring-security-oauth2-authorization-server + ${spring-security-oauth2-authorization-server.version} + + + + + + com.liuhung.engine + access-core + ${project.version} + + + com.liuhung.engine + access-sdk-justauth + ${project.version} + + + com.liuhung.engine + access-sdk-wxapp + ${project.version} + + + com.liuhung.engine + access-sdk-wxmpp + ${project.version} + + + com.liuhung.engine + access-sdk-all + ${project.version} + + + com.liuhung.engine + access-spring-boot-starter + ${project.version} + + + + + com.liuhung.engine + assistant-core + ${quafer-engine.version} + + + com.liuhung.engine + assistant-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + cache-core + ${quafer-engine.version} + + + com.liuhung.engine + cache-sdk-jetcache + ${quafer-engine.version} + + + com.liuhung.engine + cache-sdk-redis + ${quafer-engine.version} + + + com.liuhung.engine + cache-sdk-caffeine + ${quafer-engine.version} + + + com.liuhung.engine + cache-sdk-redisson + ${quafer-engine.version} + + + com.liuhung.engine + cache-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + captcha-core + ${quafer-engine.version} + + + com.liuhung.engine + captcha-sdk-behavior + ${quafer-engine.version} + + + com.liuhung.engine + captcha-sdk-graphic + ${quafer-engine.version} + + + com.liuhung.engine + captcha-sdk-hutool + ${quafer-engine.version} + + + com.liuhung.engine + captcha-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + data-core + ${quafer-engine.version} + + + com.liuhung.engine + data-sdk-jpa + ${quafer-engine.version} + + + com.liuhung.engine + data-sdk-p6spy + ${quafer-engine.version} + + + com.liuhung.engine + data-sdk-mybatis-plus + ${quafer-engine.version} + + + com.liuhung.engine + data-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + event-core + ${quafer-engine.version} + + + com.liuhung.engine + event-message-spring-boot-starter + ${quafer-engine.version} + + + com.liuhung.engine + event-pay-spring-boot-starter + ${quafer-engine.version} + + + com.liuhung.engine + event-security-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + facility-core + ${quafer-engine.version} + + + com.liuhung.engine + facility-sdk-log + ${quafer-engine.version} + + + com.liuhung.engine + facility-sdk-sentinel + ${quafer-engine.version} + + + com.liuhung.engine + facility-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + message-core + ${project.version} + + + com.liuhung.engine + message-sdk-mailing + ${project.version} + + + com.liuhung.engine + message-sdk-websocket + ${project.version} + + + com.liuhung.engine + message-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + nosql-core + ${project.version} + + + com.liuhung.engine + nosql-sdk-couchdb + ${project.version} + + + com.liuhung.engine + nosql-sdk-influxdb + ${project.version} + + + + + com.liuhung.engine + oauth2-core + ${quafer-engine.version} + + + com.liuhung.engine + oauth2-sdk-data-jpa + ${quafer-engine.version} + + + com.liuhung.engine + oauth2-sdk-authentication + ${quafer-engine.version} + + + com.liuhung.engine + oauth2-sdk-authentication-server + ${quafer-engine.version} + + + com.liuhung.engine + oauth2-sdk-authorization + ${quafer-engine.version} + + + com.liuhung.engine + oauth2-sdk-compliance + ${quafer-engine.version} + + + + + com.liuhung.engine + oss-core + ${quafer-engine.version} + + + com.liuhung.engine + oss-sdk-minio + ${quafer-engine.version} + + + com.liuhung.engine + oss-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + pay-core + ${quafer-engine.version} + + + com.liuhung.engine + pay-sdk-alipay + ${quafer-engine.version} + + + com.liuhung.engine + pay-sdk-wxpay + ${quafer-engine.version} + + + com.liuhung.engine + pay-sdk-all + ${quafer-engine.version} + + + com.liuhung.engine + pay-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + rest-core + ${quafer-engine.version} + + + com.liuhung.engine + rest-sdk-protect + ${quafer-engine.version} + + + com.liuhung.engine + rest-spring-boot-starter + ${quafer-engine.version} + + + + + com.liuhung.engine + sms-core + ${project.version} + + + com.liuhung.engine + sms-sdk-aliyun + ${project.version} + + + com.liuhung.engine + sms-sdk-chinamobile + ${project.version} + + + com.liuhung.engine + sms-sdk-huawei + ${project.version} + + + com.liuhung.engine + sms-sdk-jd + ${project.version} + + + com.liuhung.engine + sms-sdk-netease + ${project.version} + + + com.liuhung.engine + sms-sdk-qiniu + ${project.version} + + + com.liuhung.engine + sms-sdk-tencent + ${project.version} + + + com.liuhung.engine + sms-sdk-upyun + ${project.version} + + + com.liuhung.engine + sms-sdk-yunpian + ${project.version} + + + com.liuhung.engine + sms-sdk-all + ${project.version} + + + com.liuhung.engine + sms-spring-boot-starter + ${project.version} + + + + + com.liuhung.engine + web-core + ${quafer-engine.version} + + + com.liuhung.engine + web-sdk-rest + ${quafer-engine.version} + + + com.liuhung.engine + web-sdk-scan + ${quafer-engine.version} + + + com.liuhung.engine + web-spring-boot-starter + ${quafer-engine.version} + + + + + + org.apache.maven + maven-embedder + ${maven-embedder.version} + + + + org.apache.maven + maven-compat + ${maven-compat.version} + + + + org.apache.maven.shared + maven-invoker + ${maven-invoker.verison} + + + + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + + org.apache.commons + commons-text + ${commons-text.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + com.google.guava + guava + ${guava.version} + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson-bom.version} + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-bom.version} + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson-bom.version} + + + + com.fasterxml.jackson.module + jackson-module-kotlin + ${jackson-bom.version} + + + + + org.owasp.antisamy + antisamy + ${antisamy.version} + + + + + + org.redisson + redisson + ${redisson.version} + + + + org.springdoc + springdoc-openapi-common + ${springdoc.version} + + + + org.springdoc + springdoc-openapi-ui + ${springdoc.version} + + + + org.springdoc + springdoc-openapi-webflux-ui + ${springdoc.version} + + + + p6spy + p6spy + ${p6spy.version} + + + + net.logstash.logback + logstash-logback-encoder + ${logstash-logback-encoder.version} + + + + org.apache.skywalking + apm-toolkit-logback-1.x + ${skywalking.version} + + + + org.apache.skywalking + apm-toolkit-trace + ${skywalking.version} + + + + io.minio + minio + ${minio.version} + + + + + + com.alibaba.nacos + nacos-client + ${nacos.version} + + + + com.alicp.jetcache + jetcache-starter-redis-lettuce + ${jetcache.version} + + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + cn.zhxu + okhttps-jackson + ${okhttps.version} + + + + cn.zhxu + okhttps-gson + ${okhttps.version} + + + + + me.zhyd.oauth + JustAuth + ${just-auth.verison} + + + + com.github.binarywang + weixin-java-miniapp + ${weixin-java-sdk.version} + + + com.github.binarywang + weixin-java-mp + ${weixin-java-sdk.version} + + + + com.github.binarywang + weixin-java-pay + ${weixin-java-sdk.version} + + + + com.github.xiaoymin + knife4j-springdoc-ui + ${knife4j.version} + + + + + + org.camunda.bpm.springboot + camunda-bpm-spring-boot-starter-rest + ${camunda-bpm-spring-boot-starter-rest.version} + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + ${jasypt-spring-boot-starter.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus-boot-starter.version} + + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + org.mybatis + mybatis + ${mybatis.version} + + + + + + + + com.aliyun + aliyun-java-sdk-core + ${aliyun-java-sdk-core.version} + + + + com.aliyun + dysmsapi20170525 + ${dysmsapi20170525.version} + + + + + com.jdcloud.sdk + sms + ${com.jdcloud.sdk.version} + + + + com.tencentcloudapi + tencentcloud-sdk-java-sms + ${tencentcloud-sdk-java.version} + + + + com.tencentcloudapi + tencentcloud-sdk-java-common + ${tencentcloud-sdk-java.version} + + + + + + com.qiniu + qiniu-java-sdk + ${qiniu-java-sdk.version} + + + + com.yunpian.sdk + yunpian-java-sdk + ${yunpian-java-sdk.version} + + + + + + com.alipay.sdk + alipay-sdk-java + ${alipay-sdk-java.version} + + + xml-apis + xml-apis + + + + + + + + + com.aliyun + aliyun-java-sdk-green + ${aliyun-java-sdk-green.version} + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun-sdk-oss.version} + + + + com.baidu.aip + java-sdk + ${com.baidu.aip.version} + + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + + + + + + org.jboss.xnio + xnio-nio + ${xnio.version} + + + + org.jboss.xnio + xnio-api + ${xnio.version} + + + + com.squareup.okio + okio + ${okio.version} + + + + com.squareup.okhttp3 + okhttp + ${okhttp3.version} + + + + com.squareup.okhttp3 + logging-interceptor + ${okhttp3.version} + + + + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bcpkix-jdk15on.version} + + + + com.aliyun + tea-xml + ${tea-xml.version} + + + + org.dom4j + dom4j + ${dom4j.version} + + + + org.postgresql + postgresql + ${postgresql.version} + + + + + + + + org.apache.maven.archetype + archetype-packaging + ${archetype-packaging.verison} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + true + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct-processor.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot-dependencies.version} + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + UTF-8 + UTF-8 + false + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + true + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-archetype-plugin + ${maven-archetype-plugin.version} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-dependencies.version} + + + true + + + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + pl.project13.maven + git-commit-id-plugin + ${git-commit-id-plugin.version} + + + ${project.basedir}/.git + + git + + false + + true + + ${project.build.outputDirectory}/git.properties + + yyyy-MM-dd HH:mm:ss + + + + + false + false + -dirty + + + + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + + + + + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots + + + sonatype-nexus-release + Nexus Release Repository + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + \ No newline at end of file diff --git a/engine-access/access-core/pom.xml b/engine-access/access-core/pom.xml new file mode 100644 index 0000000..82ae774 --- /dev/null +++ b/engine-access/access-core/pom.xml @@ -0,0 +1,61 @@ + + + + + 4.0.0 + + + engine-access + com.liuhung.engine + 2.7.8.0 + + + 基于 Spring Authorization Server 的外部访问接入通用组件模块模块 + + access-core + 2.7.8.0 + jar + + + + com.liuhung.engine + assistant-core + + + + me.zhyd.oauth + JustAuth + + + + com.github.binarywang + weixin-java-miniapp + + + + \ No newline at end of file diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/constants/AccessConstants.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/constants/AccessConstants.java new file mode 100644 index 0000000..d33391a --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/constants/AccessConstants.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2030 liuhung + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + */ + +package com.liuhung.engine.access.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + * @description 对外接口常量 + * @author ov_00 + * @date 2023/2/19 13:52 + */ +public interface AccessConstants extends BaseConstants { + + String PROPERTY_PREFIX_ACCESS = PROPERTY_PREFIX_HERODOTUS + ".access"; + String PROPERTY_ACCESS_JUSTAUTH = PROPERTY_PREFIX_ACCESS + ".justauth"; + String ITEM_JUSTAUTH_ENABLED = PROPERTY_ACCESS_JUSTAUTH + PROPERTY_ENABLED; + String PROPERTY_ACCESS_WXAPP = PROPERTY_PREFIX_ACCESS + ".wxapp"; + String ITEM_WXAPP_ENABLED = PROPERTY_ACCESS_WXAPP + PROPERTY_ENABLED; + String PROPERTY_ACCESS_WXMPP = PROPERTY_PREFIX_ACCESS + ".wxmpp"; + String ITEM_WXMPP_ENABLED = PROPERTY_ACCESS_WXMPP + PROPERTY_ENABLED; + String CACHE_NAME_TOKEN_JUSTAUTH = CACHE_TOKEN_BASE_PREFIX + "justauth:"; +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/constants/AccessErrorCode.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/constants/AccessErrorCode.java new file mode 100644 index 0000000..80f52b1 --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/constants/AccessErrorCode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.ErrorCode; + +/** + *

Description: Access 模块错误代码

+ * + * @author : liuh + * @date : 2022/9/2 17:50 + */ +public interface AccessErrorCode extends ErrorCode { + + int ILLEGAL_ACCESS_SOURCE = ACCESS_MODULE_406_BEGIN + 1; + int ACCESS_CONFIG_ERROR = ILLEGAL_ACCESS_SOURCE + 1; +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessHandler.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessHandler.java new file mode 100644 index 0000000..417c054 --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.definition; + +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; + +/** + *

Description: 外部应用接入处理器

+ * + * @author : liuh + * @date : 2022/1/25 16:20 + */ +public interface AccessHandler { + + /** + * 外部应用接入预处理 + * 比如 微信小程序需要传入Code 和 AppId + * 比如 手机登录需要传入手机号码等 + * + * @param core 对于只需要一个参数就可以进行预处理操作的核心值。 + * @param params 核心值以外的其它参数 + * @return {@link AccessResponse} + */ + AccessResponse preProcess(String core, String... params); + + /** + * 获取接入系统中的用户信息,并转换为系统可以识别的 {@link AccessUserDetails} 类型 + * + * @param source 类别 + * @param accessPrincipal 外部系统接入所需信息 + * @return 外部系统用户信息 {@link AccessUserDetails} + */ + AccessUserDetails loadUserDetails(String source, AccessPrincipal accessPrincipal); +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessResponse.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessResponse.java new file mode 100644 index 0000000..c870a08 --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessResponse.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.definition; + +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; + +/** + *

Description: 外部接入预操作统一返回实体

+ * + * @author : liuh + * @date : 2022/1/25 16:35 + */ +public class AccessResponse { + + /** + * JustAuth 认证URL + */ + private String authorizeUrl; + /** + * 手机短信验证码是否发送成功 + */ + private Boolean success; + /** + * 微信小程序返回Session信息 + */ + private WxMaJscode2SessionResult session; + + public String getAuthorizeUrl() { + return authorizeUrl; + } + + public void setAuthorizeUrl(String authorizeUrl) { + this.authorizeUrl = authorizeUrl; + } + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public WxMaJscode2SessionResult getSession() { + return session; + } + + public void setSession(WxMaJscode2SessionResult session) { + this.session = session; + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessUserDetails.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessUserDetails.java new file mode 100644 index 0000000..3b79565 --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/definition/AccessUserDetails.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.definition; + +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import me.zhyd.oauth.enums.AuthUserGender; + +/** + *

Description: 外部程序接入返回用户信息

+ * + * @author : liuh + * @date : 2022/1/25 16:46 + */ +public class AccessUserDetails { + + /** + * JustAuth中的关键词 + * 以下内容了解后,将会使你更容易地上手JustAuth。 + *

+ * source JustAuth支持的第三方平台,比如:GITHUB、GITEE等 + * uuid 一般为第三方平台的用户ID。以下几个平台需特别注意: + * 钉钉、抖音:uuid 为用户的 unionid + * 微信公众平台登录、京东、酷家乐、美团:uuid 为用户的 openId + * 微信开放平台登录、QQ:uuid 为用户的 openId,平台支持获取unionid, unionid 在 AuthToken 中(如果支持),在登录完成后,可以通过 response.getData().getToken().getUnionId() 获取 + * Google:uuid 为用户的 sub,sub为Google的所有账户体系中用户唯一的身份标识符,详见:OpenID Connect (opens new window) + * 注:建议通过uuid + source的方式唯一确定一个用户,这样可以解决用户身份归属的问题。因为 单个用户ID 在某一平台中是唯一的,但不能保证在所有平台中都是唯一的。 + */ + @Schema(title = "用户第三方系统的唯一id", description = "在调用方集成该组件时,可以用uuid + source唯一确定一个用") + private String uuid; + + @Schema(title = "用户名") + private String userName; + + @Schema(title = "用户昵称") + private String nickName; + + @Schema(title = "用户头像") + private String avatar; + + @Schema(title = "用户网址") + private String blog; + + @Schema(title = "所在公司") + private String company; + + @Schema(title = "位置") + private String location; + + @Schema(title = "用户邮箱") + private String email; + + @Schema(title = "用户邮箱") + private String remark; + /** + * 性别 + */ + @Schema(title = "性别") + private AuthUserGender gender; + + @Schema(title = "第三方用户来源") + private String source; + + @Schema(title = "用户的授权令牌") + private String accessToken; + + @Schema(title = "第三方用户的授权令牌的有效期", description = "部分平台可能没有") + private Integer expireIn; + + @Schema(title = "刷新令牌", description = "部分平台可能没有") + private String refreshToken; + + @Schema(title = "第三方用户的刷新令牌的有效期", description = "部分平台可能没有") + private Integer refreshTokenExpireIn; + + @Schema(title = "第三方用户授予的权限", description = "部分平台可能没有") + private String scope; + + @Schema(title = "个别平台的授权信息", description = "部分平台可能没有") + private String tokenType; + + @Schema(title = "第三方用户的 ID", description = "部分平台可能没有") + private String uid; + + @Schema(title = "第三方用户的 open id", description = "部分平台可能没有") + private String openId; + + @Schema(title = "个别平台的授权信息", description = "部分平台可能没有") + private String accessCode; + + @Schema(title = "第三方用户的 union id", description = "部分平台可能没有") + private String unionId; + + @Schema(title = "小程序Appid", description = "部分平台可能没有") + private String appId; + + @Schema(title = "手机号码", description = "部分平台可能没有") + private String phoneNumber; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getBlog() { + return blog; + } + + public void setBlog(String blog) { + this.blog = blog; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public AuthUserGender getGender() { + return gender; + } + + public void setGender(AuthUserGender gender) { + this.gender = gender; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public Integer getExpireIn() { + return expireIn; + } + + public void setExpireIn(Integer expireIn) { + this.expireIn = expireIn; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public Integer getRefreshTokenExpireIn() { + return refreshTokenExpireIn; + } + + public void setRefreshTokenExpireIn(Integer refreshTokenExpireIn) { + this.refreshTokenExpireIn = refreshTokenExpireIn; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getOpenId() { + return openId; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getAccessCode() { + return accessCode; + } + + public void setAccessCode(String accessCode) { + this.accessCode = accessCode; + } + + public String getUnionId() { + return unionId; + } + + public void setUnionId(String unionId) { + this.unionId = unionId; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("uuid", uuid) + .add("userName", userName) + .add("nickName", nickName) + .add("avatar", avatar) + .add("blog", blog) + .add("company", company) + .add("location", location) + .add("email", email) + .add("remark", remark) + .add("gender", gender) + .add("source", source) + .add("accessToken", accessToken) + .add("expireIn", expireIn) + .add("refreshToken", refreshToken) + .add("refreshTokenExpireIn", refreshTokenExpireIn) + .add("scope", scope) + .add("tokenType", tokenType) + .add("uid", uid) + .add("openId", openId) + .add("accessCode", accessCode) + .add("unionId", unionId) + .add("appId", appId) + .add("phoneNumber", phoneNumber) + .toString(); + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessConfigErrorException.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessConfigErrorException.java new file mode 100644 index 0000000..cd25f75 --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessConfigErrorException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.exception; + +import com.liuhung.engine.access.core.constants.AccessErrorCode; +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: Access 配置错误

+ * @author : liuh + * @date : 2022/9/2 18:02 + */ +public class AccessConfigErrorException extends PlatformException { + + public AccessConfigErrorException() { + super(); + } + + public AccessConfigErrorException(String message) { + super(message); + } + + public AccessConfigErrorException(String message, Throwable cause) { + super(message, cause); + } + + public AccessConfigErrorException(Throwable cause) { + super(cause); + } + + protected AccessConfigErrorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(AccessErrorCode.ACCESS_CONFIG_ERROR, "Access 模块配置错误", HttpStatus.SC_PRECONDITION_FAILED); + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessHandlerNotFoundException.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessHandlerNotFoundException.java new file mode 100644 index 0000000..2f7b1ee --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessHandlerNotFoundException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.exception; + +import com.liuhung.engine.assistant.core.exception.PlatformException; + +/** + *

Description: 接入处理器未找到错误

+ * + * @author : liuh + * @date : 2022/1/26 12:03 + */ +public class AccessHandlerNotFoundException extends PlatformException { + + public AccessHandlerNotFoundException() { + super(); + } + + public AccessHandlerNotFoundException(String message) { + super(message); + } + + public AccessHandlerNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public AccessHandlerNotFoundException(Throwable cause) { + super(cause); + } + + public AccessHandlerNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessIdentityVerificationFailedException.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessIdentityVerificationFailedException.java new file mode 100644 index 0000000..276ec1a --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessIdentityVerificationFailedException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.exception; + +import com.liuhung.engine.assistant.core.exception.PlatformException; + +/** + *

Description: 接入身份认证错误

+ * + * @author : liuh + * @date : 2022/1/26 10:54 + */ +public class AccessIdentityVerificationFailedException extends PlatformException { + + public AccessIdentityVerificationFailedException() { + super(); + } + + public AccessIdentityVerificationFailedException(String message) { + super(message); + } + + public AccessIdentityVerificationFailedException(String message, Throwable cause) { + super(message, cause); + } + + public AccessIdentityVerificationFailedException(Throwable cause) { + super(cause); + } + + public AccessIdentityVerificationFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessPreProcessFailedException.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessPreProcessFailedException.java new file mode 100644 index 0000000..32908b4 --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/AccessPreProcessFailedException.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.exception; + +import com.liuhung.engine.assistant.core.exception.PlatformException; + +/** + *

Description: 接入预操作失败错误

+ * + * @author : liuh + * @date : 2022/1/26 11:10 + */ +public class AccessPreProcessFailedException extends PlatformException { + + public AccessPreProcessFailedException() { + } + + public AccessPreProcessFailedException(String message) { + super(message); + } + + public AccessPreProcessFailedException(String message, Throwable cause) { + super(message, cause); + } + + public AccessPreProcessFailedException(Throwable cause) { + super(cause); + } + + public AccessPreProcessFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/IllegalAccessArgumentException.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/IllegalAccessArgumentException.java new file mode 100644 index 0000000..8e0ca7d --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/IllegalAccessArgumentException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.exception; + +import com.liuhung.engine.assistant.core.exception.PlatformException; + +/** + *

Description: 非法的访问参数错误

+ * + * @author : liuh + * @date : 2022/1/26 12:02 + */ +public class IllegalAccessArgumentException extends PlatformException { + + public IllegalAccessArgumentException() { + super(); + } + + public IllegalAccessArgumentException(String message) { + super(message); + } + + public IllegalAccessArgumentException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalAccessArgumentException(Throwable cause) { + super(cause); + } + + public IllegalAccessArgumentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/IllegalAccessSourceException.java b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/IllegalAccessSourceException.java new file mode 100644 index 0000000..16b54db --- /dev/null +++ b/engine-access/access-core/src/main/java/com/liuhung/engine/access/core/exception/IllegalAccessSourceException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.core.exception; + +import com.liuhung.engine.access.core.constants.AccessErrorCode; +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 非法的外部访问参数类型错误

+ * + * @author : liuh + * @date : 2022/1/26 12:02 + */ +public class IllegalAccessSourceException extends PlatformException { + + public IllegalAccessSourceException() { + super(); + } + + public IllegalAccessSourceException(String message) { + super(message); + } + + public IllegalAccessSourceException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalAccessSourceException(Throwable cause) { + super(cause); + } + + public IllegalAccessSourceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(AccessErrorCode.ILLEGAL_ACCESS_SOURCE, "社交登录Source参数错误", HttpStatus.SC_PRECONDITION_FAILED); + } +} diff --git a/engine-access/access-sdk-all/pom.xml b/engine-access/access-sdk-all/pom.xml new file mode 100644 index 0000000..2d3acf7 --- /dev/null +++ b/engine-access/access-sdk-all/pom.xml @@ -0,0 +1,63 @@ + + + + + 4.0.0 + + + engine-access + com.liuhung.engine + 2.7.8.0 + + + access-sdk-all + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的外部接入统一处理逻辑业务组件模块 + + + + com.liuhung.engine + access-sdk-justauth + + + com.liuhung.engine + access-sdk-wxapp + + + com.liuhung.engine + access-sdk-wxmpp + + + com.liuhung.engine + sms-sdk-all + + + + \ No newline at end of file diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/configuration/AccessAllConfiguration.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/configuration/AccessAllConfiguration.java new file mode 100644 index 0000000..53b246c --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/configuration/AccessAllConfiguration.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.configuration; + +import com.liuhung.engine.access.business.controller.JustAuthAccessController; +import com.liuhung.engine.access.business.controller.PhoneNumberAccessController; +import com.liuhung.engine.access.business.controller.WxappAccessController; +import com.liuhung.engine.access.business.processor.AccessHandlerStrategyFactory; +import com.liuhung.engine.access.business.processor.PhoneNumberAccessHandler; +import com.liuhung.engine.access.justauth.annotation.ConditionalOnJustAuthEnabled; +import com.liuhung.engine.access.justauth.configuration.JustAuthConfiguration; +import com.liuhung.engine.access.wxapp.annotation.ConditionalOnWxappEnabled; +import com.liuhung.engine.access.wxapp.configuration.WxappConfiguration; +import com.liuhung.engine.access.wxmpp.configuration.WxmppConfiguration; +import com.liuhung.engine.assistant.core.enums.AccountType; +import com.liuhung.engine.sms.all.annotation.ConditionalOnSmsEnabled; +import com.liuhung.engine.sms.all.configuration.SmsConfiguration; +import com.liuhung.engine.sms.all.processor.SmsSendStrategyFactory; +import com.liuhung.engine.sms.all.stamp.VerificationCodeStampManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: Access 业务模块配置

+ * + * @author : liuh + * @date : 2022/1/26 14:59 + */ +@Configuration(proxyBeanMethods = false) +@Import({ + JustAuthConfiguration.class, + WxappConfiguration.class, + WxmppConfiguration.class +}) +public class AccessAllConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AccessAllConfiguration.class); + + @PostConstruct + public void init() { + log.debug("[Quafer] |- SDK [Access All] Auto Configure."); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnSmsEnabled + @Import({SmsConfiguration.class}) + static class PhoneNumberSignInConfiguration { + + @Bean(AccountType.PHONE_NUMBER_HANDLER) + @ConditionalOnBean({VerificationCodeStampManager.class, SmsSendStrategyFactory.class}) + public PhoneNumberAccessHandler phoneNumberAccessHandler(VerificationCodeStampManager verificationCodeStampManager, SmsSendStrategyFactory smsSendStrategyFactory) { + PhoneNumberAccessHandler phoneNumberAuthenticationHandler = new PhoneNumberAccessHandler(verificationCodeStampManager, smsSendStrategyFactory); + log.trace("[Quafer] |- Bean [Phone Number SignIn Handler] Auto Configure."); + return phoneNumberAuthenticationHandler; + } + } + + @Bean + @ConditionalOnMissingBean(AccessHandlerStrategyFactory.class) + public AccessHandlerStrategyFactory accessHandlerStrategyFactory() { + AccessHandlerStrategyFactory accessHandlerStrategyFactory = new AccessHandlerStrategyFactory(); + log.trace("[Quafer] |- Bean [Access Handler Strategy Factory] Auto Configure."); + return accessHandlerStrategyFactory; + } + + @Configuration(proxyBeanMethods = false) + static class ControllerConfiguration { + + @PostConstruct + public void init() { + log.debug("[Quafer] |- SDK [Access All Controller] Auto Configure."); + } + + @Bean + @ConditionalOnSmsEnabled + @ConditionalOnMissingBean + public PhoneNumberAccessController phoneNumberAccessController() { + PhoneNumberAccessController phoneNumberAuthenticationController = new PhoneNumberAccessController(); + log.trace("[Quafer] |- Bean [Phone Number Access Controller] Auto Configure."); + return phoneNumberAuthenticationController; + } + + @Bean + @ConditionalOnJustAuthEnabled + @ConditionalOnMissingBean + public JustAuthAccessController justAuthSignInController() { + JustAuthAccessController justAuthAuthenticationController = new JustAuthAccessController(); + log.trace("[Quafer] |- Bean [Just Auth Access Controller] Auto Configure."); + return justAuthAuthenticationController; + } + + @Bean + @ConditionalOnWxappEnabled + @ConditionalOnMissingBean + public WxappAccessController wxappAccessController() { + WxappAccessController wxappAccessController = new WxappAccessController(); + log.trace("[Quafer] |- Bean [Wxapp Access Controller] Auto Configure."); + return wxappAccessController; + } + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/JustAuthAccessController.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/JustAuthAccessController.java new file mode 100644 index 0000000..b63b343 --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/JustAuthAccessController.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.controller; + +import com.liuhung.engine.access.business.event.AutomaticSignInEvent; +import com.liuhung.engine.access.justauth.processor.JustAuthProcessor; +import com.liuhung.engine.assistant.core.domain.Result; +import cn.hutool.core.bean.BeanUtil; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import me.zhyd.oauth.model.AuthCallback; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +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 java.util.HashMap; +import java.util.Map; + +/** + *

Description: 社交登录第三方系统返回的Redirect Url

+ * + * @author : liuh + * @date : 2021/5/28 11:35 + */ +@RestController +@Tag(name = "社交登录Redirect Url") +public class JustAuthAccessController { + + @Autowired + private ApplicationContext applicationContext; + @Autowired + private JustAuthProcessor justAuthProcessor; + + @Operation(summary = "社交登录redirect url地址", description = "社交登录标准模式的redirect url地址,获取第三方登录返回的code") + @Parameters({ + @Parameter(name = "source", required = true, description = "社交登录的类型,具体指定是哪一个第三方系统", in = ParameterIn.PATH), + }) + @RequestMapping("/open/identity/social/{source}") + public void callback(@PathVariable("source") String source, AuthCallback callback) { + if (StringUtils.isNotBlank(source) && BeanUtil.isNotEmpty(callback)) { + Map params = ImmutableMap.of("source", source, "callback", callback); + applicationContext.publishEvent(new AutomaticSignInEvent(params)); + } + } + + @Operation(summary = "获取社交登录列表", description = "根据后台已配置社交登录信息,返回可用的社交登录控制列表") + @GetMapping("/open/identity/sources") + public Result> list() { + Map list = justAuthProcessor.getAuthorizeUrls(); + if (MapUtils.isNotEmpty(list)) { + return Result.success("获取社交登录列表成功", list); + } else { + return Result.success("社交登录没有配置", new HashMap<>()); + } + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/PhoneNumberAccessController.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/PhoneNumberAccessController.java new file mode 100644 index 0000000..4d1e68e --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/PhoneNumberAccessController.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.controller; + +import com.liuhung.engine.access.business.processor.AccessHandlerStrategyFactory; +import com.liuhung.engine.access.core.definition.AccessResponse; +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.enums.AccountType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + *

Description: 手机验证码登录

+ * + * @author : liuh + * @date : 2021/5/28 11:39 + */ +@RestController +@Tag(name = "手机验证码登录接口") +public class PhoneNumberAccessController { + + @Autowired + private AccessHandlerStrategyFactory accessHandlerStrategyFactory; + + @Operation(summary = "手机验证码发送地址", description = "接收手机号码,发送验证码,并缓存至Redis") + @Parameters({ + @Parameter(name = "mobile", required = true, description = "手机号码"), + }) + @PostMapping("/open/identity/verification-code") + public Result sendCode(@RequestParam("mobile") String mobile) { + AccessResponse response = accessHandlerStrategyFactory.preProcess(AccountType.SMS, mobile); + if (ObjectUtils.isNotEmpty(response)) { + if (response.getSuccess()) { + return Result.success("短信发送成功!"); + } else { + return Result.failure("短信发送失败!"); + } + } + return Result.failure("手机号码接收失败!"); + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/WxappAccessController.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/WxappAccessController.java new file mode 100644 index 0000000..7fa173e --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/controller/WxappAccessController.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.controller; + +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import com.liuhung.engine.access.business.dto.WxappProfile; +import com.liuhung.engine.access.business.processor.AccessHandlerStrategyFactory; +import com.liuhung.engine.access.core.definition.AccessResponse; +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.enums.AccountType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + *

Description: 微信小程序平台认证

+ * + * @author : liuh + * @date : 2021/5/28 11:40 + */ +@RestController +@Tag(name = "微信小程序平台认证接口") +public class WxappAccessController { + + @Autowired + private AccessHandlerStrategyFactory accessHandlerStrategyFactory; + + @Operation(summary = "微信小程序登录", description = "利用wx.login获取code,进行小程序登录") + @Parameters({ + @Parameter(name = "socialDetails", required = true, description = "社交登录自定义参数实体"), + }) + @PostMapping("/open/identity/wxapp") + public Result login(@Validated @RequestBody WxappProfile wxappProfile) { + AccessResponse response = accessHandlerStrategyFactory.preProcess(AccountType.WXAPP, wxappProfile.getCode(), wxappProfile.getAppId()); + if (ObjectUtils.isNotEmpty(response)) { + return Result.success("微信小程序登录成功", response.getSession()); + } else { + return Result.failure("微信小程序登录失败"); + } + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/dto/WxappProfile.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/dto/WxappProfile.java new file mode 100644 index 0000000..b27d381 --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/dto/WxappProfile.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.dto; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractDto; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + *

Description: 微信小程序登录请求实体

+ * + * @author : liuh + * @date : 2022/1/26 14:44 + */ +@Schema(name = "微信小程序登录请求实体", title = "根据code和appid返回微信小程序session信息") +public class WxappProfile extends AbstractDto { + + @Schema(name = "code", title = "前端调用小程序自己的方法返回的code") + @NotBlank(message = "微信小程序code参数不能为空") + private String code; + + @Schema(name = "appId", title = "需要前端返回给后端appId,以支持多个小程序") + @NotBlank(message = "微信小程序appId参数不能为空") + private String appId; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/event/AutomaticSignInEvent.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/event/AutomaticSignInEvent.java new file mode 100644 index 0000000..0807511 --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/event/AutomaticSignInEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.event; + +import org.springframework.context.ApplicationEvent; + +import java.util.Map; + +/** + *

Description: 自动登录事件

+ *

+ * JustAuth 接收到 Callback以后,统一走系统 /oauth/token 接口获取 Token + * + * @author : liuh + * @date : 2022/1/26 14:35 + */ +public class AutomaticSignInEvent extends ApplicationEvent { + + private final Map callbackParams; + + public AutomaticSignInEvent(Map callbackParams) { + super(callbackParams); + this.callbackParams = callbackParams; + } + + public Map getCallbackParams() { + return callbackParams; + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/processor/AccessHandlerStrategyFactory.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/processor/AccessHandlerStrategyFactory.java new file mode 100644 index 0000000..4d3d9c6 --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/processor/AccessHandlerStrategyFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.processor; + +import com.liuhung.engine.access.core.definition.AccessHandler; +import com.liuhung.engine.access.core.definition.AccessResponse; +import com.liuhung.engine.access.core.definition.AccessUserDetails; +import com.liuhung.engine.access.core.exception.AccessHandlerNotFoundException; +import com.liuhung.engine.access.core.exception.IllegalAccessArgumentException; +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.assistant.core.enums.AccountType; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

Description: Access Handler 工厂

+ *

+ * 通过该工厂模式,对接入的常规操作进行封装。避免导入引用各个组件,导致耦合性增大 + *

+ * 本处使用基于Spring Boot 的工厂模式 + * {@see :https://www.pianshen.com/article/466978086/} + * + * @author : liuh + * @date : 2021/4/4 17:40 + */ +public class AccessHandlerStrategyFactory { + + private static final Logger log = LoggerFactory.getLogger(AccessHandlerStrategyFactory.class); + + @Autowired + private final Map handlers = new ConcurrentHashMap<>(); + + public AccessResponse preProcess(String source, String core, String... params) { + AccessHandler socialAuthenticationHandler = this.getAccessHandler(source); + return socialAuthenticationHandler.preProcess(core, params); + } + + public AccessResponse preProcess(AccountType accountType, String core, String... params) { + AccessHandler socialAuthenticationHandler = this.getAccessHandler(accountType); + return socialAuthenticationHandler.preProcess(core, params); + } + + public AccessUserDetails findAccessUserDetails(String source, AccessPrincipal accessPrincipal) { + AccessHandler socialAuthenticationHandler = this.getAccessHandler(source); + AccessUserDetails accessUserDetails = socialAuthenticationHandler.loadUserDetails(source, accessPrincipal); + + log.debug("[Quafer] |- AccessHandlerFactory findAccessUserDetails."); + return accessUserDetails; + } + + public AccessHandler getAccessHandler(String source) { + if (ObjectUtils.isEmpty(source)) { + throw new IllegalAccessArgumentException("Cannot found SocialProvider"); + } + + AccountType accountType = AccountType.getAccountType(source); + if (ObjectUtils.isEmpty(accountType)) { + throw new IllegalAccessArgumentException("Cannot parse the source parameter."); + } + + return getAccessHandler(accountType); + } + + public AccessHandler getAccessHandler(AccountType accountType) { + String handlerName = accountType.getHandler(); + AccessHandler socialAuthenticationHandler = handlers.get(handlerName); + if (ObjectUtils.isNotEmpty(socialAuthenticationHandler)) { + return socialAuthenticationHandler; + } else { + throw new AccessHandlerNotFoundException("Can not found Social Handler for " + handlerName); + } + } +} diff --git a/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/processor/PhoneNumberAccessHandler.java b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/processor/PhoneNumberAccessHandler.java new file mode 100644 index 0000000..3142bb0 --- /dev/null +++ b/engine-access/access-sdk-all/src/main/java/com/liuhung/engine/access/business/processor/PhoneNumberAccessHandler.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.business.processor; + +import com.liuhung.engine.access.core.definition.AccessHandler; +import com.liuhung.engine.access.core.definition.AccessResponse; +import com.liuhung.engine.access.core.definition.AccessUserDetails; +import com.liuhung.engine.access.core.exception.AccessIdentityVerificationFailedException; +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.sms.all.processor.SmsSendStrategyFactory; +import com.liuhung.engine.sms.all.stamp.VerificationCodeStampManager; +import com.liuhung.engine.sms.core.domain.Template; +import com.google.common.collect.ImmutableMap; + +/** + *

Description: 手机短信接入处理器

+ * + * @author : liuh + * @date : 2022/1/26 11:46 + */ +public class PhoneNumberAccessHandler implements AccessHandler { + + private final VerificationCodeStampManager verificationCodeStampManager; + private final SmsSendStrategyFactory smsSendStrategyFactory; + + public PhoneNumberAccessHandler(VerificationCodeStampManager verificationCodeStampManager, SmsSendStrategyFactory smsSendStrategyFactory) { + this.verificationCodeStampManager = verificationCodeStampManager; + this.smsSendStrategyFactory = smsSendStrategyFactory; + } + + @Override + public AccessResponse preProcess(String core, String... params) { + String code = verificationCodeStampManager.create(core); + boolean result; + if (verificationCodeStampManager.getSandbox()) { + result = true; + } else { + Template template = new Template(); + template.setType(verificationCodeStampManager.getVerificationCodeTemplateId()); + template.setParams(ImmutableMap.of(BaseConstants.CODE, code)); + result = smsSendStrategyFactory.send(template, core); + } + + AccessResponse accessResponse = new AccessResponse(); + accessResponse.setSuccess(result); + return accessResponse; + } + + @Override + public AccessUserDetails loadUserDetails(String source, AccessPrincipal accessPrincipal) { + boolean isCodeOk = verificationCodeStampManager.check(accessPrincipal.getMobile(), accessPrincipal.getCode()); + if (isCodeOk) { + AccessUserDetails accessUserDetails = new AccessUserDetails(); + accessUserDetails.setUuid(accessPrincipal.getMobile()); + accessUserDetails.setPhoneNumber(accessPrincipal.getMobile()); + accessUserDetails.setUserName(accessPrincipal.getMobile()); + accessUserDetails.setSource(source); + + verificationCodeStampManager.delete(accessPrincipal.getMobile()); + return accessUserDetails; + } + + throw new AccessIdentityVerificationFailedException("Phone Verification Code Error!"); + } +} diff --git a/engine-access/access-sdk-justauth/pom.xml b/engine-access/access-sdk-justauth/pom.xml new file mode 100644 index 0000000..9ffb8ad --- /dev/null +++ b/engine-access/access-sdk-justauth/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + + engine-access + com.liuhung.engine + 2.7.8.0 + + + access-sdk-justauth + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 JustAuth 外部接入组件模块 + + + + com.liuhung.engine + access-core + + + + com.liuhung.engine + cache-sdk-jetcache + + + + \ No newline at end of file diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/annotation/ConditionalOnJustAuthEnabled.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/annotation/ConditionalOnJustAuthEnabled.java new file mode 100644 index 0000000..b27acaf --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/annotation/ConditionalOnJustAuthEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.annotation; + +import com.liuhung.engine.access.justauth.condition.JustAuthEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: JustAuth开启条件注解

+ * + * @author : liuh + * @date : 2022/1/24 14:40 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(JustAuthEnabledCondition.class) +public @interface ConditionalOnJustAuthEnabled { +} diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/condition/JustAuthEnabledCondition.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/condition/JustAuthEnabledCondition.java new file mode 100644 index 0000000..4f3fff0 --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/condition/JustAuthEnabledCondition.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.condition; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: JusAuth注入条件

+ * + * @author : liuh + * @date : 2021/5/27 22:08 + */ +public class JustAuthEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(JustAuthEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + boolean result = PropertyResolver.getBoolean(conditionContext, AccessConstants.ITEM_JUSTAUTH_ENABLED, false); + log.debug("[Quafer] |- Condition [JustAuth Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/configuration/JustAuthConfiguration.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/configuration/JustAuthConfiguration.java new file mode 100644 index 0000000..e258e31 --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/configuration/JustAuthConfiguration.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.configuration; + +import com.liuhung.engine.access.justauth.annotation.ConditionalOnJustAuthEnabled; +import com.liuhung.engine.access.justauth.processor.JustAuthAccessHandler; +import com.liuhung.engine.access.justauth.processor.JustAuthProcessor; +import com.liuhung.engine.access.justauth.properties.JustAuthProperties; +import com.liuhung.engine.access.justauth.stamp.JustAuthStateStampManager; +import com.liuhung.engine.assistant.core.enums.AccountType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: JustAuth配置

+ *

+ * 仅在存在quafer.platform.social.justauth.configs配置的情况下才注入 + * + * @author : liuh + * @date : 2021/5/22 11:25 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnJustAuthEnabled +@EnableConfigurationProperties(JustAuthProperties.class) +public class JustAuthConfiguration { + + private static final Logger log = LoggerFactory.getLogger(JustAuthConfiguration.class); + + @PostConstruct + public void init() { + log.debug("[Quafer] |- SDK [Access Just Auth] Auto Configure."); + } + + @Bean + @ConditionalOnMissingBean + public JustAuthStateStampManager justAuthStateStampManager(JustAuthProperties justAuthProperties) { + JustAuthStateStampManager justAuthStateStampManager = new JustAuthStateStampManager(); + justAuthStateStampManager.setJustAuthProperties(justAuthProperties); + log.trace("[Quafer] |- Bean [Just Auth State Redis Cache] Auto Configure."); + return justAuthStateStampManager; + } + + @Bean + @ConditionalOnBean(JustAuthStateStampManager.class) + @ConditionalOnMissingBean + public JustAuthProcessor justAuthProcessor(JustAuthStateStampManager justAuthStateStampManager, JustAuthProperties justAuthProperties) { + JustAuthProcessor justAuthProcessor = new JustAuthProcessor(); + justAuthProcessor.setJustAuthStateRedisCache(justAuthStateStampManager); + justAuthProcessor.setJustAuthProperties(justAuthProperties); + log.trace("[Quafer] |- Bean [Just Auth Request Generator] Auto Configure."); + return justAuthProcessor; + } + + @Bean(AccountType.JUST_AUTH_HANDLER) + @ConditionalOnBean(JustAuthProcessor.class) + @ConditionalOnMissingBean + public JustAuthAccessHandler justAuthAccessHandler(JustAuthProcessor justAuthProcessor) { + JustAuthAccessHandler justAuthAccessHandler = new JustAuthAccessHandler(justAuthProcessor); + log.debug("[Quafer] |- Bean [Just Auth Access Handler] Auto Configure."); + return justAuthAccessHandler; + } +} diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/processor/JustAuthAccessHandler.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/processor/JustAuthAccessHandler.java new file mode 100644 index 0000000..54f1192 --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/processor/JustAuthAccessHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.processor; + +import com.liuhung.engine.access.core.definition.AccessHandler; +import com.liuhung.engine.access.core.definition.AccessResponse; +import com.liuhung.engine.access.core.definition.AccessUserDetails; +import com.liuhung.engine.access.core.exception.AccessIdentityVerificationFailedException; +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import org.apache.commons.lang3.ObjectUtils; + +/** + *

Description: JustAuth 接入处理器

+ * + * @author : liuh + * @date : 2022/1/25 17:45 + */ +public class JustAuthAccessHandler implements AccessHandler { + + private final JustAuthProcessor justAuthProcessor; + + public JustAuthAccessHandler(JustAuthProcessor justAuthProcessor) { + this.justAuthProcessor = justAuthProcessor; + } + + @Override + public AccessResponse preProcess(String core, String... params) { + String url = justAuthProcessor.getAuthorizeUrl(core); + + AccessResponse accessResponse = new AccessResponse(); + accessResponse.setAuthorizeUrl(url); + return accessResponse; + } + + @Override + public AccessUserDetails loadUserDetails(String source, AccessPrincipal accessPrincipal) { + AuthRequest authRequest = justAuthProcessor.getAuthRequest(source); + + AuthCallback authCallback = AuthCallback.builder() + .code(accessPrincipal.getCode()) + .auth_code(accessPrincipal.getAuth_code()) + .state(accessPrincipal.getState()) + .authorization_code(accessPrincipal.getAuthorization_code()) + .oauth_token(accessPrincipal.getOauth_token()) + .oauth_verifier(accessPrincipal.getOauth_verifier()) + .build(); + + AuthResponse response = authRequest.login(authCallback); + if (response.ok()) { + AuthUser authUser = response.getData(); + return convertAuthUserToAccessUserDetails(authUser); + } + + throw new AccessIdentityVerificationFailedException(response.getMsg()); + } + + private AccessUserDetails convertAuthUserToAccessUserDetails(AuthUser authUser) { + AccessUserDetails sysSocialUser = new AccessUserDetails(); + sysSocialUser.setUuid(authUser.getUuid()); + sysSocialUser.setUserName(authUser.getUsername()); + sysSocialUser.setNickName(authUser.getNickname()); + sysSocialUser.setAvatar(authUser.getAvatar()); + sysSocialUser.setBlog(authUser.getBlog()); + sysSocialUser.setCompany(authUser.getCompany()); + sysSocialUser.setLocation(authUser.getLocation()); + sysSocialUser.setEmail(authUser.getEmail()); + sysSocialUser.setRemark(authUser.getRemark()); + sysSocialUser.setGender(authUser.getGender()); + sysSocialUser.setSource(authUser.getSource()); + AuthToken authToken = authUser.getToken(); + if (ObjectUtils.isNotEmpty(authToken)) { + setAccessUserInfo(sysSocialUser, authToken.getAccessToken(), authToken.getExpireIn(), authToken.getRefreshToken(), authToken.getRefreshTokenExpireIn(), authToken.getScope(), authToken.getTokenType(), authToken.getUid(), authToken.getOpenId(), authToken.getAccessCode(), authToken.getUnionId()); + } + + return sysSocialUser; + } + + private void setAccessUserInfo(AccessUserDetails accessUserDetails, String accessToken, Integer expireIn, String refreshToken, Integer refreshTokenExpireIn, String scope, String tokenType, String uid, String openId, String accessCode, String unionId) { + accessUserDetails.setAccessToken(accessToken); + accessUserDetails.setExpireIn(expireIn); + accessUserDetails.setRefreshToken(refreshToken); + accessUserDetails.setRefreshTokenExpireIn(refreshTokenExpireIn); + accessUserDetails.setScope(scope); + accessUserDetails.setTokenType(tokenType); + accessUserDetails.setUid(uid); + accessUserDetails.setOpenId(openId); + accessUserDetails.setAccessCode(accessCode); + accessUserDetails.setUnionId(unionId); + } +} diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/processor/JustAuthProcessor.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/processor/JustAuthProcessor.java new file mode 100644 index 0000000..dfd22b4 --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/processor/JustAuthProcessor.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.processor; + +import com.liuhung.engine.access.core.exception.AccessConfigErrorException; +import com.liuhung.engine.access.core.exception.IllegalAccessSourceException; +import com.liuhung.engine.access.justauth.properties.JustAuthProperties; +import com.liuhung.engine.access.justauth.stamp.JustAuthStateStampManager; +import cn.hutool.core.util.EnumUtil; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.request.*; +import me.zhyd.oauth.utils.AuthStateUtils; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

Description: JustAuth请求的生成器

+ * + * @author : liuh + * @date : 2021/5/22 11:23 + */ +public class JustAuthProcessor { + + private JustAuthProperties justAuthProperties; + private JustAuthStateStampManager justAuthStateStampManager; + + public void setJustAuthProperties(JustAuthProperties justAuthProperties) { + this.justAuthProperties = justAuthProperties; + } + + public void setJustAuthStateRedisCache(JustAuthStateStampManager justAuthStateStampManager) { + this.justAuthStateStampManager = justAuthStateStampManager; + } + + private JustAuthStateStampManager getJustAuthStateRedisCache() { + return justAuthStateStampManager; + } + + public AuthRequest getAuthRequest(String source) { + AuthDefaultSource authDefaultSource = parseAuthDefaultSource(source); + AuthConfig authConfig = getAuthConfig(authDefaultSource); + return getAuthRequest(authDefaultSource, authConfig); + } + + public AuthRequest getAuthRequest(String source, AuthConfig authConfig) { + AuthDefaultSource authDefaultSource = parseAuthDefaultSource(source); + return getAuthRequest(authDefaultSource, authConfig); + } + + /** + * 返回带state参数的授权url,授权回调时会带上这个state + * + * @param source 第三方登录的类别 {@link AuthDefaultSource} + * @return 返回授权地址 + */ + public String getAuthorizeUrl(String source) { + AuthRequest authRequest = this.getAuthRequest(source); + return authRequest.authorize(AuthStateUtils.createState()); + } + + public String getAuthorizeUrl(String source, AuthConfig authConfig) { + AuthRequest authRequest = this.getAuthRequest(source, authConfig); + return authRequest.authorize(AuthStateUtils.createState()); + } + + public Map getAuthorizeUrls() { + Map configs = getConfigs(); + return configs.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> getAuthorizeUrl(entry.getKey(), entry.getValue()))); + } + + @NotNull + private Map getConfigs() { + Map configs = justAuthProperties.getConfigs(); + if(MapUtils.isEmpty(configs)) { + throw new AccessConfigErrorException(); + } + return configs; + } + + + @NotNull + private AuthConfig getAuthConfig(AuthDefaultSource authDefaultSource) { + Map configs = getConfigs(); + + AuthConfig authConfig = configs.get(authDefaultSource.name()); + // 找不到对应关系,直接返回空 + if (ObjectUtils.isEmpty(authConfig)) { + throw new AccessConfigErrorException(); + } + return authConfig; + } + + private static AuthDefaultSource parseAuthDefaultSource(String source) { + AuthDefaultSource authDefaultSource; + + try { + authDefaultSource = EnumUtil.fromString(AuthDefaultSource.class, source.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalAccessSourceException(); + } + return authDefaultSource; + } + + private AuthRequest getAuthRequest(AuthDefaultSource authDefaultSource, AuthConfig authConfig) { + switch (authDefaultSource) { + case GITHUB: + return new AuthGithubRequest(authConfig, this.getJustAuthStateRedisCache()); + case WEIBO: + return new AuthWeiboRequest(authConfig, this.getJustAuthStateRedisCache()); + case GITEE: + return new AuthGiteeRequest(authConfig, this.getJustAuthStateRedisCache()); + case DINGTALK: + return new AuthDingTalkRequest(authConfig, this.getJustAuthStateRedisCache()); + case BAIDU: + return new AuthBaiduRequest(authConfig, this.getJustAuthStateRedisCache()); + case CSDN: + return new AuthCsdnRequest(authConfig, this.getJustAuthStateRedisCache()); + case CODING: + return new AuthCodingRequest(authConfig, this.getJustAuthStateRedisCache()); + case OSCHINA: + return new AuthOschinaRequest(authConfig, this.getJustAuthStateRedisCache()); + case ALIPAY: + return new AuthAlipayRequest(authConfig, this.getJustAuthStateRedisCache()); + case QQ: + return new AuthQqRequest(authConfig, this.getJustAuthStateRedisCache()); + case WECHAT_MP: + return new AuthWeChatMpRequest(authConfig, this.getJustAuthStateRedisCache()); + case WECHAT_OPEN: + return new AuthWeChatOpenRequest(authConfig, this.getJustAuthStateRedisCache()); + case WECHAT_ENTERPRISE: + return new AuthWeChatEnterpriseQrcodeRequest(authConfig, this.getJustAuthStateRedisCache()); + case WECHAT_ENTERPRISE_WEB: + return new AuthWeChatEnterpriseWebRequest(authConfig, this.getJustAuthStateRedisCache()); + case TAOBAO: + return new AuthTaobaoRequest(authConfig, this.getJustAuthStateRedisCache()); + case GOOGLE: + return new AuthGoogleRequest(authConfig, this.getJustAuthStateRedisCache()); + case FACEBOOK: + return new AuthFacebookRequest(authConfig, this.getJustAuthStateRedisCache()); + case DOUYIN: + return new AuthDouyinRequest(authConfig, this.getJustAuthStateRedisCache()); + case LINKEDIN: + return new AuthLinkedinRequest(authConfig, this.getJustAuthStateRedisCache()); + case MICROSOFT: + return new AuthMicrosoftRequest(authConfig, this.getJustAuthStateRedisCache()); + case MI: + return new AuthMiRequest(authConfig, this.getJustAuthStateRedisCache()); + case TOUTIAO: + return new AuthToutiaoRequest(authConfig, this.getJustAuthStateRedisCache()); + case TEAMBITION: + return new AuthTeambitionRequest(authConfig, this.getJustAuthStateRedisCache()); + case RENREN: + return new AuthRenrenRequest(authConfig, this.getJustAuthStateRedisCache()); + case PINTEREST: + return new AuthPinterestRequest(authConfig, this.getJustAuthStateRedisCache()); + case STACK_OVERFLOW: + return new AuthStackOverflowRequest(authConfig, this.getJustAuthStateRedisCache()); + case HUAWEI: + return new AuthHuaweiRequest(authConfig, this.getJustAuthStateRedisCache()); + case GITLAB: + return new AuthGitlabRequest(authConfig, this.getJustAuthStateRedisCache()); + case KUJIALE: + return new AuthKujialeRequest(authConfig, this.getJustAuthStateRedisCache()); + case ELEME: + return new AuthElemeRequest(authConfig, this.getJustAuthStateRedisCache()); + case MEITUAN: + return new AuthMeituanRequest(authConfig, this.getJustAuthStateRedisCache()); + case TWITTER: + return new AuthTwitterRequest(authConfig, this.getJustAuthStateRedisCache()); + case FEISHU: + return new AuthFeishuRequest(authConfig, this.getJustAuthStateRedisCache()); + case JD: + return new AuthJdRequest(authConfig, this.getJustAuthStateRedisCache()); + case ALIYUN: + return new AuthAliyunRequest(authConfig, this.getJustAuthStateRedisCache()); + case XMLY: + return new AuthXmlyRequest(authConfig, this.getJustAuthStateRedisCache()); + default: + return null; + } + } +} diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/properties/JustAuthProperties.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/properties/JustAuthProperties.java new file mode 100644 index 0000000..f5c27d5 --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/properties/JustAuthProperties.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.properties; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import me.zhyd.oauth.config.AuthConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.time.Duration; +import java.util.Map; + +/** + *

Description: 用于支持JustAuth第三方登录的配置

+ * + * @author : liuh + * @date : 2021/5/16 10:24 + */ +@ConfigurationProperties(prefix = AccessConstants.PROPERTY_ACCESS_JUSTAUTH) +public class JustAuthProperties { + + /** + * 是否开启 + */ + private Boolean enabled; + /** + * State 缓存时长,默认5分钟 + */ + private Duration timeout = Duration.ofMinutes(5); + /** + * 第三方系统登录配置信息 + */ + private Map configs; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Duration getTimeout() { + return timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + public Map getConfigs() { + return configs; + } + + public void setConfigs(Map configs) { + this.configs = configs; + } +} diff --git a/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/stamp/JustAuthStateStampManager.java b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/stamp/JustAuthStateStampManager.java new file mode 100644 index 0000000..f5af3f0 --- /dev/null +++ b/engine-access/access-sdk-justauth/src/main/java/com/liuhung/engine/access/justauth/stamp/JustAuthStateStampManager.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.justauth.stamp; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import com.liuhung.engine.access.justauth.properties.JustAuthProperties; +import com.liuhung.engine.cache.jetcache.stamp.AbstractStampManager; +import cn.hutool.core.util.IdUtil; +import me.zhyd.oauth.cache.AuthStateCache; + +import java.util.concurrent.TimeUnit; + +/** + *

Description: 自定义JustAuth State Cache

+ * + * @author : liuh + * @date : 2021/5/22 10:22 + */ +public class JustAuthStateStampManager extends AbstractStampManager implements AuthStateCache { + + public JustAuthStateStampManager() { + super(AccessConstants.CACHE_NAME_TOKEN_JUSTAUTH); + } + + private JustAuthProperties justAuthProperties; + + public void setJustAuthProperties(JustAuthProperties justAuthProperties) { + this.justAuthProperties = justAuthProperties; + } + + @Override + public void afterPropertiesSet() throws Exception { + super.setExpire(justAuthProperties.getTimeout()); + } + + @Override + public String nextStamp(String key) { + return IdUtil.fastSimpleUUID(); + } + + @Override + public void cache(String key, String value) { + this.put(key, value); + } + + @Override + public void cache(String key, String value, long expire) { + this.put(key, value, expire, TimeUnit.MILLISECONDS); + } + + @Override + public boolean containsKey(String key) { + return this.containKey(key); + } +} diff --git a/engine-access/access-sdk-wxapp/pom.xml b/engine-access/access-sdk-wxapp/pom.xml new file mode 100644 index 0000000..7c414a6 --- /dev/null +++ b/engine-access/access-sdk-wxapp/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-access + com.liuhung.engine + 2.7.8.0 + + + access-sdk-wxapp + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 微信小程序外部接入基础核心组件模块 + + + + com.liuhung.engine + access-core + + + + \ No newline at end of file diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/annotation/ConditionalOnWxappEnabled.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/annotation/ConditionalOnWxappEnabled.java new file mode 100644 index 0000000..4178fab --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/annotation/ConditionalOnWxappEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.annotation; + +import com.liuhung.engine.access.wxapp.condition.WxappEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: 微信小程序开启条件注解

+ * + * @author : liuh + * @date : 2022/1/24 14:40 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(WxappEnabledCondition.class) +public @interface ConditionalOnWxappEnabled { +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/condition/WxappEnabledCondition.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/condition/WxappEnabledCondition.java new file mode 100644 index 0000000..531a10f --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/condition/WxappEnabledCondition.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.condition; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: 微信小程序注入条件

+ * + * @author : liuh + * @date : 2021/5/27 22:13 + */ +public class WxappEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(WxappEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + boolean result = PropertyResolver.getBoolean(conditionContext, AccessConstants.ITEM_WXAPP_ENABLED); + log.debug("[Quafer] |- Condition [Wxapp Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/configuration/WxappConfiguration.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/configuration/WxappConfiguration.java new file mode 100644 index 0000000..f80ab02 --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/configuration/WxappConfiguration.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.configuration; + +import com.liuhung.engine.access.wxapp.annotation.ConditionalOnWxappEnabled; +import com.liuhung.engine.access.wxapp.processor.WxappAccessHandler; +import com.liuhung.engine.access.wxapp.processor.WxappLogHandler; +import com.liuhung.engine.access.wxapp.processor.WxappProcessor; +import com.liuhung.engine.access.wxapp.properties.WxappProperties; +import com.liuhung.engine.assistant.core.enums.AccountType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: 微信小程序后配置

+ * + * @author : liuh + * @date : 2021/3/29 9:27 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnWxappEnabled +@EnableConfigurationProperties(WxappProperties.class) +public class WxappConfiguration { + + private static final Logger log = LoggerFactory.getLogger(WxappConfiguration.class); + + @PostConstruct + public void init() { + log.debug("[Quafer] |- SDK [Access Wxapp] Auto Configure."); + } + + @Bean + @ConditionalOnMissingBean + public WxappProcessor wxappProcessor(WxappProperties wxappProperties) { + WxappProcessor wxappProcessor = new WxappProcessor(); + wxappProcessor.setWxappProperties(wxappProperties); + wxappProcessor.setWxappLogHandler(new WxappLogHandler()); + log.trace("[Quafer] |- Bean [Wxapp Processor] Auto Configure."); + return wxappProcessor; + } + + @Bean(AccountType.WECHAT_MINI_APP_HANDLER) + @ConditionalOnBean(WxappProcessor.class) + @ConditionalOnMissingBean + public WxappAccessHandler wxappAccessHandler(WxappProcessor wxappProcessor) { + WxappAccessHandler wxappAccessHandler = new WxappAccessHandler(wxappProcessor); + log.debug("[Quafer] |- Bean [Wxapp Access Handler] Auto Configure."); + return wxappAccessHandler; + } +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/enums/MiniProgramState.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/enums/MiniProgramState.java new file mode 100644 index 0000000..e0b325c --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/enums/MiniProgramState.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.enums; + +/** + *

Description: 跳转小程序类型

+ *

+ * developer为开发版;trial为体验版;formal为正式版;默认为正式版 + * + * @author : liuh + * @date : 2021/4/9 16:09 + */ +public enum MiniProgramState { + + /** + * 开发版 + */ + developer, + + /** + * 体验版 + */ + trial, + + /** + * 正式版 + */ + formal; +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappAccessHandler.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappAccessHandler.java new file mode 100644 index 0000000..beb3807 --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappAccessHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.processor; + +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; +import com.liuhung.engine.access.core.definition.AccessHandler; +import com.liuhung.engine.access.core.definition.AccessResponse; +import com.liuhung.engine.access.core.definition.AccessUserDetails; +import com.liuhung.engine.access.core.exception.AccessIdentityVerificationFailedException; +import com.liuhung.engine.access.core.exception.AccessPreProcessFailedException; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.assistant.core.enums.AccountType; +import org.apache.commons.lang3.ObjectUtils; + +/** + *

Description: 微信小程序接入处理器

+ * + * @author : liuh + * @date : 2022/1/26 10:56 + */ +public class WxappAccessHandler implements AccessHandler { + + private final WxappProcessor wxappProcessor; + + public WxappAccessHandler(WxappProcessor wxappProcessor) { + this.wxappProcessor = wxappProcessor; + } + + @Override + public AccessResponse preProcess(String core, String... params) { + WxMaJscode2SessionResult wxMaSession = wxappProcessor.login(core, params[0]); + if (ObjectUtils.isNotEmpty(wxMaSession)) { + AccessResponse accessResponse = new AccessResponse(); + accessResponse.setSession(wxMaSession); + return accessResponse; + } + + throw new AccessPreProcessFailedException("Wxapp login failed"); + } + + @Override + public AccessUserDetails loadUserDetails(String source, AccessPrincipal accessPrincipal) { + WxMaUserInfo wxMaUserInfo = wxappProcessor.getUserInfo(accessPrincipal.getAppId(), accessPrincipal.getSessionKey(), accessPrincipal.getEncryptedData(), accessPrincipal.getIv()); + if (ObjectUtils.isNotEmpty(wxMaUserInfo)) { + return convertWxMaUserInfoToAccessUserDetails(wxMaUserInfo, accessPrincipal); + } + + throw new AccessIdentityVerificationFailedException("Can not find the userinfo from Wechat!"); + } + + private AccessUserDetails convertWxMaUserInfoToAccessUserDetails(WxMaUserInfo wxMaUserInfo, AccessPrincipal accessPrincipal) { + AccessUserDetails accessUserDetails = new AccessUserDetails(); + accessUserDetails.setUuid(accessPrincipal.getOpenId()); + accessUserDetails.setUserName(wxMaUserInfo.getNickName()); + accessUserDetails.setNickName(wxMaUserInfo.getNickName()); + accessUserDetails.setAvatar(wxMaUserInfo.getAvatarUrl()); + accessUserDetails.setLocation(wxMaUserInfo.getCountry() + SymbolConstants.FORWARD_SLASH + wxMaUserInfo.getProvince() + SymbolConstants.FORWARD_SLASH + wxMaUserInfo.getCity()); + accessUserDetails.setSource(AccountType.WXAPP.name()); + accessUserDetails.setOpenId(accessPrincipal.getOpenId()); + accessUserDetails.setUnionId(accessPrincipal.getUnionId()); + accessUserDetails.setAppId(wxMaUserInfo.getWatermark().getAppid()); + accessUserDetails.setPhoneNumber(wxMaUserInfo.getWatermark().getAppid()); + return accessUserDetails; + } +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappLogHandler.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappLogHandler.java new file mode 100644 index 0000000..0905bb7 --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappLogHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.processor; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; +import cn.binarywang.wx.miniapp.bean.WxMaMessage; +import cn.binarywang.wx.miniapp.message.WxMaMessageHandler; +import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + *

Description: 微信小程序Log处理器

+ * + * @author : liuh + * @date : 2021/4/7 12:44 + */ +public class WxappLogHandler implements WxMaMessageHandler { + + private static final Logger log = LoggerFactory.getLogger(WxappLogHandler.class); + + @Override + public WxMaXmlOutMessage handle(WxMaMessage wxMaMessage, Map context, WxMaService wxMaService, WxSessionManager sessionManager) throws WxErrorException { + log.info("收到消息:" + wxMaMessage.toString()); + wxMaService.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMaMessage.toJson()) + .toUser(wxMaMessage.getFromUser()).build()); + return null; + } +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappProcessor.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappProcessor.java new file mode 100644 index 0000000..942b82c --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/processor/WxappProcessor.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.processor; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.bean.*; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; +import com.liuhung.engine.access.wxapp.properties.WxappProperties; +import com.google.common.collect.Maps; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

Description: 微信小程序核心基础代码

+ * + * @author : liuh + * @date : 2021/5/27 20:29 + */ +public class WxappProcessor implements InitializingBean { + + private static final Logger log = LoggerFactory.getLogger(WxappProcessor.class); + + private WxappProperties wxappProperties; + private WxappLogHandler wxappLogHandler; + + private Map wxMaMessagerouters = Maps.newHashMap(); + private Map wxMaServices = Maps.newHashMap(); + + public void setWxappProperties(WxappProperties wxappProperties) { + this.wxappProperties = wxappProperties; + } + + public void setWxappLogHandler(WxappLogHandler wxappLogHandler) { + this.wxappLogHandler = wxappLogHandler; + } + + @Override + public void afterPropertiesSet() throws Exception { + List configs = this.wxappProperties.getConfigs(); + if (configs == null) { + throw new WxRuntimeException("Weixin Mini App Configuraiton is not setting!"); + } + + wxMaServices = configs.stream() + .map(a -> { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); +// WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool()); + // 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常 + config.setAppid(a.getAppId()); + config.setSecret(a.getSecret()); + config.setToken(a.getToken()); + config.setAesKey(a.getAesKey()); + config.setMsgDataFormat(a.getMessageDataFormat()); + + WxMaService service = new WxMaServiceImpl(); + service.setWxMaConfig(config); + wxMaMessagerouters.put(a.getAppId(), this.newRouter(service)); + return service; + }).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a)); + + log.info("[Quafer] |- Bean [quafer Weixin Mini App] Auto Configure."); + } + + private WxMaMessageRouter newRouter(WxMaService wxMaService) { + final WxMaMessageRouter router = new WxMaMessageRouter(wxMaService); + router.rule().handler(wxappLogHandler).next(); + return router; + } + + public WxMaService getWxMaService(String appid) { + WxMaService wxMaService = wxMaServices.get(appid); + if (ObjectUtils.isEmpty(wxMaService)) { + throw new IllegalArgumentException(String.format("Cannot find the configuration of appid=[%s], please check!", appid)); + } + + return wxMaService; + } + + public WxMaMessageRouter getWxMaMessageRouter(String appid) { + return wxMaMessagerouters.get(appid); + } + + public WxMaService getWxMaService() { + String appId = wxappProperties.getDefaultAppId(); + if (StringUtils.isBlank(appId)) { + log.error("[Quafer] |- Must set [quafer.platform.social.wxapp.default-app-id] property, or use getWxMaService(String appid)!"); + throw new IllegalArgumentException("Must set [quafer.platform.social.wxapp.default-app-id] property"); + } + return this.getWxMaService(appId); + } + + /** + * 获取登录后的session信息. + * + * @param code 登录时获取的 code + * @param wxMaService 微信小程序服务 + * @return {@link WxMaJscode2SessionResult} + */ + private WxMaJscode2SessionResult getSessionInfo(String code, WxMaService wxMaService) { + try { + WxMaJscode2SessionResult sessionResult = wxMaService.getUserService().getSessionInfo(code); + log.debug("[Quafer] |- Weixin Mini App login successfully!"); + return sessionResult; + } catch (WxErrorException e) { + log.error("[Quafer] |- Weixin Mini App login failed! For reason: {}", e.getMessage()); + return null; + } + } + + /** + * 验证用户完整性 + * + * @param sessionKey 会话密钥 + * @param rawData 微信用户基本信息 + * @param signature 数据签名 + * @param wxMaService 微信小程序服务 + * @return true 完整, false 不完整 + */ + private boolean checkUserInfo(String sessionKey, String rawData, String signature, WxMaService wxMaService) { + if (wxMaService.getUserService().checkUserInfo(sessionKey, rawData, signature)) { + log.debug("[Quafer] |- Weixin Mini App user info is valid!"); + return true; + } else { + log.warn("[Quafer] |- Weixin Mini App user check failed!"); + return false; + } + } + + /** + * 解密用户信息 + * + * @param sessionKey 会话密钥 + * @param encryptedData 消息密文 + * @param iv 加密算法的初始向量 + * @param wxMaService 微信小程序服务 + * @return {@link WxMaUserInfo} + */ + private WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String iv, WxMaService wxMaService) { + WxMaUserInfo wxMaUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv); + log.debug("[Quafer] |- Weixin Mini App get user info successfully!"); + return wxMaUserInfo; + } + + /** + * 解密手机号 + *

+ * 确认下前后端传递的参数有没有做UrlEncode/UrlDecode,因为encryptedData里会包含特殊字符在传递参数时被转义,可能服务器端实际拿到的参数encryptedData并不是前端实际获取到的值,导致SDK调用微信相应接口时无法解密而报错,只要保证前端实际获取到的encryptedData和服务器端调用SDK时传入的encryptedData一致就不会报错的,SDK中方法并无问题;建议让前后台都打印下日志,看下服务端最终使用的参数值是否还是前端获取到的原始值呢。PS:SpringBoot某些场景下form表单参数是会自动做UrlDecode的... + *

+ * {@see :https://github.com/Wechat-Group/WxJava/issues/359} + * @param wxMaService 微信小程序服务 + * @return {@link WxMaPhoneNumberInfo} + */ + private WxMaPhoneNumberInfo getPhoneNumberInfo(String code, WxMaService wxMaService) { + log.info("[Quafer] |- Weixin Mini App get code: {}", code); + + WxMaPhoneNumberInfo wxMaPhoneNumberInfo; + try { + wxMaPhoneNumberInfo = wxMaService.getUserService().getPhoneNoInfo(code); + log.debug("[Quafer] |- Weixin Mini App get phone number successfully!"); + log.debug("[Quafer] |- WxMaPhoneNumberInfo : {}", wxMaPhoneNumberInfo); + return wxMaPhoneNumberInfo; + } catch (Exception e) { + log.error("[Quafer] |- Weixin Mini App get phone number failed!"); + return null; + } + } + + private boolean checkUserInfo(String rawData, String signature) { + return StringUtils.isNotBlank(rawData) && StringUtils.isNotBlank(signature); + } + + public WxMaJscode2SessionResult login(String code, String appId) { + WxMaService wxMaService = getWxMaService(appId); + if (StringUtils.isNotBlank(code) && ObjectUtils.isNotEmpty(wxMaService)) { + return this.getSessionInfo(code, wxMaService); + } else { + log.error("[Quafer] |- Weixin Mini App login failed, please check code param!"); + return null; + } + } + + public WxMaUserInfo getUserInfo(String appId, String sessionKey, String encryptedData, String iv) { + return this.getUserInfo(appId, sessionKey, encryptedData, iv, null, null); + } + + public WxMaUserInfo getUserInfo(String appId, String sessionKey, String encryptedData, String iv, String rawData, String signature) { + + WxMaService wxMaService = getWxMaService(appId); + + if (ObjectUtils.isNotEmpty(wxMaService)) { + // 用户信息校验 + if (checkUserInfo(rawData, signature)) { + if (checkUserInfo(sessionKey, rawData, signature, wxMaService)) { + return null; + } + } + + return this.getUserInfo(sessionKey, encryptedData, iv, wxMaService); + } else { + log.error("[Quafer] |- Weixin Mini App get user info failed!"); + return null; + } + } + + + + /** + * 根据直接创建的WxMaSubscribeMessage发送订阅消息 + * + * @param appId 小程序appId + * @param subscribeMessage 参见 {@link WxMaSubscribeMessage} + * @return true 发送成功,false 发送失败,或者参数subscribeId配置不对,无法获取相应的WxMaSubscribeMessage + */ + public boolean sendSubscribeMessage(String appId, WxMaSubscribeMessage subscribeMessage) { + try { + this.getWxMaService(appId).getMsgService().sendSubscribeMsg(subscribeMessage); + log.debug("[Quafer] |- Send Subscribe Message Successfully!"); + return true; + } catch (WxErrorException e) { + log.debug("[Quafer] |- Send Subscribe Message Failed!", e); + return false; + } + } + + /** + * 检查一段文本是否含有违法违规内容。 + * 应用场景举例: + * · 用户个人资料违规文字检测; + * · 媒体新闻类用户发表文章,评论内容检测; + * · 游戏类用户编辑上传的素材(如答题类小游戏用户上传的问题及答案)检测等。 频率限制:单个 appId 调用上限为 4000 次/分钟,2,000,000 次/天* + * · 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/sec-check/msgSecCheck.html + * + * @param appId 小程序appId + * @param message 需要检测的字符串 + * @return 是否违规 boolean + */ + public boolean checkMessage(String appId, String message) { + try { + this.getWxMaService(appId).getSecCheckService().checkMessage(message); + log.debug("[Quafer] |- Check Message Successfully!"); + return true; + } catch (WxErrorException e) { + log.debug("[Quafer] |- Check Message Failed!", e); + return false; + } + } + + /** + * 校验一张图片是否含有违法违规内容 + * + * @param appId 小程序appId + * @param fileUrl 需要检测图片的网地址 + * @return 是否违规 boolean + */ + public boolean checkImage(String appId, String fileUrl) { + try { + this.getWxMaService(appId).getSecCheckService().checkImage(fileUrl); + log.debug("[Quafer] |- Check Image Successfully!"); + return true; + } catch (WxErrorException e) { + log.debug("[Quafer] |- Check Image Failed! Detail is :{}", e.getMessage()); + return false; + } + } + + /** + * 校验一张图片是否含有违法违规内容. + *

+ * 应用场景举例: + * 1)图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等; + * 2)敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 1000 次/分钟,100,000 次/天 + * 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/sec-check/imgSecCheck.html + * + * @param appId 小程序appId + * @param file 图片文件 + * @return 是否违规 boolean + */ + public boolean checkImage(String appId, File file) { + try { + this.getWxMaService(appId).getSecCheckService().checkImage(file); + log.debug("[Quafer] |- Check Image Successfully!"); + return true; + } catch (WxErrorException e) { + log.debug("[Quafer] |- Check Image Failed! Detail is :{}", e.getMessage()); + return false; + } + } + + /** + * 异步校验图片/音频是否含有违法违规内容。 + * 应用场景举例: + * 语音风险识别:社交类用户发表的语音内容检测; + * 图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等; + * 敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。 + * 频率限制: + * 单个 appId 调用上限为 2000 次/分钟,200,000 次/天;文件大小限制:单个文件大小不超过10M + * 详情请见: + * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.mediaCheckAsync.html + * + * @param appId 小程序appId + * @param mediaUrl 要检测的多媒体url + * @param mediaType 媒体类型 {@link cn.binarywang.wx.miniapp.constant.WxMaConstants.SecCheckMediaType} + * @return 微信检测结果 WxMaMediaAsyncCheckResult {@link WxMaMediaAsyncCheckResult} + */ + public WxMaMediaAsyncCheckResult mediaAsyncCheck(String appId, String mediaUrl, int mediaType) { + WxMaMediaAsyncCheckResult wxMaMediaAsyncCheckResult = null; + try { + wxMaMediaAsyncCheckResult = this.getWxMaService(appId).getSecCheckService().mediaCheckAsync(mediaUrl, mediaType); + log.debug("[Quafer] |- Media Async Check Successfully!"); + } catch (WxErrorException e) { + log.debug("[Quafer] |- Media Async Check Failed! Detail is :{}", e.getMessage()); + } + + return wxMaMediaAsyncCheckResult; + } +} diff --git a/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/properties/WxappProperties.java b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/properties/WxappProperties.java new file mode 100644 index 0000000..7b96017 --- /dev/null +++ b/engine-access/access-sdk-wxapp/src/main/java/com/liuhung/engine/access/wxapp/properties/WxappProperties.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxapp.properties; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import com.liuhung.engine.access.wxapp.enums.MiniProgramState; +import com.google.common.base.MoreObjects; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.Serializable; +import java.util.List; + +/** + *

Description: 微信小程序配置属性

+ * + * @author : liuh + * @date : 2021/3/26 17:27 + */ +@ConfigurationProperties(prefix = AccessConstants.PROPERTY_ACCESS_WXAPP) +public class WxappProperties implements Serializable { + + /** + * 是否开启 + */ + private Boolean enabled; + /** + * 默认App Id + */ + private String defaultAppId; + + /** + * 小程序配置列表 + */ + private List configs; + + /** + * 小程序订阅消息配置列表 + */ + private List subscribes; + + public String getDefaultAppId() { + return defaultAppId; + } + + public void setDefaultAppId(String defaultAppId) { + this.defaultAppId = defaultAppId; + } + + public List getConfigs() { + return configs; + } + + public void setConfigs(List configs) { + this.configs = configs; + } + + public List getSubscribes() { + return subscribes; + } + + public void setSubscribes(List subscribes) { + this.subscribes = subscribes; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public static class Config { + /** + * 设置微信小程序的appid + */ + private String appId; + + /** + * 设置微信小程序的Secret + */ + private String secret; + + /** + * 设置微信小程序消息服务器配置的token + */ + private String token; + + /** + * 设置微信小程序消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String messageDataFormat; + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getAesKey() { + return aesKey; + } + + public void setAesKey(String aesKey) { + this.aesKey = aesKey; + } + + public String getMessageDataFormat() { + return messageDataFormat; + } + + public void setMessageDataFormat(String messageDataFormat) { + this.messageDataFormat = messageDataFormat; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("appid", appId) + .add("secret", secret) + .add("token", token) + .add("aesKey", aesKey) + .add("messageDataFormat", messageDataFormat) + .toString(); + } + } + + public static class Subscribe { + + /** + * 订阅消息指定的小程序跳转页面地址 + */ + private String redirectPage; + /** + * 订阅消息模版ID + */ + private String templateId; + + /** + * 自定义Message区分ID,用于获取不同的SubscribeMessageHandler + */ + private String subscribeId; + + private MiniProgramState miniProgramState = MiniProgramState.formal; + + public String getRedirectPage() { + return redirectPage; + } + + public void setRedirectPage(String redirectPage) { + this.redirectPage = redirectPage; + } + + public String getTemplateId() { + return templateId; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } + + public MiniProgramState getMiniProgramState() { + return miniProgramState; + } + + public void setMiniProgramState(MiniProgramState miniProgramState) { + this.miniProgramState = miniProgramState; + } + + public String getSubscribeId() { + return subscribeId; + } + + public void setSubscribeId(String subscribeId) { + this.subscribeId = subscribeId; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("redirectPage", redirectPage) + .add("templateId", templateId) + .add("subscribeId", subscribeId) + .add("miniProgramState", miniProgramState) + .toString(); + } + } +} diff --git a/engine-access/access-sdk-wxmpp/pom.xml b/engine-access/access-sdk-wxmpp/pom.xml new file mode 100644 index 0000000..f3ce80b --- /dev/null +++ b/engine-access/access-sdk-wxmpp/pom.xml @@ -0,0 +1,60 @@ + + + + + + engine-access + com.liuhung.engine + 2.7.8.0 + + 4.0.0 + + access-sdk-wxmpp + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的微信公众号外部接入组件模块 + + + + com.liuhung.engine + access-core + + + + com.liuhung.engine + cache-sdk-redis + + + + com.github.binarywang + weixin-java-mp + + + + \ No newline at end of file diff --git a/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/annotation/ConditionalOnWxmppEnabled.java b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/annotation/ConditionalOnWxmppEnabled.java new file mode 100644 index 0000000..bc18dcb --- /dev/null +++ b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/annotation/ConditionalOnWxmppEnabled.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxmpp.annotation; + +import com.liuhung.engine.access.wxmpp.condition.WxmppEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: 微信公众号开启条件注解

+ * + * @author : liuh + * @date : 2022/1/24 14:40 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(WxmppEnabledCondition.class) +public @interface ConditionalOnWxmppEnabled { + +} diff --git a/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/condition/WxmppEnabledCondition.java b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/condition/WxmppEnabledCondition.java new file mode 100644 index 0000000..da92c42 --- /dev/null +++ b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/condition/WxmppEnabledCondition.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxmpp.condition; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: 微信公众号注入条件

+ * + * @author : liuh + * @date : 2021/5/27 22:13 + */ +public class WxmppEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(WxmppEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + boolean result = PropertyResolver.getBoolean(conditionContext, AccessConstants.ITEM_WXMPP_ENABLED); + log.debug("[Quafer] |- Condition [Wxmpp Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/configuration/WxmppConfiguration.java b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/configuration/WxmppConfiguration.java new file mode 100644 index 0000000..5b2a1a4 --- /dev/null +++ b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/configuration/WxmppConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxmpp.configuration; + +import com.liuhung.engine.access.wxmpp.annotation.ConditionalOnWxmppEnabled; +import com.liuhung.engine.access.wxmpp.processor.WxmppLogHandler; +import com.liuhung.engine.access.wxmpp.processor.WxmppProcessor; +import com.liuhung.engine.access.wxmpp.properties.WxmppProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +import javax.annotation.PostConstruct; + +/** + *

Description: 微信公众号配置

+ * + * @author : liuh + * @date : 2021/4/7 13:25 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnWxmppEnabled +@EnableConfigurationProperties(WxmppProperties.class) +public class WxmppConfiguration { + + private static final Logger log = LoggerFactory.getLogger(WxmppConfiguration.class); + + @PostConstruct + public void init() { + log.debug("[Quafer] |- SDK [Access Wxmpp] Auto Configure."); + } + + @Bean + @ConditionalOnMissingBean + public WxmppProcessor wxmppProcessor(WxmppProperties wxmppProperties, StringRedisTemplate stringRedisTemplate) { + WxmppProcessor wxmppProcessor = new WxmppProcessor(); + wxmppProcessor.setWxmppProperties(wxmppProperties); + wxmppProcessor.setWxmppLogHandler(new WxmppLogHandler()); + wxmppProcessor.setStringRedisTemplate(stringRedisTemplate); + log.trace("[Quafer] |- Bean [Wxmpp Processor] Auto Configure."); + return wxmppProcessor; + } +} diff --git a/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/processor/WxmppLogHandler.java b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/processor/WxmppLogHandler.java new file mode 100644 index 0000000..43f5e71 --- /dev/null +++ b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/processor/WxmppLogHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxmpp.processor; + +import com.liuhung.engine.assistant.core.json.jackson2.utils.JacksonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.Map; + +/** + *

Description: 微信公众号日志处理

+ * + * @author : liuh + * @date : 2021/4/7 13:26 + */ +@Component +public class WxmppLogHandler implements WxMpMessageHandler { + + private static final Logger log = LoggerFactory.getLogger(WxmppLogHandler.class); + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + // 代码逻辑未实现,仅仅简单打印信息 + if(null!=wxMpXmlMessage){ + String msg =JacksonUtils.toJson(wxMpXmlMessage); + log.info("\n接收到请求消息,内容:{}",msg ); + } + return null; + } +} diff --git a/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/processor/WxmppProcessor.java b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/processor/WxmppProcessor.java new file mode 100644 index 0000000..7926b6c --- /dev/null +++ b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/processor/WxmppProcessor.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxmpp.processor; + +import com.liuhung.engine.access.wxmpp.properties.WxmppProperties; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

Description: 微信公众号核心服务

+ * + * @author : liuh + * @date : 2021/5/27 20:25 + */ +public class WxmppProcessor implements InitializingBean { + + private static final Logger log = LoggerFactory.getLogger(WxmppProcessor.class); + private WxMpService wxMpService; + private WxmppProperties wxmppProperties; + private WxmppLogHandler wxmppLogHandler; + private StringRedisTemplate stringRedisTemplate; + + public void setWxmppProperties(WxmppProperties wxmppProperties) { + this.wxmppProperties = wxmppProperties; + } + + public void setWxmppLogHandler(WxmppLogHandler wxmppLogHandler) { + this.wxmppLogHandler = wxmppLogHandler; + } + + public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) { + this.stringRedisTemplate = stringRedisTemplate; + } + + @Override + public void afterPropertiesSet() { + // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! + final List configs = this.wxmppProperties.getConfigs(); + if (configs == null) { + throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + + WxMpService wxMpService = new WxMpServiceImpl(); + wxMpService.setMultiConfigStorages(configs + .stream().map(a -> { + WxMpDefaultConfigImpl configStorage; + if (this.wxmppProperties.isUseRedis()) { + final WxmppProperties.RedisConfig redisConfig = this.wxmppProperties.getRedis(); + configStorage = new WxMpRedisConfigImpl(new RedisTemplateWxRedisOps(stringRedisTemplate), a.getAppId()); + } else { + configStorage = new WxMpDefaultConfigImpl(); + } + + configStorage.setAppId(a.getAppId()); + configStorage.setSecret(a.getSecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAesKey()); + return configStorage; + }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o))); + + log.info("[Quafer] |- Bean [Quafer Weixin Micro Message Public Platform] Auto Configure."); + + this.wxMpService = wxMpService; + } + + public WxMpService getWxMpService() { + if (ObjectUtils.isEmpty(this.wxMpService)) { + throw new IllegalArgumentException("Cannot find the configuration for wechat official accounts, please check!"); + } + return wxMpService; + } + + public WxMpMessageRouter getWxMpMessageRouter() { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(this.getWxMpService()); + // 记录所有事件的日志 (异步执行) + newRouter.rule().handler(this.wxmppLogHandler).next(); + return newRouter; + } +} diff --git a/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/properties/WxmppProperties.java b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/properties/WxmppProperties.java new file mode 100644 index 0000000..15c2b84 --- /dev/null +++ b/engine-access/access-sdk-wxmpp/src/main/java/com/liuhung/engine/access/wxmpp/properties/WxmppProperties.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.wxmpp.properties; + +import com.liuhung.engine.access.core.constants.AccessConstants; +import com.google.common.base.MoreObjects; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + *

Description: 微信公众号属性配置

+ * + * @author : liuh + * @date : 2021/4/7 13:17 + */ +@ConfigurationProperties(prefix = AccessConstants.PROPERTY_ACCESS_WXMPP) +public class WxmppProperties { + + /** + * 是否开启 + */ + private Boolean enabled; + /** + * 是否使用redis存储access token + */ + private boolean useRedis; + + /** + * redis 配置 + */ + private RedisConfig redis; + + public boolean isUseRedis() { + return useRedis; + } + + public void setUseRedis(boolean useRedis) { + this.useRedis = useRedis; + } + + public RedisConfig getRedis() { + return redis; + } + + public void setRedis(RedisConfig redis) { + this.redis = redis; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public static class RedisConfig { + /** + * redis服务器 主机地址 + */ + private String host; + + /** + * redis服务器 端口号 + */ + private Integer port; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("host", host) + .add("port", port) + .toString(); + } + } + + /** + * 多个公众号配置信息 + */ + private List configs; + + public List getConfigs() { + return configs; + } + + public void setConfigs(List configs) { + this.configs = configs; + } + + public static class MpConfig { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getAesKey() { + return aesKey; + } + + public void setAesKey(String aesKey) { + this.aesKey = aesKey; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("appId", appId) + .add("secret", secret) + .add("token", token) + .add("aesKey", aesKey) + .toString(); + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("useRedis", useRedis) + .add("redis", redis) + .add("configs", configs) + .toString(); + } +} diff --git a/engine-access/access-spring-boot-starter/pom.xml b/engine-access/access-spring-boot-starter/pom.xml new file mode 100644 index 0000000..a9b6ad7 --- /dev/null +++ b/engine-access/access-spring-boot-starter/pom.xml @@ -0,0 +1,49 @@ + + + + + 4.0.0 + + + engine-access + com.liuhung.engine + 2.7.8.0 + + + access-spring-boot-starter + 2.7.8.0 + jar + + + + com.liuhung.engine + access-sdk-all + + + + \ No newline at end of file diff --git a/engine-access/access-spring-boot-starter/src/main/java/com/liuhung/engine/access/autoconfigure/AutoConfiguration.java b/engine-access/access-spring-boot-starter/src/main/java/com/liuhung/engine/access/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..63775eb --- /dev/null +++ b/engine-access/access-spring-boot-starter/src/main/java/com/liuhung/engine/access/autoconfigure/AutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.access.autoconfigure; + +import com.liuhung.engine.access.business.configuration.AccessAllConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: 外部程序接入模块自动配置

+ * + * @author : liuh + * @date : 2022/1/26 16:23 + */ +@Configuration(proxyBeanMethods = false) +@Import({ + AccessAllConfiguration.class +}) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Access Starter] Auto Configure."); + } +} diff --git a/engine-access/access-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-access/access-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..bdb1119 --- /dev/null +++ b/engine-access/access-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.liuhung.engine.access.autoconfigure.AutoConfiguration \ No newline at end of file diff --git a/engine-access/pom.xml b/engine-access/pom.xml new file mode 100644 index 0000000..a103c88 --- /dev/null +++ b/engine-access/pom.xml @@ -0,0 +1,53 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + 手机短信、小程序、公众号、JustAuth等外部登录处理逻辑集成模块 + + engine-access + 2.7.8.0 + pom + + + access-core + access-sdk-justauth + access-sdk-wxapp + access-sdk-wxmpp + access-sdk-all + access-spring-boot-starter + + + \ No newline at end of file diff --git a/engine-assistant/README.md b/engine-assistant/README.md new file mode 100644 index 0000000..827af38 --- /dev/null +++ b/engine-assistant/README.md @@ -0,0 +1 @@ +## 全局通用代码模块 \ No newline at end of file diff --git a/engine-assistant/assistant-core/README.md b/engine-assistant/assistant-core/README.md new file mode 100644 index 0000000..2ce00c4 --- /dev/null +++ b/engine-assistant/assistant-core/README.md @@ -0,0 +1 @@ +> 基础通用共性代码模块包 \ No newline at end of file diff --git a/engine-assistant/assistant-core/pom.xml b/engine-assistant/assistant-core/pom.xml new file mode 100644 index 0000000..ee82a95 --- /dev/null +++ b/engine-assistant/assistant-core/pom.xml @@ -0,0 +1,171 @@ + + + + + 4.0.0 + + + engine-assistant + com.liuhung.engine + 2.7.8.0 + + + assistant-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 核心通用代码组件 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-common + + + + org.apache.commons + commons-collections4 + + + org.apache.commons + commons-text + + + org.apache.commons + commons-pool2 + + + commons-io + commons-io + + + commons-codec + commons-codec + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + + org.apache.httpcomponents + httpclient + + + + org.apache.httpcomponents + httpcore + + + + cn.hutool + hutool-all + + + com.google.guava + guava + + + com.google.code.gson + gson + + + + org.apache.skywalking + apm-toolkit-logback-1.x + + + org.apache.skywalking + apm-toolkit-trace + + + + cn.zhxu + okhttps-jackson + + + com.squareup.okio + okio + + + + + com.squareup.okio + okio + + + cn.zhxu + okhttps-gson + + + me.zhyd.oauth + JustAuth + + + + org.owasp.antisamy + antisamy + + + slf4j-simple + org.slf4j + + + + + + com.alibaba + transmittable-thread-local + + + + \ No newline at end of file diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/ConditionalOnSwaggerEnabled.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/ConditionalOnSwaggerEnabled.java new file mode 100644 index 0000000..7682cd9 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/ConditionalOnSwaggerEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.annotation; + +import com.liuhung.engine.assistant.core.conditon.SwaggerEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: Swagger条件开启主机

+ * + * @author : liuh + * @date : 2021/8/20 11:58 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Conditional(SwaggerEnabledCondition.class) +public @interface ConditionalOnSwaggerEnabled { +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/EnumeratedValue.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/EnumeratedValue.java new file mode 100644 index 0000000..a48178d --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/EnumeratedValue.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.annotation; + +import com.liuhung.engine.assistant.core.validation.EnumeratedValueValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + + +/** + *

Description: 枚举值校验注解

+ * + * @author : liuh + * @date : 2022/6/13 15:58 + */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(EnumeratedValue.List.class) +@Documented +@Constraint(validatedBy = {EnumeratedValueValidator.class}) +public @interface EnumeratedValue { + + // 默认错误消息 + String message() default "必须为指定值"; + + String[] names() default {}; + + int[] ordinals() default {}; + + // 分组 + Class[] groups() default {}; + + // 负载 + Class[] payload() default {}; + + // 指定多个时使用 + @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface List { + EnumeratedValue[] value(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/Inner.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/Inner.java new file mode 100644 index 0000000..803a45f --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/annotation/Inner.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.annotation; + +import java.lang.annotation.*; + +/** + *

Description: Feign 内部调用标记注解

+ * + * @author : liuh + * @date : 2022/5/31 12:10 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Inner { + +} \ No newline at end of file diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableParameter.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableParameter.java new file mode 100644 index 0000000..f5edb41 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableParameter.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.datatables; + +import com.google.common.base.MoreObjects; + +import java.io.Serializable; + +/** + *

Description: JQuery Datatables 组件只用的参数对象封装

+ * + * @author : liuh + * @date : 2019/11/24 15:48 + */ +public class DataTableParameter implements Serializable { + + private String name; + private Object value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .add("value", value) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableResult.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableResult.java new file mode 100644 index 0000000..2e48db1 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableResult.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.datatables; + +import com.google.common.base.MoreObjects; + +import java.io.Serializable; + +/** + *

Description: 返回给JQuery Datatables 组件使用的结果

+ * + * @author : liuh + * @date : 2019/11/24 15:48 + */ +public class DataTableResult implements Serializable { + + private int pageNumber; + private int pageSize; + private String sEcho; + private int iDisplayStart; + private int iDisplayLength; + private String jsonString; + private long total; + + public DataTableResult(String sEcho, int iDisplayStart, int iDisplayLength, String jsonString) { + this.sEcho = sEcho; + this.iDisplayStart = iDisplayStart; + this.iDisplayLength = iDisplayLength; + this.pageNumber = this.iDisplayStart / this.iDisplayLength; + this.pageSize = this.iDisplayLength; + this.jsonString = jsonString; + } + + public int getPageNumber() { + return pageNumber; + } + + public void setPageNumber(int pageNumber) { + this.pageNumber = pageNumber; + } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public String getsEcho() { + return sEcho; + } + + public void setsEcho(String sEcho) { + this.sEcho = sEcho; + } + + public int getiDisplayStart() { + return iDisplayStart; + } + + public void setiDisplayStart(int iDisplayStart) { + this.iDisplayStart = iDisplayStart; + } + + public int getiDisplayLength() { + return iDisplayLength; + } + + public void setiDisplayLength(int iDisplayLength) { + this.iDisplayLength = iDisplayLength; + } + + public String getJsonString() { + return jsonString; + } + + public void setJsonString(String jsonString) { + this.jsonString = jsonString; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("pageNumber", pageNumber) + .add("pageSize", pageSize) + .add("sEcho", sEcho) + .add("iDisplayStart", iDisplayStart) + .add("iDisplayLength", iDisplayLength) + .add("jsonString", jsonString) + .add("total", total) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableUtils.java new file mode 100644 index 0000000..bd83149 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/datatables/DataTableUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.datatables; + +import java.util.List; + +/** + *

Description: JQuery Datatable组件使用的工具类

+ * + * @author : liuh + * @date : 2019/11/24 15:49 + */ +public class DataTableUtils { + + public static final String ECHO = "sEcho"; + public static final String DISPLAY_START = "iDisplayStart"; + public static final String DISPLAY_LENGTH = "iDisplayLength"; + public static final String QUERY_JSON = "queryJson"; + public static final String DATA = "data"; + + public static DataTableResult parseDataTableParameter(List params) { + + String sEcho = null; + String jsonString = null; + int iDisplayStart = 0; + int iDisplayLength = 0; + for (DataTableParameter param : params) { + if (param.getName().equals(ECHO)) { + sEcho = param.getValue().toString(); + } + if (param.getName().equals(DISPLAY_START)) { + iDisplayStart = (int) param.getValue(); + } + if (param.getName().equals(DISPLAY_LENGTH)) { + iDisplayLength = (int) param.getValue(); + } + if (param.getName().equals(QUERY_JSON)) { + jsonString = param.getValue().toString(); + } + } + + return new DataTableResult(sEcho, iDisplayStart, iDisplayLength, jsonString); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/BaseMeta.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/BaseMeta.java new file mode 100644 index 0000000..1979a70 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/BaseMeta.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.router; + +import com.liuhung.engine.assistant.core.definition.domain.Entity; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + *

Description: Vue Router Meta 属性定义

+ * + * @author : liuh + * @date : 2022/7/14 16:51 + */ +public class BaseMeta implements Entity { + + private String title; + private String icon; + @JsonProperty("isNotKeepAlive") + private Boolean notKeepAlive = false; + @JsonProperty("isIgnoreAuth") + private Boolean ignoreAuth = false; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Boolean getNotKeepAlive() { + return notKeepAlive; + } + + public void setNotKeepAlive(Boolean notKeepAlive) { + this.notKeepAlive = notKeepAlive; + } + + public Boolean getIgnoreAuth() { + return ignoreAuth; + } + + public void setIgnoreAuth(Boolean ignoreAuth) { + this.ignoreAuth = ignoreAuth; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/ChildMeta.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/ChildMeta.java new file mode 100644 index 0000000..225276c --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/ChildMeta.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.router; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + *

Description: 子节点 Meta

+ * + * @author : liuh + * @date : 2022/7/14 16:54 + */ +public class ChildMeta extends BaseMeta{ + + @JsonProperty("isDetailContent") + private Boolean detailContent; + + public Boolean getDetailContent() { + return detailContent; + } + + public void setDetailContent(Boolean detailContent) { + this.detailContent = detailContent; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/ParentMeta.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/ParentMeta.java new file mode 100644 index 0000000..bb63170 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/ParentMeta.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.router; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + *

Description: 子级节点Meta定义

+ * + * @author : liuh + * @date : 2022/7/14 16:53 + */ +public class ParentMeta extends BaseMeta{ + + @JsonProperty("isHideAllChild") + private Boolean hideAllChild = false; + + public Boolean getHideAllChild() { + return hideAllChild; + } + + public void setHideAllChild(Boolean hideAllChild) { + this.hideAllChild = hideAllChild; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/RootMeta.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/RootMeta.java new file mode 100644 index 0000000..5988228 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/component/router/RootMeta.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.component.router; + +/** + *

Description: RootMeta

+ * + * @author : liuh + * @date : 2022/7/14 17:01 + */ +public class RootMeta extends BaseMeta{ + + private Integer sort; + + public Integer getSort() { + return sort; + } + + public void setSort(Integer sort) { + this.sort = sort; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/conditon/SwaggerEnabledCondition.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/conditon/SwaggerEnabledCondition.java new file mode 100644 index 0000000..8a9ced6 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/conditon/SwaggerEnabledCondition.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.conditon; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: Swagger 开启条件

+ * + * @author : liuh + * @date : 2022/3/17 14:34 + */ +public class SwaggerEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(SwaggerEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + boolean result = PropertyResolver.getBoolean(conditionContext, BaseConstants.ITEM_SWAGGER_ENABLED); + log.debug("[Quafer] |- Condition [Swagger Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/PropertyFinder.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/PropertyFinder.java new file mode 100644 index 0000000..de989bd --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/PropertyFinder.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.context; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import org.springframework.core.env.Environment; + +/** + *

Description: 通用属性读取器

+ * + * @author : liuh + * @date : 2022/2/1 18:10 + */ +public class PropertyFinder { + + public static String getApplicationName(Environment environment) { + return PropertyResolver.getProperty(environment, BaseConstants.ITEM_SPRING_APPLICATION_NAME); + } + + public static String getServerPort(Environment environment) { + return PropertyResolver.getProperty(environment, BaseConstants.ITEM_SERVER_PORT); + } + + public static String getSessionStoreType(Environment environment) { + return PropertyResolver.getProperty(environment, BaseConstants.ITEM_SPRING_SESSION_STORE_TYPE); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/PropertyResolver.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/PropertyResolver.java new file mode 100644 index 0000000..8330c12 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/PropertyResolver.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.context; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; + +/** + *

Description: 配置信息读取工具类

+ * + * @author : liuh + * @date : 2021/8/7 13:39 + */ +public class PropertyResolver { + + /** + * 从环境信息中获取配置信息 + * + * @param environment Spring Boot Environment {@link Environment} + * @param property 配置名称 + * @return 配置属性值 + */ + public static String getProperty(Environment environment, String property) { + return environment.getProperty(property); + } + + /** + * 从环境信息中获取配置信息 + * + * @param environment Spring Boot Environment {@link Environment} + * @param property 配置名称 + * @param defaultValue 默认值 + * @return 配置属性值 + */ + public static String getProperty(Environment environment, String property, String defaultValue) { + return environment.getProperty(property, defaultValue); + } + + /** + * 从条件上下文中获取配置信息 + * + * @param conditionContext Spring Boot ConditionContext {@link ConditionContext} + * @param property 配置名称 + * @return 配置属性值 + */ + public static String getProperty(ConditionContext conditionContext, String property) { + return getProperty(conditionContext.getEnvironment(), property); + } + + /** + * 从条件上下文中获取配置信息 + * + * @param conditionContext Spring Boot ConditionContext {@link ConditionContext} + * @param property 配置名称 + * @param defaultValue 默认值 + * @return 配置属性值 + */ + public static String getProperty(ConditionContext conditionContext, String property, String defaultValue) { + return getProperty(conditionContext.getEnvironment(), property, defaultValue); + } + + public static T getProperty(Environment environment, String property, Class targetType) { + return environment.getProperty(property, targetType); + } + + public static T getProperty(Environment environment, String property, Class targetType, T defaultValue) { + return environment.getProperty(property, targetType, defaultValue); + } + + public static T getProperty(ConditionContext conditionContext, String property, Class targetType) { + return getProperty(conditionContext.getEnvironment(), property, targetType); + } + + public static T getProperty(ConditionContext conditionContext, String property, Class targetType, T defaultValue) { + return getProperty(conditionContext.getEnvironment(), property, targetType, defaultValue); + } + + public static boolean contains(Environment environment, String property) { + return environment.containsProperty(property); + } + + public static boolean contains(ConditionContext conditionContext, String property) { + return contains(conditionContext.getEnvironment(), property); + } + + /** + * 从条件上下文中获取Boolean类型值配置信息 + * + * @param environment Spring Boot ConditionContext {@link ConditionContext} + * @param property 配置名称 + * @param defaultValue 默认值 + * @return 配置属性值 + */ + public static boolean getBoolean(Environment environment, String property, boolean defaultValue) { + return getProperty(environment, property, Boolean.class, defaultValue); + } + + /** + * 从条件上下文中获取Boolean类型值配置信息 + * + * @param environment Spring Boot ConditionContext {@link ConditionContext} + * @param property 配置名称 + * @return 配置属性值 + */ + public static boolean getBoolean(Environment environment, String property) { + return getProperty(environment, property, Boolean.class, false); + } + + /** + * 从条件上下文中获取Boolean类型值配置信息 + * + * @param conditionContext Spring Boot ConditionContext {@link ConditionContext} + * @param property 配置名称 + * @return 配置属性值 + */ + public static boolean getBoolean(ConditionContext conditionContext, String property) { + return getProperty(conditionContext, property, Boolean.class, false); + } + + /** + * 从条件上下文中获取Boolean类型值配置信息 + * + * @param conditionContext Spring Boot ConditionContext {@link ConditionContext} + * @param property 配置名称 + * @param defaultValue 默认值 + * @return 配置属性值 + */ + public static boolean getBoolean(ConditionContext conditionContext, String property, boolean defaultValue) { + return getProperty(conditionContext, property, Boolean.class, defaultValue); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/TenantContextHolder.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/TenantContextHolder.java new file mode 100644 index 0000000..5f30986 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/context/TenantContextHolder.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.context; + +import com.alibaba.ttl.TransmittableThreadLocal; + +/** + *

Description: 存储/获取当前线程的租户信息

+ * + * @author : liuh + * @date : 2022/9/4 22:34 + */ +public class TenantContextHolder { + + private static final ThreadLocal CURRENT_CONTEXT = new TransmittableThreadLocal<>(); + + public static String getTenantId() { + return CURRENT_CONTEXT.get(); + } + + public static void setTenantId(final String tenantId) { + CURRENT_CONTEXT.set(tenantId); + } + + public static void clear() { + CURRENT_CONTEXT.remove(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/AbstractRest.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/AbstractRest.java new file mode 100644 index 0000000..d023674 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/AbstractRest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition; + +import cn.zhxu.okhttps.HTTP; +import cn.zhxu.okhttps.MsgConvertor; +import cn.zhxu.okhttps.jackson.JacksonMsgConvertor; + +/** + *

File: AbstractRestApiService

+ * + *

Description: 外部Rest API抽象服务

+ * + * @author : liuh + * @date : 2021/4/10 15:33 + */ +public abstract class AbstractRest { + + /** + * 获取外部Rest API基础地址 + * @return 访问接口的统一BaseURL + */ + protected abstract String getBaseUrl(); + + protected HTTP http() { + return HTTP.builder() + .baseUrl(getBaseUrl()) + .addMsgConvertor(getMsgConvertor()) + .build(); + } + + protected MsgConvertor getMsgConvertor() { + return new JacksonMsgConvertor(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/BearerTokenResolver.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/BearerTokenResolver.java new file mode 100644 index 0000000..d583fa6 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/BearerTokenResolver.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition; + +import com.liuhung.engine.assistant.core.domain.PrincipalDetails; + +/** + *

Description: 身份信息解析器

+ * + * @author : liuh + * @date : 2022/12/28 0:08 + */ +public interface BearerTokenResolver { + + PrincipalDetails resolve(String token); +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/StrategyEvent.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/StrategyEvent.java new file mode 100644 index 0000000..7811bcd --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/StrategyEvent.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition; + +import com.liuhung.engine.assistant.core.json.jackson2.utils.JacksonUtils; + +/** + *

Description: 策略 Event 定义

+ *

+ * 为了同时支持 分布式模式 和 单体式模式。所以很多事件均需要同时支持本地发送和远程发送两种模式。 + * 抽象本定义用于统一处理两种模式的事件支持。 + * + * @author : liuh + * @date : 2022/2/5 15:32 + */ +public interface StrategyEvent { + + /** + * 创建本地事件 + * + * @param data 事件携带数据 + */ + void postLocalProcess(T data); + + /** + * 创建远程事件 + * + * @param data 事件携带数据。JSON 格式的数据。 + * @param originService 发送远程事件原始服务 + * @param destinationService 接收远程事件目的地 + */ + void postRemoteProcess(String data, String originService, String destinationService); + + /** + * 是否是本地处理事件。 + * + * @param destinationService 接收远程事件目的地 + * @return false 远程事件,local 本地事件 + */ + boolean isLocal(String destinationService); + + /** + * 发送事件 + * + * @param data 事件携带数据 + * @param originService 发送远程事件原始服务 + * @param destinationService 接收远程事件目的地 + */ + default void postProcess(String originService, String destinationService, T data) { + if (isLocal(destinationService)) { + postLocalProcess(data); + } else { + postRemoteProcess(JacksonUtils.toJson(data), originService, destinationService); + } + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/BaseConstants.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/BaseConstants.java new file mode 100644 index 0000000..dea1a5a --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/BaseConstants.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.constants; + +/** + *

Description: 基础共用常量值常量

+ * + * @author : liuh + * @date : 2022/1/13 21:18 + */ +public interface BaseConstants { + + String NONE = "none"; + String CODE = "code"; + String LOWERCASE_DEFAULT = "default"; + + String DEFAULT_TENANT_ID = "master"; + String DEFAULT_TREE_ROOT_ID = "0"; + + /* ---------- 配置属性通用常量 ---------- */ + + String PROPERTY_ENABLED = ".enabled"; + + String PROPERTY_PREFIX_SPRING = "spring"; + String PROPERTY_PREFIX_FEIGN = "feign"; + String PROPERTY_PREFIX_SERVER = "server"; + String PROPERTY_PREFIX_HERODOTUS = "quafer"; + + String PROPERTY_SPRING_CLOUD = PROPERTY_PREFIX_SPRING + ".cloud"; + String PROPERTY_SPRING_JPA = PROPERTY_PREFIX_SPRING + ".jpa"; + String PROPERTY_SPRING_REDIS = PROPERTY_PREFIX_SPRING + ".redis"; + + String ANNOTATION_PREFIX = "${"; + String ANNOTATION_SUFFIX = "}"; + + /* ---------- Quafer 自定义配置属性 ---------- */ + + String PROPERTY_PREFIX_PLATFORM = PROPERTY_PREFIX_HERODOTUS + ".platform"; + String PROPERTY_PREFIX_SECURITY = PROPERTY_PREFIX_HERODOTUS + ".security"; + String PROPERTY_PREFIX_REST = PROPERTY_PREFIX_HERODOTUS + ".rest"; + String PROPERTY_PREFIX_SWAGGER = PROPERTY_PREFIX_HERODOTUS + ".swagger"; + + String PROPERTY_PREFIX_EVENT = PROPERTY_PREFIX_HERODOTUS + ".event"; + + String ITEM_SWAGGER_ENABLED = PROPERTY_PREFIX_SWAGGER + PROPERTY_ENABLED; + + + /* ---------- Spring 家族配置属性 ---------- */ + + String ITEM_SPRING_APPLICATION_NAME = PROPERTY_PREFIX_SPRING + ".application.name"; + String ITEM_SPRING_SESSION_STORE_TYPE = PROPERTY_PREFIX_SPRING + ".session.store-type"; + String ITEM_SERVER_PORT = PROPERTY_PREFIX_SERVER + ".port"; + + String ANNOTATION_APPLICATION_NAME = ANNOTATION_PREFIX + ITEM_SPRING_APPLICATION_NAME + ANNOTATION_SUFFIX; + + + /* ---------- 通用缓存常量 ---------- */ + + String CACHE_PREFIX = "cache:"; + + String CACHE_SIMPLE_BASE_PREFIX = CACHE_PREFIX + "simple:"; + String CACHE_TOKEN_BASE_PREFIX = CACHE_PREFIX + "token:"; + + String AREA_PREFIX = "data:upms:"; + + + /* ---------- Oauth2 和 Security 通用缓存常量 ---------- */ + + /** + * Oauth2 模式类型 + */ + String AUTHORIZATION_CODE = "authorization_code"; + String IMPLICIT = "implicit"; + String PASSWORD = "password"; + String CLIENT_CREDENTIALS = "client_credentials"; + String REFRESH_TOKEN = "refresh_token"; + String SOCIAL_CREDENTIALS = "social_credentials"; + + String DEFAULT_AUTHORIZATION_ENDPOINT = "/oauth2/authorize"; + String DEFAULT_TOKEN_ENDPOINT = "/oauth2/token"; + String DEFAULT_JWK_SET_ENDPOINT = "/oauth2/jwks"; + String DEFAULT_TOKEN_REVOCATION_ENDPOINT = "/oauth2/revoke"; + String DEFAULT_TOKEN_INTROSPECTION_ENDPOINT = "/oauth2/introspect"; + String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT = "/connect/register"; + String DEFAULT_OIDC_USER_INFO_ENDPOINT = "/userinfo"; + + String OPEN_API_SECURITY_SCHEME_BEARER_NAME = "HERODOTUS_AUTH"; + + String BEARER_TYPE = "Bearer"; + String BEARER_TOKEN = BEARER_TYPE + SymbolConstants.SPACE; + String BASIC_TYPE = "Basic"; + String BASIC_TOKEN = BASIC_TYPE + SymbolConstants.SPACE; + String ROLE_PREFIX = "ROLE_"; + String AUTHORITY_PREFIX = "OP_"; + + String AUTHORITIES = "authorities"; + String AVATAR = "avatar"; + String EMPLOYEE_ID = "employeeId"; + String LICENSE = "license"; + String OPEN_ID = "openid"; + String PRINCIPAL = "principal"; + String ROLES = "roles"; + String SOURCE = "source"; + String USERNAME = "username"; + + String SPRING_SESSION_ID_ATTR_NAME = "SPRING.SESSION.ID"; + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/ErrorCode.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/ErrorCode.java new file mode 100644 index 0000000..5c142f8 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/ErrorCode.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.constants; + +import org.apache.http.HttpStatus; + +/** + *

Description: 错误代码计数器

+ *

+ * 都需要手动控制错误代码,通过代码的继承关系,方便的查看以及编写。 + *

+ * 1** 信息,服务器收到请求,需要请求者继续执行操作 + * 2** 成功,操作被成功接收并处理 + * 3** 重定向,需要进一步的操作以完成请求 + * 4** 客户端错误,请求包含语法错误或无法完成请求 + * 5** 服务器错误,服务器在处理请求的过程中发生了错误 + *

+ * 1开头的状态码 + * 100 Continue 继续。客户端应继续其请求 + * 101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 + *

+ * 2开头的状态码 + * 200 OK 请求成功。一般用于GET与POST请求 + * 201 Created 已创建。成功请求并创建了新的资源 + * 202 Accepted 已接受。已经接受请求,但未处理完成 + * 203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 + * 204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 + * 205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 + * 206 Partial Content 部分内容。服务器成功处理了部分GET请求 + *

+ * 3开头的状态码 + * 300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 + * 301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 + * 302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI + * 303 See Other 查看其它地址。与301类似。使用GET和POST请求查看 + * 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 + * 305 Use Proxy 使用代理。所请求的资源必须通过代理访问 + * 306 Unused 已经被废弃的HTTP状态码 + * 307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向 + *

+ * 4开头的状态码 + * 400 Bad Request 客户端请求的语法错误,服务器无法理解 + * 401 Unauthorized 请求要求用户的身份认证 + * 402 Payment Required 保留,将来使用 + * 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 + * 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 + * 405 Method Not Allowed 客户端请求中的方法被禁止 + * 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 + * 407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 + * 408 Request Time-out 服务器等待客户端发送的请求时间过长,超时 + * 409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 + * 410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 + * 411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息 + * 412 Precondition Failed 客户端请求信息的先决条件错误 + * 413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 + * 414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理 + * 415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 + * 416 Requested range not satisfiable 客户端请求的范围无效 + * 417 Expectation Failed 服务器无法满足Expect的请求头信息 + *

+ * 5开头的状态码 + * 500 Internal Server Error 服务器内部错误,无法完成请求 + * 501 Not Implemented 服务器不支持请求的功能,无法完成请求 + * 502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 + * 503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 + * 504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 + * 505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理 + *

+ * + * @author : liuh + * @date : 2022/5/2 0:22 + */ +public interface ErrorCode { + + /* ---------- 401 ---------- */ + int UNAUTHORIZED = HttpStatus.SC_UNAUTHORIZED * 100; + int ACCESS_DENIED = UNAUTHORIZED + 1; + int ACCOUNT_DISABLED = ACCESS_DENIED + 1; + int ACCOUNT_ENDPOINT_LIMITED = ACCOUNT_DISABLED + 1; + + int ACCOUNT_EXPIRED = ACCOUNT_ENDPOINT_LIMITED + 1; + int ACCOUNT_LOCKED = ACCOUNT_EXPIRED + 1; + int BAD_CREDENTIALS = ACCOUNT_LOCKED + 1; + int CREDENTIALS_EXPIRED = BAD_CREDENTIALS + 1; + int INVALID_CLIENT = CREDENTIALS_EXPIRED + 1; + int INVALID_TOKEN = INVALID_CLIENT + 1; + int INVALID_GRANT = INVALID_TOKEN + 1; + int UNAUTHORIZED_CLIENT = INVALID_TOKEN + 1; + int USERNAME_NOT_FOUND = UNAUTHORIZED_CLIENT + 1; + int SESSION_EXPIRED = USERNAME_NOT_FOUND + 1; + + /* ---------- 403 ---------- */ + int FORBIDDEN = HttpStatus.SC_FORBIDDEN * 100; + int INSUFFICIENT_SCOPE = FORBIDDEN + 1; + int SQL_INJECTION_REQUEST = INSUFFICIENT_SCOPE + 1; + + /* ---------- 405 ---------- */ + int METHOD_NOT_ALLOWED = HttpStatus.SC_METHOD_NOT_ALLOWED * 100; + int HTTP_REQUEST_METHOD_NOT_SUPPORTED = METHOD_NOT_ALLOWED + 1; + + /* ---------- 406 ---------- */ + int NOT_ACCEPTABLE = HttpStatus.SC_NOT_ACCEPTABLE * 100; + int UNSUPPORTED_GRANT_TYPE = NOT_ACCEPTABLE + 1; + int UNSUPPORTED_RESPONSE_TYPE = UNSUPPORTED_GRANT_TYPE + 1; + int UNSUPPORTED_TOKEN_TYPE = UNSUPPORTED_RESPONSE_TYPE + 1; + + int CACHE_MODULE_406_BEGIN = UNSUPPORTED_TOKEN_TYPE; + int CACHE_MODULE_406_END = CACHE_MODULE_406_BEGIN + 4; + int REST_MODULE_406_BEGIN = CACHE_MODULE_406_END; + int REST_MODULE_406_END = CACHE_MODULE_406_BEGIN + 3; + + int MESSAGE_MODULE_406_BEGIN = REST_MODULE_406_END; + int MESSAGE_MODULE_406_END = MESSAGE_MODULE_406_BEGIN + 2; + + /* ---------- 412 ---------- */ + int PRECONDITION_FAILED = HttpStatus.SC_PRECONDITION_FAILED * 100; + int INVALID_REDIRECT_URI = PRECONDITION_FAILED + 1; + int INVALID_REQUEST = INVALID_REDIRECT_URI + 1; + int INVALID_SCOPE = INVALID_REQUEST + 1; + + int ACCESS_MODULE_406_BEGIN = INVALID_SCOPE; + + /* ---------- 415 ---------- */ + int UNSUPPORTED_MEDIA_TYPE = HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE * 100; + int HTTP_MEDIA_TYPE_NOT_ACCEPTABLE = UNSUPPORTED_MEDIA_TYPE + 1; + + /* ---------- 500 ---------- */ + int INTERNAL_SERVER_ERROR = HttpStatus.SC_INTERNAL_SERVER_ERROR * 100; + int SERVER_ERROR = INTERNAL_SERVER_ERROR + 1; + int HTTP_MESSAGE_NOT_READABLE_EXCEPTION = SERVER_ERROR + 1; + int ILLEGAL_ARGUMENT_EXCEPTION = HTTP_MESSAGE_NOT_READABLE_EXCEPTION + 1; + int IO_EXCEPTION = ILLEGAL_ARGUMENT_EXCEPTION + 1; + int MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION = IO_EXCEPTION + 1; + int NULL_POINTER_EXCEPTION = MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION + 1; + int TYPE_MISMATCH_EXCEPTION = NULL_POINTER_EXCEPTION + 1; + + int OSS_MODULE_500_BEGIN = TYPE_MISMATCH_EXCEPTION; + int OSS_MODULE_500_END = OSS_MODULE_500_BEGIN + 12; + int SMS_MODULE_500_BEGIN = OSS_MODULE_500_END; + int SMS_MODULE_500_END = OSS_MODULE_500_END + 2; + + /* ---------- 503 ---------- */ + int SERVICE_UNAVAILABLE = HttpStatus.SC_SERVICE_UNAVAILABLE * 100; + int COOKIE_THEFT = SERVICE_UNAVAILABLE + 1; + int INVALID_COOKIE = COOKIE_THEFT + 1; + int PROVIDER_NOT_FOUND = INVALID_COOKIE + 1; + int TEMPORARILY_UNAVAILABLE = PROVIDER_NOT_FOUND + 1; + + int WEB_MODULE_503_BEGIN = TEMPORARILY_UNAVAILABLE; + int WEB_MODULE_503_END = WEB_MODULE_503_BEGIN + 1; + + /* ---------- 600 ---------- */ + int DATABASE = 60000; + + /* ---------- 601 数据库操作运行前校验 ---------- */ + int DATABASE_VALIDATION = DATABASE + 100; + int METHOD_ARGUMENT_NOT_VALID = DATABASE_VALIDATION + 1; + + /* ---------- 602 数据库操作执行 ---------- */ + int DATABASE_EXECUTION = DATABASE_VALIDATION + 100; + int BAD_SQL_GRAMMAR = DATABASE_EXECUTION + 1; + int DATA_INTEGRITY_VIOLATION = BAD_SQL_GRAMMAR + 1; + int TRANSACTION_ROLLBACK = DATA_INTEGRITY_VIOLATION + 1; + + /* ---------- 700 基础设施交互错误 ---------- */ + int BASIC_FACILITIES = DATABASE + 10000; + + /* ---------- 701 Redis 相关错误 ---------- */ + int REDIS = BASIC_FACILITIES + 100; + int PIPELINE_INVALID_COMMANDS = REDIS + 1; +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/HttpHeaders.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/HttpHeaders.java new file mode 100644 index 0000000..443fabd --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/HttpHeaders.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.constants; + +/** + *

Description: 自定义请求头

+ * + * @author : liuh + * @date : 2021/10/4 16:01 + */ +public interface HttpHeaders { + + String UNKNOWN = "unknown"; + String PROXY_CLIENT_IP = "Proxy-Client-IP"; + String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP"; + String HTTP_CLIENT_IP = "HTTP_CLIENT_IP"; + String HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"; + String X_REAL_IP = "X-Real-IP"; + String X_HERODOTUS_SESSION = "X-Quafer-Session"; + String X_HERODOTUS_FROM_IN = "X-Quafer-From-In"; + String X_HERODOTUS_TENANT_ID = "X-Quafer-Tenant-Id"; + String X_HERODOTUS_OPEN_ID = "X-Quafer-Open-Id"; +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/Sandbox.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/Sandbox.java new file mode 100644 index 0000000..3a44ef7 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/Sandbox.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.constants; + +/** + *

Description: 统一的 Sandbox 管理

+ * + * @author : liuh + * @date : 2022/1/7 20:36 + */ +public class Sandbox { + + /** + * 支付宝网关地址 + */ + private static final String ALIPAY_PRODUCTION_SERVER_URL = "https://openapi.alipay.com/gateway.do"; + private static final String ALIPAY_SANDBOX_SERVER_URL = "https://openapi.alipaydev.com/gateway.do"; + + public static String getAliPayServerUrl(boolean sandbox) { + return sandbox ? ALIPAY_SANDBOX_SERVER_URL : ALIPAY_PRODUCTION_SERVER_URL; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/SymbolConstants.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/SymbolConstants.java new file mode 100644 index 0000000..2818d42 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/constants/SymbolConstants.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.constants; + +/** + * @author liuh + */ +public interface SymbolConstants { + + String AMPERSAND = "&"; + + String AMPERSAND_ENCODED = "&"; + + String APOSTROPHE = "'"; + + String APOSTROPHE_AND_COMMA = "',"; + + String APOSTROPHE_AND_COMMA_AND_APOSTROPHE = "','"; + + String AT = "@"; + + String BACK_SLASH = "\\"; + + String BETWEEN = "BETWEEN"; + + String BLANK = ""; + + String CDATA_OPEN = ""; + + String CLOSE_BRACKET = "]"; + + String CLOSE_CURLY_BRACE = "}"; + + String CLOSE_PARENTHESIS = ")"; + + String COLON = ":"; + + String COMMA = ","; + + String COMMA_AND_APOSTROPHE = ",'"; + + String COMMA_AND_SPACE = ", "; + + String DASH = "-"; + + String DOUBLE_APOSTROPHE = "''"; + + String DOUBLE_CLOSE_BRACKET = "]]"; + + String DOUBLE_OPEN_BRACKET = "[["; + + String DOUBLE_SLASH = "//"; + + String EQUAL = "="; + + String GREATER_THAN = ">"; + + String GREATER_THAN_OR_EQUAL = ">="; + + String FORWARD_SLASH = "/"; + + String FOUR_SPACES = " "; + + String FINISH_LEFT_ANGLE = ""; + + String GBK = "GBK"; + + String IS_NOT_NULL = "IS NOT NULL"; + + String IS_NULL = "IS NULL"; + + String IN = "IN"; + + String LEFT_ANGLE = "<"; + + String LESS_THAN = "<"; + + String LESS_THAN_OR_EQUAL = "<="; + + String LIKE = "LIKE"; + + String MINUS = "-"; + + String NBSP = " "; + + String NEW_LINE = "\n"; + + String NOT_EQUAL = "!="; + + String DB_NOT_EQUAL = "<>"; + + String NOT_LIKE = "NOT LIKE"; + + String NULL = "null"; + + String OPEN_BRACKET = "["; + + String OPEN_CURLY_BRACE = "{"; + + String OPEN_PARENTHESIS = "("; + + String PERCENT = "%"; + + String PERIOD = "."; + + String PIPE = "|"; + + String PLUS = "+"; + + String POUND = "#"; + + String QUESTION = "?"; + + String QUOTE = "\""; + + String RETURN = "\r"; + + String RETURN_NEW_LINE = "\r\n"; + + String RIGHT_ANGLE = ">"; + + String SEMICOLON = ";"; + + String SLASH = FORWARD_SLASH; + + String SPACE = " "; + + String STAR = "*"; + + String TAB = "\t"; + + String TILDE = "~"; + + String UNDERLINE = "_"; + + String SUFFIX_EXCEL_2003 = ".xls"; + + String SUFFIX_EXCEL_2007 = ".xlsx"; + + String SUFFIX_JPEG = ".jpg"; + + String SUFFIX_XML = ".xml"; + + String SUFFIX_PDF = ".pdf"; + + String SUFFIX_ZIP = ".zip"; + + String SUFFIX_DOC = ".doc"; + + String SUFFIX_DOCX = ".docx"; + + String SUFFIX_PPT = ".ppt"; + + String SUFFIX_PPTX = ".pptx"; + + String SUFFIX_EXCEL = ".xls"; + + String SUFFIX_EXCELX = ".xlsx"; + + String SUFFIX_SWF = ".swf"; + + String SUFFIX_PROPERTIES = ".properties"; + + String SUFFIX_YML = ".yml"; + + String SUFFIX_YAML = ".yaml"; + + String SUFFIX_JSON = ".json"; + + String XML_DECLARATION = ""; +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/AbstractDto.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/AbstractDto.java new file mode 100644 index 0000000..7818328 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/AbstractDto.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.domain; + +/** + *

Description: AbstractDto

+ * + * @author : liuh + * @date : 2022/3/18 15:40 + */ +public abstract class AbstractDto implements Entity { +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/AbstractEntity.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/AbstractEntity.java new file mode 100644 index 0000000..09f9e0f --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/AbstractEntity.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.domain; + + +/** + *

Description : BaseCacheEntity

+ * + * @author : liuh + * @date : 2020/2/24 16:38 + */ +public abstract class AbstractEntity implements Entity { + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/Entity.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/Entity.java new file mode 100644 index 0000000..2d792c8 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/domain/Entity.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.domain; + +import java.io.Serializable; + +/** + *

Description: BaseEntity

+ * + * @author : liuh + * @date : 2020/4/29 17:27 + */ +public interface Entity extends Serializable { + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/BaseFactoryEnum.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/BaseFactoryEnum.java new file mode 100644 index 0000000..1041f9e --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/BaseFactoryEnum.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.enums; + +/** + *

Description: 基础工厂枚举

+ * + * @author : liuh + * @date : 2022/5/1 19:48 + */ +public interface BaseFactoryEnum extends EnumValue, EnumDescription{ +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/BaseUiEnum.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/BaseUiEnum.java new file mode 100644 index 0000000..0bbf9b1 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/BaseUiEnum.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.enums; + +/** + *

Description: 基础枚举定义

+ * + * @author : liuh + * @date : 2022/3/26 16:52 + */ +public interface BaseUiEnum extends EnumValue, EnumDescription { + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/EnumDescription.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/EnumDescription.java new file mode 100644 index 0000000..2d331b5 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/EnumDescription.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.enums; + +/** + *

Description: 枚举文字表述

+ * + * @author : liuh + * @date : 2022/3/26 16:50 + */ +public interface EnumDescription { + + /** + * 获取枚举文字表述 + * + * @return 枚举文字表述 + */ + String getDescription(); +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/EnumValue.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/EnumValue.java new file mode 100644 index 0000000..d8fdcfd --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/enums/EnumValue.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.enums; + +/** + *

Description: 枚举值定义

+ * + * @author : liuh + * @date : 2022/3/26 16:49 + */ +public interface EnumValue { + + /** + * 获取枚举自定义值 + * + * @return 自定义枚举值 + */ + T getValue(); +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/exception/AbstractQuaferException.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/exception/AbstractQuaferException.java new file mode 100644 index 0000000..c9e313a --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/exception/AbstractQuaferException.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.exception; + +import com.liuhung.engine.assistant.core.domain.Result; + +/** + *

Description: 自定义错误基础类

+ * + * @author : liuh + * @date : 2022/3/4 18:31 + */ +public abstract class AbstractQuaferException extends RuntimeException implements QuaferException { + + public AbstractQuaferException() { + super(); + } + + public AbstractQuaferException(String message) { + super(message); + } + + public AbstractQuaferException(String message, Throwable cause) { + super(message, cause); + } + + public AbstractQuaferException(Throwable cause) { + super(cause); + } + + protected AbstractQuaferException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Result getResult() { + Result result = Result.failure(); + result.code(getFeedback().getCode()); + result.message(getFeedback().getMessage()); + result.status(getFeedback().getStatus()); + result.stackTrace(super.getStackTrace()); + result.detail(super.getMessage()); + return result; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/exception/QuaferException.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/exception/QuaferException.java new file mode 100644 index 0000000..6baf106 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/definition/exception/QuaferException.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.definition.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.domain.Result; + +/** + *

Description: 核心 QuaferException 定义

+ * + * @author : liuh + * @date : 2022/3/8 9:06 + */ +public interface QuaferException { + + /** + * 获取反馈信息 + * + * @return 反馈信息对象 {@link Feedback} + */ + Feedback getFeedback(); + + /** + * 错误信息转换为 Result 对象。 + * + * @return 结果对象 {@link Result} + */ + Result getResult(); +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/AccessPrincipal.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/AccessPrincipal.java new file mode 100644 index 0000000..1d26962 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/AccessPrincipal.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.domain; + +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + *

Description: 外部程序接入必要参数

+ * + * @author : liuh + * @date : 2022/1/25 16:53 + */ +public class AccessPrincipal { + + /* ---------- 共性参数 ---------- */ + + @Schema(name = "后回调时带的参数code", title = "访问AuthorizeUrl后回调时带的参数code") + private String code; + + /* ---------- 微信小程序常用参数 ---------- */ + + @Schema(name = "小程序appId", title = "小程序appId") + private String appId; + + @Schema(name = "消息密文", title = "微信小程序消息密文") + private String encryptedData; + + @Schema(name = "加密算法的初始向量", title = "微信小程序加密算法的初始向量") + private String iv; + + @Schema(name = "小程序用户openId", title = "小程序用户openId") + private String openId; + + @Schema(name = "会话密钥", title = "微信小程序会话密钥") + private String sessionKey; + + @Schema(name = "唯一ID", title = "微信唯一ID") + private String unionId; + + @Schema(name = "用户非敏感信息", title = "微信小程序用户非敏感信息") + private String rawData; + + @Schema(name = "签名", title = "微信小程序签名") + private String signature; + + /* ---------- Just Auth 标准参数 ---------- */ + + @Schema(name = "后回调时带的参数auth_code", title = "该参数目前只使用于支付宝登录") + private String auth_code; + + @Schema(name = "后回调时带的参数state", title = "用于和请求AuthorizeUrl前的state比较,防止CSRF攻击") + private String state; + + @Schema(name = "华为授权登录接受code的参数名") + private String authorization_code; + + @Schema(name = "回调后返回的oauth_token", title = "Twitter回调后返回的oauth_token") + private String oauth_token; + + @Schema(name = "回调后返回的oauth_verifier", title = "Twitter回调后返回的oauth_verifier") + private String oauth_verifier; + + /* ---------- 手机短信验证码 ---------- */ + + @Schema(name = "手机号码", title = "手机短信登录唯一标识") + private String mobile; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getEncryptedData() { + return encryptedData; + } + + public void setEncryptedData(String encryptedData) { + this.encryptedData = encryptedData; + } + + public String getIv() { + return iv; + } + + public void setIv(String iv) { + this.iv = iv; + } + + public String getOpenId() { + return openId; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getSessionKey() { + return sessionKey; + } + + public void setSessionKey(String sessionKey) { + this.sessionKey = sessionKey; + } + + public String getUnionId() { + return unionId; + } + + public void setUnionId(String unionId) { + this.unionId = unionId; + } + + public String getRawData() { + return rawData; + } + + public void setRawData(String rawData) { + this.rawData = rawData; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public String getAuth_code() { + return auth_code; + } + + public void setAuth_code(String auth_code) { + this.auth_code = auth_code; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getAuthorization_code() { + return authorization_code; + } + + public void setAuthorization_code(String authorization_code) { + this.authorization_code = authorization_code; + } + + public String getOauth_token() { + return oauth_token; + } + + public void setOauth_token(String oauth_token) { + this.oauth_token = oauth_token; + } + + public String getOauth_verifier() { + return oauth_verifier; + } + + public void setOauth_verifier(String oauth_verifier) { + this.oauth_verifier = oauth_verifier; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("code", code) + .add("appId", appId) + .add("encryptedData", encryptedData) + .add("iv", iv) + .add("openId", openId) + .add("sessionKey", sessionKey) + .add("unionId", unionId) + .add("rawData", rawData) + .add("signature", signature) + .add("auth_code", auth_code) + .add("state", state) + .add("authorization_code", authorization_code) + .add("oauth_token", oauth_token) + .add("oauth_verifier", oauth_verifier) + .add("mobile", mobile) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Error.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Error.java new file mode 100644 index 0000000..fac74de --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Error.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.domain; + +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; + +/** + *

Description: 错误详情

+ * + * @author : liuh + * @date : 2021/8/18 15:48 + */ +@Schema(title = "响应错误详情", description = "为兼容Validation而增加的Validation错误信息实体") +public class Error implements Serializable { + + @Schema(title = "Exception完整信息", type = "string") + private String detail; + + @Schema(title = "额外的错误信息,目前主要是Validation的Message") + private String message; + + @Schema(title = "额外的错误代码,目前主要是Validation的Code") + private String code; + + @Schema(title = "额外的错误字段,目前主要是Validation的Field") + private String field; + + @Schema(title = "错误堆栈信息") + private StackTraceElement[] stackTrace; + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public StackTraceElement[] getStackTrace() { + return stackTrace; + } + + public void setStackTrace(StackTraceElement[] stackTrace) { + this.stackTrace = stackTrace; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("detail", detail) + .add("message", message) + .add("code", code) + .add("field", field) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Feedback.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Feedback.java new file mode 100644 index 0000000..d04ea13 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Feedback.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.domain; + +import com.liuhung.engine.assistant.core.enums.ResultErrorCodes; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import org.apache.http.HttpStatus; + +/** + *

Description: QuaferException 交互信息

+ *

+ * 主要用于自定义 QuaferException 中,显示用户体验更好的错误信息。同时可以解决更好的分包,而不再需要统一在 ResultStatus里面定义。 + * 非自定义 QuaferException 还是在 ResultErrorCodes 中定义。 + *

+ * 1** 信息,服务器收到请求,需要请求者继续执行操作 + * 2** 成功,操作被成功接收并处理 + * 3** 重定向,需要进一步的操作以完成请求 + * 4** 客户端错误,请求包含语法错误或无法完成请求 + * 5** 服务器错误,服务器在处理请求的过程中发生了错误 + *

+ * 1开头的状态码 + * 100 Continue 继续。客户端应继续其请求 + * 101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 + *

+ * 2开头的状态码 + * 200 OK 请求成功。一般用于GET与POST请求 + * 201 Created 已创建。成功请求并创建了新的资源 + * 202 Accepted 已接受。已经接受请求,但未处理完成 + * 203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 + * 204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 + * 205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 + * 206 Partial Content 部分内容。服务器成功处理了部分GET请求 + *

+ * 3开头的状态码 + * 300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 + * 301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 + * 302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI + * 303 See Other 查看其它地址。与301类似。使用GET和POST请求查看 + * 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 + * 305 Use Proxy 使用代理。所请求的资源必须通过代理访问 + * 306 Unused 已经被废弃的HTTP状态码 + * 307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向 + *

+ * 4开头的状态码 + * 400 Bad Request 客户端请求的语法错误,服务器无法理解 + * 401 Unauthorized 请求要求用户的身份认证 + * 402 Payment Required 保留,将来使用 + * 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 + * 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 + * 405 Method Not Allowed 客户端请求中的方法被禁止 + * 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 + * 407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 + * 408 Request Time-out 服务器等待客户端发送的请求时间过长,超时 + * 409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 + * 410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 + * 411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息 + * 412 Precondition Failed 客户端请求信息的先决条件错误 + * 413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 + * 414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理 + * 415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 + * 416 Requested range not satisfiable 客户端请求的范围无效 + * 417 Expectation Failed 服务器无法满足Expect的请求头信息 + *

+ * 5开头的状态码 + * 500 Internal Server Error 服务器内部错误,无法完成请求 + * 501 Not Implemented 服务器不支持请求的功能,无法完成请求 + * 502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 + * 503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 + * 504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 + * 505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理 + * + * @author : liuh + * @date : 2022/3/4 17:35 + */ +public class Feedback { + + public static final Feedback OK = new Feedback(20000, "成功", HttpStatus.SC_OK); + public static final Feedback NO_CONTENT = new Feedback(20400, "无内容", HttpStatus.SC_NO_CONTENT); + public static final Feedback ERROR = new Feedback(50000, "服务器内部错误,无法完成请求", HttpStatus.SC_INTERNAL_SERVER_ERROR); + + /** + * 自定义错误代码 + */ + private final int code; + + /** + * 用户友好的错误信息 + */ + private final String message; + + /** + * 对应 Http 请求状态码 + */ + private final int status; + + public Feedback(ResultErrorCodes resultErrorCodes, int status) { + this(resultErrorCodes.getCode(), resultErrorCodes.getMessage(), status); + } + + public Feedback(int code, String message, int status) { + this.code = code; + this.message = message; + this.status = status; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public int getStatus() { + return status; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Feedback that = (Feedback) o; + return code == that.code && status == that.status; + } + + @Override + public int hashCode() { + return Objects.hashCode(code, status); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("code", code) + .add("message", message) + .add("status", status) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/PrincipalDetails.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/PrincipalDetails.java new file mode 100644 index 0000000..a4a0a68 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/PrincipalDetails.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.domain; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + *

Description: 用户登录额外信息

+ * + * @author : liuh + * @date : 2022/7/13 14:31 + */ +public class PrincipalDetails { + + private String openId; + + private String userName; + + private Set roles; + + private String employeeId; + + private String avatar; + + public String getOpenId() { + return openId; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public String getEmployeeId() { + return employeeId; + } + + public void setEmployeeId(String employeeId) { + this.employeeId = employeeId; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put(BaseConstants.OPEN_ID, this.openId); + map.put(BaseConstants.USERNAME, this.userName); + map.put(BaseConstants.ROLES, this.roles); + map.put(BaseConstants.EMPLOYEE_ID, this.employeeId); + map.put(BaseConstants.AVATAR, this.avatar); + return map; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PrincipalDetails that = (PrincipalDetails) o; + return Objects.equal(openId, that.openId); + } + + @Override + public int hashCode() { + return Objects.hashCode(openId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("openId", openId) + .add("userName", userName) + .add("roles", roles) + .add("employeeId", employeeId) + .add("avatar", avatar) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Result.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Result.java new file mode 100644 index 0000000..65e935d --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/Result.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.domain; + + +import com.liuhung.engine.assistant.core.enums.ResultErrorCodes; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + *

Description: 统一响应实体

+ * + * @author : liuh + * @date : 2020/2/29 14:50 + */ +@Schema(title = "统一响应返回实体", description = "所有Rest接口统一返回的实体定义", example = "new Result().ok().message(\"XXX\")") +public class Result implements Serializable { + + @Schema(title = "自定义响应编码") + private int code = 0; + + @Schema(title = "响应返回信息") + private String message; + + @Schema(title = "请求路径") + private String path; + + @Schema(title = "响应返回数据") + private T data; + + @Schema(title = "http状态码") + private int status; + + @Schema(title = "链路 TraceId") + private String traceId = TraceContext.traceId(); + + @Schema(title = "响应时间戳", pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date timestamp = new Date(); + + @Schema(title = "校验错误信息") + private Error error = new Error(); + + public Result() { + super(); + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public String getPath() { + return path; + } + + public String getTraceId() { + return traceId; + } + + public T getData() { + return data; + } + + public int getStatus() { + return status; + } + + public Date getTimestamp() { + return timestamp; + } + + public Error getError() { + return error; + } + + public Result code(int code) { + this.code = code; + return this; + } + + public Result message(String message) { + this.message = message; + return this; + } + + public Result data(T data) { + this.data = data; + return this; + } + + public Result path(String path) { + this.path = path; + return this; + } + + public Result type(ResultErrorCodes resultErrorCodes) { + this.code = resultErrorCodes.getCode(); + this.message = resultErrorCodes.getMessage(); + return this; + } + + public Result traceId(String traceId) { + this.traceId = traceId; + return this; + } + + public Result status(int httpStatus) { + this.status = httpStatus; + return this; + } + + public Result stackTrace(StackTraceElement[] stackTrace) { + this.error.setStackTrace(stackTrace); + return this; + } + + public Result detail(String detail) { + this.error.setDetail(detail); + return this; + } + + public Result validation(String message, String code, String field) { + this.error.setMessage(message); + this.error.setCode(code); + this.error.setField(field); + return this; + } + + private static Result create(String message, String detail, int code, int status, T data, StackTraceElement[] stackTrace) { + Result result = new Result<>(); + if (StringUtils.isNotBlank(message)) { + result.message(message); + } + + if (StringUtils.isNotBlank(detail)) { + result.detail(detail); + } + + result.code(code); + result.status(status); + + if (ObjectUtils.isNotEmpty(data)) { + result.data(data); + } + + if (ArrayUtils.isNotEmpty(stackTrace)) { + result.stackTrace(stackTrace); + } + + return result; + } + + public static Result success(String message, int code, int status, T data) { + return create(message, null, code, status, data, null); + } + + public static Result success(String message, int code, T data) { + return success(message, code, HttpStatus.SC_OK, data); + } + + public static Result success(ResultErrorCodes resultErrorCodes, T data) { + return success(resultErrorCodes.getMessage(), resultErrorCodes.getCode(), data); + } + + public static Result success(Feedback feedback, T data) { + return success(feedback.getMessage(), feedback.getCode(), feedback.getStatus(), data); + } + + public static Result success(String message, T data) { + return success(message, Feedback.OK.getCode(), data); + } + + public static Result success(String message) { + return success(message, null); + } + + public static Result success() { + return success("操作成功!"); + } + + public static Result content(T data) { + return success("操作成功!", data); + } + + public static Result failure(String message, String detail, int code, int status, T data, StackTraceElement[] stackTrace) { + return create(message, detail, code, status, data, stackTrace); + } + + public static Result failure(String message, String detail, int code, int status, T data) { + return failure(message, detail, code, status, data, null); + } + + public static Result failure(String message, int code, int status, T data) { + return failure(message, message, code, status, data); + } + + public static Result failure(String message, String detail, int code, T data) { + return failure(message, detail, code, HttpStatus.SC_INTERNAL_SERVER_ERROR, data); + } + + public static Result failure(String message, int code, T data) { + return failure(message, message, code, data); + } + + public static Result failure(ResultErrorCodes resultErrorCodes, T data) { + return failure(resultErrorCodes.getMessage(), resultErrorCodes.getCode(), data); + } + + public static Result failure(Feedback feedback, T data) { + return failure(feedback.getMessage(), feedback.getCode(), feedback.getStatus(), data); + } + + public static Result failure(String message, T data) { + return failure(message, Feedback.ERROR.getCode(), data); + } + + public static Result failure(String message) { + return failure(message, null); + } + + public static Result failure() { + return failure("操作失败!"); + } + + public static Result empty(String message, int code, int status) { + return create(message, null, code, status, null, null); + } + + public static Result empty(String message, int code) { + return empty(message, code, Feedback.NO_CONTENT.getStatus()); + } + + public static Result empty(Feedback feedback) { + return empty(feedback.getMessage(), feedback.getCode(), feedback.getStatus()); + } + + public static Result empty(ResultErrorCodes resultErrorCodes) { + return empty(resultErrorCodes.getMessage(), resultErrorCodes.getCode()); + } + + public static Result empty(String message) { + return empty(message, Feedback.NO_CONTENT.getCode()); + } + + public static Result empty() { + return empty("未查询到相关内容!"); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("code", code) + .add("message", message) + .add("path", path) + .add("data", data) + .add("status", status) + .add("timestamp", timestamp) + .add("error", error) + .add("traceId", traceId) + .toString(); + } + + public Map toModel() { + Map result = new HashMap<>(8); + result.put("code", code); + result.put("message", message); + result.put("path", path); + result.put("data", data); + result.put("status", status); + result.put("timestamp", timestamp); + result.put("error", error); + result.put("traceId", traceId); + return result; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/SecretKey.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/SecretKey.java new file mode 100644 index 0000000..9900be2 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/domain/SecretKey.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.domain; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + *

Description: 秘钥缓存存储实体

+ * + * @author : liuh + * @date : 2021/9/30 18:17 + */ +public class SecretKey implements Serializable { + + /** + * 数据存储身份标识 + */ + private String identity; + /** + * 对称加密算法秘钥 + */ + private String symmetricKey; + + /** + * 服务器端非对称加密算法公钥 + * 1. RSA 为 Base64 格式 + * 2. SM2 为 Hex 格式 + */ + private String publicKey; + + /** + * 服务器端非对称加密算法私钥 + */ + private String privateKey; + + /** + * 本系统授权码模式中后台返回的 State + */ + private String state; + + /** + * 创建时间戳 + */ + private Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + + public SecretKey() { + } + + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + } + + public String getSymmetricKey() { + return symmetricKey; + } + + public void setSymmetricKey(String symmetricKey) { + this.symmetricKey = symmetricKey; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public Timestamp getTimestamp() { + return timestamp; + } + + public void setTimestamp(Timestamp timestamp) { + this.timestamp = timestamp; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SecretKey secretKey = (SecretKey) o; + return Objects.equal(identity, secretKey.identity) && Objects.equal(timestamp, secretKey.timestamp); + } + + @Override + public int hashCode() { + return Objects.hashCode(identity, timestamp); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("identity", identity) + .add("symmetricKey", symmetricKey) + .add("publicKey", publicKey) + .add("privateKey", privateKey) + .add("state", state) + .add("timestamp", timestamp) + .toString(); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/AccountType.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/AccountType.java new file mode 100644 index 0000000..676feba --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/AccountType.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; +import me.zhyd.oauth.config.AuthDefaultSource; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author liuh + * 登录类型:password-密码、mobile-手机号、email-邮箱、weixin-微信、weibo-微博、qq-等等 + */ +@Schema(title = "账号类型") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AccountType { + + /** + * 登录类型 + */ + INSTITUTION("INSTITUTION", "", "机构人员"), + SMS("SMS", AccountType.PHONE_NUMBER_HANDLER, "手机验证码"), + WXAPP("WXAPP", AccountType.WECHAT_MINI_APP_HANDLER, "微信小程序"), + QQ(AuthDefaultSource.QQ.name(), AccountType.JUST_AUTH_HANDLER, "QQ"), + WEIBO(AuthDefaultSource.WEIBO.name(), AccountType.JUST_AUTH_HANDLER, "微博"), + BAIDU(AuthDefaultSource.BAIDU.name(), AccountType.JUST_AUTH_HANDLER, "百度"), + WECHAT_OPEN(AuthDefaultSource.WECHAT_OPEN.name(), AccountType.JUST_AUTH_HANDLER, "微信开放平台"), + WECHAT_MP(AuthDefaultSource.WECHAT_MP.name(), AccountType.JUST_AUTH_HANDLER, "微信公众号"), + WECHAT_ENTERPRISE(AuthDefaultSource.WECHAT_ENTERPRISE.name(), AccountType.JUST_AUTH_HANDLER, "企业微信二维码"), + WECHAT_ENTERPRISE_WEB(AuthDefaultSource.WECHAT_ENTERPRISE_WEB.name(), AccountType.JUST_AUTH_HANDLER, "企业微信网页"), + DINGTALK(AuthDefaultSource.DINGTALK.name(), AccountType.JUST_AUTH_HANDLER, "钉钉"), + DINGTALK_ACCOUNT(AuthDefaultSource.DINGTALK_ACCOUNT.name(), AccountType.JUST_AUTH_HANDLER, "钉钉账号"), + ALIYUN(AuthDefaultSource.ALIYUN.name(), AccountType.JUST_AUTH_HANDLER, "阿里云"), + TAOBAO(AuthDefaultSource.TAOBAO.name(), AccountType.JUST_AUTH_HANDLER, "淘宝"), + ALIPAY(AuthDefaultSource.ALIPAY.name(), AccountType.JUST_AUTH_HANDLER, "支付宝"), + TEAMBITION(AuthDefaultSource.TEAMBITION.name(), AccountType.JUST_AUTH_HANDLER, "Teambition"), + HUAWEI(AuthDefaultSource.HUAWEI.name(), AccountType.JUST_AUTH_HANDLER, "华为"), + FEISHU(AuthDefaultSource.FEISHU.name(), AccountType.JUST_AUTH_HANDLER, "飞书"), + JD(AuthDefaultSource.JD.name(), AccountType.JUST_AUTH_HANDLER, "京东"), + DOUYIN(AuthDefaultSource.DOUYIN.name(), AccountType.JUST_AUTH_HANDLER, "抖音"), + TOUTIAO(AuthDefaultSource.TOUTIAO.name(), AccountType.JUST_AUTH_HANDLER, "今日头条"), + MI(AuthDefaultSource.MI.name(), AccountType.JUST_AUTH_HANDLER, "小米"), + RENREN(AuthDefaultSource.RENREN.name(), AccountType.JUST_AUTH_HANDLER, "人人"), + MEITUAN(AuthDefaultSource.MEITUAN.name(), AccountType.JUST_AUTH_HANDLER, "美团"), + ELEME(AuthDefaultSource.ELEME.name(), AccountType.JUST_AUTH_HANDLER, "饿了么"), + KUJIALE(AuthDefaultSource.KUJIALE.name(), AccountType.JUST_AUTH_HANDLER, "酷家乐"), + XMLY(AuthDefaultSource.XMLY.name(), AccountType.JUST_AUTH_HANDLER, "喜马拉雅"), + GITEE(AuthDefaultSource.GITEE.name(), AccountType.JUST_AUTH_HANDLER, "码云"), + OSCHINA(AuthDefaultSource.OSCHINA.name(), AccountType.JUST_AUTH_HANDLER, "开源中国"), + CSDN(AuthDefaultSource.CSDN.name(), AccountType.JUST_AUTH_HANDLER, "CSDN"), + GITHUB(AuthDefaultSource.GITHUB.name(), AccountType.JUST_AUTH_HANDLER, "Github"), + GITLAB(AuthDefaultSource.GITLAB.name(), AccountType.JUST_AUTH_HANDLER, "Gitlab"), + STACK_OVERFLOW(AuthDefaultSource.STACK_OVERFLOW.name(), AccountType.JUST_AUTH_HANDLER, "Stackoverflow"), + CODING(AuthDefaultSource.CODING.name(), AccountType.JUST_AUTH_HANDLER, "Coding"), + GOOGLE(AuthDefaultSource.GOOGLE.name(), AccountType.JUST_AUTH_HANDLER, "谷歌"), + MICROSOFT(AuthDefaultSource.MICROSOFT.name(), AccountType.JUST_AUTH_HANDLER, "微软"), + FACEBOOK(AuthDefaultSource.FACEBOOK.name(), AccountType.JUST_AUTH_HANDLER, "脸书"), + LINKEDIN(AuthDefaultSource.LINKEDIN.name(), AccountType.JUST_AUTH_HANDLER, "领英"), + TWITTER(AuthDefaultSource.TWITTER.name(), AccountType.JUST_AUTH_HANDLER, "推特"), + AMAZON(AuthDefaultSource.AMAZON.name(), AccountType.JUST_AUTH_HANDLER, "亚马逊"), + SLACK(AuthDefaultSource.SLACK.name(), AccountType.JUST_AUTH_HANDLER, "Slack"), + LINE(AuthDefaultSource.LINE.name(), AccountType.JUST_AUTH_HANDLER, "Line"), + OKTA(AuthDefaultSource.OKTA.name(), AccountType.JUST_AUTH_HANDLER, "Okta"), + PINTEREST(AuthDefaultSource.PINTEREST.name(), AccountType.JUST_AUTH_HANDLER, "Pinterest"); + + @Schema(title = "枚举值") + private final String key; + @Schema(title = "处理器") + private final String handler; + @Schema(title = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCT = new ArrayList<>(); + + static { + for (AccountType accountType : AccountType.values()) { + INDEX_MAP.put(accountType.getKey(), accountType); + JSON_STRUCT.add(accountType.ordinal(), + ImmutableMap.builder() + .put("value", accountType.ordinal()) + .put("key", accountType.name()) + .put("text", accountType.getDescription()) + .build()); + } + } + + /** + * Just Auth 第三方社交登录 + */ + public static final String JUST_AUTH_HANDLER = "JUST_AUTH"; + + /** + * 手机号验证码登录 + */ + public static final String PHONE_NUMBER_HANDLER = "PHONE_NUMBER"; + + /** + * 手机号验证码登录 + */ + public static final String WECHAT_MINI_APP_HANDLER = "WECHAT_MINI_APP"; + + AccountType(String key, String handler, String description) { + this.key = key; + this.handler = handler; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + public String getKey() { + return key; + } + + public String getDescription() { + return this.description; + } + + public String getHandler() { + return handler; + } + + public static AccountType getAccountType(String key) { + return INDEX_MAP.get(key); + } + + public static List> getJsonStruct() { + return JSON_STRUCT; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/AuthorityType.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/AuthorityType.java new file mode 100644 index 0000000..7e598d8 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/AuthorityType.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 权限资源类型

+ * + * @author : liuh + * @date : 2019/11/25 15:10 + */ +@Schema(title = "权限类型") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AuthorityType { + + /** + * enum + */ + API(0, "REST API"), + MENU(1, "功能菜单"), + PAGE(2, "Web Page"), + MINI_PAGE(3, "小程序页面"); + + @Schema(title = "枚举值") + private final Integer index; + @Schema(title = "文字") + private final String text; + + private static final Map indexMap = new HashMap<>(); + private static final List> toJsonStruct = new ArrayList<>(); + + static { + for (AuthorityType authorityType : AuthorityType.values()) { + indexMap.put(authorityType.getIndex(), authorityType); + toJsonStruct.add(authorityType.getIndex(), + ImmutableMap.builder() + .put("value", authorityType.getIndex()) + .put("key", authorityType.name()) + .put("text", authorityType.getText()) + .build()); + } + } + + AuthorityType(Integer index, String text) { + this.index = index; + this.text = text; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + public Integer getIndex() { + return index; + } + + public String getText() { + return this.text; + } + + public static AuthorityType getAuthorityType(Integer index) { + return indexMap.get(index); + } + + public static List> getToJsonStruct() { + return toJsonStruct; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/DataResource.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/DataResource.java new file mode 100644 index 0000000..8f2f1cb --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/DataResource.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +/** + *

Description: 数据资源

+ * + * @author : liuh + * @date : 2022/9/10 16:48 + */ +public enum DataResource { + DATABASE, PROPERTY; +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Database.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Database.java new file mode 100644 index 0000000..06a7667 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Database.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 数据库类型

+ * + * @author : liuh + * @date : 2022/7/21 17:02 + */ +@Schema(title = "数据库类别") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum Database implements BaseUiEnum { + /** + * 数据库类型 + */ + ORACLE(0, "Oracle"), + POSTGRESQL(1, "PostgreSQL"), + MYSQL(2, "Mysql"), + MARIADB(3, "MariaDB"), + SQLSERVER(4, "SQLServer"), + SYBASE(5, "SyBase"), + SAPDB(6, "SAPDB"), + DB2(7, "DB2"), + H2(8, "H2"), + REDIS(9, "Redis"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(name = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (Database database : Database.values()) { + INDEX_MAP.put(database.getValue(), database); + JSON_STRUCTURE.add(database.getValue(), + ImmutableMap.builder() + .put("value", database.getValue()) + .put("key", database.name()) + .put("text", database.getDescription()) + .put("index", database.getValue()) + .build()); + } + } + + Database(Integer value, String description) { + this.value = value; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDescription() { + return description; + } + + public static Database get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Protocol.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Protocol.java new file mode 100644 index 0000000..338a877 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Protocol.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +/** + *

Description: Protocol枚举

+ * + * @author : liuh + * @date : 2021/6/12 14:48 + */ +public enum Protocol { + /** + * 协议类型 + */ + HTTP("http://", "http"), + HTTPS("https://", "https"); + + private final String format; + private final String prefix; + + Protocol(String format, String prefix) { + this.format = format; + this.prefix = prefix; + } + + public String getFormat() { + return format; + } + + public String getPrefix() { + return prefix; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/ResultErrorCodes.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/ResultErrorCodes.java new file mode 100644 index 0000000..7236f46 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/ResultErrorCodes.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +import com.liuhung.engine.assistant.core.definition.constants.ErrorCode; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 统一异常处理器 + * 1** 信息,服务器收到请求,需要请求者继续执行操作 + * 2** 成功,操作被成功接收并处理 + * 3** 重定向,需要进一步的操作以完成请求 + * 4** 客户端错误,请求包含语法错误或无法完成请求 + * 5** 服务器错误,服务器在处理请求的过程中发生了错误 + *

+ * 1开头的状态码 + * 100 Continue 继续。客户端应继续其请求 + * 101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 + *

+ * 2开头的状态码 + * 200 OK 请求成功。一般用于GET与POST请求 + * 201 Created 已创建。成功请求并创建了新的资源 + * 202 Accepted 已接受。已经接受请求,但未处理完成 + * 203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 + * 204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 + * 205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 + * 206 Partial Content 部分内容。服务器成功处理了部分GET请求 + *

+ * 3开头的状态码 + * 300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 + * 301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 + * 302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI + * 303 See Other 查看其它地址。与301类似。使用GET和POST请求查看 + * 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 + * 305 Use Proxy 使用代理。所请求的资源必须通过代理访问 + * 306 Unused 已经被废弃的HTTP状态码 + * 307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向 + *

+ * 4开头的状态码 + * 400 Bad Request 客户端请求的语法错误,服务器无法理解 + * 401 Unauthorized 请求要求用户的身份认证 + * 402 Payment Required 保留,将来使用 + * 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 + * 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 + * 405 Method Not Allowed 客户端请求中的方法被禁止 + * 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 + * 407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 + * 408 Request Time-out 服务器等待客户端发送的请求时间过长,超时 + * 409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 + * 410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 + * 411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息 + * 412 Precondition Failed 客户端请求信息的先决条件错误 + * 413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 + * 414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理 + * 415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 + * 416 Requested range not satisfiable 客户端请求的范围无效 + * 417 Expectation Failed 服务器无法满足Expect的请求头信息 + *

+ * 5开头的状态码 + * 500 Internal Server Error 服务器内部错误,无法完成请求 + * 501 Not Implemented 服务器不支持请求的功能,无法完成请求 + * 502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 + * 503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 + * 504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 + * 505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理 + *

+ * --- 自定义返回码 --- + *

+ * 主要分类说明: + * 2**.** 成功,操作被成功接收并处理 + * 3**.** 需要后续操作,需要进一步的操作以完成请求 + * 4**.** HTTP请求错误,请求包含语法错误或无法完成请求, + * 5**.** 平台错误,平台相关组件运行及操作错误。 + * 6**.** 关系数据库错误,服务器在处理请求的过程中发生了数据SQL操作等底层错误 + * 600.** JDBC错误,服务器在处理请求的过程中发生了JDBC底层错误。 + * 601.** JPA错误,服务器在处理请求的过程中发生了JPA错误。 + * 602.** Hibernate错误,服务器在处理请求的过程中发生了Hibernate操作错误。 + * 603.** 接口参数Validation错误 + *

+ * 其它内容逐步补充 + * + * @author liuh + */ +@Schema(title = "响应结果状态", description = "自定义错误码以及对应的、友好的错误信息") +public enum ResultErrorCodes { + + /** + * 401.** 未经授权 Unauthorized 请求要求用户的身份认证 + */ + ACCESS_DENIED(ErrorCode.ACCESS_DENIED, "您没有权限,拒绝访问"), + ACCOUNT_DISABLED(ErrorCode.ACCOUNT_DISABLED, "该账户已经被禁用"), + ACCOUNT_ENDPOINT_LIMITED(ErrorCode.ACCOUNT_ENDPOINT_LIMITED, "您已经使用其它终端登录,请先退出其它终端"), + ACCOUNT_EXPIRED(ErrorCode.ACCOUNT_EXPIRED, "该账户已经过期"), + ACCOUNT_LOCKED(ErrorCode.ACCOUNT_LOCKED, "该账户已经被锁定"), + BAD_CREDENTIALS(ErrorCode.BAD_CREDENTIALS, "用户名或密码错误"), + CREDENTIALS_EXPIRED(ErrorCode.CREDENTIALS_EXPIRED, "该账户密码凭证已过期"), + INVALID_CLIENT(ErrorCode.INVALID_CLIENT, "客户端身份验证失败"), + INVALID_TOKEN(ErrorCode.INVALID_TOKEN, "提供的访问令牌已过期、吊销、格式错误或无效"), + INVALID_GRANT(ErrorCode.INVALID_GRANT, "提供的授权授予或刷新令牌无效、已过期或已撤销"), + UNAUTHORIZED_CLIENT(ErrorCode.UNAUTHORIZED_CLIENT, "客户端无权使用此方法请求授权码或访问令牌"), + USERNAME_NOT_FOUND(ErrorCode.USERNAME_NOT_FOUND,"用户名或密码错误"), + SESSION_EXPIRED(ErrorCode.SESSION_EXPIRED,"Session 已过期,请刷新页面后再使用"), + + /** + * 403.** 禁止的请求,与403对应 + */ + INSUFFICIENT_SCOPE(ErrorCode.INSUFFICIENT_SCOPE, "TOKEN权限不足,您需要更高级别的权限"), + SQL_INJECTION_REQUEST(ErrorCode.SQL_INJECTION_REQUEST, "疑似SQL注入请求"), + + /** + * 405.** 方法不允许 与405对应 + */ + HTTP_REQUEST_METHOD_NOT_SUPPORTED(ErrorCode.HTTP_REQUEST_METHOD_NOT_SUPPORTED, "请求使用的方法类型不支持"), + + /** + * 406.** 不接受的请求,与406对应 + */ + UNSUPPORTED_GRANT_TYPE(ErrorCode.UNSUPPORTED_GRANT_TYPE, "授权服务器不支持授权授予类型"), + UNSUPPORTED_RESPONSE_TYPE(ErrorCode.UNSUPPORTED_RESPONSE_TYPE, "授权服务器不支持使用此方法获取授权代码或访问令牌"), + UNSUPPORTED_TOKEN_TYPE(ErrorCode.UNSUPPORTED_TOKEN_TYPE, "授权服务器不支持撤销提供的令牌类型"), + + /** + * 412.* 未经授权 Precondition Failed 客户端请求信息的先决条件错误 + */ + INVALID_REDIRECT_URI(ErrorCode.INVALID_REDIRECT_URI, "OAuth2 URI 重定向的值无效"), + INVALID_REQUEST(ErrorCode.INVALID_REQUEST, "无效的请求,参数使用错误或无效."), + INVALID_SCOPE(ErrorCode.INVALID_SCOPE, "授权范围错误"), + + /** + * 415.* Unsupported Media Type 服务器无法处理请求附带的媒体格式 + */ + HTTP_MEDIA_TYPE_NOT_ACCEPTABLE(ErrorCode.HTTP_MEDIA_TYPE_NOT_ACCEPTABLE, "不支持的 Media Type"), + + /** + * 500.* Internal Server Error 服务器内部错误,无法完成请求 + */ + SERVER_ERROR(ErrorCode.SERVER_ERROR, "授权服务器遇到意外情况,无法满足请求"), + HTTP_MESSAGE_NOT_READABLE_EXCEPTION(ErrorCode.HTTP_MESSAGE_NOT_READABLE_EXCEPTION, "JSON字符串反序列化为实体出错!"), + ILLEGAL_ARGUMENT_EXCEPTION(ErrorCode.ILLEGAL_ARGUMENT_EXCEPTION, "参数不合法错误,请仔细确认参数使用是否正确。"), + IO_EXCEPTION(ErrorCode.IO_EXCEPTION, "IO异常"), + MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION(ErrorCode.MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION, "接口参数使用错误或必要参数缺失,请查阅接口文档!"), + NULL_POINTER_EXCEPTION(ErrorCode.NULL_POINTER_EXCEPTION, "后台代码执行过程中出现了空值"), + TYPE_MISMATCH_EXCEPTION(ErrorCode.TYPE_MISMATCH_EXCEPTION, "类型不匹配"), + + /** + * 503.* Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 + */ + SERVICE_UNAVAILABLE(ErrorCode.SERVICE_UNAVAILABLE, "服务不可用"), + TEMPORARILY_UNAVAILABLE(ErrorCode.TEMPORARILY_UNAVAILABLE, "由于服务器临时超载或维护,授权服务器当前无法处理该请求"), + PROVIDER_NOT_FOUND(ErrorCode.PROVIDER_NOT_FOUND, "授权服务器代码逻辑配置错误"), + COOKIE_THEFT(ErrorCode.COOKIE_THEFT, "Cookie 信息不安全"), + INVALID_COOKIE(ErrorCode.INVALID_COOKIE, "不可用的 Cookie 信息"), + + /** + * 6*.* 为数据操作相关错误 + */ + METHOD_ARGUMENT_NOT_VALID(ErrorCode.METHOD_ARGUMENT_NOT_VALID, "接口参数校验失败,参数使用错误或者未接收到参数"), + BAD_SQL_GRAMMAR(ErrorCode.BAD_SQL_GRAMMAR, "低级SQL语法错误,检查SQL能否正常运行或者字段名称是否正确"), + DATA_INTEGRITY_VIOLATION(ErrorCode.DATA_INTEGRITY_VIOLATION, "该数据正在被其它数据引用,请先删除引用关系,再进行数据删除操作"), + TRANSACTION_ROLLBACK(ErrorCode.TRANSACTION_ROLLBACK, "数据事务处理失败,数据回滚"), + + /** + * 7*.* 基础设施交互错误 + * 71.* Redis 操作出现错误 + * 72.* Cache 操作出现错误 + */ + PIPELINE_INVALID_COMMANDS(71000, "Redis管道包含一个或多个无效命令"); + + + @Schema(title = "结果代码") + private final int code; + @Schema(title = "结果信息") + private final String message; + + + ResultErrorCodes(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/ServerDevice.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/ServerDevice.java new file mode 100644 index 0000000..3050ea0 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/ServerDevice.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 服务器类型

+ * + * @author : liuh + * @date : 2022/7/21 16:29 + */ +@Schema(name = "令牌格式") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ServerDevice implements BaseUiEnum { + + /** + * enum + */ + PHYSICAL_MACHINE(0, "实体机"), + VIRTUAL_MACHINE(1, "虚拟机"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(name = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (ServerDevice serverDevice : ServerDevice.values()) { + INDEX_MAP.put(serverDevice.getValue(), serverDevice); + JSON_STRUCTURE.add(serverDevice.getValue(), + ImmutableMap.builder() + .put("value", serverDevice.getValue()) + .put("key", serverDevice.name()) + .put("text", serverDevice.getDescription()) + .put("index", serverDevice.getValue()) + .build()); + } + } + + ServerDevice(Integer value, String description) { + this.value = value; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDescription() { + return description; + } + + public static ServerDevice get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} \ No newline at end of file diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Target.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Target.java new file mode 100644 index 0000000..1a4be74 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/enums/Target.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.enums; + +/** + *

Description: 目标枚举

+ * + * 统一的目标策略使用枚举。 + * + * @author : liuh + * @date : 2022/10/10 19:33 + */ +public enum Target { + + /** + * 目标为服务本地 + */ + LOCAL, + + /** + * 目标为远程访问 + */ + REMOTE; +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/GlobalExceptionHandler.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0e9f7fa --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/GlobalExceptionHandler.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.exception; + +import com.liuhung.engine.assistant.core.definition.exception.QuaferException; +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.enums.ResultErrorCodes; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * 统一异常处理器 + * 1** 信息,服务器收到请求,需要请求者继续执行操作 + * 2** 成功,操作被成功接收并处理 + * 3** 重定向,需要进一步的操作以完成请求 + * 4** 客户端错误,请求包含语法错误或无法完成请求 + * 5** 服务器错误,服务器在处理请求的过程中发生了错误 + *

+ * 1开头的状态码 + * 100 Continue 继续。客户端应继续其请求 + * 101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 + *

+ * 2开头的状态码 + * 200 OK 请求成功。一般用于GET与POST请求 + * 201 Created 已创建。成功请求并创建了新的资源 + * 202 Accepted 已接受。已经接受请求,但未处理完成 + * 203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 + * 204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 + * 205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 + * 206 Partial Content 部分内容。服务器成功处理了部分GET请求 + *

+ * 3开头的状态码 + * 300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 + * 301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 + * 302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI + * 303 See Other 查看其它地址。与301类似。使用GET和POST请求查看 + * 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 + * 305 Use Proxy 使用代理。所请求的资源必须通过代理访问 + * 306 Unused 已经被废弃的HTTP状态码 + * 307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向 + *

+ * 4开头的状态码 + * 400 Bad Request 客户端请求的语法错误,服务器无法理解 + * 401 Unauthorized 请求要求用户的身份认证 + * 402 Payment Required 保留,将来使用 + * 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 + * 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 + * 405 Method Not Allowed 客户端请求中的方法被禁止 + * 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 + * 407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 + * 408 Request Time-out 服务器等待客户端发送的请求时间过长,超时 + * 409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 + * 410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 + * 411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息 + * 412 Precondition Failed 客户端请求信息的先决条件错误 + * 413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 + * 414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理 + * 415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 + * 416 Requested range not satisfiable 客户端请求的范围无效 + * 417 Expectation Failed 服务器无法满足Expect的请求头信息 + *

+ * 5开头的状态码 + * 500 Internal Server Error 服务器内部错误,无法完成请求 + * 501 Not Implemented 服务器不支持请求的功能,无法完成请求 + * 502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 + * 503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 + * 504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 + * 505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理 + * + * @author liuh + */ +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + private static final Map> EXCEPTION_DICTIONARY = new HashMap<>(); + + static { + // 401.** 对应错误 + EXCEPTION_DICTIONARY.put("AccessDeniedException", getUnauthorizedResult(ResultErrorCodes.ACCESS_DENIED)); + EXCEPTION_DICTIONARY.put("InsufficientAuthenticationException", getUnauthorizedResult(ResultErrorCodes.ACCESS_DENIED)); + // 405.** 对应错误 + EXCEPTION_DICTIONARY.put("HttpRequestMethodNotSupportedException", getResult(ResultErrorCodes.HTTP_REQUEST_METHOD_NOT_SUPPORTED, HttpStatus.SC_METHOD_NOT_ALLOWED)); + // 415.** 对应错误 + EXCEPTION_DICTIONARY.put("HttpMediaTypeNotAcceptableException", getUnsupportedMediaTypeResult(ResultErrorCodes.HTTP_MEDIA_TYPE_NOT_ACCEPTABLE)); + // 5*.** 对应错误 + EXCEPTION_DICTIONARY.put("IllegalArgumentException", getInternalServerErrorResult(ResultErrorCodes.ILLEGAL_ARGUMENT_EXCEPTION)); + EXCEPTION_DICTIONARY.put("NullPointerException", getInternalServerErrorResult(ResultErrorCodes.NULL_POINTER_EXCEPTION)); + EXCEPTION_DICTIONARY.put("IOException", getInternalServerErrorResult(ResultErrorCodes.IO_EXCEPTION)); + EXCEPTION_DICTIONARY.put("HttpMessageNotReadableException", getInternalServerErrorResult(ResultErrorCodes.HTTP_MESSAGE_NOT_READABLE_EXCEPTION)); + EXCEPTION_DICTIONARY.put("TypeMismatchException", getInternalServerErrorResult(ResultErrorCodes.TYPE_MISMATCH_EXCEPTION)); + EXCEPTION_DICTIONARY.put("MissingServletRequestParameterException", getInternalServerErrorResult(ResultErrorCodes.MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION)); + EXCEPTION_DICTIONARY.put("ProviderNotFoundException", getServiceUnavailableResult(ResultErrorCodes.PROVIDER_NOT_FOUND)); + EXCEPTION_DICTIONARY.put("CookieTheftException", getServiceUnavailableResult(ResultErrorCodes.COOKIE_THEFT)); + EXCEPTION_DICTIONARY.put("InvalidCookieException", getServiceUnavailableResult(ResultErrorCodes.INVALID_COOKIE)); + // 6*.** 对应错误 + EXCEPTION_DICTIONARY.put("BadSqlGrammarException", getInternalServerErrorResult(ResultErrorCodes.BAD_SQL_GRAMMAR)); + EXCEPTION_DICTIONARY.put("DataIntegrityViolationException", getInternalServerErrorResult(ResultErrorCodes.DATA_INTEGRITY_VIOLATION)); + EXCEPTION_DICTIONARY.put("TransactionRollbackException", getInternalServerErrorResult(ResultErrorCodes.TRANSACTION_ROLLBACK)); + EXCEPTION_DICTIONARY.put("BindException", getNotAcceptableResult(ResultErrorCodes.METHOD_ARGUMENT_NOT_VALID)); + EXCEPTION_DICTIONARY.put("MethodArgumentNotValidException", getNotAcceptableResult(ResultErrorCodes.METHOD_ARGUMENT_NOT_VALID)); + // 7*.** 对应错误 + EXCEPTION_DICTIONARY.put("RedisPipelineException", getResult(ResultErrorCodes.PIPELINE_INVALID_COMMANDS, HttpStatus.SC_INTERNAL_SERVER_ERROR)); + } + + protected static Result getResult(ResultErrorCodes resultErrorCodes, int httpStatus) { + return Result.failure(resultErrorCodes.getMessage(), resultErrorCodes.getCode(), httpStatus, null); + } + + /** + * 401 Unauthorized 请求要求用户的身份认证 + * + * @param resultCode 401 + * @return {@link Result} + */ + public static Result getUnauthorizedResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_UNAUTHORIZED); + } + + /** + * 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 + * + * @param resultCode 403 + * @return {@link Result} + */ + public static Result getForbiddenResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_FORBIDDEN); + } + + /** + * 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 + * + * @param resultCode 406 + * @return {@link Result} + */ + public static Result getNotAcceptableResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_NOT_ACCEPTABLE); + } + + /** + * 412 Precondition Failed 客户端请求信息的先决条件错误 + * + * @param resultCode 412 + * @return {@link Result} + */ + public static Result getPreconditionFailedResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_PRECONDITION_FAILED); + } + + /** + * 415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 + * + * @param resultCode 415 + * @return {@link Result} + */ + private static Result getUnsupportedMediaTypeResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE); + } + + /** + * 500 Internal Server Error 服务器内部错误,无法完成请求 + * + * @param resultCode 500 + * @return {@link Result} + */ + public static Result getInternalServerErrorResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_INTERNAL_SERVER_ERROR); + } + + /** + * 503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 + * + * @param resultCode 503 + * @return {@link Result} + */ + public static Result getServiceUnavailableResult(ResultErrorCodes resultCode) { + return getResult(resultCode, HttpStatus.SC_SERVICE_UNAVAILABLE); + } + + public static Result resolveException(Exception ex, String path) { + + log.trace("[Quafer] |- Global Exception Handler, Path : [{}], Exception : [{}]", path, ex); + + if (ex instanceof QuaferException) { + QuaferException exception = (QuaferException) ex; + Result result = exception.getResult(); + result.path(path); + return result; + } else { + Result result = Result.failure(); + String exceptionName = ex.getClass().getSimpleName(); + if (StringUtils.isNotEmpty(exceptionName) && EXCEPTION_DICTIONARY.containsKey(exceptionName)) { + result = EXCEPTION_DICTIONARY.get(exceptionName); + } else { + log.warn("[Quafer] |- Global Exception Handler, Can not find the exception name [{}] in dictionary, please do optimize ", exceptionName); + } + + result.path(path); + result.stackTrace(ex.getStackTrace()); + result.detail(ex.getMessage()); + + log.debug("[Quafer] |- Global Exception Handler, Error is : {}", result); + return result; + } + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/PlatformException.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/PlatformException.java new file mode 100644 index 0000000..68bcdbb --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/PlatformException.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.exception; + +import com.liuhung.engine.assistant.core.definition.exception.AbstractQuaferException; +import com.liuhung.engine.assistant.core.domain.Feedback; + +/** + *

Description: 平台基础Exception

+ * + * @author : liuh + * @date : 2019/12/18 15:31 + */ +public class PlatformException extends AbstractQuaferException { + + public PlatformException() { + super(); + } + + public PlatformException(String message) { + super(message); + } + + public PlatformException(String message, Throwable cause) { + super(message, cause); + } + + public PlatformException(Throwable cause) { + super(cause); + } + + protected PlatformException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return Feedback.ERROR; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/properties/PropertyValueIsNotSetException.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/properties/PropertyValueIsNotSetException.java new file mode 100644 index 0000000..f5d2957 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/properties/PropertyValueIsNotSetException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.exception.properties; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: Property 属性值没有设置错误

+ * + * @author : liuh + * @date : 2022/3/6 13:57 + */ +public class PropertyValueIsNotSetException extends PlatformException { + + public PropertyValueIsNotSetException() { + super(); + } + + public PropertyValueIsNotSetException(String message) { + super(message); + } + + public PropertyValueIsNotSetException(String message, Throwable cause) { + super(message, cause); + } + + public PropertyValueIsNotSetException(Throwable cause) { + super(cause); + } + + protected PropertyValueIsNotSetException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(50101, "必要的Property配置属性值没有设置", HttpStatus.SC_NOT_IMPLEMENTED); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/properties/UrlFormatIncorrectException.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/properties/UrlFormatIncorrectException.java new file mode 100644 index 0000000..31caece --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/properties/UrlFormatIncorrectException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.exception.properties; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: Url 格式错误

+ * + * @author : liuh + * @date : 2022/3/6 12:49 + */ +public class UrlFormatIncorrectException extends PlatformException { + + public UrlFormatIncorrectException() { + super(); + } + + public UrlFormatIncorrectException(String message) { + super(message); + } + + public UrlFormatIncorrectException(String message, Throwable cause) { + super(message, cause); + } + + public UrlFormatIncorrectException(Throwable cause) { + super(cause); + } + + protected UrlFormatIncorrectException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(50102, "URL格式错误或者缺少Http协议头", HttpStatus.SC_NOT_IMPLEMENTED); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/transaction/TransactionalRollbackException.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/transaction/TransactionalRollbackException.java new file mode 100644 index 0000000..63133ce --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/exception/transaction/TransactionalRollbackException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.exception.transaction; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 事务回滚Exception

+ * + * @author : liuh + * @date : 2021/9/21 11:56 + */ +public class TransactionalRollbackException extends PlatformException { + + public TransactionalRollbackException() { + super(); + } + + public TransactionalRollbackException(String message) { + super(message); + } + + public TransactionalRollbackException(String message, Throwable cause) { + super(message, cause); + } + + public TransactionalRollbackException(Throwable cause) { + super(cause); + } + + protected TransactionalRollbackException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(60001, "数据库操作失败,事务回滚", HttpStatus.SC_INTERNAL_SERVER_ERROR); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/gson/GsonUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/gson/GsonUtils.java new file mode 100644 index 0000000..bb74944 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/gson/GsonUtils.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.json.gson; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import org.apache.commons.lang3.ObjectUtils; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + *

Description: Gson 工具类

+ * + * @author : liuh + * @date : 2022/5/25 12:49 + */ +public class GsonUtils { + + private static volatile Gson instance; + private static final GsonBuilder GSON_BUILDER = new GsonBuilder(); + + static { + GSON_BUILDER.enableComplexMapKeySerialization(); + GSON_BUILDER.serializeNulls(); + GSON_BUILDER.setDateFormat("yyyy-MM-dd HH:mm:ss"); + GSON_BUILDER.disableHtmlEscaping(); + } + + private GsonUtils() { + + } + + public static Gson getInstance() { + + if (ObjectUtils.isEmpty(instance)) { + synchronized (GSON_BUILDER) { + if (ObjectUtils.isEmpty(instance)) { + instance = GSON_BUILDER.create(); + } + + } + } + + return instance; + } + + public static JsonElement toJsonElement(String content) { + return JsonParser.parseString(content); + } + + public static JsonArray toJsonArray(String content) { + return toJsonElement(content).getAsJsonArray(); + } + + public static JsonObject toJsonObject(String content) { + return toJsonElement(content).getAsJsonObject(); + } + + public static String toJson(T domain) { + return getInstance().toJson(domain); + } + + /** + * 将 json 转化为 对象 + * + * @param content json 字符串 + * @param valueType 目标对象类型 + * @param 对象类型 + * @return 转换后的对象 + */ + public static T toObject(String content, Class valueType) { + return getInstance().fromJson(content, valueType); + } + + /** + * 将 json 转化为 对象 + *

+ * new TypeToken>() {}.getType() + * new TypeToken>() {}.getType() + * new TypeToken>>() {}.getType() + * + * @param content json 字符串 + * @param typeOfT 目标对象类型 + * @param 对象类型 + * @return 转换后的对象 + */ + public static T toObject(String content, Type typeOfT) { + return getInstance().fromJson(content, typeOfT); + } + + public static T toList(String content, Class valueType) { + return getInstance().fromJson(content, new TypeToken>() { + }.getType()); + } + + public static List> toListMap(String content) { + return getInstance().fromJson(content, new TypeToken>>() { + }.getType()); + } + + public static Map toMaps(String gsonString) { + return getInstance().fromJson(gsonString, new TypeToken>() { + }.getType()); + } + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/deserializer/ArrayOrStringDeserializer.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/deserializer/ArrayOrStringDeserializer.java new file mode 100644 index 0000000..96e7ba3 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/deserializer/ArrayOrStringDeserializer.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.json.jackson2.deserializer; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + *

Description: 数组转字符串序列化

+ * + * @author : liuh + * @date : 2022/3/18 12:16 + */ +public class ArrayOrStringDeserializer extends StdDeserializer> { + + public ArrayOrStringDeserializer() { + super(Set.class); + } + + public JavaType getValueType() { + return TypeFactory.defaultInstance().constructType(String.class); + } + + @Override + public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException { + JsonToken token = jp.getCurrentToken(); + if (token.isScalarValue()) { + String list = jp.getText(); + list = list.replaceAll("\\s+", ","); + return new LinkedHashSet(Arrays.asList(StringUtils.commaDelimitedListToStringArray(list))); + } else { + return jp.readValueAs(new TypeReference>() { + }); + } + } + + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/deserializer/XssStringJsonDeserializer.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/deserializer/XssStringJsonDeserializer.java new file mode 100644 index 0000000..2827961 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/deserializer/XssStringJsonDeserializer.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.json.jackson2.deserializer; + +import com.liuhung.engine.assistant.core.utils.XssUtils; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + *

Description: Xss Json 处理

+ * + * @author : liuh + * @date : 2021/8/30 23:58 + */ +public class XssStringJsonDeserializer extends JsonDeserializer { + + private static final Logger log = LoggerFactory.getLogger(XssStringJsonDeserializer.class); + + @Override + public Class handledType() { + return String.class; + } + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + String value = jsonParser.getValueAsString(); + if (StringUtils.isNotBlank(value)) { + return XssUtils.cleaning(value); + } + + return value; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JacksonUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JacksonUtils.java new file mode 100644 index 0000000..fb46c72 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JacksonUtils.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.json.jackson2.utils; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +/** + * @author liuh + */ +@Component +public class JacksonUtils { + + private static final Logger logger = LoggerFactory.getLogger(JacksonUtils.class); + + private static ObjectMapper OBJECT_MAPPER; + + @Autowired + private ObjectMapper objectMapper; + + @PostConstruct + public void init() { + if (ObjectUtils.isNotEmpty(this.objectMapper)) { + OBJECT_MAPPER = this.objectMapper; + } else { + OBJECT_MAPPER = new ObjectMapper(); + } + } + + public static ObjectMapper getObjectMapper() { + return OBJECT_MAPPER; + } + + public static ObjectMapper registerModule(Module module) { + return getObjectMapper().registerModules(module); + } + + public static String toJson(T domain) { + try { + return getObjectMapper().writeValueAsString(domain); + } catch (JsonProcessingException e) { + logger.error("[Quafer] |- Jackson json processing error, when to json! {}", e.getMessage()); + return null; + } + } + + public static TypeFactory getTypeFactory() { + return getObjectMapper().getTypeFactory(); + } + + public static T toObject(String content, Class valueType) { + try { + return getObjectMapper().readValue(content, valueType); + } catch (JsonProcessingException e) { + logger.error("[Quafer] |- Jackson json processing error, when to object with value type! {}", e.getMessage()); + return null; + } + } + + public static T toObject(String content, TypeReference typeReference) { + try { + return getObjectMapper().readValue(content, typeReference); + } catch (JsonProcessingException e) { + logger.error("[Quafer] |- Jackson json processing error, when to object with type reference! {}", e.getMessage()); + return null; + } + } + + public static T toObject(String content, JavaType javaType) { + try { + return getObjectMapper().readValue(content, javaType); + } catch (JsonProcessingException e) { + logger.error("[Quafer] |- Jackson json processing error, when to object with java type! {}", e.getMessage()); + return null; + } + } + + public static List toList(String content, Class clazz) { + JavaType javaType = getObjectMapper().getTypeFactory().constructParametricType(List.class, clazz); + return toObject(content, javaType); + } + + public static Map toMap(String content, Class keyClass, Class valueClass) { + JavaType javaType = getObjectMapper().getTypeFactory().constructMapType(Map.class, keyClass, valueClass); + return toObject(content, javaType); + } + + public static Set toSet(String content, Class clazz) { + JavaType javaType = getTypeFactory().constructCollectionLikeType(Set.class, clazz); + return toObject(content, javaType); + } + + public static T[] toArray(String content, Class clazz) { + JavaType javaType = getTypeFactory().constructArrayType(clazz); + return toObject(content, javaType); + } + + public static T[] toArray(String content) { + return toObject(content, new TypeReference() {}); + } + + public static JsonNode toNode(String content) { + try { + return getObjectMapper().readTree(content); + } catch (JsonProcessingException e) { + logger.error("[Quafer] |- Jackson json processing error, when to node with string! {}", e.getMessage()); + return null; + } + } + + public static JsonNode toNode(JsonParser jsonParser) { + try { + return getObjectMapper().readTree(jsonParser); + } catch (IOException e) { + logger.error("[Quafer] |- Jackson io error, when to node with json parser! {}", e.getMessage()); + return null; + } + } + + public static JsonParser createParser(String content) { + try { + return getObjectMapper().createParser(content); + } catch (IOException e) { + logger.error("[Quafer] |- Jackson io error, when create parser! {}", e.getMessage()); + return null; + } + } + + public static R loop(JsonNode jsonNode, Function function) { + if (jsonNode.isObject()) { + Iterator> it = jsonNode.fields(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + loop(entry.getValue(), function); + } + } + + if (jsonNode.isArray()) { + for (JsonNode node : jsonNode) { + loop(node, function); + } + } + + if (jsonNode.isValueNode()) { + return function.apply(jsonNode); + } else { + return null; + } + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JacksonYamlUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JacksonYamlUtils.java new file mode 100644 index 0000000..7c35731 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JacksonYamlUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.json.jackson2.utils; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Description : 基于Jackson Yaml 的 yml处理工具

+ * + * @author : liuh + * @date : 2020/5/3 8:50 + */ +public class JacksonYamlUtils { + + private static final Logger log = LoggerFactory.getLogger(JacksonYamlUtils.class); + + private static final ObjectMapper objectMapper; + + static { + objectMapper = new ObjectMapper(new YAMLFactory()); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + private static ObjectMapper getObjectMapper() { + return objectMapper; + } + + public static String writeAsString(T entity) { + return writeAsString(entity, true); + } + + /** + * 将实体转化为Yaml形式的字符串 + * + * @param domain 可序列化的试题 + * @param removeQuote 是否要去除转化后字符串的双引号 + * @param 任意类型 + * @return 字符串形式的Yaml + */ + public static String writeAsString(D domain, boolean removeQuote) { + try { + String yaml = getObjectMapper().writeValueAsString(domain); + if (StringUtils.isNotBlank(yaml) && removeQuote) { + return StringUtils.remove(yaml, SymbolConstants.QUOTE); + } else { + return yaml; + } + } catch (JsonProcessingException e) { + log.error("[Quafer] |- Yaml writeAsString processing error! {}", e.getMessage()); + } + + return null; + } + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JsonNodeUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JsonNodeUtils.java new file mode 100644 index 0000000..6d744d6 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/json/jackson2/utils/JsonNodeUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.json.jackson2.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.util.Map; +import java.util.Set; + +/** + *

Description: This class is a straight copy from Spring Authorization Server.

+ * + * @author : liuh + * @date : 2022/10/24 15:31 + */ +public class JsonNodeUtils { + + public static final TypeReference INSTANT = new TypeReference() { + }; + + public static final TypeReference> STRING_SET = new TypeReference>() { + }; + + public static final TypeReference> STRING_OBJECT_MAP = new TypeReference>() { + }; + + public static String findStringValue(JsonNode jsonNode, String fieldName) { + if (jsonNode == null) { + return null; + } + JsonNode value = jsonNode.findValue(fieldName); + return (value != null && value.isTextual()) ? value.asText() : null; + } + + public static Boolean findBooleanValue(JsonNode jsonNode, String fieldName) { + if (jsonNode == null) { + return null; + } + JsonNode value = jsonNode.findValue(fieldName); + return (value != null && value.isBoolean()) ? value.asBoolean() : null; + } + + public static T findValue(JsonNode jsonNode, String fieldName, TypeReference valueTypeReference, ObjectMapper mapper) { + if (jsonNode == null) { + return null; + } + JsonNode value = jsonNode.findValue(fieldName); + return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null; + } + + public static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) { + if (jsonNode == null) { + return null; + } + JsonNode value = jsonNode.findValue(fieldName); + return (value != null && value.isObject()) ? value : null; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/ConvertUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/ConvertUtils.java new file mode 100644 index 0000000..796bd0f --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/ConvertUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.enums.Protocol; +import org.apache.commons.lang3.StringUtils; + +/** + *

Description: 转换工具类

+ * + * @author : liuh + * @date : 2021/6/13 13:38 + */ +public class ConvertUtils { + + /** + * 检测地址相关字符串是否以"/"结尾,如果没有就帮助增加一个 ""/"" + * + * @param url http 请求地址字符串 + * @return 结构合理的请求地址字符串 + */ + public static String wellFormed(String url) { + if (StringUtils.endsWith(url, SymbolConstants.FORWARD_SLASH)) { + return url; + } else { + return url + SymbolConstants.FORWARD_SLASH; + } + } + + /** + * 将IP地址加端口号,转换为http地址。 + * + * @param address ip地址加端口号,格式:ip:port + * @param protocol http协议类型 {@link Protocol} + * @param endWithForwardSlash 是否在结尾添加“/” + * @return http格式地址 + */ + public static String addressToUri(String address, Protocol protocol, boolean endWithForwardSlash) { + StringBuilder stringBuilder = new StringBuilder(); + + if (!StringUtils.startsWith(address, protocol.getFormat())) { + stringBuilder.append(protocol.getFormat()); + } + + if (endWithForwardSlash) { + stringBuilder.append(wellFormed(address)); + } else { + stringBuilder.append(address); + } + + return stringBuilder.toString(); + } + + /** + * 将IP地址加端口号,转换为http地址。 + * + * @param address ip地址加端口号,格式:ip:port + * @param endWithForwardSlash 是否在结尾添加“/” + * @return http格式地址 + */ + public static String addressToUri(String address, boolean endWithForwardSlash) { + return addressToUri(address, Protocol.HTTP, endWithForwardSlash); + } + + /** + * 将IP地址加端口号,转换为http地址。 + * + * @param address ip地址加端口号,格式:ip:port + * @return http格式地址 + */ + public static String addressToUri(String address) { + return addressToUri(address, false); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/DateTimeUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/DateTimeUtils.java new file mode 100644 index 0000000..9f46ad0 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/DateTimeUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + *

Description: 特殊日期处理

+ * + * @author : liuh + * @date : 2022/7/2 22:54 + */ +public class DateTimeUtils { + + private static final String DEFAULT_DATA_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + private static final String DEFAULT_TIME_ZONE_NAME = "Asia/Shanghai"; + + public static String zonedDateTimeToString(ZonedDateTime zonedDateTime) { + return zonedDateTimeToString(zonedDateTime, DEFAULT_DATA_TIME_FORMAT); + } + + public static String zonedDateTimeToString(ZonedDateTime zonedDateTime, String format) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format).withZone(ZoneId.of(DEFAULT_TIME_ZONE_NAME)); + return zonedDateTime.format(formatter); + } + + public static ZonedDateTime stringToZonedDateTime(String dateString){ + return stringToZonedDateTime(dateString, DEFAULT_DATA_TIME_FORMAT); + } + + public static ZonedDateTime stringToZonedDateTime(String dateString, String format){ + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format).withZone(ZoneId.of(DEFAULT_TIME_ZONE_NAME)); + return ZonedDateTime.parse(dateString, formatter); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/EnvUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/EnvUtils.java new file mode 100644 index 0000000..a2cd9ba --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/EnvUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + *

Description: 运行环境信息相关工具类

+ * + * @author : liuh + * @date : 2021/6/13 15:10 + */ +public class EnvUtils { + + private static final Logger log = LoggerFactory.getLogger(EnvUtils.class); + + /** + * 获取运行主机ip地址 + * + * @return ip地址,或者null + */ + public static String getHostAddress() { + InetAddress address; + try { + address = InetAddress.getLocalHost(); + return address.getHostAddress(); + } catch (UnknownHostException e) { + log.error("[Quafer] |- Get host address error: {}", e.getLocalizedMessage()); + return null; + } + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/RegexPool.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/RegexPool.java new file mode 100644 index 0000000..f485ecf --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/RegexPool.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + +/** + *

Description: 常用正则表达式

+ * + * @author : liuh + * @date : 2021/10/12 10:43 + */ +public interface RegexPool extends cn.hutool.core.lang.RegexPool { + + /** + * 匹配大括号以及其中的内容, + *

+ * 示例: "ab{gnfnm}ah{hell}o",匹配结果:{gnfnm}、{hell} + */ + String BRACES_AND_CONTENT = "\\{([^}])*\\}"; + + /** + * 匹配所有字符 + *

+ * 示例:String cat = "abc", cat.split((?!^)) 匹配结果: array["a", "b", "c"] + */ + String ALL_CHARACTERS = "(?!^)"; + + /** + * 单引号字符串等式 + * + * 示例:pattern='/open/**' 匹配结果:pattern 和 /open/** + */ + String SINGLE_QUOTE_STRING_EQUATION = "(\\w+)\\s*=\\s*'(.*?)'"; + +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/ResourceUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/ResourceUtils.java new file mode 100644 index 0000000..18cab01 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/ResourceUtils.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; + +/** + *

Description: 资源文件处理工具类

+ * + * @author : liuh + * @date : 2021/8/29 21:39 + */ +public class ResourceUtils { + + private static final Logger log = LoggerFactory.getLogger(ResourceUtils.class); + + private static volatile ResourceUtils INSTANCE; + + private final PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver; + + private ResourceUtils() { + this.pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + } + + private static ResourceUtils getInstance() { + if (ObjectUtils.isEmpty(INSTANCE)) { + synchronized (ResourceUtils.class) { + if (ObjectUtils.isEmpty(INSTANCE)) { + INSTANCE = new ResourceUtils(); + } + } + } + + return INSTANCE; + } + + private PathMatchingResourcePatternResolver getPathMatchingResourcePatternResolver() { + return this.pathMatchingResourcePatternResolver; + } + + private static PathMatchingResourcePatternResolver getResolver() { + return getInstance().getPathMatchingResourcePatternResolver(); + } + + public static Resource getResource(String location) { + return getResolver().getResource(location); + } + + public static File getFile(String location) throws IOException { + return getResource(location).getFile(); + } + + public static InputStream getInputStream(String location) throws IOException { + return getResource(location).getInputStream(); + } + + public static String getFilename(String location) { + return ResourceUtils.getResource(location).getFilename(); + } + + public static URI getURI(String location) throws IOException { + return getResource(location).getURI(); + } + + public static URL getURL(String location) throws IOException { + return getResource(location).getURL(); + } + + public static long contentLength(String location) throws IOException { + return getResource(location).contentLength(); + } + + public static long lastModified(String location) throws IOException { + return getResource(location).lastModified(); + } + + public static boolean exists(String location) { + return getResource(location).exists(); + } + + public static boolean isFile(String location) { + return getResource(location).isFile(); + } + + public static boolean isReadable(String location) { + return getResource(location).isReadable(); + } + + public static boolean isOpen(String location) { + return ResourceUtils.getResource(location).isOpen(); + } + + public static Resource[] getResources(String locationPattern) throws IOException { + return getResolver().getResources(locationPattern); + } + + public static boolean isUrl(String location) { + return org.springframework.util.ResourceUtils.isUrl(location); + } + + public static boolean isClasspathUrl(String location) { + return StringUtils.startsWith(location, ResourceLoader.CLASSPATH_URL_PREFIX); + } + + public static boolean isClasspathAllUrl(String location) { + return StringUtils.startsWith(location, ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX); + } + + public static boolean isJarUrl(URL url) { + return org.springframework.util.ResourceUtils.isJarURL(url); + } + + public static boolean isFileUrl(URL url) { + return org.springframework.util.ResourceUtils.isFileURL(url); + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/SqlInjectionUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/SqlInjectionUtils.java new file mode 100644 index 0000000..15c1705 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/SqlInjectionUtils.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.json.gson.GsonUtils; +import cn.hutool.core.net.URLDecoder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + *

Description: SQL注入处理工具类

+ * + * @author : liuh + * @date : 2021/9/1 10:17 + */ +public class SqlInjectionUtils { + + private static final Logger log = LoggerFactory.getLogger(SqlInjectionUtils.class); + + private static final String SQL_REGEX = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; + + private static final Pattern SQL_PATTERN = Pattern.compile(SQL_REGEX, Pattern.CASE_INSENSITIVE); + + private static boolean matching(String lowerValue, String param) { + if (SQL_PATTERN.matcher(param).find()) { + log.error("[Quafer] |- The parameter contains keywords {} that do not allow SQL!", lowerValue); + return true; + } + return false; + } + + private static String toLowerCase(Object obj) { + //这里需要将参数转换为小写来处理 + return Optional.ofNullable(obj) + .map(Object::toString) + .map(String::toLowerCase) + .orElse(""); + } + + private static boolean checking(Object value) { + //这里需要将参数转换为小写来处理 + String lowerValue = toLowerCase(value); + return matching(lowerValue, lowerValue); + } + + /** + * get请求sql注入校验 + * + * @param value 具体的检验 + * @return 是否存在不合规内容 + */ + public static boolean checkForGet(String value) { + + //参数需要url编码 + //这里需要将参数转换为小写来处理 + //不改变原值 + //value示例 order=asc&pageNum=1&pageSize=100&parentId=0 + String lowerValue = URLDecoder.decode(value, StandardCharsets.UTF_8).toLowerCase(); + + //获取到请求中所有参数值-取每个key=value组合第一个等号后面的值 + return Stream.of(lowerValue.split("\\&")) + .map(kp -> kp.substring(kp.indexOf(SymbolConstants.EQUAL) + 1)) + .parallel() + .anyMatch(param -> matching(lowerValue, param)); + } + + /** + * post请求sql注入校验 + * + * @param value 具体的检验 + * @return 是否存在不合规内容 + */ + public static boolean checkForPost(String value) { + + List result = new ArrayList<>(); + + JsonElement jsonElement = GsonUtils.toJsonElement(value); + iterator(jsonElement, result); + + return CollectionUtils.isNotEmpty(result); + } + + private static void iterator(JsonElement jsonElement, List result) { + if (jsonElement.isJsonNull()) { + return; + } + + if (jsonElement.isJsonPrimitive()) { + boolean hasInjection = checking(jsonElement.toString()); + if (hasInjection) { + result.add(jsonElement); + } + return; + } + + if (jsonElement.isJsonArray()) { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + if (ObjectUtils.isNotEmpty(jsonArray)) { + for (JsonElement je : jsonArray) { + iterator(je, result); + } + } + return; + } + + if (jsonElement.isJsonObject()) { + Set> es = jsonElement.getAsJsonObject().entrySet(); + for (Map.Entry en : es) { + iterator(en.getValue(), result); + } + } + } + +} \ No newline at end of file diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/XssUtils.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/XssUtils.java new file mode 100644 index 0000000..cf22014 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/utils/XssUtils.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.utils; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.owasp.validator.html.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; + +/** + *

Description: Antisamy 单例 工具类

+ * + * @author : liuh + * @date : 2021/8/29 16:15 + */ +public class XssUtils { + + private static final Logger log = LoggerFactory.getLogger(XssUtils.class); + + private static volatile XssUtils INSTANCE; + private final AntiSamy antiSamy; + private final String nbsp; + private final String quot; + + private XssUtils() { + Policy policy = createPolicy(); + this.antiSamy = ObjectUtils.isNotEmpty(policy) ? new AntiSamy(policy) : new AntiSamy(); + this.nbsp = cleanHtml(" "); + this.quot = cleanHtml("\""); + } + + private static XssUtils getInstance() { + if (ObjectUtils.isEmpty(INSTANCE)) { + synchronized (XssUtils.class) { + if (ObjectUtils.isEmpty(INSTANCE)) { + INSTANCE = new XssUtils(); + } + } + } + + return INSTANCE; + } + + private Policy createPolicy() { + try { + URL url = ResourceUtils.getURL("classpath:antisamy/antisamy-anythinggoes.xml"); + return Policy.getInstance(url); + } catch (IOException | PolicyException e) { + log.warn("[Quafer] |- Antisamy create policy error! {}", e.getMessage()); + return null; + } + } + + private CleanResults scan(String taintedHtml) throws ScanException, PolicyException { + return antiSamy.scan(taintedHtml); + } + + private String cleanHtml(String taintedHtml) { + try { + log.trace("[Quafer] |- Before Antisamy Scan, value is: [{}]", taintedHtml); + + // 使用AntiSamy清洗数据 + final CleanResults cleanResults = scan(taintedHtml); + String result = cleanResults.getCleanHTML(); + log.trace("[Quafer] |- After Antisamy Scan, value is: [{}]", result); + return result; + } catch (ScanException | PolicyException e) { + log.error("[Quafer] |- Antisamy scan catch error! {}", e.getMessage()); + return taintedHtml; + } + } + + public static String cleaning(String taintedHTML) { + // 对转义的HTML特殊字符(<、>、"等)进行反转义,因为AntiSamy调用scan方法时会将特殊字符转义 + String cleanHtml = StringEscapeUtils.unescapeHtml4(getInstance().cleanHtml(taintedHTML)); + //AntiSamy会把“ ”转换成乱码,把双引号转换成""" 先将 的乱码替换为空,双引号的乱码替换为双引号 + String temp = cleanHtml.replaceAll(getInstance().nbsp, ""); + temp = temp.replaceAll(getInstance().quot, "\""); + String result = temp.replaceAll("\n", ""); + log.trace("[Quafer] |- After Antisamy Well Formed, value is: [{}]", result); + return result; + } +} diff --git a/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/validation/EnumeratedValueValidator.java b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/validation/EnumeratedValueValidator.java new file mode 100644 index 0000000..16f8ed1 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/java/com/liuhung/engine/assistant/core/validation/EnumeratedValueValidator.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.core.validation; + +import com.liuhung.engine.assistant.core.annotation.EnumeratedValue; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + *

Description: 枚举值校验逻辑

+ * + * @author : liuh + * @date : 2022/6/13 15:58 + */ +public class EnumeratedValueValidator implements ConstraintValidator { + + private String[] names; + private int[] ordinals; + + @Override + public void initialize(EnumeratedValue constraintAnnotation) { + names = constraintAnnotation.names(); + ordinals = constraintAnnotation.ordinals(); + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { + if (value instanceof String) { + for (String name : names) { + if (name.equals(value)) { + return true; + } + } + } else if (value instanceof Integer) { + for (int ordinal : ordinals) { + if (ordinal == (Integer) value) { + return true; + } + } + } + return false; + } +} diff --git a/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy-anythinggoes.xml b/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy-anythinggoes.xml new file mode 100644 index 0000000..33d4f06 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy-anythinggoes.xml @@ -0,0 +1,2667 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + g + grin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy-ebay.xml b/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy-ebay.xml new file mode 100644 index 0000000..1ab846e --- /dev/null +++ b/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy-ebay.xml @@ -0,0 +1,2450 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + g + grin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy.xsd b/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy.xsd new file mode 100644 index 0000000..6d82fd2 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/resources/antisamy/antisamy.xsd @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine-assistant/assistant-core/src/main/resources/banner.txt b/engine-assistant/assistant-core/src/main/resources/banner.txt new file mode 100644 index 0000000..3542641 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +${AnsiColor.BLUE} + + ██╗ ██╗ ██╗ ██╗███████╗██████╗ ██████╗ ██████╗ ██████╗ ████████╗██╗ ██╗███████╗ ██╗ ██╗ + ██╔╝██╔╝ ██║ ██║██╔════╝██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗╚══██╔══╝██║ ██║██╔════╝ ╚██╗╚██╗ +██╔╝██╔╝ ███████║█████╗ ██████╔╝██║ ██║██║ ██║██║ ██║ ██║ ██║ ██║███████╗ ╚██╗╚██╗ +╚██╗╚██╗ ██╔══██║██╔══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║ ██║ ██║ ██║╚════██║ ██╔╝██╔╝ + ╚██╗╚██╗ ██║ ██║███████╗██║ ██║╚██████╔╝██████╔╝╚██████╔╝ ██║ ╚██████╔╝███████║ ██╔╝██╔╝ + ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ + +${AnsiColor.BRIGHT_YELLOW} :: Quafer :: ${application.formatted-version} based on ${AnsiColor.RED}:: Spring Boot :: ${spring-boot.formatted-version} ${AnsiColor.BRIGHT_WHITE} diff --git a/engine-assistant/assistant-core/src/main/resources/config.properties b/engine-assistant/assistant-core/src/main/resources/config.properties new file mode 100644 index 0000000..7fe1161 --- /dev/null +++ b/engine-assistant/assistant-core/src/main/resources/config.properties @@ -0,0 +1,25 @@ +# +# Copyright (c) 2020-2030 ZHENGGENGWEI(\u7801\u5320\u541B) +# +# quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# quafer Engine \u91C7\u7528APACHE LICENSE 2.0\u5F00\u6E90\u534F\u8BAE\uFF0C\u60A8\u5728\u4F7F\u7528\u8FC7\u7A0B\u4E2D\uFF0C\u9700\u8981\u6CE8\u610F\u4EE5\u4E0B\u51E0\u70B9\uFF1A +# +# 1.\u8BF7\u4E0D\u8981\u5220\u9664\u548C\u4FEE\u6539\u6839\u76EE\u5F55\u4E0B\u7684LICENSE\u6587\u4EF6\u3002 +# 2.\u8BF7\u4E0D\u8981\u5220\u9664\u548C\u4FEE\u6539 quafer Engine \u6E90\u7801\u5934\u90E8\u7684\u7248\u6743\u58F0\u660E\u3002 +# 3.\u8BF7\u4FDD\u7559\u6E90\u7801\u548C\u76F8\u5173\u63CF\u8FF0\u6587\u4EF6\u7684\u9879\u76EE\u51FA\u5904\uFF0C\u4F5C\u8005\u58F0\u660E\u7B49\u3002 +# 4.\u5206\u53D1\u6E90\u7801\u65F6\u5019\uFF0C\u8BF7\u6CE8\u660E\u8F6F\u4EF6\u51FA\u5904 https://git.liuhung.com/gz/quafer-engine +# 5.\u5728\u4FEE\u6539\u5305\u540D\uFF0C\u6A21\u5757\u540D\u79F0\uFF0C\u9879\u76EE\u4EE3\u7801\u7B49\u65F6\uFF0C\u8BF7\u6CE8\u660E\u8F6F\u4EF6\u51FA\u5904 https://git.liuhung.com/gz/quafer-engine +# 6.\u82E5\u60A8\u7684\u9879\u76EE\u65E0\u6CD5\u6EE1\u8DB3\u4EE5\u4E0A\u51E0\u70B9\uFF0C\u53EF\u7533\u8BF7\u5546\u4E1A\u6388\u6743 +# + diff --git a/engine-assistant/assistant-spring-boot-starter/README.md b/engine-assistant/assistant-spring-boot-starter/README.md new file mode 100644 index 0000000..12663e8 --- /dev/null +++ b/engine-assistant/assistant-spring-boot-starter/README.md @@ -0,0 +1,6 @@ +## Assistant Starter 模块 + +**包含以下内容:** +1. 常用第三方工具类依赖。 +2. Hutool SpringUtil 工具类自动配置。 +3. 扩展的 Jackson 配置,Jackson工具类 ObjectMapper 注入。 diff --git a/engine-assistant/assistant-spring-boot-starter/pom.xml b/engine-assistant/assistant-spring-boot-starter/pom.xml new file mode 100644 index 0000000..827f1c3 --- /dev/null +++ b/engine-assistant/assistant-spring-boot-starter/pom.xml @@ -0,0 +1,50 @@ + + + + + + engine-assistant + com.liuhung.engine + 2.7.8.0 + + 4.0.0 + + assistant-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud Assistant 模块统一 Starter + + + + com.liuhung.engine + assistant-core + + + + \ No newline at end of file diff --git a/engine-assistant/assistant-spring-boot-starter/src/main/java/com/liuhung/engine/assistant/autoconfigure/AutoConfiguration.java b/engine-assistant/assistant-spring-boot-starter/src/main/java/com/liuhung/engine/assistant/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..1344cb2 --- /dev/null +++ b/engine-assistant/assistant-spring-boot-starter/src/main/java/com/liuhung/engine/assistant/autoconfigure/AutoConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.autoconfigure; + +import cn.hutool.extra.spring.SpringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: Definition 自动配置

+ * + * @author : liuh + * @date : 2022/1/13 20:00 + */ +@Configuration(proxyBeanMethods = false) +@Import({ + SpringUtil.class, + JacksonConfiguration.class +}) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Assistant Starter] Auto Configure."); + } +} diff --git a/engine-assistant/assistant-spring-boot-starter/src/main/java/com/liuhung/engine/assistant/autoconfigure/JacksonConfiguration.java b/engine-assistant/assistant-spring-boot-starter/src/main/java/com/liuhung/engine/assistant/autoconfigure/JacksonConfiguration.java new file mode 100644 index 0000000..7feb1aa --- /dev/null +++ b/engine-assistant/assistant-spring-boot-starter/src/main/java/com/liuhung/engine/assistant/autoconfigure/JacksonConfiguration.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.assistant.autoconfigure; + +import com.liuhung.engine.assistant.core.json.jackson2.deserializer.XssStringJsonDeserializer; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +import javax.annotation.PostConstruct; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +/** + *

Description: Jackson 配置

+ * + * @author : liuh + * @date : 2022/6/1 0:09 + */ +@Configuration(proxyBeanMethods = false) +@AutoConfigureBefore(JacksonAutoConfiguration.class) +public class JacksonConfiguration { + + private static final Logger log = LoggerFactory.getLogger(JacksonConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Assistant Jackson] Auto Configure."); + } + + @Bean(name = "jacksonObjectMapper") + @Primary + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + // 设置为中国上海时区 + objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); + // 空值不序列化 +// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + // 序列化时,日期的统一格式 + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + // 排序key + objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); + // 忽略空bean转json错误 + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + // 忽略在json字符串中存在,在java类中不存在字段,防止错误。 + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // 单引号处理 + objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); + + /** + * 序列换成json时,将所有的long变成string + * js中long过长精度丢失 + */ + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Long.class, ToStringSerializer.instance); + simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); + simpleModule.addDeserializer(String.class, new XssStringJsonDeserializer()); + + objectMapper.registerModule(simpleModule); + objectMapper.registerModule(new Jdk8Module()); + objectMapper.registerModule(new JavaTimeModule()); + + log.trace("[Quafer] |- Bean [Jackson Object Mapper] Auto Configure."); + return objectMapper; + } + + /** + * 转换器全局配置 + * + * @return MappingJackson2HttpMessageConverter + */ + @Bean + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { + log.trace("[Quafer] |- Bean [Jackson Http Message Converter] Auto Configure."); + return new MappingJackson2HttpMessageConverter(objectMapper); + } + + @Configuration(proxyBeanMethods = false) + @ComponentScan({ + "com.liuhung.engine.assistant.core.json.jackson2.utils" + }) + static class JacksonUtilsConfiguration { + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Assistant Jackson Utils] Auto Configure."); + } + } +} diff --git a/engine-assistant/assistant-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-assistant/assistant-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..1a59b3f --- /dev/null +++ b/engine-assistant/assistant-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.liuhung.engine.assistant.autoconfigure.AutoConfiguration +com.liuhung.engine.assistant.autoconfigure.JacksonConfiguration \ No newline at end of file diff --git a/engine-assistant/pom.xml b/engine-assistant/pom.xml new file mode 100644 index 0000000..1c55f39 --- /dev/null +++ b/engine-assistant/pom.xml @@ -0,0 +1,47 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + engine-assistant + 2.7.8.0 + pom + + + assistant-core + assistant-spring-boot-starter + + + \ No newline at end of file diff --git a/engine-cache/cache-core/README.md b/engine-cache/cache-core/README.md new file mode 100644 index 0000000..2b70fa4 --- /dev/null +++ b/engine-cache/cache-core/README.md @@ -0,0 +1 @@ +## 缓存共性通用代码模块组件 diff --git a/engine-cache/cache-core/pom.xml b/engine-cache/cache-core/pom.xml new file mode 100644 index 0000000..d44cade --- /dev/null +++ b/engine-cache/cache-core/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-cache + com.liuhung.engine + 2.7.8.0 + + + cache-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 缓存通用代码组件 + + + + com.liuhung.engine + assistant-core + + + + \ No newline at end of file diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/constants/CacheConstants.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/constants/CacheConstants.java new file mode 100644 index 0000000..7c3a002 --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/constants/CacheConstants.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: Cache Property值常量

+ * + * @author : liuh + * @date : 2022/1/13 21:22 + */ +public interface CacheConstants extends BaseConstants { + + /* ---------- Quafer 配置属性(第二层) ---------- */ + /** + * platform + */ + String PROPERTY_PREFIX_CACHE = PROPERTY_PREFIX_HERODOTUS + ".cache"; + + /* ---------- Spring 相关基础配置属性(第一层) ---------- */ + + String PROPERTY_REDIS_REDISSON = PROPERTY_SPRING_REDIS + ".redisson"; + + String ITEM_REDISSON_ENABLED = PROPERTY_REDIS_REDISSON + PROPERTY_ENABLED; +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/constants/CacheErrorCode.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/constants/CacheErrorCode.java new file mode 100644 index 0000000..d67da1a --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/constants/CacheErrorCode.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.ErrorCode; + +/** + *

Description: Cache 相关错误代码

+ * + * @author : liuh + * @date : 2022/5/2 13:25 + */ +public interface CacheErrorCode extends ErrorCode { + + int STAMP_DELETE_FAILED = CACHE_MODULE_406_BEGIN + 1; + int STAMP_HAS_EXPIRED = STAMP_DELETE_FAILED + 1; + int STAMP_MISMATCH = STAMP_HAS_EXPIRED + 1; + int STAMP_PARAMETER_ILLEGAL = STAMP_MISMATCH + 1; +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/MaximumLimitExceededException.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/MaximumLimitExceededException.java new file mode 100644 index 0000000..2687cf1 --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/MaximumLimitExceededException.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.exception; + +/** + *

Description: 超出最大数量限制

+ * + * @author : liuh + * @date : 2022/7/6 23:03 + */ +public class MaximumLimitExceededException extends RuntimeException { + + public MaximumLimitExceededException() { + super(); + } + + public MaximumLimitExceededException(String message) { + super(message); + } + + public MaximumLimitExceededException(String message, Throwable cause) { + super(message, cause); + } + + public MaximumLimitExceededException(Throwable cause) { + super(cause); + } + + protected MaximumLimitExceededException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampDeleteFailedException.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampDeleteFailedException.java new file mode 100644 index 0000000..96b2a0f --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampDeleteFailedException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.cache.core.constants.CacheErrorCode; +import org.apache.http.HttpStatus; + +/** + *

Description: Stamp签章删除失败Exception

+ * + * @author : liuh + * @date : 2021/8/23 13:51 + */ +public class StampDeleteFailedException extends PlatformException { + + public StampDeleteFailedException() { + super(); + } + + public StampDeleteFailedException(String message) { + super(message); + } + + public StampDeleteFailedException(String message, Throwable cause) { + super(message, cause); + } + + public StampDeleteFailedException(Throwable cause) { + super(cause); + } + + protected StampDeleteFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(CacheErrorCode.STAMP_DELETE_FAILED, "从缓存中删除签章失败", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampHasExpiredException.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampHasExpiredException.java new file mode 100644 index 0000000..4c81cd0 --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampHasExpiredException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.cache.core.constants.CacheErrorCode; +import org.apache.http.HttpStatus; + +/** + *

Description: Stamp签章 已过期错误

+ * + * @author : liuh + * @date : 2021/8/23 12:36 + */ +public class StampHasExpiredException extends PlatformException { + + public StampHasExpiredException() { + super(); + } + + public StampHasExpiredException(String message) { + super(message); + } + + public StampHasExpiredException(String message, Throwable cause) { + super(message, cause); + } + + public StampHasExpiredException(Throwable cause) { + super(cause); + } + + protected StampHasExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(CacheErrorCode.STAMP_HAS_EXPIRED, "签章已过期", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampMismatchException.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampMismatchException.java new file mode 100644 index 0000000..2336a92 --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampMismatchException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.cache.core.constants.CacheErrorCode; +import org.apache.http.HttpStatus; + +/** + *

Description: Stamp签章校验错误

+ * + * @author : liuh + * @date : 2021/8/23 12:32 + */ +public class StampMismatchException extends PlatformException { + + public StampMismatchException() { + super(); + } + + public StampMismatchException(String message) { + super(message); + } + + public StampMismatchException(String message, Throwable cause) { + super(message, cause); + } + + public StampMismatchException(Throwable cause) { + super(cause); + } + + protected StampMismatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(CacheErrorCode.STAMP_MISMATCH, "签章信息无法匹配", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampParameterIllegalException.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampParameterIllegalException.java new file mode 100644 index 0000000..ffa2b7c --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/exception/StampParameterIllegalException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.cache.core.constants.CacheErrorCode; +import org.apache.http.HttpStatus; + +/** + *

Description: 请求参数中缺少幂等Token错误

+ * + * @author : liuh + * @date : 2021/8/23 12:29 + */ +public class StampParameterIllegalException extends PlatformException { + + public StampParameterIllegalException() { + super(); + } + + public StampParameterIllegalException(String message) { + super(message); + } + + public StampParameterIllegalException(String message, Throwable cause) { + super(message, cause); + } + + public StampParameterIllegalException(Throwable cause) { + super(cause); + } + + protected StampParameterIllegalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(CacheErrorCode.STAMP_PARAMETER_ILLEGAL, "缺少签章身份标记参数", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/properties/CacheProperties.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/properties/CacheProperties.java new file mode 100644 index 0000000..760d8d3 --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/properties/CacheProperties.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.properties; + +import com.liuhung.engine.cache.core.constants.CacheConstants; +import com.google.common.base.MoreObjects; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.HashMap; +import java.util.Map; + +/** + *

Description: 缓存配置属性

+ * + * @author : liuh + * @date : 2021/7/13 10:16 + */ +@ConfigurationProperties(prefix = CacheConstants.PROPERTY_PREFIX_CACHE) +public class CacheProperties extends Expire { + + /** + * 是否允许存储空值 + */ + private Boolean allowNullValues = true; + + /** + * 缓存名称转换分割符。默认值,"-" + *

+ * 默认缓存名称采用 Redis Key 格式(使用 ":" 分割),使用 ":" 分割的字符串作为Map的Key,":"会丢失。 + * 指定一个分隔符,用于 ":" 分割符的转换 + */ + private String separator = "-"; + + /** + * 针对不同实体单独设置的过期时间,如果不设置,则统一使用默认时间。 + */ + private Map expires = new HashMap<>(); + + public Boolean getAllowNullValues() { + return allowNullValues; + } + + public void setAllowNullValues(Boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + + public Map getExpires() { + return expires; + } + + public void setExpires(Map expires) { + this.expires = expires; + } + + public String getSeparator() { + return separator; + } + + public void setSeparator(String separator) { + this.separator = separator; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("allowNullValues", allowNullValues) + .add("separator", separator) + .toString(); + } +} diff --git a/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/properties/Expire.java b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/properties/Expire.java new file mode 100644 index 0000000..17ba2f3 --- /dev/null +++ b/engine-cache/cache-core/src/main/java/com/liuhung/engine/cache/core/properties/Expire.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.core.properties; + +import org.apache.commons.lang3.ObjectUtils; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + *

Description: 自定义二级缓存过期时间通用属性

+ * + * @author : liuh + * @date : 2021/10/25 17:41 + */ +public class Expire { + + /** + * 统一缓存时长,默认:1 + */ + private Long duration = 1L; + + /** + * 统一缓存时长单位,默认:小时。 + */ + private TimeUnit unit = TimeUnit.HOURS; + + /** + * Redis缓存TTL设置,默认:1小时,单位小时 + *

+ * 使用Duration类型,配置参数形式如下: + * "?ns" //纳秒 + * "?us" //微秒 + * "?ms" //毫秒 + * "?s" //秒 + * "?m" //分 + * "?h" //小时 + * "?d" //天 + */ + private Duration ttl; + + public Long getDuration() { + return duration; + } + + public void setDuration(Long duration) { + this.duration = duration; + } + + public TimeUnit getUnit() { + return unit; + } + + public void setUnit(TimeUnit unit) { + this.unit = unit; + } + + public Duration getTtl() { + if (ObjectUtils.isEmpty(this.ttl)) { + this.ttl = convertToDuration(this.duration, this.unit); + } + return ttl; + } + + private Duration convertToDuration(Long duration, TimeUnit timeUnit) { + switch (timeUnit) { + case DAYS: + return Duration.ofDays(duration); + case HOURS: + return Duration.ofHours(duration); + case SECONDS: + return Duration.ofSeconds(duration); + default: + return Duration.ofMinutes(duration); + } + } +} diff --git a/engine-cache/cache-sdk-caffeine/README.md b/engine-cache/cache-sdk-caffeine/README.md new file mode 100644 index 0000000..2d54b7e --- /dev/null +++ b/engine-cache/cache-sdk-caffeine/README.md @@ -0,0 +1,6 @@ +## Caffeine 相关代码组件模块 + +**包含以下内容:** + +1. Caffeine 配置。 +2. 扩展的 Caffeine Cache Manager 配置 \ No newline at end of file diff --git a/engine-cache/cache-sdk-caffeine/pom.xml b/engine-cache/cache-sdk-caffeine/pom.xml new file mode 100644 index 0000000..1a36df0 --- /dev/null +++ b/engine-cache/cache-sdk-caffeine/pom.xml @@ -0,0 +1,61 @@ + + + + + 4.0.0 + + + engine-cache + com.liuhung.engine + 2.7.8.0 + + + 基于 Spring Authorization Server 的 quafer Cloud Caffeine 组件相关代码模块 + + cache-sdk-caffeine + 2.7.8.0 + jar + + + + com.liuhung.engine + cache-core + + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework + spring-context-support + + + + \ No newline at end of file diff --git a/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/configuration/CaffeineConfiguration.java b/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/configuration/CaffeineConfiguration.java new file mode 100644 index 0000000..5057929 --- /dev/null +++ b/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/configuration/CaffeineConfiguration.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.caffeine.configuration; + +import com.liuhung.engine.cache.caffeine.enhance.QuaferCaffeineCacheManager; +import com.liuhung.engine.cache.core.properties.CacheProperties; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Caffeine 配置

+ * Caffeine 是基于 Java 8 的高性能,接近最佳的缓存库。 + * Caffeine 使用 Google Guava 启发的 API 提供内存缓存。 改进取决于您设计 Guava 缓存和 ConcurrentLinkedHashMap 的体验。 + * @author : liuh + * @date : 2022/5/23 17:56 + */ +@Configuration(proxyBeanMethods = false) +public class CaffeineConfiguration { + + private static final Logger log = LoggerFactory.getLogger(CaffeineConfiguration.class); + + @Autowired + private CacheProperties cacheProperties; + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Cache Caffeine] Auto Configure."); + } + + @Bean + public Caffeine caffeine() { + Caffeine caffeine = Caffeine + .newBuilder() + .expireAfterWrite(cacheProperties.getDuration(), cacheProperties.getUnit()); + + log.trace("[Quafer] |- Bean [Caffeine] Auto Configure."); + + return caffeine; + } + + @Bean + @ConditionalOnMissingBean(CaffeineCacheManager.class) + public CaffeineCacheManager caffeineCacheManager(Caffeine caffeine) { + QuaferCaffeineCacheManager quaferCaffeineCacheManager = new QuaferCaffeineCacheManager(cacheProperties); + quaferCaffeineCacheManager.setCaffeine(caffeine); + log.trace("[Quafer] |- Bean [Caffeine Cache Manager] Auto Configure."); + return quaferCaffeineCacheManager; + } +} diff --git a/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/enhance/CaffeineNeverExpire.java b/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/enhance/CaffeineNeverExpire.java new file mode 100644 index 0000000..786042d --- /dev/null +++ b/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/enhance/CaffeineNeverExpire.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.caffeine.enhance; + +import com.github.benmanes.caffeine.cache.Expiry; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + *

Description: Caffeine 缓存永不过期时间配置

+ * + * @author : liuh + * @date : 2021/7/30 15:07 + */ +public class CaffeineNeverExpire implements Expiry { + + @Override + public long expireAfterCreate(@NonNull K key, @NonNull V value, long currentTime) { + return Long.MAX_VALUE; + } + + @Override + public long expireAfterUpdate(@NonNull K key, @NonNull V value, long currentTime, @NonNegative long currentDuration) { + return currentDuration; + } + + @Override + public long expireAfterRead(@NonNull K key, @NonNull V value, long currentTime, @NonNegative long currentDuration) { + return currentDuration; + } +} diff --git a/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/enhance/QuaferCaffeineCacheManager.java b/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/enhance/QuaferCaffeineCacheManager.java new file mode 100644 index 0000000..310be4b --- /dev/null +++ b/engine-cache/cache-sdk-caffeine/src/main/java/com/liuhung/engine/cache/caffeine/enhance/QuaferCaffeineCacheManager.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.caffeine.enhance; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.cache.core.properties.CacheProperties; +import com.liuhung.engine.cache.core.properties.Expire; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.caffeine.CaffeineCacheManager; + +import java.util.Map; + +/** + *

Description: 扩展的 CaffeineCacheManager

+ *

+ * 用于支持 Caffeine 缓存可以针对实体进行单独的过期时间设定 + * + * @author : liuh + * @date : 2021/10/25 18:12 + */ +public class QuaferCaffeineCacheManager extends CaffeineCacheManager { + + private static final Logger log = LoggerFactory.getLogger(QuaferCaffeineCacheManager.class); + + private final CacheProperties cacheProperties; + + public QuaferCaffeineCacheManager(CacheProperties cacheProperties) { + this.cacheProperties = cacheProperties; + this.setAllowNullValues(cacheProperties.getAllowNullValues()); + } + + public QuaferCaffeineCacheManager(CacheProperties cacheProperties, String... cacheNames) { + super(cacheNames); + this.cacheProperties = cacheProperties; + this.setAllowNullValues(cacheProperties.getAllowNullValues()); + } + + @Override + protected Cache createNativeCaffeineCache(String name) { + Map expires = cacheProperties.getExpires(); + if (MapUtils.isNotEmpty(expires)) { + String key = StringUtils.replace(name, SymbolConstants.COLON, cacheProperties.getSeparator()); + if(expires.containsKey(key)) { + Expire expire = expires.get(key); + log.debug("[Quafer] |- CACHE - Caffeine cache [{}] is setted to use CUSTEM exprie.", name); + return Caffeine.newBuilder().expireAfterWrite(expire.getDuration(), expire.getUnit()).build(); + } + } + + return super.createNativeCaffeineCache(name); + } +} diff --git a/engine-cache/cache-sdk-jetcache/README.md b/engine-cache/cache-sdk-jetcache/README.md new file mode 100644 index 0000000..b291f3f --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/README.md @@ -0,0 +1,9 @@ +## JetCache 相关代码组件模块 + +**包含以下内容:** +1. JetCache 配置。 +2. 基于 JetCache 的,Mybatis 二级缓存扩展。(暂时不要使用,新版本 JetCache 代码逻辑变化,已有代码不适用) +3. 自研 JetCache 缓存手动创建工具类 (暂时不要使用,新版本 JetCache 代码逻辑变化,已有代码不适用) +4. 签章(Stamp)管理定义类 + +### 额外说明 \ No newline at end of file diff --git a/engine-cache/cache-sdk-jetcache/pom.xml b/engine-cache/cache-sdk-jetcache/pom.xml new file mode 100644 index 0000000..a9195d3 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + engine-cache + com.liuhung.engine + 2.7.8.0 + + + cache-sdk-jetcache + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud JetCache 组件相关代码模块 + + + + com.liuhung.engine + assistant-spring-boot-starter + + + + com.liuhung.engine + cache-core + + + + com.liuhung.engine + cache-sdk-redis + + + + com.liuhung.engine + cache-sdk-caffeine + + + + com.alicp.jetcache + jetcache-starter-redis-lettuce + + + + org.mybatis + mybatis + compile + true + + + + \ No newline at end of file diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/annotation/EnableQuaferJetCache.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/annotation/EnableQuaferJetCache.java new file mode 100644 index 0000000..2183e8e --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/annotation/EnableQuaferJetCache.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.annotation; + +import com.liuhung.engine.cache.jetcache.configuration.JetCacheConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 手动开启JetCache注入

+ * + * @author : liuh + * @date : 2022/1/14 22:51 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(JetCacheConfiguration.class) +public @interface EnableQuaferJetCache { +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/configuration/JetCacheConfiguration.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/configuration/JetCacheConfiguration.java new file mode 100644 index 0000000..31452de --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/configuration/JetCacheConfiguration.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.configuration; + +import com.liuhung.engine.cache.caffeine.configuration.CaffeineConfiguration; +import com.liuhung.engine.cache.core.properties.CacheProperties; +import com.liuhung.engine.cache.jetcache.enhance.QuaferCacheManager; +import com.liuhung.engine.cache.jetcache.enhance.JetCacheCreateCacheFactory; +import com.liuhung.engine.cache.jetcache.utils.JetCacheUtils; +import com.liuhung.engine.cache.redis.configuration.CacheRedisConfiguration; +import com.alicp.jetcache.CacheManager; +import com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; + +import javax.annotation.PostConstruct; + +/** + *

Description: JetCacheConfiguration

+ *

+ * 新增JetCache配置,解决JetCache依赖循环问题 + * + * @author : liuh + * @date : 2021/12/4 10:44 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(CacheProperties.class) +@Import({CaffeineConfiguration.class, CacheRedisConfiguration.class}) +@AutoConfigureAfter(JetCacheAutoConfiguration.class) +public class JetCacheConfiguration { + + private static final Logger log = LoggerFactory.getLogger(JetCacheConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Cache JetCache] Auto Configure."); + } + + @Bean + @ConditionalOnClass(CacheManager.class) + public JetCacheCreateCacheFactory jetCacheCreateCacheFactory(CacheManager jcCacheManager) { + JetCacheCreateCacheFactory factory = new JetCacheCreateCacheFactory(jcCacheManager); + JetCacheUtils.setJetCacheCreateCacheFactory(factory); + log.trace("[Quafer] |- Bean [Jet Cache Create Cache Factory] Auto Configure."); + return factory; + } + + @Bean + @Primary + @ConditionalOnMissingBean + public QuaferCacheManager quaferCacheManager(JetCacheCreateCacheFactory jetCacheCreateCacheFactory, CacheProperties cacheProperties) { + QuaferCacheManager quaferCacheManager = new QuaferCacheManager(jetCacheCreateCacheFactory, cacheProperties); + quaferCacheManager.setAllowNullValues(cacheProperties.getAllowNullValues()); + log.trace("[Quafer] |- Bean [Jet Cache Quafer Cache Manager] Auto Configure."); + return quaferCacheManager; + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheCreateCacheFactory.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheCreateCacheFactory.java new file mode 100644 index 0000000..d168d78 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheCreateCacheFactory.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.enhance; + +import com.alicp.jetcache.Cache; +import com.alicp.jetcache.CacheManager; +import com.alicp.jetcache.anno.CacheType; +import com.alicp.jetcache.template.QuickConfig; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.time.Duration; + +/** + *

Description: JetCache 手动创建Cache 工厂

+ * + * @author : liuh + * @date : 2022/7/23 10:49 + */ +public class JetCacheCreateCacheFactory { + + private final CacheManager cacheManager; + + public JetCacheCreateCacheFactory(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + public Cache create(String name) { + return create(name, Duration.ofHours(2L)); + } + + public Cache create(String name, Duration expire) { + return create(name, expire, true); + } + + public Cache create(String name, Duration expire, Boolean cacheNullValue) { + return create(name, expire, cacheNullValue, null); + } + + public Cache create(String name, Duration expire, Boolean cacheNullValue, Boolean syncLocal) { + return create(name, CacheType.BOTH, expire, cacheNullValue, syncLocal); + } + + public Cache create(String name, CacheType cacheType) { + return create(name, cacheType, null); + } + + public Cache create(String name, CacheType cacheType, Duration expire) { + return create(name, cacheType, expire, true); + } + + public Cache create(String name, CacheType cacheType, Duration expire, Boolean cacheNullValue) { + return create(name, cacheType, expire, cacheNullValue, null); + } + + public Cache create(String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal) { + return create(null, name, cacheType, expire, cacheNullValue, syncLocal); + } + + public Cache create(String area, String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal) { + return create(area, name, cacheType, expire, cacheNullValue, syncLocal, null); + } + + public Cache create(String area, String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal, Duration localExpire) { + return create(area, name, cacheType, expire, cacheNullValue, syncLocal, localExpire, null); + } + + public Cache create(String area, String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal, Duration localExpire, Integer localLimit) { + return create(area, name, cacheType, expire, cacheNullValue, syncLocal, localExpire, localLimit, false); + } + + public Cache create(String area, String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal, Duration localExpire, Integer localLimit, Boolean useAreaInPrefix) { + return create(area, name, cacheType, expire, cacheNullValue, syncLocal, localExpire, localLimit, useAreaInPrefix, false, null); + } + + public Cache create(String area, String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal, Duration localExpire, Integer localLimit, Boolean useAreaInPrefix, Boolean penetrationProtect, Duration penetrationProtectTimeout) { + QuickConfig.Builder builder = StringUtils.isEmpty(area) ? QuickConfig.newBuilder(name) : QuickConfig.newBuilder(area, name); + builder.cacheType(cacheType); + builder.expire(expire); + if (cacheType == CacheType.BOTH) { + builder.syncLocal(syncLocal); + } + builder.localExpire(localExpire); + builder.localLimit(localLimit); + builder.cacheNullValue(cacheNullValue); + builder.useAreaInPrefix(useAreaInPrefix); + if (ObjectUtils.isNotEmpty(penetrationProtect)) { + builder.penetrationProtect(penetrationProtect); + if (BooleanUtils.isTrue(penetrationProtect) && ObjectUtils.isNotEmpty(penetrationProtectTimeout)) { + builder.penetrationProtectTimeout(penetrationProtectTimeout); + } + } + + QuickConfig quickConfig = builder.build(); + return create(quickConfig); + } + + + @SuppressWarnings("unchecked") + private Cache create(QuickConfig quickConfig) { + return cacheManager.getOrCreateCache(quickConfig); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheSpringCache.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheSpringCache.java new file mode 100644 index 0000000..e89a4e4 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheSpringCache.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.enhance; + +import com.liuhung.engine.assistant.core.json.jackson2.utils.JacksonUtils; +import com.alicp.jetcache.Cache; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.support.AbstractValueAdaptingCache; +import org.springframework.lang.Nullable; + +import java.util.concurrent.Callable; + +/** + *

Description: 基于 JetCache 的 Spring Cache 扩展

+ * + * @author : liuh + * @date : 2022/7/23 11:11 + */ +public class JetCacheSpringCache extends AbstractValueAdaptingCache { + + private static final Logger log = LoggerFactory.getLogger(JetCacheSpringCache.class); + + private final String cacheName; + private final Cache cache; + + public JetCacheSpringCache(String cacheName, Cache cache, boolean allowNullValues) { + super(allowNullValues); + this.cacheName = cacheName; + this.cache = cache; + } + + @Override + public String getName() { + return this.cacheName; + } + + @Override + public final Cache getNativeCache() { + return this.cache; + } + + @Override + @Nullable + protected Object lookup(Object key) { + Object value = cache.get(key); + if (ObjectUtils.isNotEmpty(value)) { + log.trace("[Quafer] |- CACHE - Lookup data in quafer cache, value is : [{}]", JacksonUtils.toJson(value)); + return value; + } + + return null; + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + public T get(Object key, Callable valueLoader) { + + log.trace("[Quafer] |- CACHE - Get data in quafer cache, key: {}", key); + + return (T) fromStoreValue(cache.computeIfAbsent(key, k -> { + try { + return toStoreValue(valueLoader.call()); + } + catch (Throwable ex) { + throw new ValueRetrievalException(key, valueLoader, ex); + } + })); + } + + @Override + @Nullable + public void put(Object key, @Nullable Object value) { + log.trace("[Quafer] |- CACHE - Put data in quafer cache, key: {}", key); + cache.put(key, this.toStoreValue(value)); + } + + + @Override + @Nullable + public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { + log.trace("[Quafer] |- CACHE - PutIfPresent data in quafer cache, key: {}", key); + Object existing = cache.putIfAbsent(key, toStoreValue(value)); + return toValueWrapper(existing); + } + + @Override + public void evict(Object key) { + log.trace("[Quafer] |- CACHE - Evict data in quafer cache, key: {}", key); + cache.remove(key); + } + + @Override + public boolean evictIfPresent(Object key) { + log.trace("[Quafer] |- CACHE - EvictIfPresent data in quafer cache, key: {}", key); + return cache.remove(key); + } + + @Override + public void clear() { + log.trace("[Quafer] |- CACHE - Clear data in quafer cache."); + cache.close(); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheSpringCacheManager.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheSpringCacheManager.java new file mode 100644 index 0000000..f4ec769 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/JetCacheSpringCacheManager.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.enhance; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.lang.Nullable; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

Description: 基于 JetCache 的 Spring Cache Manager 扩展

+ * + * @author : liuh + * @date : 2022/7/23 14:06 + */ +public class JetCacheSpringCacheManager implements CacheManager { + + private static final Logger log = LoggerFactory.getLogger(JetCacheSpringCacheManager.class); + + private boolean dynamic = true; + private boolean allowNullValues = true; + + private final Map cacheMap = new ConcurrentHashMap<>(16); + + private final JetCacheCreateCacheFactory jetCacheCreateCacheFactory; + + public JetCacheSpringCacheManager(JetCacheCreateCacheFactory jetCacheCreateCacheFactory) { + this.jetCacheCreateCacheFactory = jetCacheCreateCacheFactory; + } + + public JetCacheSpringCacheManager(JetCacheCreateCacheFactory jetCacheCreateCacheFactory, String... cacheNames) { + this.jetCacheCreateCacheFactory = jetCacheCreateCacheFactory; + setCacheNames(Arrays.asList(cacheNames)); + } + + public void setAllowNullValues(boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + + public boolean isAllowNullValues() { + return allowNullValues; + } + + private void setCacheNames(@Nullable Collection cacheNames) { + if (cacheNames != null) { + for (String name : cacheNames) { + this.cacheMap.put(name, createJetCache(name)); + } + this.dynamic = false; + } else { + this.dynamic = true; + } + } + + protected Cache createJetCache(String name) { + com.alicp.jetcache.Cache cache = jetCacheCreateCacheFactory.create(name); + log.debug("[Quafer] |- CACHE - Quafer cache [{}] is CREATED.", name); + return new JetCacheSpringCache(name, cache, allowNullValues); + } + + protected Cache createJetCache(String name, Duration expire) { + com.alicp.jetcache.Cache cache = jetCacheCreateCacheFactory.create(name, expire, allowNullValues, true); + log.debug("[Quafer] |- CACHE - Quafer cache [{}] with expire is CREATED.", name); + return new JetCacheSpringCache(name, cache, allowNullValues); + } + + private String availableCacheName(String name) { + if (StringUtils.endsWith(name, SymbolConstants.COLON)) { + return name; + } else { + return name + SymbolConstants.COLON; + } + } + + + @Override + @Nullable + public Cache getCache(String name) { + String usedName = availableCacheName(name); + return this.cacheMap.computeIfAbsent(usedName, cacheName -> + this.dynamic ? createJetCache(cacheName) : null); + } + + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(this.cacheMap.keySet()); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/QuaferCacheManager.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/QuaferCacheManager.java new file mode 100644 index 0000000..2f78cf6 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/QuaferCacheManager.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.enhance; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.cache.core.properties.CacheProperties; +import com.liuhung.engine.cache.core.properties.Expire; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.Cache; + +import java.util.Map; + +/** + *

Description: 自定义 缓存管理器

+ * + * @author : liuh + * @date : 2022/7/23 17:02 + */ +public class QuaferCacheManager extends JetCacheSpringCacheManager { + + private static final Logger log = LoggerFactory.getLogger(QuaferCacheManager.class); + + private final CacheProperties cacheProperties; + + public QuaferCacheManager(JetCacheCreateCacheFactory jetCacheCreateCacheFactory, CacheProperties cacheProperties) { + super(jetCacheCreateCacheFactory); + this.cacheProperties = cacheProperties; + this.setAllowNullValues(cacheProperties.getAllowNullValues()); + } + + public QuaferCacheManager(JetCacheCreateCacheFactory jetCacheCreateCacheFactory, CacheProperties cacheProperties, String... cacheNames) { + super(jetCacheCreateCacheFactory, cacheNames); + this.cacheProperties = cacheProperties; + } + + @Override + protected Cache createJetCache(String name) { + Map expires = cacheProperties.getExpires(); + if (MapUtils.isNotEmpty(expires)) { + String key = StringUtils.replace(name, SymbolConstants.COLON, cacheProperties.getSeparator()); + if (expires.containsKey(key)) { + Expire expire = expires.get(key); + log.debug("[Quafer] |- CACHE - Cache [{}] is set to use CUSTOM expire.", name); + return super.createJetCache(name, expire.getTtl()); + } + } + return super.createJetCache(name); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/QuaferMybatisCache.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/QuaferMybatisCache.java new file mode 100644 index 0000000..843a7e7 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/enhance/QuaferMybatisCache.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.enhance; + +import cn.hutool.extra.spring.SpringUtil; +import org.apache.ibatis.cache.Cache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

Description: 扩展的Mybatis二级缓存

+ * + * @author : liuh + * @date : 2021/10/26 17:02 + */ +public class QuaferMybatisCache implements Cache { + + private static final Logger log = LoggerFactory.getLogger(QuaferMybatisCache.class); + + private final String id; + private final com.alicp.jetcache.Cache cache; + private final AtomicInteger counter = new AtomicInteger(0); + + public QuaferMybatisCache(String id) { + this.id = id; + JetCacheCreateCacheFactory jetCacheCreateCacheFactory = SpringUtil.getBean("jetCacheCreateCacheFactory"); + this.cache = jetCacheCreateCacheFactory.create(this.id); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public void putObject(Object key, Object value) { + cache.put(key, value); + counter.incrementAndGet(); + log.debug("[Quafer] |- CACHE - Put data into Mybatis Cache, with key: [{}]", key); + } + + @Override + public Object getObject(Object key) { + Object obj = cache.get(key); + log.debug("[Quafer] |- CACHE - Get data from Mybatis Cache, with key: [{}]", key); + return obj; + } + + @Override + public Object removeObject(Object key) { + Object obj = cache.remove(key); + counter.decrementAndGet(); + log.debug("[Quafer] |- CACHE - Remove data from Mybatis Cache, with key: [{}]", key); + return obj; + } + + @Override + public void clear() { + cache.close(); + log.debug("[Quafer] |- CACHE - Clear Mybatis Cache."); + } + + @Override + public int getSize() { + return counter.get(); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/AbstractCountStampManager.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/AbstractCountStampManager.java new file mode 100644 index 0000000..761c2ab --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/AbstractCountStampManager.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.stamp; + +import com.liuhung.engine.cache.core.exception.MaximumLimitExceededException; +import cn.hutool.crypto.SecureUtil; +import com.alicp.jetcache.anno.CacheType; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.time.Duration; + +/** + *

Description: 计数类型的缓存

+ *

+ * 这里的泛型使用了 Long 主要是为了兼顾存储 System.currentTimeMillis()。否则类型不一致,还要建两个 Stamp + * + * @author : liuh + * @date : 2022/7/6 22:59 + */ +public abstract class AbstractCountStampManager extends AbstractStampManager { + + private static final Logger log = LoggerFactory.getLogger(AbstractCountStampManager.class); + + public AbstractCountStampManager(String cacheName) { + super(cacheName); + } + + public AbstractCountStampManager(String cacheName, CacheType cacheType) { + super(cacheName, cacheType); + } + + public AbstractCountStampManager(String cacheName, CacheType cacheType, Duration expire) { + super(cacheName, cacheType, expire); + } + + /** + * 在缓存有效期内进行计数 + * + * @param identity 缓存 Key 的区分标识 + * @param maxTimes 允许的最大限制次数 + * @return 当前错误次数 + * @throws MaximumLimitExceededException 超出最大限制次数错误 + */ + public int counting(String identity, int maxTimes) throws MaximumLimitExceededException { + return counting(identity, maxTimes, null); + } + + /** + * 在缓存有效期内进行计数 + * + * @param identity 缓存 Key 的区分标识 + * @param maxTimes 允许的最大限制次数 + * @param expire 过期时间 + * @return 当前错误次数 + * @throws MaximumLimitExceededException 超出最大限制次数错误 + */ + public int counting(String identity, int maxTimes, Duration expire) throws MaximumLimitExceededException { + return counting(identity, maxTimes, expire, false); + } + + /** + * 在缓存有效期内进行计数 + * + * @param identity 缓存 Key 的区分标识 + * @param maxTimes 允许的最大限制次数 + * @param expire 过期时间 + * @param function 用于在日志中区分是哪个功能在调用。 + * @return 当前错误次数 + * @throws MaximumLimitExceededException 超出最大限制次数错误 + */ + public int counting(String identity, int maxTimes, Duration expire, String function) throws MaximumLimitExceededException { + return counting(identity, maxTimes, expire, false, function); + } + + /** + * 在缓存有效期内进行计数 + * + * @param identity 缓存 Key 的区分标识 + * @param maxTimes 允许的最大限制次数 + * @param expire 过期时间 + * @param useMd5 是否用 MD5 对区分标识进行混淆加密 + * @return 当前错误次数 + * @throws MaximumLimitExceededException 超出最大限制次数错误 + */ + public int counting(String identity, int maxTimes, Duration expire, boolean useMd5) throws MaximumLimitExceededException { + return counting(identity, maxTimes, expire, useMd5, "AbstractCountStampManager"); + } + + /** + * 在缓存有效期内进行计数 + * + * @param identity 缓存 Key 的区分标识 + * @param maxTimes 允许的最大限制次数 + * @param expire 过期时间 + * @param useMd5 是否用 MD5 对区分标识进行混淆加密 + * @param function 用于在日志中区分是哪个功能在调用。 + * @return 当前错误次数 + * @throws MaximumLimitExceededException 超出最大限制次数错误 + */ + public int counting(String identity, int maxTimes, Duration expire, boolean useMd5, String function) throws MaximumLimitExceededException { + Assert.notNull(identity, "identity cannot be null"); + + String key = useMd5 ? SecureUtil.md5(identity) : identity; + String expireKey = key + "_expire"; + + Long index = get(key); + + if (ObjectUtils.isEmpty(index)) { + index = 0L; + } + + if (index == 0) { + // 第一次读取剩余次数,因为缓存中还没有值,所以先创建缓存,同时缓存中计数为1。 + if (ObjectUtils.isNotEmpty(expire) && !expire.isZero()) { + // 如果传入的 expire 不为零,那么就用 expire 参数值 + create(key, expire); + put(expireKey, System.currentTimeMillis(), expire); + } else { + // 如果没有传入 expire 值,那么就默认使用 StampManager 自身配置的过期时间 + create(key); + put(expireKey, System.currentTimeMillis()); + } + } else { + // 不管是注解上配置Duration值还是StampProperties中配置的Duration值,是不会变的 + // 所以第一次存入expireKey对应的System.currentTimeMillis()时间后,这个值也不应该变化。 + // 因此,这里只更新访问次数的标记值 + Duration newDuration = calculateRemainingTime(expire, expireKey, function); + put(key, index + 1L, newDuration); + + // times 计数相当于数组的索引是 从0~n,所以需要 + if (index == maxTimes - 1) { + throw new MaximumLimitExceededException("Requests are too frequent. Please try again later!"); + } + } + + int times = new Long(index + 1L).intValue(); + log.debug("[Quafer] |- {} has been recorded [{}] times.", function, times); + return times; + } + + /** + * 计算剩余过期时间 + *

+ * 每次create或者put,缓存的过期时间都会被覆盖。(注意:Jetcache put 方法的参数名:expireAfterWrite)。 + * 因为Jetcache没有Redis的incr之类的方法,那么每次放入Times值,都会更新过期时间,实际操作下来是变相的延长了过期时间。 + * + * @param configuredDuration 注解上配置的、且可以正常解析的Duration值 + * @param expireKey 时间标记存储Key值。 + * @return 还剩余的过期时间 {@link Duration} + */ + private Duration calculateRemainingTime(Duration configuredDuration, String expireKey, String function) { + Long begin = get(expireKey); + Long current = System.currentTimeMillis(); + long interval = current - begin; + + log.debug("[Quafer] |- {} operation interval [{}] millis.", function, interval); + + Duration duration; + if (!configuredDuration.isZero()) { + duration = configuredDuration.minusMillis(interval); + } else { + duration = getExpire().minusMillis(interval); + } + + return duration; + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/AbstractStampManager.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/AbstractStampManager.java new file mode 100644 index 0000000..3d60961 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/AbstractStampManager.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.stamp; + +import com.liuhung.engine.cache.core.exception.StampDeleteFailedException; +import com.liuhung.engine.cache.core.exception.StampHasExpiredException; +import com.liuhung.engine.cache.core.exception.StampMismatchException; +import com.liuhung.engine.cache.core.exception.StampParameterIllegalException; +import com.liuhung.engine.cache.jetcache.utils.JetCacheUtils; +import com.alicp.jetcache.AutoReleaseLock; +import com.alicp.jetcache.Cache; +import com.alicp.jetcache.anno.CacheType; +import org.apache.commons.lang3.ObjectUtils; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + *

Description: 抽象Stamp管理

+ * + * @param 签章缓存对应Key值的类型。 + * @param 签章缓存存储数据,对应的具体存储值的类型, + * @author : liuh + * @date : 2021/8/23 11:51 + */ +public abstract class AbstractStampManager implements StampManager { + + private static final Duration DEFAULT_EXPIRE = Duration.ofMinutes(30); + + private String cacheName; + private CacheType cacheType; + private Duration expire; + private Cache cache; + + public AbstractStampManager(String cacheName) { + this(cacheName, CacheType.BOTH); + } + + public AbstractStampManager(String cacheName, CacheType cacheType) { + this(cacheName, cacheType, DEFAULT_EXPIRE); + } + + public AbstractStampManager(String cacheName, CacheType cacheType, Duration expire) { + this.cacheName = cacheName; + this.cacheType = cacheType; + this.expire = expire; + this.cache = JetCacheUtils.create(this.cacheName, this.cacheType, this.expire); + } + + /** + * 指定数据存储缓存 + * + * @return {@link Cache} + */ + protected Cache getCache() { + return this.cache; + } + + @Override + public Duration getExpire() { + return this.expire; + } + + public void setExpire(Duration expire) { + this.expire = expire; + } + + @Override + public boolean check(K key, V value) { + if (ObjectUtils.isEmpty(value)) { + throw new StampParameterIllegalException("Parameter Stamp value is null"); + } + + V storedStamp = this.get(key); + if (ObjectUtils.isEmpty(storedStamp)) { + throw new StampHasExpiredException("Stamp is invalid!"); + } + + if (ObjectUtils.notEqual(storedStamp, value)) { + throw new StampMismatchException("Stamp is mismathch!"); + } + + return true; + } + + @Override + public V get(K key) { + return this.getCache().get(key); + } + + @Override + public void delete(K key) throws StampDeleteFailedException { + boolean result = this.getCache().remove(key); + if (!result) { + throw new StampDeleteFailedException("Delete Stamp From Storage Failed"); + } + } + + @Override + public void put(K key, V value, long expireAfterWrite, TimeUnit timeUnit) { + this.getCache().put(key, value, expireAfterWrite, timeUnit); + } + + @Override + public AutoReleaseLock lock(K key, long expire, TimeUnit timeUnit) { + return this.getCache().tryLock(key, expire, timeUnit); + } + + @Override + public boolean lockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action) { + return this.getCache().tryLockAndRun(key, expire, timeUnit, action); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/StampManager.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/StampManager.java new file mode 100644 index 0000000..370c948 --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/stamp/StampManager.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.stamp; + +import com.liuhung.engine.cache.core.exception.*; +import com.liuhung.engine.cache.core.exception.StampDeleteFailedException; +import com.liuhung.engine.cache.core.exception.StampHasExpiredException; +import com.alicp.jetcache.AutoReleaseLock; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.InitializingBean; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + *

Description: Stamp 服务接口

+ *

+ * 此Stamp非OAuth2 Stamp。而是用于在特定条件下生成后,在一定时间就会消除的标记性Stamp。 + * 例如,幂等、短信验证码、Auth State等,用时生成,然后进行验证,之后再删除的标记Stamp。 + * + * @param 签章缓存对应Key值的类型。 + * @param 签章缓存存储数据,对应的具体存储值的类型, + * @author : liuh + * @date : 2021/8/22 15:00 + */ +public interface StampManager extends InitializingBean { + + /** + * 过期时间 + * + * @return {@link Duration} + */ + Duration getExpire(); + + /** + * 保存与Key对应的Stamp签章值 + * + * @param key 存储Key + * @param value 与Key对应的Stamp + * @param expireAfterWrite 过期时间 + * @param timeUnit 过期时间单位 + */ + void put(K key, V value, long expireAfterWrite, TimeUnit timeUnit); + + /** + * 保存与Key对应的Stamp签章值 + * + * @param key 存储Key + * @param value 与Key对应的Stamp值 + * @param expire 过期时间{@link Duration} + */ + default void put(K key, V value, Duration expire) { + put(key, value, expire.toMillis(), TimeUnit.MILLISECONDS); + } + + /** + * 保存与Key对应的Stamp签章值 + * + * @param key 存储Key + * @param value 与Key对应的Stamp值 + */ + default void put(K key, V value) { + put(key, value, getExpire()); + } + + /** + * 生成缓存值策略方法,该方法负责生成具体存储的值。 + * + * @param key 签章存储Key值 + * @return {@link String} + */ + V nextStamp(K key); + + /** + * 创建具体的Stamp签章值,并存储至本地缓存 + * + * @param key 签章存储Key值 + * @param expireAfterWrite 写入之后过期时间。注意:该值每次写入都会覆盖。如果有一个时间周期内的反复存取操作,需要手动计算时间差。 + * @param timeUnit 时间单位 + * @return 创建的签章值 + */ + default V create(K key, long expireAfterWrite, TimeUnit timeUnit) { + V value = this.nextStamp(key); + this.put(key, value, expireAfterWrite, timeUnit); + return value; + } + + /** + * 创建具体的Stamp签章值,并存储至本地缓存 + * + * @param key 签章存储Key值 + * @param expire 过期时间{@link Duration} + * @return 创建的签章值 + */ + default V create(K key, Duration expire) { + return create(key, expire.toMillis(), TimeUnit.MILLISECONDS); + } + + /** + * 创建具体的Stamp签章值,并存储至本地缓存 + * + * @param key 与签章存储Key值 + * @return 创建的签章值 + */ + default V create(K key) { + return create(key, getExpire()); + } + + /** + * 校验Stamp值,与本地存储的Stamp 是否匹配 + * + * @param key 与Stamp对应的Key值 + * @param value 外部传入的Stamp值 + * @return ture 匹配,false 不匹配 + * @throws StampParameterIllegalException 传入Stamp错误 + * @throws StampHasExpiredException 本地数据中没有Stamp或者Stamp已经过期。 + * @throws StampMismatchException Stamp与本地存储值不匹配 + */ + boolean check(K key, V value) throws StampParameterIllegalException, StampHasExpiredException, StampMismatchException; + + /** + * 根据key读取Stamp + * + * @param key 存储数据Key值 + * @return 存储的Stamp值 + */ + V get(K key); + + /** + * 删除与Key对应的Stamp + * + * @param key 存储数据Key值 + * @throws StampDeleteFailedException Stamp删除错误 + */ + void delete(K key) throws StampDeleteFailedException; + + default boolean containKey(K key) { + V value = get(key); + return ObjectUtils.isNotEmpty(value); + } + + /** + * 锁定值 + *

+ * 非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。 + * + * @param key 存储Key + * @param expire 过期时间 + * @param timeUnit 过期时间单位 + * @return {@link AutoReleaseLock} + * @see JetCache Wiki + */ + AutoReleaseLock lock(K key, long expire, TimeUnit timeUnit); + + /** + * 锁定值 + *

+ * 非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。 + * + * @param key 存储Key + * @param expire 过期时间{@link Duration} + * @return {@link AutoReleaseLock} + * @see JetCache Wiki + */ + default AutoReleaseLock lock(K key, Duration expire) { + return lock(key, expire.toMillis(), TimeUnit.MILLISECONDS); + } + + /** + * 锁定值 + *

+ * 非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。 + * * + * + * @param key 存储Key + * @return {@link AutoReleaseLock} + * @see JetCache Wiki + */ + default AutoReleaseLock lock(K key) { + return lock(key, getExpire()); + } + + /** + * 锁定并执行操作 + *

+ * 非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。 + * + * @param key 存储Key + * @param expire 过期时间 + * @param timeUnit 过期时间单位 + * @param action 需要执行的操作 {@link Runnable} + * @return 是否执行成功 + * @see JetCache Wiki + */ + boolean lockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action); + + /** + * 锁定并执行操作 + *

+ * 非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。 + * + * @param key 存储Key + * @param expire 过期时间{@link Duration} + * @param action 需要执行的操作 {@link Runnable} + * @return 是否执行成功 + * @see JetCache Wiki + */ + default boolean lockAndRun(K key, Duration expire, Runnable action) { + return lockAndRun(key, expire.toMillis(), TimeUnit.MILLISECONDS, action); + } + + /** + * 锁定并执行操作 + *

+ * 非堵塞的尝试获取一个锁,如果对应的key还没有锁,返回一个AutoReleaseLock,否则立即返回空。如果Cache实例是本地的,它是一个本地锁,在本JVM中有效;如果是redis等远程缓存,它是一个不十分严格的分布式锁。锁的超时时间由expire和timeUnit指定。多级缓存的情况会使用最后一级做tryLock操作。 + * + * @param key 存储Key + * @param action 需要执行的操作 {@link Runnable} + * @return 是否执行成功 + * @see JetCache Wiki + */ + default boolean lockAndRun(K key, Runnable action) { + return lockAndRun(key, getExpire(), action); + } +} diff --git a/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/utils/JetCacheUtils.java b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/utils/JetCacheUtils.java new file mode 100644 index 0000000..1be951d --- /dev/null +++ b/engine-cache/cache-sdk-jetcache/src/main/java/com/liuhung/engine/cache/jetcache/utils/JetCacheUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.jetcache.utils; + +import com.liuhung.engine.cache.jetcache.enhance.JetCacheCreateCacheFactory; +import com.alicp.jetcache.Cache; +import com.alicp.jetcache.anno.CacheType; +import org.apache.commons.lang3.ObjectUtils; + +import java.time.Duration; + +/** + *

Description: JetCache 单例工具类

+ * + * @author : liuh + * @date : 2022/8/9 15:43 + */ +public class JetCacheUtils { + + private static volatile JetCacheUtils instance; + private JetCacheCreateCacheFactory jetCacheCreateCacheFactory; + + private JetCacheUtils() { + + } + + private void init(JetCacheCreateCacheFactory jetCacheCreateCacheFactory) { + this.jetCacheCreateCacheFactory = jetCacheCreateCacheFactory; + } + + private JetCacheCreateCacheFactory getJetCacheCreateCacheFactory() { + return jetCacheCreateCacheFactory; + } + + public static JetCacheUtils getInstance() { + if (ObjectUtils.isEmpty(instance)) { + synchronized (JetCacheUtils.class) { + if (ObjectUtils.isEmpty(instance)) { + instance = new JetCacheUtils(); + } + } + } + return instance; + } + + public static void setJetCacheCreateCacheFactory(JetCacheCreateCacheFactory jetCacheCreateCacheFactory) { + getInstance().init(jetCacheCreateCacheFactory); + } + + public static Cache create(String name, Duration expire) { + return create(name, expire, true); + } + + public static Cache create(String name, Duration expire, Boolean cacheNullValue) { + return create(name, expire, cacheNullValue, null); + } + + public static Cache create(String name, Duration expire, Boolean cacheNullValue, Boolean syncLocal) { + return create(name, CacheType.BOTH, expire, cacheNullValue, syncLocal); + } + + public static Cache create(String name, CacheType cacheType) { + return create(name, cacheType, null); + } + + public static Cache create(String name, CacheType cacheType, Duration expire) { + return create(name, cacheType, expire, true); + } + + public static Cache create(String name, CacheType cacheType, Duration expire, Boolean cacheNullValue) { + return create(name, cacheType, expire, cacheNullValue, null); + } + + public static Cache create(String name, CacheType cacheType, Duration expire, Boolean cacheNullValue, Boolean syncLocal) { + return getInstance().getJetCacheCreateCacheFactory().create(name, cacheType, expire, cacheNullValue, syncLocal); + } +} diff --git a/engine-cache/cache-sdk-redis/README.md b/engine-cache/cache-sdk-redis/README.md new file mode 100644 index 0000000..1fa3b5b --- /dev/null +++ b/engine-cache/cache-sdk-redis/README.md @@ -0,0 +1,11 @@ +## Redis 相关代码组件模块 + +**包含以下内容:** + +1. Redis 配置。 +2. 扩展的 Redis Cache Manager 配置 +3. 基于 Redis 的 Spring Session 共享配置。通过设置 `spring.session.store-type=redis` 开启。 + +### 说明 + +spring session 共享,还支持 mongodb,jdbc,所以暂时将Redis session 共享放置在此包,后续根据实际使用再行调整。 \ No newline at end of file diff --git a/engine-cache/cache-sdk-redis/pom.xml b/engine-cache/cache-sdk-redis/pom.xml new file mode 100644 index 0000000..05094c2 --- /dev/null +++ b/engine-cache/cache-sdk-redis/pom.xml @@ -0,0 +1,68 @@ + + + + + + 4.0.0 + + + engine-cache + com.liuhung.engine + 2.7.8.0 + + + cache-sdk-redis + 2.7.8.0 + jar + + + + com.liuhung.engine + cache-core + + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + org.springframework.session + spring-session-data-redis + + + + org.springframework.boot + spring-boot-starter-web + compile + true + + + + + \ No newline at end of file diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/annotation/ConditionalOnRedisSessionSharing.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/annotation/ConditionalOnRedisSessionSharing.java new file mode 100644 index 0000000..d8aa2bc --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/annotation/ConditionalOnRedisSessionSharing.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.annotation; + +import com.liuhung.engine.cache.redis.condition.RedisSessionSharingCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: 基于 Redis Session 共享条件注解

+ * + * @author : liuh + * @date : 2022/5/23 22:34 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(RedisSessionSharingCondition.class) +public @interface ConditionalOnRedisSessionSharing { +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/annotation/EnableQuaferRedis.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/annotation/EnableQuaferRedis.java new file mode 100644 index 0000000..c9451f9 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/annotation/EnableQuaferRedis.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.annotation; + +import com.liuhung.engine.cache.redis.configuration.CacheRedisConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 开启 Quafer Redis

+ * + * @author : liuh + * @date : 2022/12/29 21:25 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(CacheRedisConfiguration.class) +public @interface EnableQuaferRedis { +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/condition/RedisSessionSharingCondition.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/condition/RedisSessionSharingCondition.java new file mode 100644 index 0000000..2313bd6 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/condition/RedisSessionSharingCondition.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.condition; + +import com.liuhung.engine.assistant.core.context.PropertyFinder; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: 开启基于 Redis 的 Session 共享条件

+ * + * @author : liuh + * @date : 2022/5/23 22:30 + */ +public class RedisSessionSharingCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(RedisSessionSharingCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + String property = PropertyFinder.getSessionStoreType(conditionContext.getEnvironment()); + boolean result = StringUtils.isNotBlank(property) && StringUtils.equalsIgnoreCase(property, "redis"); + log.debug("[Quafer] |- Condition [Redis Session Sharing] value is [{}]", result); + return result; + } +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/configuration/CacheRedisConfiguration.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/configuration/CacheRedisConfiguration.java new file mode 100644 index 0000000..2cafefb --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/configuration/CacheRedisConfiguration.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.configuration; + +import com.liuhung.engine.cache.core.properties.CacheProperties; +import com.liuhung.engine.cache.redis.enhance.QuaferRedisCacheManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import javax.annotation.PostConstruct; + +/** + *

Description: Redis 配置

+ * + * @author : liuh + * @date : 2022/5/23 17:00 + */ +@Configuration(proxyBeanMethods = false) +@Import(RedisSessionSharingConfiguration.class) +public class CacheRedisConfiguration { + + private static final Logger log = LoggerFactory.getLogger(CacheRedisConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Cache Redis] Auto Configure."); + } + + private RedisSerializer keySerializer() { + return new StringRedisSerializer(); + } + + private RedisSerializer valueSerializer() { + return new Jackson2JsonRedisSerializer<>(Object.class); + } + + /** + * 重新配置一个RedisTemplate + * + * @return {@link RedisTemplate} + */ + @Bean(name = "redisTemplate") + @ConditionalOnMissingBean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(keySerializer()); + redisTemplate.setHashKeySerializer(keySerializer()); + redisTemplate.setValueSerializer(valueSerializer()); + redisTemplate.setHashValueSerializer(valueSerializer()); + redisTemplate.setDefaultSerializer(valueSerializer()); + redisTemplate.afterPropertiesSet(); + + log.trace("[Quafer] |- Bean [Redis Template] Auto Configure."); + + return redisTemplate; + } + + @Bean(name = "stringRedisTemplate") + @ConditionalOnMissingBean + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { + StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); + stringRedisTemplate.setConnectionFactory(redisConnectionFactory); + stringRedisTemplate.afterPropertiesSet(); + log.trace("[Quafer] |- Bean [String Redis Template] Auto Configure."); + return stringRedisTemplate; + } + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + log.trace("[Quafer] |- Bean [Redis Message Listener Container] Auto Configure."); + return container; + } + + @Bean + public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, CacheProperties cacheProperties) { + RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); + + // 注意:这里 RedisCacheConfiguration 每一个方法调用之后,都会返回一个新的 RedisCacheConfiguration 对象,所以要注意对象的引用关系。 + RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(cacheProperties.getTtl()); + + boolean allowNullValues = cacheProperties.getAllowNullValues(); + if (!allowNullValues) { + // 注意:这里 RedisCacheConfiguration 每一个方法调用之后,都会返回一个新的 RedisCacheConfiguration 对象,所以要注意对象的引用关系。 + redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues(); + } + + QuaferRedisCacheManager quaferRedisCacheManager = new QuaferRedisCacheManager(redisCacheWriter, redisCacheConfiguration, cacheProperties); + quaferRedisCacheManager.setTransactionAware(false); + quaferRedisCacheManager.afterPropertiesSet(); + log.trace("[Quafer] |- Bean [Redis Cache Manager] Auto Configure."); + return quaferRedisCacheManager; + } + + @Configuration(proxyBeanMethods = false) + @ComponentScan({ + "com.liuhung.engine.cache.redis.utils" + }) + static class RedisUtilsConfiguration { + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Cache Redis Utils] Auto Configure."); + } + } +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/configuration/RedisSessionSharingConfiguration.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/configuration/RedisSessionSharingConfiguration.java new file mode 100644 index 0000000..d6b7dff --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/configuration/RedisSessionSharingConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.configuration; + +import com.liuhung.engine.cache.redis.annotation.ConditionalOnRedisSessionSharing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.FlushMode; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.web.http.CookieSerializer; +import org.springframework.session.web.http.DefaultCookieSerializer; + +import javax.annotation.PostConstruct; + +/** + *

Description: 基于 Redis 的 Session 共享配置

+ * + * @author : liuh + * @date : 2022/5/23 22:21 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnRedisSessionSharing +public class RedisSessionSharingConfiguration { + + private static final Logger log = LoggerFactory.getLogger(CacheRedisConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Cache Redis Session Sharing] Auto Configure."); + } + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE) + static class QuaferRedisHttpSessionConfiguration { + + @Bean + public CookieSerializer cookieSerializer() { + DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); + cookieSerializer.setUseHttpOnlyCookie(false); + cookieSerializer.setSameSite(null); + cookieSerializer.setCookiePath("/"); + cookieSerializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); + log.trace("[Quafer] |- Bean [Cookie Serializer] Auto Configure."); + return cookieSerializer; + } + } +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/definition/AbstractStringRedisCache.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/definition/AbstractStringRedisCache.java new file mode 100644 index 0000000..1364f27 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/definition/AbstractStringRedisCache.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.definition; + +import com.liuhung.engine.cache.core.constants.CacheConstants; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + *

Description: SimpleRedisCache的基础实现

+ *

+ * 增加这一层是为了方便扩展,比如说支持JustAuth + * + * @author : liuh + * @date : 2021/5/21 23:45 + */ +public abstract class AbstractStringRedisCache implements StringRedisCache { + + private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(10); + private static final String DEFAULT_PREFIX = CacheConstants.CACHE_SIMPLE_BASE_PREFIX + "default:"; + + private StringRedisTemplate stringRedisTemplate; + private String prefix = DEFAULT_PREFIX; + private long defaultTimeout = DEFAULT_TIMEOUT.toMillis(); + + public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) { + this.stringRedisTemplate = stringRedisTemplate; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public void setDefaultTimeout(long defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + + @Override + public void cache(String key, String value) { + this.cache(key, value, this.defaultTimeout); + } + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + * @param timeout 指定缓存过期时间(毫秒) + */ + @Override + public void cache(String key, String value, long timeout) { + this.stringRedisTemplate.opsForValue().set(this.prefix + key, value, timeout, TimeUnit.MILLISECONDS); + } + + /** + * 获取缓存内容 + * + * @param key 缓存key + * @return 缓存内容 + */ + @Override + public String get(String key) { + return this.stringRedisTemplate.opsForValue().get(this.prefix + key); + } + + /** + * 是否存在key,如果对应key的value值已过期,也返回false + * + * @param key 缓存key + * @return true:存在key,并且value没过期;false:key不存在或者已过期 + */ + @Override + public boolean containsKey(String key) { + Long expire = this.stringRedisTemplate.getExpire(this.prefix + key, TimeUnit.MILLISECONDS); + if (expire == null) { + expire = 0L; + } + return expire > 0; + } + + @Override + public boolean delete(String key) { + if (containsKey(key)) { + return this.stringRedisTemplate.delete(key); + } + return true; + } +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/definition/StringRedisCache.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/definition/StringRedisCache.java new file mode 100644 index 0000000..07fcc72 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/definition/StringRedisCache.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.definition; + +import org.springframework.beans.factory.InitializingBean; + +/** + *

Description: 简单数据的Redis存储

+ *

+ * 很多使用Redis进行缓存的数据都比较简单,通常就是单一的键值对存储。例如,手机验证码、JustAuth的State数据、第三方集成的Token等。 + * 因此,抽象出SimpleRedisCache,对此类操作进行定义。 + * + * @author : liuh + * @date : 2021/5/21 23:37 + */ +public interface StringRedisCache extends InitializingBean { + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + */ + void cache(String key, String value); + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + * @param timeout 指定缓存过期时间(毫秒) + */ + void cache(String key, String value, long timeout); + + /** + * 获取缓存内容 + * + * @param key 缓存key + * @return 缓存内容 + */ + String get(String key); + + /** + * 是否存在key,如果对应key的value值已过期,也返回false + * + * @param key 缓存key + * @return true:存在key,并且value没过期;false:key不存在或者已过期 + */ + boolean containsKey(String key); + + boolean delete(String key); +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/enhance/QuaferRedisCacheManager.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/enhance/QuaferRedisCacheManager.java new file mode 100644 index 0000000..82d7cc0 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/enhance/QuaferRedisCacheManager.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.enhance; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.cache.core.properties.CacheProperties; +import com.liuhung.engine.cache.core.properties.Expire; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.cache.RedisCache; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; + +import java.util.Map; + +/** + *

Description: 扩展的RedisCacheManager

+ *

+ * 用于支持 Redis 缓存可以针对实体进行单独的过期时间设定 + * + * @author : liuh + * @date : 2021/10/25 20:49 + */ +public class QuaferRedisCacheManager extends RedisCacheManager { + + private static final Logger log = LoggerFactory.getLogger(QuaferRedisCacheManager.class); + + private CacheProperties cacheProperties; + + public QuaferRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, CacheProperties cacheProperties) { + super(cacheWriter, defaultCacheConfiguration); + this.cacheProperties = cacheProperties; + } + + public QuaferRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, CacheProperties cacheProperties, String... initialCacheNames) { + super(cacheWriter, defaultCacheConfiguration, initialCacheNames); + this.cacheProperties = cacheProperties; + } + + public QuaferRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, CacheProperties cacheProperties, String... initialCacheNames) { + super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames); + this.cacheProperties = cacheProperties; + } + + public QuaferRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map initialCacheConfigurations, CacheProperties cacheProperties) { + super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations); + this.cacheProperties = cacheProperties; + } + + public QuaferRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map initialCacheConfigurations, boolean allowInFlightCacheCreation) { + super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation); + } + + @Override + protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) { + Map expires = cacheProperties.getExpires(); + if (MapUtils.isNotEmpty(expires)) { + String key = StringUtils.replace(name, SymbolConstants.COLON, cacheProperties.getSeparator()); + if (expires.containsKey(key)) { + Expire expire = expires.get(key); + log.debug("[Quafer] |- CACHE - Redis cache [{}] is setted to use CUSTEM exprie.", name); + cacheConfig = cacheConfig.entryTtl(expire.getTtl()); + } + } + + return super.createRedisCache(name, cacheConfig); + } +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/session/QuaferHttpSessionIdResolver.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/session/QuaferHttpSessionIdResolver.java new file mode 100644 index 0000000..f089e09 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/session/QuaferHttpSessionIdResolver.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.session; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.session.web.http.CookieHttpSessionIdResolver; +import org.springframework.session.web.http.CookieSerializer; +import org.springframework.session.web.http.DefaultCookieSerializer; +import org.springframework.session.web.http.HttpSessionIdResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collections; +import java.util.List; + +/** + *

Description: QuaferHttpSessionIdResolver

+ *

+ * 扩展的 HttpSessionIdResolver, 以同时支持页面和接口的 Session 共享 + * + * @author : liuh + * @date : 2022/12/2 21:37 + */ +public class QuaferHttpSessionIdResolver implements HttpSessionIdResolver { + + private static final Logger log = LoggerFactory.getLogger(QuaferHttpSessionIdResolver.class); + + private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class.getName() + .concat(".WRITTEN_SESSION_ID_ATTR"); + + private final String headerName; + private CookieSerializer cookieSerializer = new DefaultCookieSerializer(); + + + public QuaferHttpSessionIdResolver(String headerName) { + if (StringUtils.isBlank(headerName)) { + throw new IllegalArgumentException("headerName cannot be null"); + } + this.headerName = headerName; + } + + private String resolveHeaderSessionId(HttpServletRequest request) { + String headerValue = request.getHeader(this.headerName); + if (StringUtils.isNotBlank(headerValue)) { + log.debug("[Quafer] |- Resolve http session id [{}] from header in request [{}]", headerValue, request.getRequestURI()); + return headerValue; + } + return null; + } + + private List resolveHeaderSessionIds(HttpServletRequest request) { + String id = resolveHeaderSessionId(request); + return StringUtils.isNotBlank(id) ? Collections.singletonList(id) : Collections.emptyList(); + } + + @Override + public List resolveSessionIds(HttpServletRequest request) { + + List idsInHeader = resolveHeaderSessionIds(request); + if (CollectionUtils.isNotEmpty(idsInHeader)) { + return idsInHeader; + } else { + return this.cookieSerializer.readCookieValues(request); + } + } + + private void changeSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) { + this.cookieSerializer.writeCookieValue(new CookieSerializer.CookieValue(request, response, sessionId)); + response.setHeader(this.headerName, sessionId); + } + + @Override + public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) { + + if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) { + return; + } + + String id = sessionId; + String quaferSessionId = resolveHeaderSessionId(request); + if (StringUtils.isNotBlank(quaferSessionId)) { + id = quaferSessionId; + } + + request.setAttribute(WRITTEN_SESSION_ID_ATTR, id); + changeSessionId(request, response, id); + } + + @Override + public void expireSession(HttpServletRequest request, HttpServletResponse response) { + changeSessionId(request, response, ""); + } + + /** + * Sets the {@link CookieSerializer} to be used. + * + * @param cookieSerializer the cookieSerializer to set. Cannot be null. + */ + public void setCookieSerializer(CookieSerializer cookieSerializer) { + if (cookieSerializer == null) { + throw new IllegalArgumentException("cookieSerializer cannot be null"); + } + this.cookieSerializer = cookieSerializer; + } +} diff --git a/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/utils/RedisBitMapUtils.java b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/utils/RedisBitMapUtils.java new file mode 100644 index 0000000..fb32041 --- /dev/null +++ b/engine-cache/cache-sdk-redis/src/main/java/com/liuhung/engine/cache/redis/utils/RedisBitMapUtils.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redis.utils; + +import com.google.common.hash.Funnels; +import com.google.common.hash.Hashing; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.connection.RedisStringCommands; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +/** + *

Description: Redis BitMap 工具类

+ *

+ * · Redis的Bitmaps这个“数据结构”可以实现对位的操作。Bitmaps本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作 + * · 可把Bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量 + * · 单个bitmaps的最大长度是512MB,即2^32个比特位 + *

+ * bitmaps的最大优势是节省存储空间。比如在一个以自增id代表不同用户的系统中,我们只需要512MB空间就可以记录40亿用户的某个单一信息,相比mysql节省了大量的空间 + *

+ * · 有两种类型的位操作:一类是对特定bit位的操作,比如设置/获取某个特定比特位的值。另一类是批量bit位操作,例如在给定范围内统计为1的比特位个数 + *

+ * 1.1 优点: + *

+ * 节省空间:通过一个bit位来表示某个元素对应的值或者状态,其中key就是对应元素的值。实际上8个bit可以组成一个Byte,所以是及其节省空间的。 + *

+ * 效率高:setbit和getbit的时间复杂度都是O(1),其他位运算效率也高。 + *

+ * 1.2 缺点: + * 本质上位只有0和1的区别,所以用位做业务数据记录,就不需要在意value的值。 + * + * @author : liuh + * @date : 2022/12/29 17:41 + * @see + */ +@Component +public class RedisBitMapUtils { + + private static StringRedisTemplate stringRedisTemplate; + + @Autowired + @Qualifier(value = "stringRedisTemplate") + public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) { + RedisBitMapUtils.stringRedisTemplate = stringRedisTemplate; + } + + /** + * 计算 Hash 值 + * + * @param key bitmap结构的key + * @return Hash 值 + */ + private static long hash(String key) { + return Math.abs(Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(StandardCharsets.UTF_8)).asInt()); + } + + + /** + * 设置与 param 对应的二进制位的值,{@param param}会经过hash计算进行存储。 + * + * @param key bitmap数据结构的key + * @param param 要设置偏移的key,该key会经过hash运算。 + * @param value true:即该位设置为1,否则设置为0 + * @return 返回设置该value之前的值。 + */ + public static Boolean setBit(String key, String param, boolean value) { + return stringRedisTemplate.opsForValue().setBit(key, hash(param), value); + } + + /** + * 查询与指定 param 对应二进制位的值,{@param param}会经过hash计算进行存储。 + * + * @param key bitmap结构的key + * @param param 要移除偏移的key,该key会经过hash运算。 + * @return 若偏移位上的值为1,那么返回true。 + */ + public static boolean getBit(String key, String param) { + return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().getBit(key, hash(param))); + } + + /** + * 将指定offset偏移量的值设置为1; + * + * @param key bitmap结构的key + * @param offset 指定的偏移量。 + * @param value true:即该位设置为1,否则设置为0 + * @return 返回设置该value之前的值。 + */ + public static Boolean setBit(String key, Long offset, boolean value) { + return stringRedisTemplate.opsForValue().setBit(key, offset, value); + } + + /** + * 获取指定 offset 偏移量的值; + * + * @param key bitmap结构的key + * @param offset 指定的偏移量。 + * @return 若偏移位上的值为 1,那么返回true。 + */ + public static Boolean getBit(String key, long offset) { + return stringRedisTemplate.opsForValue().getBit(key, offset); + } + + /** + * 统计对应的bitmap上value为1的数量 + * + * @param key bitmap的key + * @return value等于1的数量 + */ + public static Long bitCount(String key) { + return stringRedisTemplate.execute((RedisCallback) connection -> connection.stringCommands().bitCount(key.getBytes(StandardCharsets.UTF_8))); + } + + /** + * 统计指定范围中value为1的数量 + * + * @param key bitMap中的key + * @param start 该参数的单位是byte(1byte=8bit),{@code setBit(key,7,true);}进行存储时,单位是bit。那么只需要统计[0,1]便可以统计到上述set的值。 + * @param end 该参数的单位是byte。 + * @return 在指定范围[start*8,end*8]内所有value=1的数量 + */ + public static Long bitCount(String key, int start, int end) { + return stringRedisTemplate.execute((RedisCallback) connection -> connection.stringCommands().bitCount(key.getBytes(), start, end)); + } + + /** + * 对一个或多个保存二进制的字符串key进行元操作,并将结果保存到saveKey上。 + *

+ * bitop and saveKey key [key...],对一个或多个key逻辑并,结果保存到saveKey。 + * bitop or saveKey key [key...],对一个或多个key逻辑或,结果保存到saveKey。 + * bitop xor saveKey key [key...],对一个或多个key逻辑异或,结果保存到saveKey。 + * bitop xor saveKey key,对一个或多个key逻辑非,结果保存到saveKey。 + *

+ * + * @param op 元操作类型; + * @param saveKey 元操作后将结果保存到saveKey所在的结构中。 + * @param destKey 需要进行元操作的类型。 + * @return 1:返回元操作值。 + */ + public static Long bitOp(RedisStringCommands.BitOperation op, String saveKey, String... destKey) { + byte[][] bytes = new byte[destKey.length][]; + for (int i = 0; i < destKey.length; i++) { + bytes[i] = destKey[i].getBytes(); + } + return stringRedisTemplate.execute((RedisCallback) connection -> connection.stringCommands().bitOp(op, saveKey.getBytes(), bytes)); + } + + /** + * 对一个或多个保存二进制的字符串key进行元操作,并将结果保存到saveKey上,并返回统计之后的结果。 + * + * @param op 元操作类型; + * @param saveKey 元操作后将结果保存到saveKey所在的结构中。 + * @param destKey 需要进行元操作的类型。 + * @return 返回saveKey结构上value=1的所有数量值。 + */ + public static Long bitOpResult(RedisStringCommands.BitOperation op, String saveKey, String... destKey) { + bitOp(op, saveKey, destKey); + return bitCount(saveKey); + } +} diff --git a/engine-cache/cache-sdk-redisson/README.md b/engine-cache/cache-sdk-redisson/README.md new file mode 100644 index 0000000..74040bd --- /dev/null +++ b/engine-cache/cache-sdk-redisson/README.md @@ -0,0 +1,3 @@ +## Redisson相关代码组件模块 + +**包含以下内容:** diff --git a/engine-cache/cache-sdk-redisson/pom.xml b/engine-cache/cache-sdk-redisson/pom.xml new file mode 100644 index 0000000..80bcd5f --- /dev/null +++ b/engine-cache/cache-sdk-redisson/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + + engine-cache + com.liuhung.engine + 2.7.8.0 + + + cache-sdk-redisson + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud Redisson 组件相关代码模块 + + + + com.liuhung.engine + cache-core + + + + org.redisson + redisson + + + + \ No newline at end of file diff --git a/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/annotation/ConditionalOnRedissonEnabled.java b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/annotation/ConditionalOnRedissonEnabled.java new file mode 100644 index 0000000..183a866 --- /dev/null +++ b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/annotation/ConditionalOnRedissonEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redisson.annotation; + +import com.liuhung.engine.cache.core.constants.CacheConstants; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.*; + +/** + *

Description: 是否开启 Redisson 条件注解

+ * + * @author : liuh + * @date : 2021/10/22 14:40 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@ConditionalOnProperty(value = CacheConstants.ITEM_REDISSON_ENABLED, havingValue = "true") +public @interface ConditionalOnRedissonEnabled { +} diff --git a/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/annotation/EnableQuaferRedisson.java b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/annotation/EnableQuaferRedisson.java new file mode 100644 index 0000000..9f0e70e --- /dev/null +++ b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/annotation/EnableQuaferRedisson.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redisson.annotation; + +import com.liuhung.engine.cache.redisson.configuration.RedissonConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 手动开启Redisson注入

+ * + * @author : liuh + * @date : 2022/1/14 23:06 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(RedissonConfiguration.class) +public @interface EnableQuaferRedisson { +} diff --git a/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/configuration/RedissonConfiguration.java b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/configuration/RedissonConfiguration.java new file mode 100644 index 0000000..047f3f5 --- /dev/null +++ b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/configuration/RedissonConfiguration.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redisson.configuration; + +import com.liuhung.engine.cache.redisson.annotation.ConditionalOnRedissonEnabled; +import com.liuhung.engine.cache.redisson.properties.RedissonProperties; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.utils.ResourceUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.codec.JsonJacksonCodec; +import org.redisson.config.ClusterServersConfig; +import org.redisson.config.Config; +import org.redisson.config.SentinelServersConfig; +import org.redisson.config.SingleServerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; + +/** + *

Description: Redisson配置

+ * + * @author : liuh + * @date : 2021/10/22 13:56 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnRedissonEnabled +@EnableConfigurationProperties(RedissonProperties.class) +public class RedissonConfiguration { + + private static final Logger log = LoggerFactory.getLogger(RedissonConfiguration.class); + + @Autowired + private RedissonProperties redissonProperties; + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Cache Redisson] Auto Configure."); + } + + private File readConfigFile() { + String configFile = redissonProperties.getConfig(); + if (StringUtils.isNotBlank(configFile)) { + try { + return ResourceUtils.getFile(configFile); + } catch (IOException e) { + log.error("[Quafer] |- Can not found the config file [{}], check whether the format is correct.", configFile); + } + } + + return null; + } + + private Config getConfigByFile() { + try { + File configFile = readConfigFile(); + if (ObjectUtils.isNotEmpty(configFile)) { + if (StringUtils.endsWithIgnoreCase(configFile.getName(), SymbolConstants.SUFFIX_YAML)) { + return Config.fromYAML(configFile); + } + + if (StringUtils.endsWithIgnoreCase(configFile.getName(), SymbolConstants.SUFFIX_JSON)) { + return Config.fromJSON(configFile); + } + } + } catch (IOException e) { + log.error("[Quafer] |- Redisson loading the config file error!"); + } + + return null; + } + + private Config getDefaultConfig() { + Config config = new Config(); + + switch (redissonProperties.getMode()) { + case CLUSTER: + ClusterServersConfig clusterServersConfig = config.useClusterServers(); + BeanUtils.copyProperties(redissonProperties.getClusterServersConfig(), clusterServersConfig, ClusterServersConfig.class); + redissonProperties.getClusterServersConfig().getNodeAddresses().parallelStream().forEach(clusterServersConfig::addNodeAddress); + break; + case SENTINEL: + SentinelServersConfig sentinelServersConfig = config.useSentinelServers(); + BeanUtils.copyProperties(redissonProperties.getSentinelServersConfig(), sentinelServersConfig, SentinelServersConfig.class); + redissonProperties.getSentinelServersConfig().getSentinelAddresses().parallelStream().forEach(sentinelServersConfig::addSentinelAddress); + break; + default: + SingleServerConfig singleServerConfig = config.useSingleServer(); + BeanUtils.copyProperties(redissonProperties.getSingleServerConfig(), singleServerConfig, SingleServerConfig.class); + break; + } + + config.setCodec(new JsonJacksonCodec()); + //默认情况下,看门狗的检查锁的超时时间是30秒钟 + config.setLockWatchdogTimeout(1000 * 30); + return config; + } + + @Bean + public RedissonClient redissonClient() { + Config config = getConfigByFile(); + if (ObjectUtils.isEmpty(config)) { + config = getDefaultConfig(); + } + + RedissonClient redissonClient = Redisson.create(config); + + log.trace("[Quafer] |- Bean [Redisson Client] Auto Configure."); + + return redissonClient; + } +} diff --git a/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/properties/RedissonProperties.java b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/properties/RedissonProperties.java new file mode 100644 index 0000000..1717410 --- /dev/null +++ b/engine-cache/cache-sdk-redisson/src/main/java/com/liuhung/engine/cache/redisson/properties/RedissonProperties.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.redisson.properties; + +import com.liuhung.engine.cache.core.constants.CacheConstants; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.google.common.base.MoreObjects; +import org.apache.commons.lang3.StringUtils; +import org.redisson.config.ClusterServersConfig; +import org.redisson.config.SentinelServersConfig; +import org.redisson.config.SingleServerConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

Description: Redisson 配置

+ * + * @author : liuh + * @date : 2021/10/22 14:02 + */ +@ConfigurationProperties(prefix = CacheConstants.PROPERTY_REDIS_REDISSON) +public class RedissonProperties { + + /** + * Redisson 使用模式 + */ + public enum Mode { + /** + * 单机 + */ + SINGLE, + /** + * 哨兵 + */ + SENTINEL, + /** + * 集群 + */ + CLUSTER + } + + /** + * 是否开启 Redisson + */ + private Boolean enabled = false; + + /** + * Redis 模式 + */ + private Mode mode = Mode.SINGLE; + + /** + * 配置文件路径 + */ + private String config; + + /** + * 单体配置 + */ + private SingleServerConfig singleServerConfig; + /** + * 集群配置 + */ + private ClusterServersConfig clusterServersConfig; + + /** + * 哨兵配置 + */ + private SentinelServersConfig sentinelServersConfig; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public String getConfig() { + return config; + } + + public void setConfig(String config) { + this.config = config; + } + + public SingleServerConfig getSingleServerConfig() { + return singleServerConfig; + } + + public void setSingleServerConfig(SingleServerConfig singleServerConfig) { + this.singleServerConfig = singleServerConfig; + } + + public ClusterServersConfig getClusterServersConfig() { + return clusterServersConfig; + } + + public void setClusterServersConfig(ClusterServersConfig clusterServersConfig) { + this.clusterServersConfig = clusterServersConfig; + } + + public SentinelServersConfig getSentinelServersConfig() { + return sentinelServersConfig; + } + + public void setSentinelServersConfig(SentinelServersConfig sentinelServersConfig) { + this.sentinelServersConfig = sentinelServersConfig; + } + + public boolean isExternalConfig() { + return StringUtils.isNotBlank(this.getConfig()); + } + + public boolean isYamlConfig() { + if (this.isExternalConfig()) { + return StringUtils.endsWithIgnoreCase(this.getConfig(), SymbolConstants.SUFFIX_YAML); + } else { + return false; + } + } + + public boolean isJsonConfig() { + if (this.isExternalConfig()) { + return StringUtils.endsWithIgnoreCase(this.getConfig(), SymbolConstants.SUFFIX_JSON); + } else { + return false; + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("enabled", enabled) + .add("mode", mode) + .add("config", config) + .toString(); + } +} diff --git a/engine-cache/cache-spring-boot-starter/README.md b/engine-cache/cache-spring-boot-starter/README.md new file mode 100644 index 0000000..30642a6 --- /dev/null +++ b/engine-cache/cache-spring-boot-starter/README.md @@ -0,0 +1,3 @@ +## 缓存模块统一 Starter 模块 + +**包含以下内容:** diff --git a/engine-cache/cache-spring-boot-starter/pom.xml b/engine-cache/cache-spring-boot-starter/pom.xml new file mode 100644 index 0000000..8e24d5a --- /dev/null +++ b/engine-cache/cache-spring-boot-starter/pom.xml @@ -0,0 +1,60 @@ + + + + + 4.0.0 + + + engine-cache + com.liuhung.engine + 2.7.8.0 + + + cache-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud Cache 模块统一 Starter + + + + com.liuhung.engine + cache-sdk-redisson + + + com.liuhung.engine + cache-sdk-jetcache + + + + org.springframework.boot + spring-boot-starter-cache + + + + \ No newline at end of file diff --git a/engine-cache/cache-spring-boot-starter/src/main/java/com/liuhung/engine/cache/autoconfigure/AutoConfiguration.java b/engine-cache/cache-spring-boot-starter/src/main/java/com/liuhung/engine/cache/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..8f6c1c3 --- /dev/null +++ b/engine-cache/cache-spring-boot-starter/src/main/java/com/liuhung/engine/cache/autoconfigure/AutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.cache.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Cache 配置

+ * + * @author : liuh + * @date : 2022/1/13 22:29 + */ +@Configuration(proxyBeanMethods = false) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Cache Starter] Auto Configure."); + } +} diff --git a/engine-cache/cache-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-cache/cache-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..7843c03 --- /dev/null +++ b/engine-cache/cache-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.liuhung.engine.cache.autoconfigure.AutoConfiguration +com.liuhung.engine.cache.jetcache.configuration.JetCacheConfiguration +com.liuhung.engine.cache.redisson.configuration.RedissonConfiguration \ No newline at end of file diff --git a/engine-cache/pom.xml b/engine-cache/pom.xml new file mode 100644 index 0000000..2172f77 --- /dev/null +++ b/engine-cache/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + engine-cache + 2.7.8.0 + pom + + + cache-core + cache-sdk-redis + cache-sdk-caffeine + cache-sdk-jetcache + cache-sdk-redisson + cache-spring-boot-starter + + + \ No newline at end of file diff --git a/engine-captcha/captcha-core/README.md b/engine-captcha/captcha-core/README.md new file mode 100644 index 0000000..5af4121 --- /dev/null +++ b/engine-captcha/captcha-core/README.md @@ -0,0 +1 @@ +## 验证通用代码模块 diff --git a/engine-captcha/captcha-core/pom.xml b/engine-captcha/captcha-core/pom.xml new file mode 100644 index 0000000..6194dfc --- /dev/null +++ b/engine-captcha/captcha-core/pom.xml @@ -0,0 +1,77 @@ + + + + + 4.0.0 + + + engine-captcha + com.liuhung.engine + 2.7.8.0 + + + captcha-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + cache-sdk-jetcache + + + + + + + ${project.basedir}/src/main/java + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + + txt + ttf + png + + + + + + + \ No newline at end of file diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/algorithm/GaussianBlur.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/algorithm/GaussianBlur.java new file mode 100644 index 0000000..3057d39 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/algorithm/GaussianBlur.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.algorithm; + +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + *

Description: 高斯模糊算法

+ * + * @author : liuh + * @date : 2021/12/11 12:23 + */ +public class GaussianBlur { + + /** + * 高斯模糊 + * + * @param image 原图 + * @param pixelX X像素点 + * @param pixelY Y像素点 + * @param matrix 高斯模糊矩阵 + * @param values 高斯模糊存周边像素值 + * @param size 模糊区域大小 + */ + public static void execute(BufferedImage image, int pixelX, int pixelY, int[][] matrix, int[] values, int size) { + // 抠图区域高斯模糊 + readPixel(image, pixelX, pixelY, values, size); + fillMatrix(matrix, values); + image.setRGB(pixelX, pixelY, averageMatrix(matrix)); + } + + private static void readPixel(BufferedImage bufferedImage, int x, int y, int[] pixels, int size) { + int xStart = x - 1; + int yStart = y - 1; + + int current = 0; + for (int i = xStart; i < size + xStart; i++) { + for (int j = yStart; j < size + yStart; j++) { + int tempX = calculatePixel(x, i, bufferedImage.getWidth()); + int tempY = calculatePixel(y, j, bufferedImage.getHeight()); + pixels[current++] = bufferedImage.getRGB(tempX, tempY); + } + } + } + + private static int calculatePixel(int value, int step, int bound) { + int pixel = step; + if (pixel < 0) { + pixel = -pixel; + } else if (pixel >= bound) { + pixel = value; + } + return pixel; + } + + private static void fillMatrix(int[][] matrix, int[] values) { + int filled = 0; + for (int[] x : matrix) { + for (int j = 0; j < x.length; j++) { + x[j] = values[filled++]; + } + } + } + + private static int averageMatrix(int[][] matrix) { + int r = 0; + int g = 0; + int b = 0; + for (int[] x : matrix) { + for (int j = 0; j < x.length; j++) { + if (j == 1) { + continue; + } + Color c = new Color(x[j]); + r += c.getRed(); + g += c.getGreen(); + b += c.getBlue(); + } + } + + return new Color(r / 8, g / 8, b / 8).getRGB(); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/constants/CaptchaConstants.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/constants/CaptchaConstants.java new file mode 100644 index 0000000..cce2115 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/constants/CaptchaConstants.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: 验证码常量

+ * + * @author : liuh + * @date : 2022/1/18 19:06 + */ +public interface CaptchaConstants extends BaseConstants { + + /** + * security + */ + String PROPERTY_PREFIX_CAPTCHA = PROPERTY_PREFIX_HERODOTUS + ".captcha"; + + String CACHE_NAME_TOKEN_CAPTCHA = CACHE_TOKEN_BASE_PREFIX + "captcha:"; + + String CACHE_NAME_CAPTCHA_JIGSAW = CACHE_NAME_TOKEN_CAPTCHA + "jigsaw:"; + String CACHE_NAME_CAPTCHA_WORD_CLICK = CACHE_NAME_TOKEN_CAPTCHA + "word_click:"; + String CACHE_NAME_CAPTCHA_GRAPHIC = CACHE_NAME_TOKEN_CAPTCHA + "graphic:"; +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/AbstractGraphicRenderer.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/AbstractGraphicRenderer.java new file mode 100644 index 0000000..879fd63 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/AbstractGraphicRenderer.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition; + +import com.liuhung.engine.captcha.core.constants.CaptchaConstants; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.liuhung.engine.captcha.core.dto.GraphicCaptcha; +import com.liuhung.engine.captcha.core.dto.Verification; +import com.liuhung.engine.captcha.core.exception.*; +import com.liuhung.engine.captcha.core.exception.CaptchaHasExpiredException; +import com.liuhung.engine.captcha.core.exception.CaptchaIsEmptyException; +import cn.hutool.core.util.IdUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.awt.*; + +/** + *

Description: 抽象的图形验证码

+ * + * @author : liuh + * @date : 2021/12/20 20:42 + */ +public abstract class AbstractGraphicRenderer extends AbstractRenderer { + + private GraphicCaptcha graphicCaptcha; + + public AbstractGraphicRenderer() { + super(CaptchaConstants.CACHE_NAME_CAPTCHA_GRAPHIC); + } + + protected Font getFont() { + return this.getResourceProvider().getGraphicFont(); + } + + protected int getWidth() { + return this.getCaptchaProperties().getGraphics().getWidth(); + } + + protected int getHeight() { + return this.getCaptchaProperties().getGraphics().getHeight(); + } + + protected int getLength() { + return this.getCaptchaProperties().getGraphics().getLength(); + } + + @Override + public Captcha getCapcha(String key) { + String identity = key; + if (StringUtils.isBlank(identity)) { + identity = IdUtil.fastUUID(); + } + + this.create(identity); + return getGraphicCaptcha(); + } + + @Override + public boolean verify(Verification verification) { + + if (ObjectUtils.isEmpty(verification) || StringUtils.isEmpty(verification.getIdentity())) { + throw new CaptchaParameterIllegalException("Parameter value is illegal"); + } + + if (StringUtils.isEmpty(verification.getCharacters())) { + throw new CaptchaIsEmptyException("Captcha is empty"); + } + + String store = this.get(verification.getIdentity()); + if (StringUtils.isEmpty(store)) { + throw new CaptchaHasExpiredException("Stamp is invalid!"); + } + + this.delete(verification.getIdentity()); + + String real = verification.getCharacters(); + + if (!StringUtils.equalsIgnoreCase(store, real)) { + throw new CaptchaMismatchException(); + } + + return true; + } + + private GraphicCaptcha getGraphicCaptcha() { + return graphicCaptcha; + } + + protected void setGraphicCaptcha(GraphicCaptcha graphicCaptcha) { + this.graphicCaptcha = graphicCaptcha; + } + + @Override + public String nextStamp(String key) { + Metadata metadata = draw(); + + GraphicCaptcha graphicCaptcha = new GraphicCaptcha(); + graphicCaptcha.setIdentity(key); + graphicCaptcha.setGraphicImageBase64(metadata.getGraphicImageBase64()); + graphicCaptcha.setCategory(getCategory()); + this.setGraphicCaptcha(graphicCaptcha); + + return metadata.getCharacters(); + } +} \ No newline at end of file diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/AbstractRenderer.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/AbstractRenderer.java new file mode 100644 index 0000000..672d807 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/AbstractRenderer.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition; + +import com.liuhung.engine.cache.jetcache.stamp.AbstractStampManager; +import com.liuhung.engine.captcha.core.properties.CaptchaProperties; +import com.liuhung.engine.captcha.core.provider.ResourceProvider; +import cn.hutool.core.img.ImgUtil; +import com.alicp.jetcache.anno.CacheType; + +import java.awt.image.BufferedImage; +import java.time.Duration; + +/** + *

Description: 基础绘制器

+ * + * @author : liuh + * @date : 2021/12/21 21:46 + */ +public abstract class AbstractRenderer extends AbstractStampManager implements Renderer { + + protected static final String BASE64_PNG_IMAGE_PREFIX = "data:image/png;base64,"; + protected static final String BASE64_GIF_IMAGE_PREFIX = "data:image/gif;base64,"; + + private ResourceProvider resourceProvider; + + public AbstractRenderer(String cacheName) { + super(cacheName); + } + + public AbstractRenderer(String cacheName, CacheType cacheType) { + super(cacheName, cacheType); + } + + public AbstractRenderer(String cacheName, CacheType cacheType, Duration expire) { + super(cacheName, cacheType, expire); + } + + public void setResourceProvider(ResourceProvider resourceProvider) { + this.resourceProvider = resourceProvider; + } + + public ResourceProvider getResourceProvider() { + return resourceProvider; + } + + protected CaptchaProperties getCaptchaProperties() { + return getResourceProvider().getCaptchaProperties(); + } + + protected String getBase64ImagePrefix() { + return BASE64_PNG_IMAGE_PREFIX; + } + + protected String toBase64(BufferedImage bufferedImage) { + String image = ImgUtil.toBase64(bufferedImage, ImgUtil.IMAGE_TYPE_PNG); + return getBase64ImagePrefix() + image; + } + + @Override + public void afterPropertiesSet() throws Exception { + + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/Renderer.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/Renderer.java new file mode 100644 index 0000000..3ab6511 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/Renderer.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition; + +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.liuhung.engine.captcha.core.dto.Verification; + +/** + *

Description: 基础绘制器定义

+ * + * @author : liuh + * @date : 2021/12/21 15:36 + */ +public interface Renderer { + + /** + * 验证码绘制 + * + * @return 绘制的验证码和校验信息 {@link Metadata} + */ + Metadata draw(); + + /** + * 创建验证码 + * + * @param key 验证码标识,用于标记在缓存中的存储 + * @return 验证码数据 {@link Captcha} + */ + Captcha getCapcha(String key); + + /** + * 验证码校验 + * + * @param verification 前端传入的验证值 + * @return true 验证成功,返回错误信息 + */ + boolean verify(Verification verification); + + /** + * 获取验证码类别 + * + * @return 验证码类别 + */ + String getCategory(); +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/domain/Coordinate.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/domain/Coordinate.java new file mode 100644 index 0000000..7406869 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/domain/Coordinate.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.domain; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import java.io.Serializable; + +/** + *

Description: 坐标

+ * + * @author : liuh + * @date : 2021/12/11 15:30 + */ +public class Coordinate implements Serializable { + + private int x; + private int y; + + public Coordinate() { + } + + public Coordinate(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Coordinate that = (Coordinate) o; + return x == that.x && y == that.y; + } + + @Override + public int hashCode() { + return Objects.hashCode(x, y); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("x", x) + .add("y", y) + .toString(); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/domain/Metadata.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/domain/Metadata.java new file mode 100644 index 0000000..5ea4a20 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/domain/Metadata.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.domain; + +import java.util.List; + +/** + *

Description: 图形验证码元数据

+ * + * @author : liuh + * @date : 2021/12/11 12:25 + */ +public class Metadata { + + /** + * 滑块拼图验证码生成的带抠图背景图Base64 + */ + private String originalImageBase64; + /** + * 滑块拼图验证码滑块拼图Base64 + */ + private String sliderImageBase64; + /** + * 滑块拼图验证码抠图位置坐标。 + */ + private Coordinate coordinate; + /** + * 文字点选验证码生成的带文字背景图。 + */ + private String wordClickImageBase64; + /** + * 文字点选验证码文字坐标信息列表 + */ + private List coordinates; + /** + * 文字点选验证码校验文字 + */ + private List words; + /** + * 图形验证码生成的图片 + */ + private String graphicImageBase64; + /** + * 图形验证码校验内容 + */ + private String characters; + + public String getOriginalImageBase64() { + return originalImageBase64; + } + + public void setOriginalImageBase64(String originalImageBase64) { + this.originalImageBase64 = originalImageBase64; + } + + public String getSliderImageBase64() { + return sliderImageBase64; + } + + public void setSliderImageBase64(String sliderImageBase64) { + this.sliderImageBase64 = sliderImageBase64; + } + + public Coordinate getCoordinate() { + return coordinate; + } + + public void setCoordinate(Coordinate coordinate) { + this.coordinate = coordinate; + } + + public String getWordClickImageBase64() { + return wordClickImageBase64; + } + + public void setWordClickImageBase64(String wordClickImageBase64) { + this.wordClickImageBase64 = wordClickImageBase64; + } + + public List getCoordinates() { + return coordinates; + } + + public void setCoordinates(List coordinates) { + this.coordinates = coordinates; + } + + public List getWords() { + return words; + } + + public void setWords(List words) { + this.words = words; + } + + public String getGraphicImageBase64() { + return graphicImageBase64; + } + + public void setGraphicImageBase64(String graphicImageBase64) { + this.graphicImageBase64 = graphicImageBase64; + } + + public String getCharacters() { + return characters; + } + + public void setCharacters(String characters) { + this.characters = characters; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaCategory.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaCategory.java new file mode 100644 index 0000000..d60d2ab --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaCategory.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 验证码类别

+ * + * @author : liuh + * @date : 2021/12/14 14:39 + */ +@Schema(title = "验证码类别") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum CaptchaCategory { + + /** + * 验证码类别 + */ + JIGSAW(CaptchaCategory.JIGSAW_CAPTCHA, "滑块拼图验证码"), + WORD_CLICK(CaptchaCategory.WORD_CLICK_CAPTCHA, "文字点选验证码"), + ARITHMETIC(CaptchaCategory.ARITHMETIC_CAPTCHA, "算数类型验证码"), + CHINESE(CaptchaCategory.CHINESE_CAPTCHA, "中文类型验证码"), + CHINESE_GIF(CaptchaCategory.CHINESE_GIF_CAPTCHA, "中文GIF类型验证码"), + SPEC_GIF(CaptchaCategory.SPEC_GIF_CAPTCHA, "GIF类型验证码"), + SPEC(CaptchaCategory.SPEC_CAPTCHA, "PNG类型验证码"), + HUTOOL_LINE(CaptchaCategory.HUTOOL_LINE_CAPTCHA, "Hutool线段干扰验证码"), + HUTOOL_CIRCLE(CaptchaCategory.HUTOOL_CIRCLE_CAPTCHA, "Hutool圆圈干扰验证码"), + HUTOOL_SHEAR(CaptchaCategory.HUTOOL_SHEAR_CAPTCHA, "Hutool扭曲干扰验证码"), + HUTOOL_GIF(CaptchaCategory.HUTOOL_GIF_CAPTCHA, "Hutool GIF验证码"); + + public static final String JIGSAW_CAPTCHA = "JIGSAW"; + public static final String WORD_CLICK_CAPTCHA = "WORD_CLICK"; + public static final String ARITHMETIC_CAPTCHA = "ARITHMETIC"; + public static final String CHINESE_CAPTCHA = "CHINESE"; + public static final String CHINESE_GIF_CAPTCHA = "CHINESE_GIF"; + public static final String SPEC_CAPTCHA = "SPEC"; + public static final String SPEC_GIF_CAPTCHA = "SPEC_GIF"; + public static final String HUTOOL_LINE_CAPTCHA = "HUTOOL_LINE"; + public static final String HUTOOL_CIRCLE_CAPTCHA = "HUTOOL_CIRCLE"; + public static final String HUTOOL_SHEAR_CAPTCHA = "HUTOOL_SHEAR"; + public static final String HUTOOL_GIF_CAPTCHA = "HUTOOL_GIF"; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCT = new ArrayList<>(); + + static { + for (CaptchaCategory captchaCategory : CaptchaCategory.values()) { + INDEX_MAP.put(captchaCategory.getConstant(), captchaCategory); + JSON_STRUCT.add(captchaCategory.ordinal(), + ImmutableMap.builder() + .put("value", captchaCategory.ordinal()) + .put("key", captchaCategory.name()) + .put("text", captchaCategory.getDescription()) + .build()); + } + } + + @Schema(title = "常量值") + private final String constant; + @Schema(title = "文字") + private final String description; + + CaptchaCategory(String constant, String description) { + this.constant = constant; + this.description = description; + } + + public String getConstant() { + return constant; + } + + public String getDescription() { + return description; + } + + public static CaptchaCategory getCaptchaCategory(String name) { + return INDEX_MAP.get(name); + } + + public static List> getJsonStruct() { + return JSON_STRUCT; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaCharacter.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaCharacter.java new file mode 100644 index 0000000..c2aef97 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaCharacter.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.enums; + +import com.liuhung.engine.captcha.core.provider.RandomProvider; + +/** + *

Description: 验证码字符类型

+ * + * @author : liuh + * @date : 2021/12/21 16:26 + */ +public enum CaptchaCharacter { + + /** + * 验证码字母显示类别 + */ + NUM_AND_CHAR(RandomProvider.NUM_MIN_INDEX, RandomProvider.CHAR_MAX_INDEX, "数字和字母混合"), + ONLY_NUM(RandomProvider.NUM_MIN_INDEX, RandomProvider.NUM_MAX_INDEX, "纯数字"), + ONLY_CHAR(RandomProvider.CHAR_MIN_INDEX, RandomProvider.CHAR_MAX_INDEX,"纯字母"), + ONLY_UPPER_CHAR(RandomProvider.UPPER_MIN_INDEX, RandomProvider.UPPER_MAX_INDEX, "纯大写字母"), + ONLY_LOWER_CHAR(RandomProvider.LOWER_MIN_INDEX, RandomProvider.LOWER_MAX_INDEX,"纯小写字母"), + NUM_AND_UPPER_CHAR(RandomProvider.NUM_MIN_INDEX, RandomProvider.UPPER_MAX_INDEX,"数字和大写字母"); + + /** + * 字符枚举值开始位置 + */ + private final int start; + /** + * 字符枚举值结束位置 + */ + private final int end; + /** + * 类型说明 + */ + private final String description; + + CaptchaCharacter(int start, int end, String description) { + this.start = start; + this.end = end; + this.description = description; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public String getDescription() { + return description; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaFont.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaFont.java new file mode 100644 index 0000000..49552e1 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaFont.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.enums; + +/** + *

Description: 字体资源

+ * + * @author : liuh + * @date : 2021/12/21 16:00 + */ +public enum CaptchaFont { + /** + * 内置字体类型 + */ + ACTION("Action.ttf"), + BEATAE("Beatae.ttf"), + EPILOG("Epilog.ttf"), + FRESNEL("Fresnel.ttf"), + HEADACHE("Headache.ttf"), + LEXOGRAPHER("Lexographer.ttf"), + PREFIX("Prefix"), + PROG_BOT("ProgBot"), + ROBOT_TEACHER("RobotTeacher.ttf"), + SCANDAL("Scandal.ttf"); + + private final String fontName; + + CaptchaFont(String fontName) { + this.fontName = fontName; + } + + public String getFontName() { + return fontName; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaResource.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaResource.java new file mode 100644 index 0000000..947cd0c --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/CaptchaResource.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.enums; + +/** + *

Description: 验证码资源

+ * + * @author : liuh + * @date : 2021/12/11 15:27 + */ +public enum CaptchaResource { + + /** + * 验证码资源类型 + */ + JIGSAW_ORIGINAL("Jigsaw original image","滑动拼图底图"), + JIGSAW_TEMPLATE("Jigsaw template image","滑动拼图滑块底图"), + WORD_CLICK("Word click image","文字点选底图"); + + private final String content; + private final String description; + + CaptchaResource(String type, String description) { + this.content = type; + this.description = description; + } + + public String getContent() { + return content; + } + + public String getDescription() { + return description; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/FontStyle.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/FontStyle.java new file mode 100644 index 0000000..d806bed --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/definition/enums/FontStyle.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.definition.enums; + +import java.awt.*; + +/** + *

Description: 字体风格

+ * + * 定义此类的目的: + * 1. 设置字体风格和字体大小,最初都是使用int类型参数,很容混淆出错,增加个枚举类型以示区别 + * 2. 枚举类型让配置参数配置更便捷。 + * + * @author : liuh + * @date : 2021/12/23 10:33 + */ +public enum FontStyle { + + /** + * 字体风格 + */ + PLAIN(Font.PLAIN), + BOLD(Font.BOLD), + ITALIC(Font.ITALIC); + + private final int mapping; + + FontStyle(int mapping) { + this.mapping = mapping; + } + + public int getMapping() { + return mapping; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/Captcha.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/Captcha.java new file mode 100644 index 0000000..680ec93 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/Captcha.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.dto; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractDto; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + *

Description: 验证码返回数据基础类

+ * + * @author : liuh + * @date : 2021/12/13 17:06 + */ +public abstract class Captcha extends AbstractDto { + + @Schema(title = "验证码身份") + private String identity; + + @Schema(title = "验证码类别") + private String category; + + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/GraphicCaptcha.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/GraphicCaptcha.java new file mode 100644 index 0000000..102d8b0 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/GraphicCaptcha.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.dto; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +/** + *

Description: 图形验证码

+ * + * @author : liuh + * @date : 2021/12/21 22:19 + */ +public class GraphicCaptcha extends Captcha { + + /** + * 图形验证码成的图。 + */ + private String graphicImageBase64; + + public GraphicCaptcha() { + } + + public String getGraphicImageBase64() { + return graphicImageBase64; + } + + public void setGraphicImageBase64(String graphicImageBase64) { + this.graphicImageBase64 = graphicImageBase64; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GraphicCaptcha that = (GraphicCaptcha) o; + return Objects.equal(graphicImageBase64, that.graphicImageBase64); + } + + @Override + public int hashCode() { + return Objects.hashCode(graphicImageBase64); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("graphicImageBase64", graphicImageBase64) + .toString(); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/Verification.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/Verification.java new file mode 100644 index 0000000..f0ecbaf --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/dto/Verification.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.dto; + +import com.liuhung.engine.captcha.core.definition.domain.Coordinate; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import java.util.List; + +/** + *

Description: 验证数据实体

+ * + * @author : liuh + * @date : 2021/12/14 16:04 + */ +public class Verification extends Captcha { + + /** + * 滑块拼图验证参数 + */ + private Coordinate coordinate; + /** + * 文字点选验证参数 + */ + private List coordinates; + /** + * 图形验证码验证参数 + */ + private String characters; + + public Verification() { + } + + public Coordinate getCoordinate() { + return coordinate; + } + + public void setCoordinate(Coordinate coordinate) { + this.coordinate = coordinate; + } + + public List getCoordinates() { + return coordinates; + } + + public void setCoordinates(List coordinates) { + this.coordinates = coordinates; + } + + public String getCharacters() { + return characters; + } + + public void setCharacters(String characters) { + this.characters = characters; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Verification that = (Verification) o; + return Objects.equal(characters, that.characters); + } + + @Override + public int hashCode() { + return Objects.hashCode(characters); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("characters", characters) + .toString(); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaCategoryIsIncorrectException.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaCategoryIsIncorrectException.java new file mode 100644 index 0000000..e9a6a14 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaCategoryIsIncorrectException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码分类错误

+ * + * @author : liuh + * @date : 2021/12/15 17:51 + */ +public class CaptchaCategoryIsIncorrectException extends PlatformException { + + public CaptchaCategoryIsIncorrectException() { + super(); + } + + public CaptchaCategoryIsIncorrectException(String message) { + super(message); + } + + public CaptchaCategoryIsIncorrectException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaCategoryIsIncorrectException(Throwable cause) { + super(cause); + } + + protected CaptchaCategoryIsIncorrectException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40608, "验证码分类错误", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaHandlerNotExistException.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaHandlerNotExistException.java new file mode 100644 index 0000000..e9912b7 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaHandlerNotExistException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码处理器不存在

+ * + * @author : liuh + * @date : 2021/12/15 17:53 + */ +public class CaptchaHandlerNotExistException extends PlatformException { + + public CaptchaHandlerNotExistException() { + super(); + } + + public CaptchaHandlerNotExistException(String message) { + super(message); + } + + public CaptchaHandlerNotExistException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaHandlerNotExistException(Throwable cause) { + super(cause); + } + + protected CaptchaHandlerNotExistException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40609, "验证码处理器不存在", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaHasExpiredException.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaHasExpiredException.java new file mode 100644 index 0000000..571e14f --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaHasExpiredException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码已过期

+ * + * @author : liuh + * @date : 2021/12/15 18:06 + */ +public class CaptchaHasExpiredException extends PlatformException { + + public CaptchaHasExpiredException() { + super(); + } + + public CaptchaHasExpiredException(String message) { + super(message); + } + + public CaptchaHasExpiredException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaHasExpiredException(Throwable cause) { + super(cause); + } + + protected CaptchaHasExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40610, "验证码已过期", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaIsEmptyException.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaIsEmptyException.java new file mode 100644 index 0000000..b1d66ae --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaIsEmptyException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码为空

+ * + * @author : liuh + * @date : 2021/12/24 18:11 + */ +public class CaptchaIsEmptyException extends PlatformException { + + public CaptchaIsEmptyException() { + super(); + } + + public CaptchaIsEmptyException(String message) { + super(message); + } + + public CaptchaIsEmptyException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaIsEmptyException(Throwable cause) { + super(cause); + } + + protected CaptchaIsEmptyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40611, "验证码不能为空", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaMismatchException.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaMismatchException.java new file mode 100644 index 0000000..a35ec89 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaMismatchException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码不匹配错误

+ * + * @author : liuh + * @date : 2021/12/15 17:56 + */ +public class CaptchaMismatchException extends PlatformException { + + public CaptchaMismatchException() { + super(); + } + + public CaptchaMismatchException(String message) { + super(message); + } + + public CaptchaMismatchException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaMismatchException(Throwable cause) { + super(cause); + } + + protected CaptchaMismatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40612, "验证码不匹配", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaParameterIllegalException.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaParameterIllegalException.java new file mode 100644 index 0000000..950dc56 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/exception/CaptchaParameterIllegalException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码校验参数错误

+ * + * @author : liuh + * @date : 2021/12/15 17:54 + */ +public class CaptchaParameterIllegalException extends PlatformException { + + public CaptchaParameterIllegalException() { + super(); + } + + public CaptchaParameterIllegalException(String message) { + super(message); + } + + public CaptchaParameterIllegalException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaParameterIllegalException(Throwable cause) { + super(cause); + } + + protected CaptchaParameterIllegalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40613, "验证码参数格式错误", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/processor/CaptchaRendererFactory.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/processor/CaptchaRendererFactory.java new file mode 100644 index 0000000..1a127dc --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/processor/CaptchaRendererFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.processor; + +import com.liuhung.engine.captcha.core.definition.Renderer; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.liuhung.engine.captcha.core.dto.Verification; +import com.liuhung.engine.captcha.core.exception.CaptchaCategoryIsIncorrectException; +import com.liuhung.engine.captcha.core.exception.CaptchaHandlerNotExistException; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

Description: Captcha 工厂

+ * + * @author : liuh + * @date : 2021/12/14 14:38 + */ +@Component +public class CaptchaRendererFactory { + + @Autowired + private final Map handlers = new ConcurrentHashMap<>(8); + + public Renderer getRenderer(String category) { + CaptchaCategory captchaCategory = CaptchaCategory.getCaptchaCategory(category); + + if (ObjectUtils.isEmpty(captchaCategory)) { + throw new CaptchaCategoryIsIncorrectException("Captcha category is incorrect."); + } + + Renderer renderer = handlers.get(captchaCategory.getConstant()); + if (ObjectUtils.isEmpty(renderer)) { + throw new CaptchaHandlerNotExistException(); + } + + return renderer; + } + + public Captcha getCaptcha(String identity, String category) { + Renderer renderer = getRenderer(category); + return renderer.getCapcha(identity); + } + + public boolean verify(Verification verification) { + Renderer renderer = getRenderer(verification.getCategory()); + return renderer.verify(verification); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/properties/CaptchaProperties.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/properties/CaptchaProperties.java new file mode 100644 index 0000000..3e7e1e1 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/properties/CaptchaProperties.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.properties; + +import com.liuhung.engine.captcha.core.constants.CaptchaConstants; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCharacter; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaFont; +import com.liuhung.engine.captcha.core.definition.enums.FontStyle; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

Description: 验证码配置参数

+ * + * @author : liuh + * @date : 2021/12/11 12:25 + */ +@ConfigurationProperties(prefix = CaptchaConstants.PROPERTY_PREFIX_CAPTCHA) +public class CaptchaProperties { + + private Graphics graphics = new Graphics(); + /** + * 水印配置 + */ + private Watermark watermark = new Watermark(); + /** + * 滑块拼图验证码配置 + */ + private Jigsaw jigsaw = new Jigsaw(); + /** + * 文字点选验证码配置 + */ + private WordClick wordClick = new WordClick(); + + public Graphics getGraphics() { + return graphics; + } + + public void setGraphics(Graphics graphics) { + this.graphics = graphics; + } + + public Watermark getWatermark() { + return watermark; + } + + public void setWatermark(Watermark watermark) { + this.watermark = watermark; + } + + public Jigsaw getJigsaw() { + return jigsaw; + } + + public void setJigsaw(Jigsaw jigsaw) { + this.jigsaw = jigsaw; + } + + public WordClick getWordClick() { + return wordClick; + } + + public void setWordClick(WordClick wordClick) { + this.wordClick = wordClick; + } + + public static class Graphics { + /** + * 验证码字符个数 + */ + private int length = 5; + /** + * 验证码显示宽度 + */ + private int width = 130; + /** + * 验证码显示高度 + */ + private int height = 48; + /** + * 算数类型验证码算法复杂度 + */ + private int complexity = 2; + /** + * 字符类型 + */ + private CaptchaCharacter letter = CaptchaCharacter.NUM_AND_CHAR; + + private CaptchaFont font = CaptchaFont.LEXOGRAPHER; + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public CaptchaFont getFont() { + return font; + } + + public void setFont(CaptchaFont captchaFont) { + this.font = captchaFont; + } + + public CaptchaCharacter getLetter() { + return letter; + } + + public void setLetter(CaptchaCharacter letter) { + this.letter = letter; + } + + public int getComplexity() { + return complexity; + } + + public void setComplexity(int complexity) { + this.complexity = complexity; + } + } + + /** + * 右下角水印文字(我的水印) + */ + public static class Watermark { + /** + * 水印内容 + */ + private String content = "quafer Cloud"; + /** + * 水印字体 + */ + private String fontName = "WenQuanZhengHei.ttf"; + /** + * 字体样式: 0:PLAIN; 1:BOLD; 2:ITALI; + */ + private FontStyle fontStyle = FontStyle.BOLD; + + /** + * 水印文字中,汉字的大小,默认:25 + */ + private Integer fontSize = 25; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getFontName() { + return fontName; + } + + public void setFontName(String fontName) { + this.fontName = fontName; + } + + public Integer getFontSize() { + return fontSize; + } + + public void setFontSize(Integer fontSize) { + this.fontSize = fontSize; + } + + public FontStyle getFontStyle() { + return fontStyle; + } + + public void setFontStyle(FontStyle fontStyle) { + this.fontStyle = fontStyle; + } + } + + /** + * 拼图滑块验证码 + */ + public static class Jigsaw { + /** + * 拼图滑块验证码原图资源路径,格式:classpath:/xxx + */ + private String originalResource = "classpath*:images/jigsaw/original/*.png"; + /** + * 拼图滑块验证码拼图模版资源路径,格式:classpath:/xxx + */ + private String templateResource = "classpath*:images/jigsaw/template/*.png"; + + /** + * 滑动干扰项, 可选值为(0/1/2), 默认值为:0,即无干扰项 + */ + private Integer interference = 0; + + /** + * 偏差值,滑动结果与标准结果间可接受的偏差值。默认:5 + */ + private Integer deviation = 5; + + public String getOriginalResource() { + return originalResource; + } + + public void setOriginalResource(String originalResource) { + this.originalResource = originalResource; + } + + public String getTemplateResource() { + return templateResource; + } + + public void setTemplateResource(String templateResource) { + this.templateResource = templateResource; + } + + public Integer getInterference() { + return interference; + } + + public void setInterference(Integer interference) { + this.interference = interference; + } + + public Integer getDeviation() { + return deviation; + } + + public void setDeviation(Integer deviation) { + this.deviation = deviation; + } + } + + /** + * 文字点选验证码 + */ + public static class WordClick { + + /** + * 文字点选验证码资源路径,格式:classpath:/xxx + */ + private String imageResource = "classpath*:images/word-click/*.png"; + + /** + * 文字点选验证码文字个数 + */ + private Integer wordCount = 5; + /** + * 随机颜色 + */ + private boolean randomColor = true; + /** + * 字体样式: 0:PLAIN; 1:BOLD; 2:ITALI; + */ + private FontStyle fontStyle = FontStyle.BOLD; + /** + * 水印字体 + */ + private String fontName = "WenQuanZhengHei.ttf"; + /** + * 文字点选验证码资源路径字体大小 + */ + private Integer fontSize = 25; + + public String getImageResource() { + return imageResource; + } + + public void setImageResource(String imageResource) { + this.imageResource = imageResource; + } + + public Integer getWordCount() { + return wordCount; + } + + public void setWordCount(Integer wordCount) { + this.wordCount = wordCount; + } + + public Integer getFontSize() { + return fontSize; + } + + public void setFontSize(Integer fontSize) { + this.fontSize = fontSize; + } + + public boolean isRandomColor() { + return randomColor; + } + + public void setRandomColor(boolean randomColor) { + this.randomColor = randomColor; + } + + public String getFontName() { + return fontName; + } + + public void setFontName(String fontName) { + this.fontName = fontName; + } + + public FontStyle getFontStyle() { + return fontStyle; + } + + public void setFontStyle(FontStyle fontStyle) { + this.fontStyle = fontStyle; + } + } +} + diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/provider/RandomProvider.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/provider/RandomProvider.java new file mode 100644 index 0000000..49b7246 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/provider/RandomProvider.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.provider; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCharacter; +import cn.hutool.core.util.RandomUtil; + +import java.awt.*; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + *

Description: 验证码随机代码工具类

+ * + * @author : liuh + * @date : 2021/12/11 15:26 + */ +public class RandomProvider { + + private static final String[] CHARACTERS = {"2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}; + + /** + * 数字的最大枚举值,不包括最大值 + */ + public static final int NUM_MIN_INDEX = 0; + /** + * 数字的最大枚举值,不包括最大值 + */ + public static final int NUM_MAX_INDEX = 8; + /** + * 字符的最小枚举值,包括最小值 + */ + public static final int CHAR_MIN_INDEX = NUM_MAX_INDEX; + /** + * 字符的最大枚举值,不包括最大值 + */ + public static final int CHAR_MAX_INDEX = CHARACTERS.length; + /** + * 大写字符最小枚举值 + */ + public static final int UPPER_MIN_INDEX = CHAR_MIN_INDEX; + /** + * 大写字符最大枚举值 + */ + public static final int UPPER_MAX_INDEX = UPPER_MIN_INDEX + 23; + /** + * 小写字母最小枚举值 + */ + public static final int LOWER_MIN_INDEX = UPPER_MAX_INDEX; + /** + * 小写字母最大枚举值 + */ + public static final int LOWER_MAX_INDEX = CHAR_MAX_INDEX; + /** + * 常用言责 + */ + private static final int[][] COLOR = {{0, 135, 255}, {51, 153, 51}, {255, 102, 102}, {255, 153, 0}, {153, 102, 0}, {153, 102, 153}, {51, 153, 153}, {102, 102, 255}, {0, 102, 204}, {204, 51, 51}, {0, 153, 204}, {0, 51, 102}}; + private static final String[] DICTIONARY = new String[]{"\u7684", "\u4e00", "\u4e86", "\u662f", "\u6211", "\u4e0d", "\u5728", "\u4eba", "\u4eec", "\u6709", "\u6765", "\u4ed6", "\u8fd9", "\u4e0a", "\u7740", "\u4e2a", "\u5730", "\u5230", "\u5927", "\u91cc", "\u8bf4", "\u5c31", "\u53bb", "\u5b50", "\u5f97", "\u4e5f", "\u548c", "\u90a3", "\u8981", "\u4e0b", "\u770b", "\u5929", "\u65f6", "\u8fc7", "\u51fa", "\u5c0f", "\u4e48", "\u8d77", "\u4f60", "\u90fd", "\u628a", "\u597d", "\u8fd8", "\u591a", "\u6ca1", "\u4e3a", "\u53c8", "\u53ef", "\u5bb6", "\u5b66", "\u53ea", "\u4ee5", "\u4e3b", "\u4f1a", "\u6837", "\u5e74", "\u60f3", "\u751f", "\u540c", "\u8001", "\u4e2d", "\u5341", "\u4ece", "\u81ea", "\u9762", "\u524d", "\u5934", "\u9053", "\u5b83", "\u540e", "\u7136", "\u8d70", "\u5f88", "\u50cf", "\u89c1", "\u4e24", "\u7528", "\u5979", "\u56fd", "\u52a8", "\u8fdb", "\u6210", "\u56de", "\u4ec0", "\u8fb9", "\u4f5c", "\u5bf9", "\u5f00", "\u800c", "\u5df1", "\u4e9b", "\u73b0", "\u5c71", "\u6c11", "\u5019", "\u7ecf", "\u53d1", "\u5de5", "\u5411", "\u4e8b", "\u547d", "\u7ed9", "\u957f", "\u6c34", "\u51e0", "\u4e49", "\u4e09", "\u58f0", "\u4e8e", "\u9ad8", "\u624b", "\u77e5", "\u7406", "\u773c", "\u5fd7", "\u70b9", "\u5fc3", "\u6218", "\u4e8c", "\u95ee", "\u4f46", "\u8eab", "\u65b9", "\u5b9e", "\u5403", "\u505a", "\u53eb", "\u5f53", "\u4f4f", "\u542c", "\u9769", "\u6253", "\u5462", "\u771f", "\u5168", "\u624d", "\u56db", "\u5df2", "\u6240", "\u654c", "\u4e4b", "\u6700", "\u5149", "\u4ea7", "\u60c5", "\u8def", "\u5206", "\u603b", "\u6761", "\u767d", "\u8bdd", "\u4e1c", "\u5e2d", "\u6b21", "\u4eb2", "\u5982", "\u88ab", "\u82b1", "\u53e3", "\u653e", "\u513f", "\u5e38", "\u6c14", "\u4e94", "\u7b2c", "\u4f7f", "\u5199", "\u519b", "\u5427", "\u6587", "\u8fd0", "\u518d", "\u679c", "\u600e", "\u5b9a", "\u8bb8", "\u5feb", "\u660e", "\u884c", "\u56e0", "\u522b", "\u98de", "\u5916", "\u6811", "\u7269", "\u6d3b", "\u90e8", "\u95e8", "\u65e0", "\u5f80", "\u8239", "\u671b", "\u65b0", "\u5e26", "\u961f", "\u5148", "\u529b", "\u5b8c", "\u5374", "\u7ad9", "\u4ee3", "\u5458", "\u673a", "\u66f4", "\u4e5d", "\u60a8", "\u6bcf", "\u98ce", "\u7ea7", "\u8ddf", "\u7b11", "\u554a", "\u5b69", "\u4e07", "\u5c11", "\u76f4", "\u610f", "\u591c", "\u6bd4", "\u9636", "\u8fde", "\u8f66", "\u91cd", "\u4fbf", "\u6597", "\u9a6c", "\u54ea", "\u5316", "\u592a", "\u6307", "\u53d8", "\u793e", "\u4f3c", "\u58eb", "\u8005", "\u5e72", "\u77f3", "\u6ee1", "\u65e5", "\u51b3", "\u767e", "\u539f", "\u62ff", "\u7fa4", "\u7a76", "\u5404", "\u516d", "\u672c", "\u601d", "\u89e3", "\u7acb", "\u6cb3", "\u6751", "\u516b", "\u96be", "\u65e9", "\u8bba", "\u5417", "\u6839", "\u5171", "\u8ba9", "\u76f8", "\u7814", "\u4eca", "\u5176", "\u4e66", "\u5750", "\u63a5", "\u5e94", "\u5173", "\u4fe1", "\u89c9", "\u6b65", "\u53cd", "\u5904", "\u8bb0", "\u5c06", "\u5343", "\u627e", "\u4e89", "\u9886", "\u6216", "\u5e08", "\u7ed3", "\u5757", "\u8dd1", "\u8c01", "\u8349", "\u8d8a", "\u5b57", "\u52a0", "\u811a", "\u7d27", "\u7231", "\u7b49", "\u4e60", "\u9635", "\u6015", "\u6708", "\u9752", "\u534a", "\u706b", "\u6cd5", "\u9898", "\u5efa", "\u8d76", "\u4f4d", "\u5531", "\u6d77", "\u4e03", "\u5973", "\u4efb", "\u4ef6", "\u611f", "\u51c6", "\u5f20", "\u56e2", "\u5c4b", "\u79bb", "\u8272", "\u8138", "\u7247", "\u79d1", "\u5012", "\u775b", "\u5229", "\u4e16", "\u521a", "\u4e14", "\u7531", "\u9001", "\u5207", "\u661f", "\u5bfc", "\u665a", "\u8868", "\u591f", "\u6574", "\u8ba4", "\u54cd", "\u96ea", "\u6d41", "\u672a", "\u573a", "\u8be5", "\u5e76", "\u5e95", "\u6df1", "\u523b", "\u5e73", "\u4f1f", "\u5fd9", "\u63d0", "\u786e", "\u8fd1", "\u4eae", "\u8f7b", "\u8bb2", "\u519c", "\u53e4", "\u9ed1", "\u544a", "\u754c", "\u62c9", "\u540d", "\u5440", "\u571f", "\u6e05", "\u9633", "\u7167", "\u529e", "\u53f2", "\u6539", "\u5386", "\u8f6c", "\u753b", "\u9020", "\u5634", "\u6b64", "\u6cbb", "\u5317", "\u5fc5", "\u670d", "\u96e8", "\u7a7f", "\u5185", "\u8bc6", "\u9a8c", "\u4f20", "\u4e1a", "\u83dc", "\u722c", "\u7761", "\u5174", "\u5f62", "\u91cf", "\u54b1", "\u89c2", "\u82e6", "\u4f53", "\u4f17", "\u901a", "\u51b2", "\u5408", "\u7834", "\u53cb", "\u5ea6", "\u672f", "\u996d", "\u516c", "\u65c1", "\u623f", "\u6781", "\u5357", "\u67aa", "\u8bfb", "\u6c99", "\u5c81", "\u7ebf", "\u91ce", "\u575a", "\u7a7a", "\u6536", "\u7b97", "\u81f3", "\u653f", "\u57ce", "\u52b3", "\u843d", "\u94b1", "\u7279", "\u56f4", "\u5f1f", "\u80dc", "\u6559", "\u70ed", "\u5c55", "\u5305", "\u6b4c", "\u7c7b", "\u6e10", "\u5f3a", "\u6570", "\u4e61", "\u547c", "\u6027", "\u97f3", "\u7b54", "\u54e5", "\u9645", "\u65e7", "\u795e", "\u5ea7", "\u7ae0", "\u5e2e", "\u5566", "\u53d7", "\u7cfb", "\u4ee4", "\u8df3", "\u975e", "\u4f55", "\u725b", "\u53d6", "\u5165", "\u5cb8", "\u6562", "\u6389", "\u5ffd", "\u79cd", "\u88c5", "\u9876", "\u6025", "\u6797", "\u505c", "\u606f", "\u53e5", "\u533a", "\u8863", "\u822c", "\u62a5", "\u53f6", "\u538b", "\u6162", "\u53d4", "\u80cc", "\u7ec6"}; + private static final List WORDS = Arrays.stream(DICTIONARY).collect(Collectors.toList()); + + /** + * 获得指定范围内的随机数 + *

+ * Tips:用自己的方法重新包装,方便今后修改。 + *

+ * Hutool RandomUtil 支持负数;common-lang3 RandomUtils不支持负数 + * + * @param startInclusive 最小数(包含) + * @param endExclusive 最大数(不包含) + * @return 随机数 + */ + public static int randomInt(final int startInclusive, final int endExclusive) { + return RandomUtil.randomInt(startInclusive, endExclusive); + } + + /** + * 获得指定范围内的随机数 [0,limit) + *

+ * Tips:用自己的方法重新包装,方便今后修改 + * + * @param bound 限制随机数的范围,不包括这个数 + * @return 随机数 + */ + public static int randomInt(int bound) { + return RandomUtil.randomInt(bound); + } + + /** + * 从字典中随机获取指定数量的汉字。 + * + * @param wordCount 汉字数量 + * @return 随机获取指定数量的汉字 + */ + public static List randomWords(int wordCount) { + return RandomUtil.randomEleList(WORDS, wordCount); + } + + public static Color[] randomColors(int number) { + List colors = IntStream.range(0, number).mapToObj(i -> randomColor()).collect(Collectors.toList()); + Color[] result = new Color[colors.size()]; + return colors.toArray(result); + } + + /** + * 获取随机常用颜色 + * + * @return 随机常用颜色 + */ + public static Color randomColor() { + int[] color = COLOR[randomInt(COLOR.length)]; + return new Color(color[0], color[1], color[2]); + } + + public static Color randomColor(int min, int max) { + if (min > 255) { + min = 255; + } + if (max > 255) { + max = 255; + } + if (min < 0) { + min = 0; + } + if (max < 0) { + max = 0; + } + if (min > max) { + min = 0; + max = 255; + } + return new Color(randomInt(min, max), randomInt(min, max), randomInt(min, max)); + } + + private static String randomCharacter(CaptchaCharacter captchaCharacter) { + return CHARACTERS[randomInt(captchaCharacter.getStart(), captchaCharacter.getEnd())]; + } + + public static String[] randomCharacters(int number, CaptchaCharacter captchaCharacter) { + List characters = IntStream.range(0, number).mapToObj(i -> randomCharacter(captchaCharacter)).collect(Collectors.toList()); + String[] result = new String[characters.size()]; + return characters.toArray(result); + } +} diff --git a/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/provider/ResourceProvider.java b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/provider/ResourceProvider.java new file mode 100644 index 0000000..63c0931 --- /dev/null +++ b/engine-captcha/captcha-core/src/main/java/com/liuhung/engine/captcha/core/provider/ResourceProvider.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.core.provider; + +import com.liuhung.engine.assistant.core.utils.ResourceUtils; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaResource; +import com.liuhung.engine.captcha.core.definition.enums.FontStyle; +import com.liuhung.engine.captcha.core.properties.CaptchaProperties; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.img.FontUtil; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.IdUtil; +import cn.hutool.system.SystemUtil; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; +import org.springframework.util.FileCopyUtils; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

Description: 验证码静态资源加载器

+ * + * @author : liuh + * @date : 2021/12/22 18:52 + */ +@Component +public class ResourceProvider implements InitializingBean { + + private static final Logger log = LoggerFactory.getLogger(ResourceProvider.class); + + private static final String FONT_RESOURCE = "classpath*:/fonts/*.ttf"; + private static final String FONT_FOLDER = "/usr/share/fonts/quafer/"; + + private final Map imageIndexes = new ConcurrentHashMap<>(); + private final Map jigsawOriginalImages = new ConcurrentHashMap<>(); + private final Map jigsawTemplateImages = new ConcurrentHashMap<>(); + private final Map wordClickImages = new ConcurrentHashMap<>(); + private Map fonts = new ConcurrentHashMap<>(); + + private final CaptchaProperties captchaProperties; + + public ResourceProvider(CaptchaProperties captchaProperties) { + this.captchaProperties = captchaProperties; + } + + public CaptchaProperties getCaptchaProperties() { + return captchaProperties; + } + + @Override + public void afterPropertiesSet() throws Exception { + + String systemName = SystemUtil.getOsInfo().getName(); + log.debug("[Quafer] |- Before captcha resource loading, check system. Current system is [{}]", systemName); + + log.debug("[Quafer] |- Captcha resource loading is BEGIN!"); + + loadImages(jigsawOriginalImages, getCaptchaProperties().getJigsaw().getOriginalResource(), CaptchaResource.JIGSAW_ORIGINAL); + + loadImages(jigsawTemplateImages, getCaptchaProperties().getJigsaw().getTemplateResource(), CaptchaResource.JIGSAW_TEMPLATE); + + loadImages(wordClickImages, getCaptchaProperties().getWordClick().getImageResource(), CaptchaResource.WORD_CLICK); + + loadFonts(); + + log.debug("[Quafer] |- Jigsaw captcha resource loading is END!"); + } + + private static String getBase64Image(Resource resource) { + try { + InputStream inputStream = resource.getInputStream(); + byte[] bytes = FileCopyUtils.copyToByteArray(inputStream); + return Base64.encode(bytes); + } catch (IOException e) { + log.error("[Quafer] |- Captcha get image catch io error!", e); + } + return null; + } + + private static Map getImages(String location) { + if (ResourceUtils.isClasspathAllUrl(location)) { + try { + Resource[] resources = ResourceUtils.getResources(location); + Map images = new ConcurrentHashMap<>(); + if (ArrayUtils.isNotEmpty(resources)) { + Arrays.stream(resources).forEach(resource -> { + String data = getBase64Image(resource); + if (StringUtils.isNotBlank(data)) { + images.put(IdUtil.fastSimpleUUID(), data); + } + }); + } + return images; + } catch (IOException e) { + log.error("[Quafer] |- Analysis the location [{}] catch io error!", location, e); + } + } + + return new ConcurrentHashMap<>(8); + } + + private void loadImages(Map container, String location, CaptchaResource captchaResource) { + Map resource = getImages(location); + + if (MapUtils.isNotEmpty(resource)) { + container.putAll(resource); + log.debug("[Quafer] |- {} load complete, total number is [{}]", captchaResource.getContent(), resource.size()); + imageIndexes.put(captchaResource.name(), resource.keySet().toArray(new String[0])); + } + } + + private static Font getFont(Resource resource) { + + try { + return FontUtil.createFont(resource.getInputStream()); + } catch (IORuntimeException e) { + // 虽然 java.awt.Font 抛出的是 IOException, 因为使用 Hutool FontUtil 将错误又包装了一次。所以出错时必须要拦截 IORuntimeException,否则会导致错误不被拦截直接抛出,应用启动失败。 + log.warn("[Quafer] |- Can not read font in the resources folder, maybe in docker."); + // TODO: 2022-10-21 尝试在 docker alpine 下解决字体问题的多种方式之一。目前改用 debian,下面代码已经不再需要。暂留,确保确实没有问题后再做处理 + Font fontInfileSystem = getFontUnderDocker(resource.getFilename()); + if (ObjectUtils.isNotEmpty(fontInfileSystem)) { + return fontInfileSystem; + } + } catch (IOException e) { + log.error("[Quafer] |- Resource object in resources folder catch io error!", e); + } + + return null; + } + + private static Font getFontUnderDocker(String filename) { + if (SystemUtil.getOsInfo().isLinux()) { + String path = FONT_FOLDER + filename; + + File file = new File(path); + if (ObjectUtils.isNotEmpty(file) && FileUtil.exist(file)) { + System.out.println(file.getAbsolutePath()); + try { + Font font = FontUtil.createFont(file); + log.debug("[Quafer] |- Read font [{}] under the DOCKER.", font.getFontName()); + return font; + } catch (IORuntimeException e) { + log.error("[Quafer] |- Read font under the DOCKER catch error."); + } catch (NullPointerException e) { + log.error("[Quafer] |- Read font under the DOCKER catch null error."); + } + } + } + return null; + } + + private static Map getFonts(String location) { + + if (ResourceUtils.isClasspathAllUrl(location)) { + try { + Resource[] resources = ResourceUtils.getResources(location); + Map fonts = new ConcurrentHashMap<>(); + if (ArrayUtils.isNotEmpty(resources)) { + Arrays.stream(resources).forEach(resource -> { + Font font = getFont(resource); + if (ObjectUtils.isNotEmpty(font)) { + fonts.put(resource.getFilename(), font); + } + }); + } + return fonts; + } catch (IOException e) { + log.error("[Quafer] |- Analysis the location [{}] catch io error!", location, e); + } + } + + return new ConcurrentHashMap<>(8); + } + + private void loadFonts() { + if (MapUtils.isEmpty(fonts)) { + this.fonts = getFonts(FONT_RESOURCE); + log.debug("[Quafer] |- Font load complete, total number is [{}]", fonts.size()); + } + } + + private Font getDefaultFont(String fontName, int fontSize, FontStyle fontStyle) { + if (StringUtils.isNotBlank(fontName)) { + return new Font(fontName, fontStyle.getMapping(), fontSize); + } else { + return new Font("WenQuanYi Zen Hei", fontStyle.getMapping(), fontSize); + } + } + + public Font getFont(String fontName, int fontSize, FontStyle fontStyle) { + if (MapUtils.isEmpty(fonts) || ObjectUtils.isEmpty(fonts.get(fontName))) { + return getDefaultFont(fontName, fontSize, fontStyle); + } else { + return fonts.get(fontName).deriveFont(fontStyle.getMapping(), Integer.valueOf(fontSize).floatValue()); + } + } + + public Font getFont(String fontName) { + return getFont(fontName, 32, FontStyle.BOLD); + } + + public Font getGraphicFont() { + String fontName = getCaptchaProperties().getGraphics().getFont().getFontName(); + return this.getFont(fontName); + } + + public Font getWaterMarkFont(int fontSize) { + String fontName = getCaptchaProperties().getWatermark().getFontName(); + FontStyle fontStyle = getCaptchaProperties().getWatermark().getFontStyle(); + return getFont(fontName, fontSize, fontStyle); + } + + public Font getChineseFont() { + return getFont("WenQuanYi Zen Hei", 25, FontStyle.PLAIN); + } + + private String getRandomBase64Image(Map container, CaptchaResource captchaResource) { + String[] data = this.imageIndexes.get(captchaResource.name()); + if (ArrayUtils.isNotEmpty(data)) { + int randomInt = RandomProvider.randomInt(0, data.length); + return container.get(data[randomInt]); + } + return null; + } + + protected BufferedImage getRandomImage(Map container, CaptchaResource captchaResource) { + String data = getRandomBase64Image(container, captchaResource); + if (StringUtils.isNotBlank(data)) { + return ImgUtil.toImage(data); + } + + return null; + } + + public String getRandomBase64OriginalImage() { + return getRandomBase64Image(jigsawOriginalImages, CaptchaResource.JIGSAW_ORIGINAL); + } + + public String getRandomBase64TemplateImage() { + return getRandomBase64Image(jigsawTemplateImages, CaptchaResource.JIGSAW_TEMPLATE); + } + + public BufferedImage getRandomOriginalImage() { + return getRandomImage(jigsawOriginalImages, CaptchaResource.JIGSAW_ORIGINAL); + } + + public BufferedImage getRandomTemplateImage() { + return getRandomImage(jigsawOriginalImages, CaptchaResource.JIGSAW_ORIGINAL); + } + + public BufferedImage getRandomWordClickImage() { + return getRandomImage(wordClickImages, CaptchaResource.WORD_CLICK); + } +} diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Action.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Action.ttf new file mode 100644 index 0000000..8439247 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Action.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Beatae.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Beatae.ttf new file mode 100644 index 0000000..779ab8e Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Beatae.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Epilog.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Epilog.ttf new file mode 100644 index 0000000..bd9614a Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Epilog.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Fresnel.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Fresnel.ttf new file mode 100644 index 0000000..7e32a51 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Fresnel.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Headache.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Headache.ttf new file mode 100644 index 0000000..26fa0af Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Headache.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Lexographer.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Lexographer.ttf new file mode 100644 index 0000000..411af54 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Lexographer.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Prefix.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Prefix.ttf new file mode 100644 index 0000000..a1aeed0 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Prefix.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/ProgBot.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/ProgBot.ttf new file mode 100644 index 0000000..c98d122 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/ProgBot.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/RobotTeacher.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/RobotTeacher.ttf new file mode 100644 index 0000000..09e2de0 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/RobotTeacher.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/Scandal.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/Scandal.ttf new file mode 100644 index 0000000..22e3a7e Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/Scandal.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/WenQuanZhengHei.ttf b/engine-captcha/captcha-core/src/main/resources/fonts/WenQuanZhengHei.ttf new file mode 100644 index 0000000..f84e9fe Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/fonts/WenQuanZhengHei.ttf differ diff --git a/engine-captcha/captcha-core/src/main/resources/fonts/license.txt b/engine-captcha/captcha-core/src/main/resources/fonts/license.txt new file mode 100644 index 0000000..719f68f --- /dev/null +++ b/engine-captcha/captcha-core/src/main/resources/fonts/license.txt @@ -0,0 +1,55 @@ +文泉驿是一个开源汉字字体项目 + +由旅美学者房骞骞(FangQ) + +于2004年10月创建 + +集中力量解决GNU/Linux + +高质量中文字体匮乏的状况 + +目前,文泉驿已经开发并发布了 + +第一个完整覆盖GB18030汉字 + +(包含27000多个汉字) + +的多规格点阵汉字字型文件 + +第一个覆盖GBK字符集的 + +开源矢量字型文件(文泉驿正黑) + +并提供了目前包含字符数目最多的 + +开源字体——GNU Unifont——中 + +绝大多数中日韩文相关的符号 + +这些字型文件已经逐渐成为 + +主流Linux/Unix发行版 + +中文桌面的首选中文字体 + +目前Ubuntu、Fedora、Slackware + +Magic Linux、CDLinux + +使用文泉驿作为默认中文字体 + +Debian、Gentoo、Mandriva + +ArchLinux、Frugalware + +则提供了官方源支持 + +而FreeBSD则在其ports中有提供 + +所以,今天我们所要分享的就是 + +文泉驿正黑体 + +可在Linux/UNIX,Windows + +Mac OS和嵌入式操作系统中使用 \ No newline at end of file diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg01.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg01.png new file mode 100644 index 0000000..f2a58b7 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg01.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg02.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg02.png new file mode 100644 index 0000000..78a1a3c Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg02.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg03.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg03.png new file mode 100644 index 0000000..5d2fc68 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg03.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg04.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg04.png new file mode 100644 index 0000000..992e8ff Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg04.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg05.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg05.png new file mode 100644 index 0000000..4f91e82 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg05.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg06.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg06.png new file mode 100644 index 0000000..ae10126 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg06.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg07.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg07.png new file mode 100644 index 0000000..3b07dcd Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg07.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg08.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg08.png new file mode 100644 index 0000000..5ea54d4 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg08.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg09.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg09.png new file mode 100644 index 0000000..4b9844a Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg09.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg10.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg10.png new file mode 100644 index 0000000..022aabf Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg10.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg11.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg11.png new file mode 100644 index 0000000..914908e Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg11.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg12.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg12.png new file mode 100644 index 0000000..f0f3ce5 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg12.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg13.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg13.png new file mode 100644 index 0000000..c5697f3 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg13.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg14.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg14.png new file mode 100644 index 0000000..e29e7a3 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg14.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg15.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg15.png new file mode 100644 index 0000000..2425f41 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg15.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg16.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg16.png new file mode 100644 index 0000000..c1730ad Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg16.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg17.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg17.png new file mode 100644 index 0000000..10ea85b Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg17.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg18.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg18.png new file mode 100644 index 0000000..a63f0ac Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg18.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg19.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg19.png new file mode 100644 index 0000000..fe9c4cc Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg19.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg20.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg20.png new file mode 100644 index 0000000..7db9563 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/original/bg20.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/1.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/1.png new file mode 100644 index 0000000..1905026 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/1.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/2.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/2.png new file mode 100644 index 0000000..b1482d4 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/2.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/3.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/3.png new file mode 100644 index 0000000..cdbb0b1 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/3.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/4.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/4.png new file mode 100644 index 0000000..bc69c96 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/4.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/5.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/5.png new file mode 100644 index 0000000..0080a54 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/5.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/6.png b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/6.png new file mode 100644 index 0000000..b07c3b4 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/jigsaw/template/6.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg01.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg01.png new file mode 100644 index 0000000..50dfe28 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg01.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg02.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg02.png new file mode 100644 index 0000000..15b38ad Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg02.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg03.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg03.png new file mode 100644 index 0000000..e2e487b Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg03.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg04.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg04.png new file mode 100644 index 0000000..c34baa4 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg04.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg05.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg05.png new file mode 100644 index 0000000..0b3d11a Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg05.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg06.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg06.png new file mode 100644 index 0000000..4cd9cbb Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg06.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg07.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg07.png new file mode 100644 index 0000000..67797a1 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg07.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg08.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg08.png new file mode 100644 index 0000000..1721aea Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg08.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg09.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg09.png new file mode 100644 index 0000000..8d5060a Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg09.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg10.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg10.png new file mode 100644 index 0000000..c99fbcb Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg10.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg11.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg11.png new file mode 100644 index 0000000..6a951d3 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg11.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg12.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg12.png new file mode 100644 index 0000000..a38ada5 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg12.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg13.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg13.png new file mode 100644 index 0000000..07af86a Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg13.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg14.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg14.png new file mode 100644 index 0000000..9559375 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg14.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg15.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg15.png new file mode 100644 index 0000000..cb1ebb6 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg15.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg16.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg16.png new file mode 100644 index 0000000..106b456 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg16.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg17.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg17.png new file mode 100644 index 0000000..bcdbe76 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg17.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg18.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg18.png new file mode 100644 index 0000000..ae94e09 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg18.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg19.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg19.png new file mode 100644 index 0000000..bef9318 Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg19.png differ diff --git a/engine-captcha/captcha-core/src/main/resources/images/word-click/bg20.png b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg20.png new file mode 100644 index 0000000..36cfbde Binary files /dev/null and b/engine-captcha/captcha-core/src/main/resources/images/word-click/bg20.png differ diff --git a/engine-captcha/captcha-sdk-behavior/README.md b/engine-captcha/captcha-sdk-behavior/README.md new file mode 100644 index 0000000..531c377 --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/README.md @@ -0,0 +1,5 @@ +## 图形验证码组件模块 + +**包含以下内容:** +1. 拼图滑块验证码绘制。 +2. 文字点选验证码绘制。 \ No newline at end of file diff --git a/engine-captcha/captcha-sdk-behavior/pom.xml b/engine-captcha/captcha-sdk-behavior/pom.xml new file mode 100644 index 0000000..f2a6279 --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-captcha + com.liuhung.engine + 2.7.8.0 + + + captcha-sdk-behavior + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + captcha-core + + + + \ No newline at end of file diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/configuration/BehaviorCaptchaConfiguration.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/configuration/BehaviorCaptchaConfiguration.java new file mode 100644 index 0000000..55011ab --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/configuration/BehaviorCaptchaConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.configuration; + +import com.liuhung.engine.captcha.behavior.renderer.JigsawCaptchaRenderer; +import com.liuhung.engine.captcha.behavior.renderer.WordClickCaptchaRenderer; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.provider.ResourceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: 行为验证码配置

+ * + * @author : liuh + * @date : 2022/1/18 20:57 + */ +@Configuration(proxyBeanMethods = false) +public class BehaviorCaptchaConfiguration { + + private static final Logger log = LoggerFactory.getLogger(BehaviorCaptchaConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Captcha Behavior] Auto Configure."); + } + + @Bean(CaptchaCategory.JIGSAW_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public JigsawCaptchaRenderer jigsawCaptchaRenderer(ResourceProvider resourceProvider) { + JigsawCaptchaRenderer jigsawCaptchaRenderer = new JigsawCaptchaRenderer(); + jigsawCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Jigsaw Captcha Renderer] Auto Configure."); + return jigsawCaptchaRenderer; + } + + @Bean(CaptchaCategory.WORD_CLICK_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public WordClickCaptchaRenderer wordClickCaptchaRenderer(ResourceProvider resourceProvider) { + WordClickCaptchaRenderer wordClickCaptchaRenderer = new WordClickCaptchaRenderer(); + wordClickCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Word Click Captcha Renderer] Auto Configure."); + return wordClickCaptchaRenderer; + } +} diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/definition/AbstractBehaviorRenderer.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/definition/AbstractBehaviorRenderer.java new file mode 100644 index 0000000..a2231e7 --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/definition/AbstractBehaviorRenderer.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.definition; + +import com.liuhung.engine.captcha.core.definition.AbstractRenderer; +import com.alicp.jetcache.anno.CacheType; + +import java.awt.*; +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +/** + *

Description: 验证码通用基础类

+ * + * @param 验证码缓存对应Key值的类型。 + * @param 验证码缓存存储数据的值的类型 + * @author : liuh + * @date : 2021/12/11 13:53 + */ +public abstract class AbstractBehaviorRenderer extends AbstractRenderer { + + public AbstractBehaviorRenderer(String cacheName) { + super(cacheName); + } + + public AbstractBehaviorRenderer(String cacheName, CacheType cacheType) { + super(cacheName, cacheType); + } + + public AbstractBehaviorRenderer(String cacheName, CacheType cacheType, Duration expire) { + super(cacheName, cacheType, expire); + } + + protected int getEnOrZhLength(String s) { + int enCount = 0; + int zhCount = 0; + for (int i = 0; i < s.length(); i++) { + int length = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8).length; + if (length > 1) { + zhCount++; + } else { + enCount++; + } + } + int zhOffset = getHalfWatermarkFontSize() * zhCount + 5; + int enOffset = enCount * 8; + return zhOffset + enOffset; + } + + private int getWatermarkFontSize() { + return getCaptchaProperties().getWatermark().getFontSize(); + } + + private int getHalfWatermarkFontSize() { + return getWatermarkFontSize() / 2; + } + + protected void addWatermark(Graphics graphics, int width, int height) { + int fontSize = getHalfWatermarkFontSize(); + Font watermakFont = this.getResourceProvider().getWaterMarkFont(fontSize); + graphics.setFont(watermakFont); + graphics.setColor(Color.white); + String content = this.getCaptchaProperties().getWatermark().getContent(); + graphics.drawString(content, width - getEnOrZhLength(content), height - getHalfWatermarkFontSize() + 7); + } + + protected boolean isUnderOffset(int actualValue, int standardValue, int threshold) { + return actualValue < standardValue - threshold; + } + + protected boolean isOverOffset(int actualValue, int standardValue, int threshold) { + return actualValue > standardValue + threshold; + } + + protected boolean isDeflected(int actualValue, int standardValue, int threshold) { + return isUnderOffset(actualValue, standardValue, threshold) || isOverOffset(actualValue, standardValue, threshold); + } +} diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/dto/JigsawCaptcha.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/dto/JigsawCaptcha.java new file mode 100644 index 0000000..088d063 --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/dto/JigsawCaptcha.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.dto; + +import com.liuhung.engine.captcha.core.dto.Captcha; + +/** + *

Description: 滑块拼图验证码返回前端信息

+ * + * @author : liuh + * @date : 2021/12/13 17:06 + */ +public class JigsawCaptcha extends Captcha { + + private String originalImageBase64; + + private String sliderImageBase64; + + public String getOriginalImageBase64() { + return originalImageBase64; + } + + public void setOriginalImageBase64(String originalImageBase64) { + this.originalImageBase64 = originalImageBase64; + } + + public String getSliderImageBase64() { + return sliderImageBase64; + } + + public void setSliderImageBase64(String sliderImageBase64) { + this.sliderImageBase64 = sliderImageBase64; + } +} diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/dto/WordClickCaptcha.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/dto/WordClickCaptcha.java new file mode 100644 index 0000000..831ebea --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/dto/WordClickCaptcha.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.dto; + +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.google.common.base.MoreObjects; + +/** + *

Description: 文字点选验证码返回前台信息

+ * + * @author : liuh + * @date : 2021/12/14 11:35 + */ +public class WordClickCaptcha extends Captcha { + + /** + * 文字点选验证码生成的带文字背景图。 + */ + private String wordClickImageBase64; + + /** + * 文字点选验证码文字 + */ + private String words; + + /** + * 需要点击的文字数量 + */ + private Integer wordsCount; + + public String getWordClickImageBase64() { + return wordClickImageBase64; + } + + public void setWordClickImageBase64(String wordClickImageBase64) { + this.wordClickImageBase64 = wordClickImageBase64; + } + + public String getWords() { + return words; + } + + public void setWords(String words) { + this.words = words; + } + + public Integer getWordsCount() { + return wordsCount; + } + + public void setWordsCount(Integer wordsCount) { + this.wordsCount = wordsCount; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("wordClickImageBase64", wordClickImageBase64) + .add("words", words) + .add("wordsCount", wordsCount) + .toString(); + } +} diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/JigsawCaptchaRenderer.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/JigsawCaptchaRenderer.java new file mode 100644 index 0000000..f4fe9c2 --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/JigsawCaptchaRenderer.java @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.renderer; + +import com.liuhung.engine.captcha.behavior.definition.AbstractBehaviorRenderer; +import com.liuhung.engine.captcha.behavior.dto.JigsawCaptcha; +import com.liuhung.engine.captcha.core.algorithm.GaussianBlur; +import com.liuhung.engine.captcha.core.constants.CaptchaConstants; +import com.liuhung.engine.captcha.core.definition.domain.Coordinate; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.liuhung.engine.captcha.core.dto.Verification; +import com.liuhung.engine.captcha.core.exception.CaptchaHasExpiredException; +import com.liuhung.engine.captcha.core.exception.CaptchaMismatchException; +import com.liuhung.engine.captcha.core.exception.CaptchaParameterIllegalException; +import com.liuhung.engine.captcha.core.provider.RandomProvider; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.IdUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Objects; + +/** + *

Description: 拼图滑块验证码处理器

+ * + * @author : liuh + * @date : 2021/12/11 15:29 + */ +@Component +public class JigsawCaptchaRenderer extends AbstractBehaviorRenderer { + + private static final Logger log = LoggerFactory.getLogger(JigsawCaptchaRenderer.class); + + private static final int AREA_SIZE = 3; + private static final int AREA_ARRAY_SIZE = AREA_SIZE * AREA_SIZE; + private static final int BOLD = 5; + private static final int OFFSET = 100; + + public JigsawCaptchaRenderer() { + super(CaptchaConstants.CACHE_NAME_CAPTCHA_JIGSAW); + } + + @Override + public String getCategory() { + return CaptchaCategory.JIGSAW.getConstant(); + } + + private JigsawCaptcha jigsawCaptcha; + + @Override + public Captcha getCapcha(String key) { + String identity = key; + if (StringUtils.isBlank(identity)) { + identity = IdUtil.fastUUID(); + } + + this.create(identity); + return this.jigsawCaptcha; + } + + @Override + public Coordinate nextStamp(String key) { + + Metadata metadata = draw(); + + JigsawCaptcha jigsawCaptcha = new JigsawCaptcha(); + jigsawCaptcha.setIdentity(key); + jigsawCaptcha.setOriginalImageBase64(metadata.getOriginalImageBase64()); + jigsawCaptcha.setSliderImageBase64(metadata.getSliderImageBase64()); + + this.jigsawCaptcha = jigsawCaptcha; + + return metadata.getCoordinate(); + } + + @Override + public boolean verify(Verification verification) { + + if (ObjectUtils.isEmpty(verification) || ObjectUtils.isEmpty(verification.getCoordinate())) { + throw new CaptchaParameterIllegalException("Parameter Stamp value is null"); + } + + Coordinate store = this.get(verification.getIdentity()); + if (ObjectUtils.isEmpty(store)) { + throw new CaptchaHasExpiredException("Stamp is invalid!"); + } + + this.delete(verification.getIdentity()); + + Coordinate real = verification.getCoordinate(); + + if (this.isDeflected(real.getX(), store.getX(), getCaptchaProperties().getJigsaw().getDeviation()) || real.getY() != store.getY()) { + throw new CaptchaMismatchException(); + } + + return true; + } + + @Override + public Metadata draw() { + + // 原生图片 + BufferedImage originalImage = this.getResourceProvider().getRandomOriginalImage(); + + // 设置水印 + Graphics backgroundGraphics = originalImage.getGraphics(); + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + addWatermark(backgroundGraphics, width, height); + + // 抠图图片 + String sliderImageBase64 = this.getResourceProvider().getRandomBase64TemplateImage(); + BufferedImage templateImage = ImgUtil.toImage(sliderImageBase64); + + return draw(originalImage, templateImage, sliderImageBase64); + } + + /** + * 绘制滑块拼图验证码图片元素 + * + * @param originalImage 原始图片(验证码背景图) + * @param templateImage 模版图片(拼图模版图片,抠图和滑块拼图的形状) + * @param sliderImageBase64 滑块拼图图片Base64 + * @return 滑块拼图验证码数据 + */ + private Metadata draw(BufferedImage originalImage, BufferedImage templateImage, String sliderImageBase64) { + + int originalImageWidth = originalImage.getWidth(); + int originalImageHeight = originalImage.getHeight(); + int templateImageWidth = templateImage.getWidth(); + int templateImageHeight = templateImage.getHeight(); + + log.trace("[Quafer] |- Jigsaw captcha original image width is [{}], height is [{}].", originalImageWidth, originalImageHeight); + log.trace("[Quafer] |- Jigsaw captcha template image width is [{}], height is [{}].", templateImageWidth, templateImageHeight); + + // 随机生成拼图坐标 + Coordinate coordinate = createImageMattingCoordinate(originalImageWidth, originalImageHeight, templateImageWidth, templateImageHeight); + int x = coordinate.getX(); + int y = coordinate.getY(); + + // 根据模版抠出新的拼图图像 + BufferedImage jigsawImage = new BufferedImage(templateImageWidth, templateImageHeight, templateImage.getType()); + Graphics2D graphics = jigsawImage.createGraphics(); + + // 如果需要生成RGB格式,需要做如下配置,Transparency 设置透明 + jigsawImage = graphics.getDeviceConfiguration().createCompatibleImage(templateImageWidth, templateImageHeight, Transparency.TRANSLUCENT); + + // 新建的图像根据模板颜色赋值,源图生成遮罩 + mattingByTemplate(originalImage, templateImage, jigsawImage, x, 0); + + // 添加干扰项 + int interferencePosition = createInterferencePosition(originalImageWidth, templateImageWidth, x); + if (interferencePosition != 0) { + addInterference(originalImage, sliderImageBase64, interferencePosition); + } + + // 设置“抗锯齿”的属性 + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setStroke(new BasicStroke(BOLD, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + graphics.drawImage(jigsawImage, 0, 0, null); + graphics.dispose(); + + log.trace("[Quafer] |- Jigsaw captcha jigsaw image width is [{}], height is [{}].", jigsawImage.getWidth(), jigsawImage.getHeight()); + + Metadata metadata = new Metadata(); + metadata.setOriginalImageBase64(toBase64(originalImage)); + metadata.setSliderImageBase64(toBase64(jigsawImage)); + metadata.setCoordinate(coordinate); + + return metadata; + } + + /** + * 获取随机的抠出拼图坐标 + * + * @param originalImageWidth 原始图片宽度 + * @param originalImageHeight 原始图片高度 + * @param templateImageWidth 拼图模版宽度 + * @param templateImageHeight 拼图模版高度 + * @return 拼图坐标 {@link Coordinate} + */ + private Coordinate createImageMattingCoordinate(int originalImageWidth, int originalImageHeight, int templateImageWidth, int templateImageHeight) { + + int availableWidth = originalImageWidth - templateImageWidth; + int availableHeight = originalImageHeight - templateImageHeight; + + int x = BOLD; + int y = BOLD; + + if (availableWidth > 0) { + x = RandomProvider.randomInt(availableWidth - OFFSET) + OFFSET; + } + + if (availableHeight > 0) { + y = RandomProvider.randomInt(availableHeight) + BOLD; + } + + log.debug("[Quafer] |- Jigsaw captcha image matting coordinate is x: [{}], y: [{}].", x, y); + return new Coordinate(x, y); + } + + /** + * 根据拼图模版图片抠图 + * + * @param originalImage 原图 + * @param templateImage 拼图模板图 + * @param jigsawImage 新抠出的小图 + * @param x 随机扣取坐标X + * @param y 随机扣取坐标y + */ + private void mattingByTemplate(BufferedImage originalImage, BufferedImage templateImage, BufferedImage jigsawImage, int x, int y) { + // 临时数组遍历用于高斯模糊存周边像素值 + int[][] matrix = new int[AREA_SIZE][AREA_SIZE]; + int[] values = new int[AREA_ARRAY_SIZE]; + + int templateImageWidth = templateImage.getWidth(); + int templateImageHeight = templateImage.getHeight(); + + // 模板图像宽度 + for (int i = 0; i < templateImageWidth; i++) { + // 模板图片高度 + for (int j = 0; j < templateImageHeight; j++) { + + int pixelX = x + i; + int pixelY = y + j; + + // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中 + int templateImageRgb = getImageRgb(templateImage, i, j); + if (templateImageRgb < 0) { + jigsawImage.setRGB(i, j, getImageRgb(originalImage, pixelX, pixelY)); + // 抠图区域高斯模糊 + GaussianBlur.execute(originalImage, pixelX, pixelY, matrix, values, AREA_SIZE); + } + + //防止数组越界判断 + if (isOutOfBound(i, j, templateImageWidth, templateImageHeight)) { + continue; + } + + // 描边处理,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色 + if (isCritical(templateImage, i, j, templateImageRgb)) { + jigsawImage.setRGB(i, j, Color.white.getRGB()); + originalImage.setRGB(pixelX, pixelY, Color.white.getRGB()); + } + } + } + } + + private int getImageRgb(BufferedImage bufferedImage, int i, int j) { + return bufferedImage.getRGB(i, j); + } + + + private int getTemplateImageRightBorderRgb(BufferedImage templateImage, int i, int j) { + return getImageRgb(templateImage, i + 1, j); + } + + private int getTemplateImageBottomBorderRgb(BufferedImage templateImage, int i, int j) { + return getImageRgb(templateImage, i, j + 1); + } + + /** + * 防止数组越界判断 + * + * @param x x 坐标值 + * @param y y 坐标值 + * @param templateImageWidth 拼图图片宽度 + * @param templateImageHeight 拼图图片高度度 + * @return 是否越界, true 越界, false 没有越界 + */ + private boolean isOutOfBound(int x, int y, int templateImageWidth, int templateImageHeight) { + return x == (templateImageWidth - 1) || y == (templateImageHeight - 1); + } + + private boolean isPixelBoundary(int main, int boarder) { + return main < 0 && boarder >= 0; + } + + private boolean isNoPixelBoundary(int main, int boarder) { + return main >= 0 && boarder < 0; + } + + private boolean isBoundary(int main, int boarder) { + return isNoPixelBoundary(main, boarder) || isPixelBoundary(main, boarder); + } + + private boolean isCritical(BufferedImage templateImage, int x, int y, int baseRgb) { + int rightBorderRgb = getTemplateImageRightBorderRgb(templateImage, x, y); + int bottomBorderRgb = getTemplateImageBottomBorderRgb(templateImage, x, y); + // 描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色 + return isBoundary(baseRgb, rightBorderRgb) || isBoundary(baseRgb, bottomBorderRgb); + } + + private int createInterferencePosition(int originalImageWidth, int templateImageWidth, int x) { + + int interferenceOptions = getCaptchaProperties().getJigsaw().getInterference(); + + int position = 0; + + if (interferenceOptions > 0) { + if (originalImageWidth - x - BOLD > templateImageWidth * 2) { + // 在原扣图右边插入干扰图 + position = RandomProvider.randomInt(x + templateImageWidth + BOLD, originalImageWidth - templateImageWidth); + } else { + // 在原扣图左边插入干扰图 + position = RandomProvider.randomInt(OFFSET, x - templateImageWidth - BOLD); + } + } + + if (interferenceOptions > 1) { + position = RandomProvider.randomInt(templateImageWidth, OFFSET - templateImageWidth); + } + + return position; + } + + private void addInterference(BufferedImage originalImage, String sliderImageBase64, int position) { + while (true) { + String data = this.getResourceProvider().getRandomBase64TemplateImage(); + if (!sliderImageBase64.equals(data)) { + interferenceByTemplate(originalImage, Objects.requireNonNull(ImgUtil.toImage(data)), position, 0); + break; + } + } + } + + /** + * 根据拼图模版图片绘制干扰 + * + * @param originalImage 原图 + * @param templateImage 拼图模板图 + * @param x 随机扣取坐标X + * @param y 随机扣取坐标y + */ + private void interferenceByTemplate(BufferedImage originalImage, BufferedImage templateImage, int x, int y) { + //临时数组遍历用于高斯模糊存周边像素值 + int[][] matrix = new int[AREA_SIZE][AREA_SIZE]; + int[] values = new int[AREA_ARRAY_SIZE]; + + int templateImageWidth = templateImage.getWidth(); + int templateImageHeight = templateImage.getHeight(); + // 模板图像宽度 + for (int i = 0; i < templateImageWidth; i++) { + // 模板图片高度 + for (int j = 0; j < templateImageHeight; j++) { + + int pixelX = x + i; + int pixelY = y + j; + + // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中 + int templateImageRgb = getImageRgb(templateImage, i, j); + if (templateImageRgb < 0) { + // 抠图区域高斯模糊 + GaussianBlur.execute(originalImage, pixelX, pixelY, matrix, values, AREA_SIZE); + } + + // 防止数组越界判断 + if (isOutOfBound(i, j, templateImageWidth, templateImageHeight)) { + continue; + } + + //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色 + if (isCritical(templateImage, i, j, templateImageRgb)) { + originalImage.setRGB(pixelX, pixelY, Color.white.getRGB()); + } + } + } + } +} diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/WordClickCaptchaRenderer.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/WordClickCaptchaRenderer.java new file mode 100644 index 0000000..ed043ad --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/WordClickCaptchaRenderer.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.renderer; + +import com.liuhung.engine.captcha.behavior.definition.AbstractBehaviorRenderer; +import com.liuhung.engine.captcha.behavior.dto.WordClickCaptcha; +import com.liuhung.engine.captcha.core.constants.CaptchaConstants; +import com.liuhung.engine.captcha.core.definition.domain.Coordinate; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.definition.enums.FontStyle; +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.liuhung.engine.captcha.core.dto.Verification; +import com.liuhung.engine.captcha.core.exception.CaptchaHasExpiredException; +import com.liuhung.engine.captcha.core.exception.CaptchaMismatchException; +import com.liuhung.engine.captcha.core.exception.CaptchaParameterIllegalException; +import com.liuhung.engine.captcha.core.provider.RandomProvider; +import cn.hutool.core.util.IdUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + *

Description: 文字点选验证码处理器

+ * + * @author : liuh + * @date : 2021/12/14 9:31 + */ +@Component +public class WordClickCaptchaRenderer extends AbstractBehaviorRenderer> { + + private WordClickCaptcha wordClickCaptcha; + + public WordClickCaptchaRenderer() { + super(CaptchaConstants.CACHE_NAME_CAPTCHA_WORD_CLICK); + } + + private Font getFont() { + int fontSize = this.getCaptchaProperties().getWordClick().getFontSize(); + String fontName = this.getCaptchaProperties().getWordClick().getFontName(); + FontStyle fontStyle = this.getCaptchaProperties().getWordClick().getFontStyle(); + return this.getResourceProvider().getFont(fontName, fontSize, fontStyle); + } + + @Override + public String getCategory() { + return CaptchaCategory.WORD_CLICK.getConstant(); + } + + @Override + public List nextStamp(String key) { + + Metadata metadata = draw(); + + WordClickObfuscator wordClickObfuscator = new WordClickObfuscator(metadata.getWords(), metadata.getCoordinates()); + + WordClickCaptcha wordClickCaptcha = new WordClickCaptcha(); + wordClickCaptcha.setIdentity(key); + wordClickCaptcha.setWordClickImageBase64(metadata.getWordClickImageBase64()); + wordClickCaptcha.setWords(wordClickObfuscator.getWordString()); + wordClickCaptcha.setWordsCount(metadata.getWords().size()); + this.wordClickCaptcha = wordClickCaptcha; + return wordClickObfuscator.getCoordinates(); + } + + @Override + public Captcha getCapcha(String key) { + String identity = key; + if (StringUtils.isBlank(identity)) { + identity = IdUtil.fastUUID(); + } + + this.create(identity); + return this.wordClickCaptcha; + } + + @Override + public boolean verify(Verification verification) { + + if (ObjectUtils.isEmpty(verification) || CollectionUtils.isEmpty(verification.getCoordinates())) { + throw new CaptchaParameterIllegalException("Parameter Stamp value is null"); + } + + List store = this.get(verification.getIdentity()); + if (CollectionUtils.isEmpty(store)) { + throw new CaptchaHasExpiredException("Stamp is invalid!"); + } + + this.delete(verification.getIdentity()); + + List real = verification.getCoordinates(); + + for (int i = 0; i < store.size(); i++) { + if (isDeflected(real.get(i).getX(), store.get(i).getX(), this.getFontSize()) || isDeflected(real.get(i).getX(), store.get(i).getX(), this.getFontSize())) { + throw new CaptchaMismatchException(); + } + } + + return true; + } + + @Override + public Metadata draw() { + + BufferedImage backgroundImage = this.getResourceProvider().getRandomWordClickImage(); + + int wordCount = getCaptchaProperties().getWordClick().getWordCount(); + + List words = RandomProvider.randomWords(wordCount); + + Graphics backgroundGraphics = backgroundImage.getGraphics(); + int backgroundImageWidth = backgroundImage.getWidth(); + int backgroundImageHeight = backgroundImage.getHeight(); + + List coordinates = IntStream.range(0, words.size()) + .mapToObj(index -> drawWord(backgroundGraphics, backgroundImageWidth, backgroundImageHeight, index, wordCount, words.get(index))).collect(Collectors.toList()); + + addWatermark(backgroundGraphics, backgroundImageWidth, backgroundImageHeight); + + //创建合并图片 + BufferedImage combinedImage = new BufferedImage(backgroundImageWidth, backgroundImageHeight, BufferedImage.TYPE_INT_RGB); + Graphics combinedGraphics = combinedImage.getGraphics(); + combinedGraphics.drawImage(backgroundImage, 0, 0, null); + + //定义随机1到arr.length某一个字不参与校验 + int excludeWordIndex = RandomProvider.randomInt(1, wordCount) - 1; + words.remove(excludeWordIndex); + coordinates.remove(excludeWordIndex); + + Metadata metadata = new Metadata(); + metadata.setWordClickImageBase64(toBase64(backgroundImage)); + metadata.setCoordinates(coordinates); + metadata.setWords(words); + return metadata; + } + + private Coordinate drawWord(Graphics graphics, int width, int height, int index, int wordCount, String word) { + Coordinate coordinate = randomWordCoordinate(width, height, index, wordCount); + + //随机字体颜色 + if (getCaptchaProperties().getWordClick().isRandomColor()) { + graphics.setColor(new Color(RandomProvider.randomInt(1, 255), RandomProvider.randomInt(1, 255), RandomProvider.randomInt(1, 255))); + } else { + graphics.setColor(Color.BLACK); + } + + // 设置角度 + AffineTransform affineTransform = new AffineTransform(); + affineTransform.rotate(Math.toRadians(RandomProvider.randomInt(-45, 45)), 0, 0); + Font rotatedFont = this.getFont().deriveFont(affineTransform); + graphics.setFont(rotatedFont); + graphics.drawString(word, coordinate.getX(), coordinate.getY()); + return coordinate; + } + + private int getFontSize() { + return this.getCaptchaProperties().getWordClick().getFontSize(); + } + + private int getHalfFontSize() { + return this.getFontSize() / 2; + } + + /** + * 根据汉字排序的枚举值值,计算汉字的坐标点。 + * + * @param backgroundImageWidth 图片宽度 + * @param backgroundImageHeight 图片高度 + * @param wordIndex 汉字排序的枚举值值 + * @param wordCount 显示汉字的总数量 + * @return 当前汉字的坐标 {@link Coordinate} + */ + private Coordinate randomWordCoordinate(int backgroundImageWidth, int backgroundImageHeight, int wordIndex, int wordCount) { + int wordSize = getFontSize(); + int halfWordSize = getHalfFontSize(); + + int averageWidth = backgroundImageWidth / (wordCount + 1); + int x, y; + if (averageWidth < halfWordSize) { + x = RandomProvider.randomInt(getStartInclusive(halfWordSize), backgroundImageWidth); + } else { + if (wordIndex == 0) { + x = RandomProvider.randomInt(getStartInclusive(halfWordSize), getEndExclusive(wordIndex, averageWidth, halfWordSize)); + } else { + x = RandomProvider.randomInt(averageWidth * wordIndex + halfWordSize, getEndExclusive(wordIndex, averageWidth, halfWordSize)); + } + } + y = RandomProvider.randomInt(wordSize, backgroundImageHeight - wordSize); + return new Coordinate(x, y); + } + + /** + * 获取默认随机数起始点 + * + * @param halfWordSize 半个汉字的大小 + * @return 最小的随机 x 坐标 + */ + private int getStartInclusive(int halfWordSize) { + return 1 + halfWordSize; + } + + /** + * 获取默认随机数终点 + * + * @param wordIndex 汉字的枚举值值(当前是第几个汉字) + * @param averageWidth 栅格宽度 + * @param halfWordSize 半个汉字的大小 + * @return 最大的随机 x 坐标 + */ + private int getEndExclusive(int wordIndex, int averageWidth, int halfWordSize) { + return averageWidth * (wordIndex + 1) - halfWordSize; + } +} diff --git a/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/WordClickObfuscator.java b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/WordClickObfuscator.java new file mode 100644 index 0000000..55c5943 --- /dev/null +++ b/engine-captcha/captcha-sdk-behavior/src/main/java/com/liuhung/engine/captcha/behavior/renderer/WordClickObfuscator.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.behavior.renderer; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.captcha.core.definition.domain.Coordinate; +import cn.hutool.core.util.RandomUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *

Description: 文字点选信息混淆器

+ * + * @author : liuh + * @date : 2021/12/17 12:19 + */ +public class WordClickObfuscator { + /** + * 文字点选验证码文字坐标信息列表 + */ + private final List coordinates; + /** + * 文字点选验证码校验文字 + */ + private final List words; + + private String wordString; + + public WordClickObfuscator(List originalWords, List originalCoordinates) { + this.coordinates = new ArrayList<>(); + this.words = new ArrayList<>(); + this.execute(originalWords, originalCoordinates); + } + + private void execute(List originalWords, List originalCoordinates) { + + int[] indexes = RandomUtil.randomInts(originalWords.size()); + + Arrays.stream(indexes).forEach(value -> { + this.words.add(this.words.size(), originalWords.get(value)); + this.coordinates.add(this.coordinates.size(), originalCoordinates.get(value)); + }); + + this.wordString = StringUtils.join(getWords(), SymbolConstants.COMMA); + } + + public List getCoordinates() { + return coordinates; + } + + public List getWords() { + return words; + } + + public String getWordString() { + return this.wordString; + } +} diff --git a/engine-captcha/captcha-sdk-graphic/README.md b/engine-captcha/captcha-sdk-graphic/README.md new file mode 100644 index 0000000..f292b94 --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/README.md @@ -0,0 +1,20 @@ +## Hutool 图形验证码组件模块 + +**包含以下内容:** +1. 算术计算验证码。 +2. 汉字验证码。 +3. 汉字 Gif 验证码 +4. 传统字母数字验证码 +5. 传统字母数字 Gif 验证码 + +可设置多种字体: +* Action.ttf +* Beatae.ttf +* Epilog.ttf +* Fresnel.ttf +* Headache.ttf +* Lexographer.ttf +* Prefix +* ProgBot +* RobotTeacher.ttf +* Scandal.ttf \ No newline at end of file diff --git a/engine-captcha/captcha-sdk-graphic/pom.xml b/engine-captcha/captcha-sdk-graphic/pom.xml new file mode 100644 index 0000000..b1e27ee --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-captcha + com.liuhung.engine + 2.7.8.0 + + + captcha-sdk-graphic + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + captcha-core + + + + \ No newline at end of file diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/configuration/GraphicCaptchaConfiguration.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/configuration/GraphicCaptchaConfiguration.java new file mode 100644 index 0000000..d15d7eb --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/configuration/GraphicCaptchaConfiguration.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.configuration; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.provider.ResourceProvider; +import com.liuhung.engine.captcha.graphic.renderer.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: 图形验证码配置

+ * + * @author : liuh + * @date : 2022/1/18 20:56 + */ +@Configuration(proxyBeanMethods = false) +public class GraphicCaptchaConfiguration { + + private static final Logger log = LoggerFactory.getLogger(GraphicCaptchaConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Captcha Graphic] Auto Configure."); + } + + @Bean(CaptchaCategory.ARITHMETIC_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public ArithmeticCaptchaRenderer arithmeticCaptchaRenderer(ResourceProvider resourceProvider) { + ArithmeticCaptchaRenderer arithmeticCaptchaRenderer = new ArithmeticCaptchaRenderer(); + arithmeticCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Arithmetic Captcha Renderer] Auto Configure."); + return arithmeticCaptchaRenderer; + } + + @Bean(CaptchaCategory.CHINESE_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public ChineseCaptchaRenderer chineseCaptchaRenderer(ResourceProvider resourceProvider) { + ChineseCaptchaRenderer chineseCaptchaRenderer = new ChineseCaptchaRenderer(); + chineseCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Chinese Captcha Renderer] Auto Configure."); + return chineseCaptchaRenderer; + } + + @Bean(CaptchaCategory.CHINESE_GIF_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public ChineseGifCaptchaRenderer chineseGifCaptchaRenderer(ResourceProvider resourceProvider) { + ChineseGifCaptchaRenderer chineseGifCaptchaRenderer = new ChineseGifCaptchaRenderer(); + chineseGifCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Chinese Gif Captcha Renderer] Auto Configure."); + return chineseGifCaptchaRenderer; + } + + @Bean(CaptchaCategory.SPEC_GIF_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public SpecGifCaptchaRenderer specGifCaptchaRenderer(ResourceProvider resourceProvider) { + SpecGifCaptchaRenderer specGifCaptchaRenderer = new SpecGifCaptchaRenderer(); + specGifCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Spec Gif Captcha Renderer] Auto Configure."); + return specGifCaptchaRenderer; + } + + @Bean(CaptchaCategory.SPEC_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public SpecCaptchaRenderer specCaptchaRenderer(ResourceProvider resourceProvider) { + SpecCaptchaRenderer specCaptchaRenderer = new SpecCaptchaRenderer(); + specCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Spec Captcha Renderer] Auto Configure."); + return specCaptchaRenderer; + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractBaseGraphicRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractBaseGraphicRenderer.java new file mode 100644 index 0000000..6b86295 --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractBaseGraphicRenderer.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.definition; + +import com.liuhung.engine.captcha.core.definition.AbstractGraphicRenderer; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCharacter; +import com.liuhung.engine.captcha.core.provider.RandomProvider; + +import java.awt.*; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.QuadCurve2D; +import java.awt.image.BufferedImage; +import java.util.List; + +/** + *

Description: 基础图像绘制器

+ * + * @author : liuh + * @date : 2021/12/23 22:46 + */ +public abstract class AbstractBaseGraphicRenderer extends AbstractGraphicRenderer { + + protected String[] getWordCharacters() { + int number = this.getCaptchaProperties().getGraphics().getLength(); + List words = RandomProvider.randomWords(number); + String[] content = new String[words.size()]; + return words.toArray(content); + } + + protected String[] getCharCharacters() { + int number = getCaptchaProperties().getGraphics().getLength(); + CaptchaCharacter captchaCharacter = getCaptchaProperties().getGraphics().getLetter(); + return RandomProvider.randomCharacters(number, captchaCharacter); + } + + /** + * 需要绘制的验证码字符内容 + * + * @return 需要绘制的字符内容 + */ + protected abstract String[] getDrawCharacters(); + + private BufferedImage createPngBufferedImage(String[] characters, String benchmark, boolean isArithmetic) { + return createBufferedImage(characters, benchmark, isArithmetic, false, 0); + } + + protected BufferedImage createPngBufferedImage(String[] characters) { + return createPngBufferedImage(characters, "W", false); + } + + protected BufferedImage createArithmeticBufferedImage(String[] characters) { + return createPngBufferedImage(characters, "8", true); + } + + protected BufferedImage createGifBufferedImage(String[] characters, int alpha) { + return createBufferedImage(characters, "王", false, true, alpha); + } + + /** + * 绘制验证码图形 + * + * @param isGif 是否是 Gif 类型 + * @param isArithmetic 是否是算数类型 + * @param characters 内容字符 + * @param benchmark 内容宽度标尺 + * @param alpha 透明度,仅针对 Gif类型。 + * @return {@link BufferedImage} + */ + private BufferedImage createBufferedImage(String[] characters, String benchmark, boolean isArithmetic, boolean isGif, int alpha) { + + BufferedImage bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + // 获得图形上下文 + Graphics2D graphics = bufferedImage.createGraphics(); + + // 填充背景颜色 + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, getWidth(), getHeight()); + + // 抗锯齿 + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 画干扰线 + if (!isArithmetic) { + drawInterfereLine(graphics, isGif); + } + + Color[] colors = RandomProvider.randomColors(characters.length); + + drawCharacter(graphics, characters, colors, benchmark, isGif, alpha); + + graphics.dispose(); + + return bufferedImage; + } + + /** + * 设置随机颜色 + * + * @param graphics 图形 {@link Graphics2D} + */ + private void drawColor(Graphics2D graphics) { + graphics.setColor(RandomProvider.randomColor()); + } + + /** + * 设置透明度 + * + * @param graphics 图形 {@link Graphics2D} + * @param alpha 透明度 {@link Float} + */ + private void drawAlpha(Graphics2D graphics, float alpha) { + AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); + graphics.setComposite(alphaComposite); + } + + /** + * 干扰线透明度,主要用于 Gif 验证码 + * + * @param graphics 图形 {@link Graphics2D} + */ + private void drawAlphaForLine(Graphics2D graphics) { + drawAlpha(graphics, 0.7f); + } + + + /** + * 获取透明度,从0到1,自动计算步长 + * + * @param length 内容长度 + * @param alpha 透明度 + * @param index 绘制内容枚举值 + * @return 透明度 + */ + private float getAlpha(int length, int alpha, int index) { + int num = alpha + index; + float r = (float) 1 / length; + float s = (length + 1) * r; + return num > length ? (num * r - s) : num * r; + } + + /** + * 干扰线透明度,主要用于 Gif 验证码 + * + * @param graphics 图形 {@link Graphics2D} + * @param length 内容长度 + * @param alpha 透明度 + * @param index 当前内容枚举值 + */ + private void drawAlphaForCharacter(Graphics2D graphics, int length, int alpha, int index) { + drawAlpha(graphics, getAlpha(length, alpha, index)); + } + + private int randomCtrlX() { + return RandomProvider.randomInt(getWidth() / 4, getWidth() / 4 * 3); + } + + private int randomCtrlY() { + return RandomProvider.randomInt(5, getHeight() - 5); + } + + /** + * 绘制贝塞尔曲线 + * + * @param graphics 图形 {@link Graphics2D} + */ + private void drawBezierCurve(Graphics2D graphics) { + drawColor(graphics); + int x1 = 5; + int y1 = RandomProvider.randomInt(5, getHeight() / 2); + int x2 = getWidth() - 5; + int y2 = RandomProvider.randomInt(getHeight() / 2, getHeight() - 5); + int ctrlx1 = randomCtrlX(); + int ctrly1 = randomCtrlY(); + if (RandomProvider.randomInt(2) == 0) { + int ty = y1; + y1 = y2; + y2 = ty; + } + // 二阶贝塞尔曲线 + if (RandomProvider.randomInt(2) == 0) { + QuadCurve2D shape = new QuadCurve2D.Double(); + shape.setCurve(x1, y1, ctrlx1, ctrly1, x2, y2); + graphics.draw(shape); + } else { + // 三阶贝塞尔曲线 + int ctrlx2 = randomCtrlX(); + int ctrly2 = randomCtrlY(); + CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); + graphics.draw(shape); + } + } + + /** + * 绘制干扰线 + * + * @param graphics 图形 {@link Graphics2D} + * @param isGif 是否是 Gif + */ + private void drawInterfereLine(Graphics2D graphics, boolean isGif) { + if (isGif) { + // 设置透明度 + drawAlphaForLine(graphics); + } + graphics.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + drawBezierCurve(graphics); + } + + /** + * 绘制干扰圆 + * + * @param graphics 图形 {@link Graphics2D} + */ + private void drawOval(Graphics2D graphics) { + int x = RandomProvider.randomInt(getWidth() - 5); + int y = RandomProvider.randomInt(getHeight() - 5); + int width = RandomProvider.randomInt(5, 30); + int height = 5 + RandomProvider.randomInt(5, 30); + graphics.drawOval(x, y, width, height); + } + + /** + * 绘制验证码 + * + * @param graphics 图形 {@link Graphics2D} + * @param characters 验证码内容 + * @param benchmark 表尺字符 + * @param isGif 是否设置透明度 + * @param alpha 透明度 + */ + private void drawCharacter(Graphics2D graphics, String[] characters, Color[] colors, String benchmark, boolean isGif, int alpha) { + + graphics.setFont(getFont()); + + FontMetrics fontMetrics = graphics.getFontMetrics(); + // 每一个字符所占的宽度 + int fW = getWidth() / characters.length; + // 字符的左右边距 + int fSp = (fW - (int) fontMetrics.getStringBounds(benchmark, graphics).getWidth()) / 2; + + for (int i = 0; i < characters.length; i++) { + // 设置透明度 + if (isGif) { + drawAlphaForCharacter(graphics, characters.length, alpha, i); + } + graphics.setColor(colors[i]); + drawOval(graphics); + // 文字的纵坐标 + int fY = getHeight() - ((getHeight() - (int) fontMetrics.getStringBounds(String.valueOf(characters[i]), graphics).getHeight()) >> 1); + graphics.drawString(characters[i], i * fW + fSp - 3, fY - 3); + } + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractGifGraphicRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractGifGraphicRenderer.java new file mode 100644 index 0000000..9de39a2 --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractGifGraphicRenderer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.definition; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.img.gif.AnimatedGifEncoder; +import org.apache.commons.lang3.StringUtils; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.util.stream.IntStream; + +/** + *

Description: Gif 类型图形验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/21 23:25 + */ +public abstract class AbstractGifGraphicRenderer extends AbstractBaseGraphicRenderer { + + @Override + protected String getBase64ImagePrefix() { + return BASE64_GIF_IMAGE_PREFIX; + } + + @Override + public Metadata draw() { + + String[] drawCharacters = this.getDrawCharacters(); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // gif编码类 + AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); + // 生成字符 + gifEncoder.start(out); + // 设置量化器取样间隔 + gifEncoder.setQuality(180); + // 帧延迟 (默认100) + int delay = 100; + //设置帧延迟 + gifEncoder.setDelay(delay); + //帧循环次数 + gifEncoder.setRepeat(0); + + IntStream.range(0, drawCharacters.length).forEach(i -> { + BufferedImage frame = createGifBufferedImage(drawCharacters, i); + gifEncoder.addFrame(frame); + frame.flush(); + }); + + gifEncoder.finish(); + + String characters = StringUtils.join(drawCharacters, SymbolConstants.BLANK); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(getBase64ImagePrefix() + Base64.encode(out.toByteArray())); + metadata.setCharacters(characters); + return metadata; + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractPngGraphicRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractPngGraphicRenderer.java new file mode 100644 index 0000000..05f484a --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/definition/AbstractPngGraphicRenderer.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.definition; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import org.apache.commons.lang3.StringUtils; + +import java.awt.image.BufferedImage; + +/** + *

Description: Png 类型图形验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/21 23:17 + */ +public abstract class AbstractPngGraphicRenderer extends AbstractBaseGraphicRenderer { + + @Override + public Metadata draw() { + String[] drawCharacters = this.getDrawCharacters(); + + BufferedImage bufferedImage = createPngBufferedImage(drawCharacters); + + String characters = StringUtils.join(drawCharacters, SymbolConstants.BLANK); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(toBase64(bufferedImage)); + metadata.setCharacters(characters); + + return metadata; + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ArithmeticCaptchaRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ArithmeticCaptchaRenderer.java new file mode 100644 index 0000000..ee88038 --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ArithmeticCaptchaRenderer.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.renderer; + +import com.liuhung.engine.assistant.core.utils.RegexPool; +import com.liuhung.engine.captcha.graphic.definition.AbstractBaseGraphicRenderer; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.provider.RandomProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.awt.image.BufferedImage; + +/** + *

Description: 算数类型一般验证码

+ * + * @author : liuh + * @date : 2021/12/20 22:55 + */ +@Component +public class ArithmeticCaptchaRenderer extends AbstractBaseGraphicRenderer { + + private static final Logger log = LoggerFactory.getLogger(ArithmeticCaptchaRenderer.class); + + private int complexity = 2; + /** + * 计算结果 + */ + private String computedResult; + + @Override + public String getCategory() { + return CaptchaCategory.ARITHMETIC.getConstant(); + } + + @Override + protected String getBase64ImagePrefix() { + return BASE64_PNG_IMAGE_PREFIX; + } + + @Override + protected String[] getDrawCharacters() { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < complexity; i++) { + builder.append(RandomProvider.randomInt(10)); + if (i < complexity - 1) { + int type = RandomProvider.randomInt(1, 4); + if (type == 1) { + builder.append("+"); + } else if (type == 2) { + builder.append("-"); + } else if (type == 3) { + builder.append("x"); + } + } + } + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("javascript"); + + try { + computedResult = String.valueOf(engine.eval(builder.toString().replaceAll("x", "*"))); + } catch (ScriptException e) { + log.error("[Quafer] |- Arithmetic png captcha eval expression error!", e); + } + + builder.append("=?"); + + String result = builder.toString(); + return result.split(RegexPool.ALL_CHARACTERS); + } + + @Override + public Metadata draw() { + String[] drawContent = getDrawCharacters(); + BufferedImage bufferedImage = createArithmeticBufferedImage(drawContent); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(toBase64(bufferedImage)); + metadata.setCharacters(this.computedResult); + return metadata; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.complexity = this.getCaptchaProperties().getGraphics().getComplexity(); + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ChineseCaptchaRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ChineseCaptchaRenderer.java new file mode 100644 index 0000000..58fddff --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ChineseCaptchaRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.renderer; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.graphic.definition.AbstractPngGraphicRenderer; +import org.springframework.stereotype.Component; + +import java.awt.*; + +/** + *

Description: 中文类型验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/20 22:55 + */ +@Component +public class ChineseCaptchaRenderer extends AbstractPngGraphicRenderer { + + @Override + public String getCategory() { + return CaptchaCategory.CHINESE.getConstant(); + } + + @Override + protected String[] getDrawCharacters() { + return this.getWordCharacters(); + } + + @Override + protected Font getFont() { + return this.getResourceProvider().getChineseFont(); + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ChineseGifCaptchaRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ChineseGifCaptchaRenderer.java new file mode 100644 index 0000000..7daed1f --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/ChineseGifCaptchaRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.renderer; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.graphic.definition.AbstractGifGraphicRenderer; +import org.springframework.stereotype.Component; + +import java.awt.*; + +/** + *

Description: 中文Gif类型验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/20 22:55 + */ +@Component +public class ChineseGifCaptchaRenderer extends AbstractGifGraphicRenderer { + + @Override + public String getCategory() { + return CaptchaCategory.CHINESE_GIF.getConstant(); + } + + @Override + protected String[] getDrawCharacters() { + return this.getWordCharacters(); + } + + @Override + protected Font getFont() { + return this.getResourceProvider().getChineseFont(); + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/SpecCaptchaRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/SpecCaptchaRenderer.java new file mode 100644 index 0000000..5b1bd10 --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/SpecCaptchaRenderer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.renderer; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.graphic.definition.AbstractPngGraphicRenderer; +import org.springframework.stereotype.Component; + +/** + *

Description: 类型验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/20 20:39 + */ +@Component +public class SpecCaptchaRenderer extends AbstractPngGraphicRenderer { + + @Override + public String getCategory() { + return CaptchaCategory.SPEC.getConstant(); + } + + @Override + protected String[] getDrawCharacters() { + return this.getCharCharacters(); + } +} diff --git a/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/SpecGifCaptchaRenderer.java b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/SpecGifCaptchaRenderer.java new file mode 100644 index 0000000..68b2774 --- /dev/null +++ b/engine-captcha/captcha-sdk-graphic/src/main/java/com/liuhung/engine/captcha/graphic/renderer/SpecGifCaptchaRenderer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.graphic.renderer; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.graphic.definition.AbstractGifGraphicRenderer; +import org.springframework.stereotype.Component; + +/** + *

Description: Gif 类型验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/20 22:54 + */ +@Component +public class SpecGifCaptchaRenderer extends AbstractGifGraphicRenderer { + + @Override + public String getCategory() { + return CaptchaCategory.SPEC_GIF.getConstant(); + } + + @Override + protected String[] getDrawCharacters() { + return this.getCharCharacters(); + } +} diff --git a/engine-captcha/captcha-sdk-hutool/README.md b/engine-captcha/captcha-sdk-hutool/README.md new file mode 100644 index 0000000..5de4a53 --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/README.md @@ -0,0 +1,6 @@ +## 图形验证码组件模块 + +**包含以下内容:** +1. 圆圈干扰验证码 +2. 扭曲干扰验证码 +3. 线段干扰验证码 diff --git a/engine-captcha/captcha-sdk-hutool/pom.xml b/engine-captcha/captcha-sdk-hutool/pom.xml new file mode 100644 index 0000000..432fbe1 --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-captcha + com.liuhung.engine + 2.7.8.0 + + + captcha-sdk-hutool + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + captcha-core + + + + \ No newline at end of file diff --git a/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/configuration/HutoolCaptchaConfiguration.java b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/configuration/HutoolCaptchaConfiguration.java new file mode 100644 index 0000000..a582057 --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/configuration/HutoolCaptchaConfiguration.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.hutool.configuration; + +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import com.liuhung.engine.captcha.core.provider.ResourceProvider; +import com.liuhung.engine.captcha.hutool.renderer.CircleCaptchaRenderer; +import com.liuhung.engine.captcha.hutool.renderer.GifCaptchaRenderer; +import com.liuhung.engine.captcha.hutool.renderer.LineCaptchaRenderer; +import com.liuhung.engine.captcha.hutool.renderer.ShearCaptchaRenderer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Hutool 验证码配置

+ * + * @author : liuh + * @date : 2022/1/18 20:57 + */ +@Configuration(proxyBeanMethods = false) +public class HutoolCaptchaConfiguration { + + private static final Logger log = LoggerFactory.getLogger(HutoolCaptchaConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Captcha Hutool] Auto Configure."); + } + + @Bean(CaptchaCategory.HUTOOL_LINE_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public LineCaptchaRenderer lineCaptchaRenderer(ResourceProvider resourceProvider) { + LineCaptchaRenderer lineCaptchaRenderer = new LineCaptchaRenderer(); + lineCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Hutool Line Captcha Renderer] Auto Configure."); + return lineCaptchaRenderer; + } + + @Bean(CaptchaCategory.HUTOOL_CIRCLE_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public CircleCaptchaRenderer circleCaptchaRenderer(ResourceProvider resourceProvider) { + CircleCaptchaRenderer circleCaptchaRenderer = new CircleCaptchaRenderer(); + circleCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Hutool Circle Captcha Renderer] Auto Configure."); + return circleCaptchaRenderer; + } + + @Bean(CaptchaCategory.HUTOOL_SHEAR_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public ShearCaptchaRenderer shearCaptchaRenderer(ResourceProvider resourceProvider) { + ShearCaptchaRenderer shearCaptchaRenderer = new ShearCaptchaRenderer(); + shearCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Hutool Shear Captcha Renderer] Auto Configure."); + return shearCaptchaRenderer; + } + + @Bean(CaptchaCategory.HUTOOL_GIF_CAPTCHA) + @ConditionalOnBean(ResourceProvider.class) + public GifCaptchaRenderer gifCaptchaRenderer(ResourceProvider resourceProvider) { + GifCaptchaRenderer gifCaptchaRenderer = new GifCaptchaRenderer(); + gifCaptchaRenderer.setResourceProvider(resourceProvider); + log.trace("[Quafer] |- Bean [Hutool Gif Captcha Renderer] Auto Configure."); + return gifCaptchaRenderer; + } +} diff --git a/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/CircleCaptchaRenderer.java b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/CircleCaptchaRenderer.java new file mode 100644 index 0000000..2a4712e --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/CircleCaptchaRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.hutool.renderer; + +import com.liuhung.engine.captcha.core.definition.AbstractGraphicRenderer; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.CircleCaptcha; + +/** + *

Description: Hutool圆圈干扰验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/23 23:09 + */ +public class CircleCaptchaRenderer extends AbstractGraphicRenderer { + + @Override + public Metadata draw() { + CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(this.getWidth(), this.getHeight(), this.getLength(), 20); + circleCaptcha.setFont(this.getFont()); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(circleCaptcha.getImageBase64Data()); + metadata.setCharacters(circleCaptcha.getCode()); + return metadata; + } + + @Override + public String getCategory() { + return CaptchaCategory.HUTOOL_CIRCLE.getConstant(); + } +} diff --git a/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/GifCaptchaRenderer.java b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/GifCaptchaRenderer.java new file mode 100644 index 0000000..1beb941 --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/GifCaptchaRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.hutool.renderer; + +import com.liuhung.engine.captcha.core.definition.AbstractGraphicRenderer; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.GifCaptcha; + +/** + *

Description: Hutool GIF验证码

+ * + * @author : liuh + * @date : 2021/12/23 23:08 + */ +public class GifCaptchaRenderer extends AbstractGraphicRenderer { + + @Override + public Metadata draw() { + GifCaptcha gifCaptcha = CaptchaUtil.createGifCaptcha(this.getWidth(), this.getHeight(), this.getLength()); + gifCaptcha.setFont(this.getFont()); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(gifCaptcha.getImageBase64Data()); + metadata.setCharacters(gifCaptcha.getCode()); + return metadata; + } + + @Override + public String getCategory() { + return CaptchaCategory.HUTOOL_GIF.getConstant(); + } +} diff --git a/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/LineCaptchaRenderer.java b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/LineCaptchaRenderer.java new file mode 100644 index 0000000..2845f75 --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/LineCaptchaRenderer.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.hutool.renderer; + +import com.liuhung.engine.captcha.core.definition.AbstractGraphicRenderer; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; +import org.springframework.stereotype.Component; + +/** + *

Description: Hutool Line Captcha

+ * + * @author : liuh + * @date : 2021/12/23 22:44 + */ +@Component +public class LineCaptchaRenderer extends AbstractGraphicRenderer { + + @Override + public Metadata draw() { + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(this.getWidth(), this.getHeight(), this.getLength(), 150); + lineCaptcha.setFont(this.getFont()); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(lineCaptcha.getImageBase64Data()); + metadata.setCharacters(lineCaptcha.getCode()); + return metadata; + } + + @Override + public String getCategory() { + return CaptchaCategory.HUTOOL_LINE.getConstant(); + } +} diff --git a/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/ShearCaptchaRenderer.java b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/ShearCaptchaRenderer.java new file mode 100644 index 0000000..9040baf --- /dev/null +++ b/engine-captcha/captcha-sdk-hutool/src/main/java/com/liuhung/engine/captcha/hutool/renderer/ShearCaptchaRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.hutool.renderer; + +import com.liuhung.engine.captcha.core.definition.AbstractGraphicRenderer; +import com.liuhung.engine.captcha.core.definition.domain.Metadata; +import com.liuhung.engine.captcha.core.definition.enums.CaptchaCategory; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.ShearCaptcha; + +/** + *

Description: Hutool扭曲干扰验证码绘制器

+ * + * @author : liuh + * @date : 2021/12/23 23:08 + */ +public class ShearCaptchaRenderer extends AbstractGraphicRenderer { + + @Override + public Metadata draw() { + ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(this.getWidth(), this.getHeight(), this.getLength(), 4); + shearCaptcha.setFont(this.getFont()); + + Metadata metadata = new Metadata(); + metadata.setGraphicImageBase64(shearCaptcha.getImageBase64Data()); + metadata.setCharacters(shearCaptcha.getCode()); + return metadata; + } + + @Override + public String getCategory() { + return CaptchaCategory.HUTOOL_SHEAR.getConstant(); + } +} diff --git a/engine-captcha/captcha-spring-boot-starter/README.md b/engine-captcha/captcha-spring-boot-starter/README.md new file mode 100644 index 0000000..30642a6 --- /dev/null +++ b/engine-captcha/captcha-spring-boot-starter/README.md @@ -0,0 +1,3 @@ +## 缓存模块统一 Starter 模块 + +**包含以下内容:** diff --git a/engine-captcha/captcha-spring-boot-starter/pom.xml b/engine-captcha/captcha-spring-boot-starter/pom.xml new file mode 100644 index 0000000..2891830 --- /dev/null +++ b/engine-captcha/captcha-spring-boot-starter/pom.xml @@ -0,0 +1,59 @@ + + + + + 4.0.0 + + + engine-captcha + com.liuhung.engine + 2.7.8.0 + + + captcha-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + captcha-sdk-behavior + + + com.liuhung.engine + captcha-sdk-graphic + + + com.liuhung.engine + captcha-sdk-hutool + + + + \ No newline at end of file diff --git a/engine-captcha/captcha-spring-boot-starter/src/main/java/com/liuhung/engine/captcha/autoconfigure/AutoConfiguration.java b/engine-captcha/captcha-spring-boot-starter/src/main/java/com/liuhung/engine/captcha/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..ee676a2 --- /dev/null +++ b/engine-captcha/captcha-spring-boot-starter/src/main/java/com/liuhung/engine/captcha/autoconfigure/AutoConfiguration.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.captcha.autoconfigure; + +import com.liuhung.engine.captcha.core.processor.CaptchaRendererFactory; +import com.liuhung.engine.captcha.core.properties.CaptchaProperties; +import com.liuhung.engine.captcha.core.provider.ResourceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: 验证码自动注入

+ * + * @author : liuh + * @date : 2022/1/18 21:12 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(CaptchaProperties.class) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Captcha Starter] Auto Configure."); + } + + @Bean + @ConditionalOnMissingBean + public ResourceProvider resourceProvider(CaptchaProperties captchaProperties) { + ResourceProvider resourceProvider = new ResourceProvider(captchaProperties); + log.trace("[Quafer] |- Bean [Resource Provider] Auto Configure."); + return resourceProvider; + } + + @Bean + @ConditionalOnMissingBean + public CaptchaRendererFactory captchaRendererFactory() { + CaptchaRendererFactory captchaRendererFactory = new CaptchaRendererFactory(); + log.trace("[Quafer] |- Bean [Captcha Renderer Factory] Auto Configure."); + return captchaRendererFactory; + } +} diff --git a/engine-captcha/captcha-spring-boot-starter/src/main/resources/META-INF/spring.factories b/engine-captcha/captcha-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..344351b --- /dev/null +++ b/engine-captcha/captcha-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.liuhung.engine.captcha.autoconfigure.AutoConfiguration,\ + com.liuhung.engine.captcha.behavior.configuration.BehaviorCaptchaConfiguration,\ + com.liuhung.engine.captcha.graphic.configuration.GraphicCaptchaConfiguration,\ + com.liuhung.engine.captcha.hutool.configuration.HutoolCaptchaConfiguration \ No newline at end of file diff --git a/engine-captcha/pom.xml b/engine-captcha/pom.xml new file mode 100644 index 0000000..9f11df3 --- /dev/null +++ b/engine-captcha/pom.xml @@ -0,0 +1,52 @@ + + + + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + 4.0.0 + + engine-captcha + pom + + captcha-core + captcha-sdk-hutool + captcha-sdk-behavior + captcha-sdk-graphic + captcha-spring-boot-starter + + + + 8 + 8 + + + \ No newline at end of file diff --git a/engine-data/data-core/pom.xml b/engine-data/data-core/pom.xml new file mode 100644 index 0000000..30dd8a4 --- /dev/null +++ b/engine-data/data-core/pom.xml @@ -0,0 +1,68 @@ + + + + + 4.0.0 + + + engine-data + com.liuhung.engine + 2.7.8.0 + + + data-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + assistant-core + + + + org.springframework.data + spring-data-jpa + + + org.hibernate + hibernate-core + + + + com.mysql + mysql-connector-j + + + org.postgresql + postgresql + + + \ No newline at end of file diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/constants/DataConstants.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/constants/DataConstants.java new file mode 100644 index 0000000..2391b84 --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/constants/DataConstants.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: 数据常量

+ * + * @author : liuh + * @date : 2022/1/19 18:10 + */ +public interface DataConstants extends BaseConstants { + + String PROPERTY_PREFIX_MULTI_TENANCY = PROPERTY_PREFIX_HERODOTUS + ".multi-tenancy"; + + String ITEM_MULTI_TENANCY_ENABLED = PROPERTY_PREFIX_MULTI_TENANCY + PROPERTY_ENABLED; + String AREA_PREFIX = "data:core:"; + String REGION_MULTI_TENANCY = AREA_PREFIX + "tenancy"; + + String ITEM_SPRING_SQL_INIT_PLATFORM = "spring.sql.init.platform"; + + String ANNOTATION_SQL_INIT_PLATFORM = ANNOTATION_PREFIX + ITEM_SPRING_SQL_INIT_PLATFORM + ANNOTATION_SUFFIX; +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/BaseEntity.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/BaseEntity.java new file mode 100644 index 0000000..597112f --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/BaseEntity.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.entity; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import java.util.Date; + +/** + *

Description : 实体通用属性

+ * + * @author : liuh + * @date : 2020/4/29 16:09 + */ +@MappedSuperclass +public abstract class BaseEntity extends AbstractEntity { + + @Schema(title = "数据创建时间") + @Column(name = "create_time", updatable = false) + @CreatedDate + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime = new Date(); + + @Schema(title = "数据更新时间") + @Column(name = "update_time") + @LastModifiedDate + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime = new Date(); + + @Schema(title = "排序值") + @Column(name = "ranking") + private Integer ranking = 0; + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public Integer getRanking() { + return ranking; + } + + public void setRanking(Integer ranking) { + this.ranking = ranking; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("createTime", createTime) + .add("updateTime", updateTime) + .add("ranking", ranking) + .toString(); + } +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/BaseSysEntity.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/BaseSysEntity.java new file mode 100644 index 0000000..f455e75 --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/BaseSysEntity.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.entity; + +import com.liuhung.engine.data.core.enums.DataItemStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; + +/** + *

Description: 框架基础权限实体通用基础类

+ * + * @author : liuh + * @date : 2019/11/25 15:05 + */ +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseSysEntity extends BaseEntity { + + @Schema(title = "数据状态") + @Column(name = "status") + @Enumerated(EnumType.ORDINAL) + private DataItemStatus status = DataItemStatus.ENABLE; + + @Schema(title = "是否为保留数据", description = "True 为不能删,False为可以删除") + @Column(name = "is_reserved") + private Boolean reserved = Boolean.FALSE; + + @Schema(title = "版本号") + @Column(name = "reversion") + private Integer reversion = 0; + + /** + * 角色描述,UI界面显示使用 + */ + @Schema(title = "备注") + @Column(name = "description", length = 512) + private String description; + + public DataItemStatus getStatus() { + return status; + } + + public void setStatus(DataItemStatus status) { + this.status = status; + } + + public Boolean getReserved() { + return reserved; + } + + public void setReserved(Boolean reserved) { + this.reserved = reserved; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getReversion() { + return reversion; + } + + public void setReversion(Integer reversion) { + this.reversion = reversion; + } +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/DefaultSysEntity.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/DefaultSysEntity.java new file mode 100644 index 0000000..ae51bb1 --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/entity/DefaultSysEntity.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.entity; + +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + *

Description : BaseSystemEntity

+ * + * @author : liuh + * @date : 2020/4/29 17:43 + */ +@MappedSuperclass +public abstract class DefaultSysEntity extends BaseSysEntity { + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "id", length = 64) + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/enums/DataItemStatus.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/enums/DataItemStatus.java new file mode 100644 index 0000000..9e8015b --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/enums/DataItemStatus.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author liuh + */ +@Schema(title = "数据状态") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum DataItemStatus implements BaseUiEnum { + + /** + * 数据条目已启用 + */ + ENABLE(0, "启用"), + /** + * 数据条目被启用 + */ + FORBIDDEN(1, "禁用"), + /** + * 数据条目被锁定 + */ + LOCKING(2, "锁定"), + /** + * 数据条目已过期 + */ + EXPIRED(3, "过期"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(title = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (DataItemStatus dataItemStatus : DataItemStatus.values()) { + INDEX_MAP.put(dataItemStatus.getValue(), dataItemStatus); + JSON_STRUCTURE.add(dataItemStatus.getValue(), + ImmutableMap.builder() + .put("value", dataItemStatus.getValue()) + .put("key", dataItemStatus.name()) + .put("text", dataItemStatus.getDescription()) + .build()); + } + } + + DataItemStatus(Integer value, String description) { + this.value = value; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDescription() { + return this.description; + } + + public static DataItemStatus get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/repository/BaseRepository.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/repository/BaseRepository.java new file mode 100644 index 0000000..440bd70 --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/repository/BaseRepository.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.repository; + +import com.liuhung.engine.assistant.core.definition.domain.Entity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.QueryHint; +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +/** + *

Description : 基础Repository

+ * + * @author : liuh + * @date : 2020/4/29 15:21 + */ +@NoRepositoryBean +public interface BaseRepository extends JpaRepository, JpaSpecificationExecutor { + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + List findAll(); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + List findAll(Sort sort); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + Optional findOne(Specification specification); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + List findAll(Specification specification); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + Page findAll(Specification specification, Pageable pageable); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + List findAll(Specification specification, Sort sort); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + long count(Specification specification); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + Page findAll(Pageable pageable); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + Optional findById(ID id); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + @Override + long count(); + + @Transactional + @Override + void deleteById(ID id); +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/BaseLayeredService.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/BaseLayeredService.java new file mode 100644 index 0000000..8618a2b --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/BaseLayeredService.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.service; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.definition.domain.Entity; + +import java.io.Serializable; + +/** + *

Description: 基于自研Hibernate多层二级缓存的基础服务

+ * + * @author : liuh + * @date : 2021/7/14 14:32 + */ +public abstract class BaseLayeredService< E extends Entity, ID extends Serializable> implements WriteableService { + + protected String like(String property) { + return SymbolConstants.PERCENT + property + SymbolConstants.PERCENT; + } +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/ReadableService.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/ReadableService.java new file mode 100644 index 0000000..ac8d01b --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/ReadableService.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.service; + +import com.liuhung.engine.assistant.core.definition.domain.Entity; +import com.liuhung.engine.data.core.repository.BaseRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; + +import java.io.Serializable; +import java.util.List; + +/** + *

Description : 只读Service,可以提供基于视图实体的操作

+ * + * @author : liuh + * @date : 2020/2/15 11:56 + */ +public interface ReadableService { + + /** + * 获取Repository + * + * @return {@link BaseRepository} + */ + BaseRepository getRepository(); + + /** + * 根据ID查询数据 + * + * @param id 数据ID + * @return 与ID对应的数据,如果不存在则返回空 + */ + default E findById(ID id) { + return getRepository().findById(id).orElse(null); + } + + /** + * 数据是否存在 + * + * @param id 数据ID + * @return true 存在,false 不存在 + */ + default boolean existsById(ID id) { + return getRepository().existsById(id); + } + + /** + * 查询数量 + * + * @return 数据数量 + */ + default long count() { + return getRepository().count(); + } + + /** + * 查询数量 + * + * @param specification {@link Specification} + * @return 数据数量 + */ + default long count(Specification specification) { + return getRepository().count(specification); + } + + /** + * 查询全部数据 + * + * @return 全部数据列表 + */ + default List findAll() { + return getRepository().findAll(); + } + + /** + * 查询全部数据 + * + * @param sort {@link Sort} + * @return 已排序的全部数据列表 + */ + default List findAll(Sort sort) { + return getRepository().findAll(sort); + } + + /** + * 查询全部数据 + * + * @param specification {@link Specification} + * @return 全部数据列表 + */ + default List findAll(Specification specification) { + return getRepository().findAll(specification); + } + + /** + * 查询全部数据 + * + * @param specification {@link Specification} + * @param sort {@link Sort} + * @return 全部数据列表 + */ + default List findAll(Specification specification, Sort sort) { + return getRepository().findAll(specification, sort); + } + + /** + * 查询分页数据 + * + * @param pageable {@link Pageable} + * @return 分页数据 + */ + default Page findByPage(Pageable pageable) { + return getRepository().findAll(pageable); + } + + /** + * 查询分页数据 + * + * @param pageNumber 当前页码, 起始页码 0 + * @param pageSize 每页显示的数据条数 + * @return 分页数据 + */ + default Page findByPage(int pageNumber, int pageSize) { + return findByPage(PageRequest.of(pageNumber, pageSize)); + } + + /** + * 查询分页数据 + * + * @param pageNumber 当前页码, 起始页码 0 + * @param pageSize 每页显示的数据条数 + * @param sort 排序 + * @return 分页数据 + */ + default Page findByPage(int pageNumber, int pageSize, Sort sort) { + return findByPage(PageRequest.of(pageNumber, pageSize, sort)); + } + + /** + * 查询分页数据 + * + * @param pageNumber 当前页码, 起始页码 0 + * @param pageSize 每页显示的数据条数 + * @param direction {@link org.springframework.data.domain.Sort.Direction} + * @param properties 排序的属性名称 + * @return 分页数据 + */ + default Page findByPage(int pageNumber, int pageSize, Sort.Direction direction, String... properties) { + return findByPage(PageRequest.of(pageNumber, pageSize, direction, properties)); + } + + /** + * 查询分页数据 + * + * @param specification {@link Specification} + * @param pageable {@link Pageable} + * @return 分页数据 + */ + default Page findByPage(Specification specification, Pageable pageable) { + return getRepository().findAll(specification, pageable); + } + + /** + * 查询分页数据 + * + * @param specification {@link Specification} + * @param pageNumber 当前页码, 起始页码 0 + * @param pageSize 每页显示的数据条数 + * @return 分页数据 + */ + default Page findByPage(Specification specification, int pageNumber, int pageSize) { + return getRepository().findAll(specification, PageRequest.of(pageNumber, pageSize)); + } + + /** + * 查询分页数据 + * + * @param pageNumber 当前页码, 起始页码 0 + * @param pageSize 每页显示的数据条数 + * @param direction {@link org.springframework.data.domain.Sort.Direction} + * @return 分页数据 + */ + default Page findByPage(int pageNumber, int pageSize, Sort.Direction direction) { + return findByPage(PageRequest.of(pageNumber, pageSize, direction)); + } +} diff --git a/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/WriteableService.java b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/WriteableService.java new file mode 100644 index 0000000..f4350fd --- /dev/null +++ b/engine-data/data-core/src/main/java/com/liuhung/engine/data/core/service/WriteableService.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.core.service; + +import com.liuhung.engine.assistant.core.definition.domain.Entity; + +import java.io.Serializable; +import java.util.List; + +/** + *

Description: 可读、可写的Service基础接口

+ * + * @author : liuh + * @date : 2021/7/7 16:47 + */ +public interface WriteableService extends ReadableService { + + /** + * 删除数据 + * + * @param entity 数据对应实体 + */ + default void delete(E entity) { + getRepository().delete(entity); + } + + /** + * 批量全部删除 + */ + default void deleteAllInBatch() { + getRepository().deleteAllInBatch(); + } + + /** + * 删除指定多个数据 + * + * @param entities 数据对应实体集合 + */ + default void deleteAll(Iterable entities) { + getRepository().deleteAll(entities); + } + + /** + * 删除全部数据 + */ + default void deleteAll() { + getRepository().deleteAll(); + } + + /** + * 根据ID删除数据 + * + * @param id 数据对应ID + */ + default void deleteById(ID id) { + getRepository().deleteById(id); + } + + /** + * 保存数据 + * + * @param domain 数据对应实体 + * @return 已保存数据 + */ + default E save(E domain) { + return getRepository().save(domain); + } + + /** + * 批量保存 + * + * @param entities 实体集合 + * @return 已经保存的实体集合 + */ + default List saveAll(Iterable entities) { + return getRepository().saveAll(entities); + } + + /** + * 保存并且刷新 + * + * @param entity 实体 + * @return 保存后实体 + */ + default E saveAndFlush(E entity) { + return getRepository().saveAndFlush(entity); + } + + /** + * 保存或者更新 + * + * @param entity 实体 + * @return 保存后实体 + */ + default E saveOrUpdate(E entity) { + return saveAndFlush(entity); + } + + /** + * 刷新实体状态 + */ + default void flush() { + getRepository().flush(); + } +} diff --git a/engine-data/data-sdk-jpa/pom.xml b/engine-data/data-sdk-jpa/pom.xml new file mode 100644 index 0000000..d598b2d --- /dev/null +++ b/engine-data/data-sdk-jpa/pom.xml @@ -0,0 +1,57 @@ + + + + + 4.0.0 + + + engine-data + com.liuhung.engine + 2.7.8.0 + + + data-sdk-jpa + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + data-core + + + + com.zaxxer + HikariCP + + + + + \ No newline at end of file diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/annotation/ConditionalOnMultiTenancyEnabled.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/annotation/ConditionalOnMultiTenancyEnabled.java new file mode 100644 index 0000000..d1db6d2 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/annotation/ConditionalOnMultiTenancyEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.annotation; + +import com.liuhung.engine.data.jpa.condition.MultiTenancyEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: Influxdb条件注解

+ * + * @author : liuh + * @date : 2021/11/18 10:12 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(MultiTenancyEnabledCondition.class) +public @interface ConditionalOnMultiTenancyEnabled { +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/annotation/EnableQuaferJpa.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/annotation/EnableQuaferJpa.java new file mode 100644 index 0000000..5262f86 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/annotation/EnableQuaferJpa.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.annotation; + +import com.liuhung.engine.data.jpa.configuration.DataJpaConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 手动开启阿里云短信

+ * + * @author : liuh + * @date : 2022/1/17 21:22 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(DataJpaConfiguration.class) +public @interface EnableQuaferJpa { +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/condition/MultiTenancyEnabledCondition.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/condition/MultiTenancyEnabledCondition.java new file mode 100644 index 0000000..8100b6a --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/condition/MultiTenancyEnabledCondition.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.condition; + +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import com.liuhung.engine.data.core.constants.DataConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: Couchdb 注入开启条件

+ * + * @author : liuh + * @date : 2021/11/17 18:06 + */ +public class MultiTenancyEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(MultiTenancyEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + boolean result = PropertyResolver.getBoolean(conditionContext, DataConstants.ITEM_MULTI_TENANCY_ENABLED); + log.debug("[Quafer] |- Condition [Multi Tenancy Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/configuration/DataJpaConfiguration.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/configuration/DataJpaConfiguration.java new file mode 100644 index 0000000..a7f7428 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/configuration/DataJpaConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: Data JPA 模块可配置

+ * + * @author : liuh + * @date : 2022/9/8 18:12 + */ +@Configuration(proxyBeanMethods = false) +@Import({ + MultiTenancyConfiguration.class +}) +public class DataJpaConfiguration { + + private static final Logger log = LoggerFactory.getLogger(DataJpaConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- Module [Data JPA] Auto Configure."); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/configuration/MultiTenancyConfiguration.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/configuration/MultiTenancyConfiguration.java new file mode 100644 index 0000000..26e7742 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/configuration/MultiTenancyConfiguration.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.configuration; + +import com.liuhung.engine.data.jpa.annotation.ConditionalOnMultiTenancyEnabled; +import com.liuhung.engine.data.jpa.properties.MultiTenancyProperties; +import com.liuhung.engine.data.jpa.tenancy.MultiTenancyConnectionProvider; +import com.liuhung.engine.data.jpa.tenancy.MultiTenancyDataSourceLookup; +import com.liuhung.engine.data.jpa.tenancy.MultiTenancyIdentifierResolver; +import org.hibernate.cfg.Environment; +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; +import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.lookup.DataSourceLookup; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.util.Map; +import java.util.function.Supplier; + +/** + *

Description: Data JPA 模块 多租户配置

+ * + * @author : liuh + * @date : 2022/9/8 22:15 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnMultiTenancyEnabled +@EnableConfigurationProperties(MultiTenancyProperties.class) +public class MultiTenancyConfiguration { + + private static final Logger log = LoggerFactory.getLogger(DataJpaConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Data Multi Tenancy] Auto Configure."); + } + + @Bean + public JpaVendorAdapter jpaVendorAdapter() { + return new HibernateJpaVendorAdapter(); + } + + @Bean + public DataSourceLookup dataSourceLookup(DataSource dataSource, MultiTenancyProperties multiTenancyProperties) { + MultiTenancyDataSourceLookup multiTenancyDataSourceLookup = new MultiTenancyDataSourceLookup(dataSource, multiTenancyProperties); + log.debug("[Quafer] |- Bean [Multi Tenancy DataSource Lookup] Auto Configure."); + return multiTenancyDataSourceLookup; + } + + @Bean + public MultiTenantConnectionProvider multiTenantConnectionProvider(DataSource dataSource, DataSourceLookup dataSourceLookup) { + MultiTenancyConnectionProvider multiTenancyConnectionProvider = new MultiTenancyConnectionProvider(dataSource, dataSourceLookup); + log.debug("[Quafer] |- Bean [Multi Tenancy Connection Provider] Auto Configure."); + return multiTenancyConnectionProvider; + } + + @Bean + public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() { + MultiTenancyIdentifierResolver multiTenancyIdentifierResolver = new MultiTenancyIdentifierResolver(); + log.debug("[Quafer] |- Bean [Multi Tenancy Connection Provider] Auto Configure."); + return multiTenancyIdentifierResolver; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, HibernateProperties hibernateProperties, JpaVendorAdapter jpaVendorAdapter, JpaProperties jpaProperties, MultiTenancyProperties multiTenancyProperties, MultiTenantConnectionProvider multiTenantConnectionProvider, CurrentTenantIdentifierResolver currentTenantIdentifierResolver) { + + Supplier defaultDdlMode = hibernateProperties::getDdlAuto; + Map properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(defaultDdlMode)); + + properties.put(Environment.MULTI_TENANT, multiTenancyProperties.getTenancyStrategy()); + properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider); + properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver); + LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); + emf.setDataSource(dataSource); + //此处不能省略,哪怕你使用了 @EntityScan,实际上 @EntityScan 会失效 + emf.setPackagesToScan(multiTenancyProperties.getPackageToScan()); + emf.setJpaVendorAdapter(jpaVendorAdapter); + emf.setJpaPropertyMap(properties); + return emf; + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/AbstractArrayType.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/AbstractArrayType.java new file mode 100644 index 0000000..8cc6975 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/AbstractArrayType.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import java.io.Serializable; +import java.sql.*; + +/** + *

Description: 抽象数组类型

+ * + * @author : liuh + * @date : 2019/11/15 10:34 + */ +@SuppressWarnings("unchecked") +public abstract class AbstractArrayType extends AbstractUserType { + + protected static final int[] SQL_TYPES = {Types.ARRAY}; + + @Override + public final int[] sqlTypes() { + return SQL_TYPES; + } + + protected abstract Object safeReturnedObject(); + + @Override + public Object nullSafeGet(ResultSet resultSet, String[] strings, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException, SQLException { + Array array = resultSet.getArray(strings[0]); + T[] typeArray = (T[]) array.getArray(); + if (typeArray == null) { + return safeReturnedObject(); + } + + return typeArray; + } + + protected abstract String dbRealTypeName(); + + @Override + public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SharedSessionContractImplementor sharedSessionContractImplementor) throws HibernateException, SQLException { + Connection connection = preparedStatement.getConnection(); + T[] typeArray = (T[]) value; + Array array = connection.createArrayOf(dbRealTypeName(), typeArray); + if (null != array) { + preparedStatement.setArray(index, array); + } else { + preparedStatement.setNull(index, SQL_TYPES[0]); + } + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + return value == null ? null : ((T[]) value).clone(); + } + +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/AbstractUserType.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/AbstractUserType.java new file mode 100644 index 0000000..2f606ec --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/AbstractUserType.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate; + +import org.hibernate.HibernateException; +import org.hibernate.usertype.UserType; + +import java.io.Serializable; +import java.util.Objects; + +/** + *

Description: 抽象用户定义类型

+ * + * @author : liuh + * @date : 2019/11/15 10:32 + */ +public abstract class AbstractUserType implements UserType { + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return Objects.equals(x, y); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x == null ? 0 : x.hashCode(); + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public Serializable disassemble(Object value) throws HibernateException { + return (Serializable) this.deepCopy(value); + } + + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + return this.deepCopy(cached); + } + + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return this.deepCopy(original); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/QuaferPhysicalNamingStrategy.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/QuaferPhysicalNamingStrategy.java new file mode 100644 index 0000000..044446c --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/QuaferPhysicalNamingStrategy.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; + +/** + *

Description: 使用hbm2ddl自动创建表时,默认将@Colume中的信息转换为小写,小写的字段名称与其它的字段标准不同(驼峰式,单词首字母大写) 复写原始类,生成符合标准的字段名称。

+ * + * @author : liuh + * @date : 2019/11/15 10:34 + */ +public class QuaferPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl { + + @Override + public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) { + + // Hibernate 默认使用 Identifier.getCanonicalName()的值最为最终的值,text是原始值。如果quoted为true则使用text,否则就进行小写转换。所以此处quoted设置为true。参见具体方法。 + return new Identifier(name.getText(), true); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/cache/spi/QuaferDomainDataStorageAccess.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/cache/spi/QuaferDomainDataStorageAccess.java new file mode 100644 index 0000000..e55b630 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/cache/spi/QuaferDomainDataStorageAccess.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.cache.spi; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.context.TenantContextHolder; +import cn.hutool.crypto.SecureUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.Cache; + +/** + *

Description: 自定义Hibernate二级缓存DomainDataStorageAccess

+ * + * @author : liuh + * @date : 2021/7/12 22:06 + */ +public class QuaferDomainDataStorageAccess implements DomainDataStorageAccess { + + private static final Logger log = LoggerFactory.getLogger(QuaferDomainDataStorageAccess.class); + + private Cache cache; + + public QuaferDomainDataStorageAccess() { + } + + public QuaferDomainDataStorageAccess(Cache cache) { + this.cache = cache; + } + + private String secure(Object key) { + String original = String.valueOf(key); + if (StringUtils.isNotBlank(original) && StringUtils.startsWith(original, "sql:")) { + String recent = SecureUtil.md5(original); + log.trace("[Quafer] |- SPI - Secure the sql type key [{}] to [{}]", original, recent); + return recent; + } + return original; + } + + private String getTenantId() { + String tenantId = TenantContextHolder.getTenantId(); + String result = StringUtils.isNotBlank(tenantId) ? tenantId : BaseConstants.DEFAULT_TENANT_ID; + log.trace("[Quafer] |- SPI - Tenant identifier for jpa second level cache is : [{}]", result); + return StringUtils.toRootLowerCase(result); + } + + private String wrapper(Object key) { + String original = secure(key); + String tenantId = getTenantId(); + + String result = tenantId + SymbolConstants.COLON + original; + log.trace("[Quafer] |- SPI - Current cache key is : [{}]", result); + return result; + } + + private Object get(Object key) { + Cache.ValueWrapper value = cache.get(key); + + if (ObjectUtils.isNotEmpty(value)) { + return value.get(); + } + return null; + } + + @Override + public boolean contains(Object key) { + String wrapperKey = wrapper(key); + Object value = this.get(wrapperKey); + log.trace("[Quafer] |- SPI - check is key : [{}] exist.", wrapperKey); + return ObjectUtils.isNotEmpty(value); + } + + @Override + public Object getFromCache(Object key, SharedSessionContractImplementor session) { + String wrapperKey = wrapper(key); + Object value = this.get(wrapperKey); + log.trace("[Quafer] |- SPI - get from cache key is : [{}], value is : [{}]", wrapperKey, value); + return value; + } + + @Override + public void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) { + String wrapperKey = wrapper(key); + log.trace("[Quafer] |- SPI - put into cache key is : [{}], value is : [{}]", wrapperKey, value); + cache.put(wrapperKey, value); + } + + @Override + public void removeFromCache(Object key, SharedSessionContractImplementor session) { + String wrapperKey = wrapper(key); + log.trace("[Quafer] |- SPI - remove from cache key is : [{}]", wrapperKey); + cache.evict(wrapperKey); + } + + @Override + public void evictData(Object key) { + String wrapperKey = wrapper(key); + log.trace("[Quafer] |- SPI - evict key : [{}] from cache.", wrapperKey); + cache.evict(wrapperKey); + } + + @Override + public void clearCache(SharedSessionContractImplementor session) { + this.evictData(); + } + + @Override + public void evictData() { + log.trace("[Quafer] |- SPI - clear all cache data."); + cache.clear(); + } + + @Override + public void release() { + log.trace("[Quafer] |- SPI - cache release."); + cache.invalidate(); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/cache/spi/QuaferRegionFactory.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/cache/spi/QuaferRegionFactory.java new file mode 100644 index 0000000..25364d7 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/cache/spi/QuaferRegionFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.cache.spi; + +import cn.hutool.extra.spring.SpringUtil; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; +import org.hibernate.cache.spi.support.RegionFactoryTemplate; +import org.hibernate.cache.spi.support.StorageAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.springframework.cache.CacheManager; + +import java.util.Map; + +/** + *

Description: 自定义Hibernate二级缓存RegionFactory

+ * + * @author : liuh + * @date : 2021/7/12 22:04 + */ +public class QuaferRegionFactory extends RegionFactoryTemplate { + + private CacheManager cacheManager; + + @Override + protected StorageAccess createQueryResultsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory) { + return new QuaferDomainDataStorageAccess(cacheManager.getCache(regionName)); + } + + @Override + protected StorageAccess createTimestampsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory) { + return new QuaferDomainDataStorageAccess(cacheManager.getCache(regionName)); + } + + @Override + protected DomainDataStorageAccess createDomainDataStorageAccess(DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) { + return new QuaferDomainDataStorageAccess(cacheManager.getCache(regionConfig.getRegionName())); + } + + @Override + protected void prepareForUse(SessionFactoryOptions settings, Map configValues) { + this.cacheManager = SpringUtil.getBean("quaferCacheManager"); + } + + @Override + protected void releaseFromUse() { + cacheManager = null; + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/JsonbArrayType.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/JsonbArrayType.java new file mode 100644 index 0000000..f6101b1 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/JsonbArrayType.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.postgresql; + +import org.hibernate.HibernateException; +import org.springframework.util.ObjectUtils; + +import java.io.Serializable; + +/** + *

Description: 自定义Jsonb数组类型

+ * + * @author : liuh + * @date : 2019/11/15 10:32 + */ +public class JsonbArrayType extends TextArrayType implements Serializable { + + @Override + protected String dbRealTypeName() { + return "JSONB"; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public int hashCode(Object x) throws HibernateException { + if (x == null) { + return 0; + } + + return x.hashCode(); + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return ObjectUtils.nullSafeEquals(x, y); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/JsonbType.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/JsonbType.java new file mode 100644 index 0000000..c53d4ee --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/JsonbType.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.postgresql; + +import com.liuhung.engine.data.jpa.hibernate.AbstractUserType; +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.SerializationException; +import org.springframework.util.ObjectUtils; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +/** + *

Description: 自定义JsonB类型

+ * + * @author : liuh + * @date : 2019/11/15 10:33 + */ +public class JsonbType extends AbstractUserType implements Serializable { + + @Override + public int[] sqlTypes() { + return new int[]{Types.JAVA_OBJECT}; + } + + @Override + public Class returnedClass() { + return String.class; + } + + @Override + public Object nullSafeGet(ResultSet resultSet, String[] strings, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException, SQLException { + if (resultSet.getString(strings[0]) == null) { + return null; + } + return resultSet.getString(strings[0]); + } + + @Override + public void nullSafeSet(PreparedStatement preparedStatement, Object o, int i, SharedSessionContractImplementor sharedSessionContractImplementor) throws HibernateException, SQLException { + if (o == null) { + preparedStatement.setNull(i, Types.OTHER); + } else { + preparedStatement.setObject(i, o, Types.OTHER); + } + } + + @Override + public Object deepCopy(Object o) throws HibernateException { + if (o == null) { + return null; + } + return o.toString(); + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public Serializable disassemble(Object o) throws HibernateException { + Object copy = deepCopy(o); + if (copy instanceof Serializable) { + return (Serializable) copy; + } + throw new SerializationException(String.format("Cannot serialize '%s', %s is not Serializable.", o, o.getClass()), null); + } + + @Override + public int hashCode(Object x) throws HibernateException { + if (x == null) { + return 0; + } + + return x.hashCode(); + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return ObjectUtils.nullSafeEquals(x, y); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/NumericArrayType.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/NumericArrayType.java new file mode 100644 index 0000000..bde831e --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/NumericArrayType.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.postgresql; + + +import com.liuhung.engine.data.jpa.hibernate.AbstractArrayType; +import org.hibernate.HibernateException; +import org.springframework.util.ObjectUtils; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + *

Description: 自定义数字数组类型

+ * + * @author : liuh + * @date : 2019/11/15 10:33 + */ +public class NumericArrayType extends AbstractArrayType implements Serializable { + + @Override + public Class returnedClass() { + return BigDecimal[].class; + } + + @Override + protected Object safeReturnedObject() { + return new BigDecimal[]{}; + } + + @Override + protected String dbRealTypeName() { + return "NUMERIC"; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public int hashCode(Object x) throws HibernateException { + if (x == null) { + return 0; + } + + return x.hashCode(); + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return ObjectUtils.nullSafeEquals(x, y); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/QuaferPostgreSQLDialect.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/QuaferPostgreSQLDialect.java new file mode 100644 index 0000000..077ccec --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/QuaferPostgreSQLDialect.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.postgresql; + +import org.hibernate.dialect.PostgreSQL10Dialect; +import org.hibernate.type.descriptor.sql.LongVarbinaryTypeDescriptor; +import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +import java.sql.Types; + +/** + *

Description: 自定义PostgreSQLDialect,增加JSONB等类型支持

+ * + * @author : liuh + * @date : 2019/11/15 10:31 + */ +public class QuaferPostgreSQLDialect extends PostgreSQL10Dialect { + + public QuaferPostgreSQLDialect() { + super(); + this.registerColumnType(Types.JAVA_OBJECT, "jsonb"); + this.registerColumnType(Types.ARRAY, "text[]"); + } + +// /** +// * 重载getAddForeignKeyConstraintString方法,阻止ddl-auto添加外键关联 +// * +// * @param constraintName +// * @param foreignKey +// * @param referencedTable +// * @param primaryKey +// * @param referencesPrimaryKey +// * @return String +// */ +// @Override +// public String getAddForeignKeyConstraintString(String constraintName, String[] foreignKey, String referencedTable, String[] primaryKey, boolean referencesPrimaryKey) { +// // 设置foreignkey对应的列值可以为空 +// return " alter " + foreignKey[0] + " set default null "; +// } + + + /** + * 在 JPA 环境下,映射 PostgreSQL TEXT 专有类型的三种处理方式中的一种。 + *

+ * 这个方法将 @Lob 对应的 CLOB 和 BLOB 合并进行了处理 + * + * @param sqlTypeDescriptor SQL 类型描述器 + * @return 处理后的 SQL 类型描述器 + * @see 参考资料1 + * @see 参考资料2 + * @see 参考资料3 + * @see 参考资料4 + */ + @Override + public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + switch (sqlTypeDescriptor.getSqlType()) { + case Types.CLOB: + return LongVarcharTypeDescriptor.INSTANCE; + case Types.BLOB: + return LongVarbinaryTypeDescriptor.INSTANCE; + } + return super.remapSqlTypeDescriptor(sqlTypeDescriptor); + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/TextArrayType.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/TextArrayType.java new file mode 100644 index 0000000..951eeee --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/hibernate/postgresql/TextArrayType.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.hibernate.postgresql; + +import com.liuhung.engine.data.jpa.hibernate.AbstractArrayType; +import org.hibernate.HibernateException; +import org.springframework.util.ObjectUtils; + +import java.io.Serializable; + +/** + *

Description: 自定义文本数组类型

+ * + * @author : liuh + * @date : 2019/11/15 10:34 + */ +public class TextArrayType extends AbstractArrayType implements Serializable { + + @Override + public Class returnedClass() { + return String[].class; + } + + @Override + protected Object safeReturnedObject() { + return new String[]{}; + } + + @Override + protected String dbRealTypeName() { + return "TEXT"; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public int hashCode(Object x) throws HibernateException { + if (x == null) { + return 0; + } + + return x.hashCode(); + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return ObjectUtils.nullSafeEquals(x, y); + } + +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/properties/MultiTenancyDataSource.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/properties/MultiTenancyDataSource.java new file mode 100644 index 0000000..84d382b --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/properties/MultiTenancyDataSource.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.properties; + +/** + *

Description: 租户数据源配置

+ * + * @author : liuh + * @date : 2022/9/8 18:24 + */ +public class MultiTenancyDataSource { + + private String url; + private String username; + private String password; + private String driverClassName; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + 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 getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/properties/MultiTenancyProperties.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/properties/MultiTenancyProperties.java new file mode 100644 index 0000000..460cc6f --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/properties/MultiTenancyProperties.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.properties; + +import com.liuhung.engine.assistant.core.enums.DataResource; +import com.liuhung.engine.data.core.constants.DataConstants; +import org.apache.commons.lang3.ArrayUtils; +import org.hibernate.MultiTenancyStrategy; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +/** + *

Description: 自定义 JPA 配置

+ * + * @author : liuh + * @date : 2022/9/8 18:22 + */ +@ConfigurationProperties(prefix = DataConstants.PROPERTY_PREFIX_MULTI_TENANCY) +public class MultiTenancyProperties { + + /** + * 是否开始多租户 + */ + private Boolean enabled; + + /** + * 数据资源类型 + */ + private DataResource resource = DataResource.DATABASE; + + /** + * 多租户数据隔离策略 + */ + private MultiTenancyStrategy tenancyStrategy = MultiTenancyStrategy.DATABASE; + + private String[] packageToScan; + + /** + * 多租户数据源配置 + */ + private Map dataSources; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public DataResource getResource() { + return resource; + } + + public void setResource(DataResource resource) { + this.resource = resource; + } + + public Map getDataSources() { + return dataSources; + } + + public void setDataSources(Map dataSources) { + this.dataSources = dataSources; + } + + public MultiTenancyStrategy getTenancyStrategy() { + return tenancyStrategy; + } + + public void setTenancyStrategy(MultiTenancyStrategy tenancyStrategy) { + this.tenancyStrategy = tenancyStrategy; + } + + public String[] getPackageToScan() { + String defaultPackage = "com.liuhung"; + if (ArrayUtils.isEmpty(packageToScan)) { + return new String[] {defaultPackage}; + } else { + return ArrayUtils.add(packageToScan, defaultPackage); + } + } + + public void setPackageToScan(String[] packageToScan) { + this.packageToScan = packageToScan; + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyConnectionProvider.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyConnectionProvider.java new file mode 100644 index 0000000..b2d1881 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyConnectionProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.tenancy; + +import org.hibernate.engine.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.datasource.lookup.DataSourceLookup; + +import javax.sql.DataSource; + +/** + *

Description: 数据库连接提供者

+ *

+ * 通过该类明确,在租户系统中具体使用的是哪个 Database 或 Schema + * + * @author : liuh + * @date : 2022/9/8 18:14 + */ +public class MultiTenancyConnectionProvider extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl { + + private static final Logger log = LoggerFactory.getLogger(MultiTenancyConnectionProvider.class); + + private final DataSource defaultDataSource; + private final DataSourceLookup dataSourceLookup; + + public MultiTenancyConnectionProvider(DataSource dataSource, DataSourceLookup dataSourceLookup) { + this.defaultDataSource = dataSource; + this.dataSourceLookup = dataSourceLookup; + } + + /** + * 在没有指定 tenantId 的情况下选择的数据源(例如启动处理) + * + * @return {@link DataSource} + */ + @Override + protected DataSource selectAnyDataSource() { + log.debug("[Quafer] |- Select any dataSource: " + defaultDataSource); + return defaultDataSource; + } + + @Override + protected DataSource selectDataSource(String tenantIdentifier) { + DataSource dataSource = dataSourceLookup.getDataSource(tenantIdentifier); + log.debug("[Quafer] |- Select dataSource from [{}] : [{}]", tenantIdentifier, dataSource); + return dataSource; + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyDataSourceLookup.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyDataSourceLookup.java new file mode 100644 index 0000000..eb7d995 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyDataSourceLookup.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.tenancy; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.data.jpa.properties.MultiTenancyDataSource; +import com.liuhung.engine.data.jpa.properties.MultiTenancyProperties; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup; + +import javax.sql.DataSource; +import java.util.Map; +import java.util.Properties; + +/** + *

Description: DataSource 查询

+ * + * @author : liuh + * @date : 2022/9/8 18:55 + */ +public class MultiTenancyDataSourceLookup extends MapDataSourceLookup { + + private final MultiTenancyProperties multiTenancyProperties; + + public MultiTenancyDataSourceLookup(DataSource defaultDataSource, MultiTenancyProperties multiTenancyProperties) { + this.multiTenancyProperties = multiTenancyProperties; + initDefaultDataSource(defaultDataSource); + initDataSource(defaultDataSource); + } + + private void initDefaultDataSource(DataSource defaultDataSource) { + addDataSource(BaseConstants.DEFAULT_TENANT_ID, defaultDataSource); + } + + private void initDataSource(DataSource defaultDataSource) { + Map dataSources = multiTenancyProperties.getDataSources(); + if (MapUtils.isNotEmpty(dataSources)) { + dataSources.forEach((tenantIdentifier, multiTenancyDataSource)-> { + addDataSource(tenantIdentifier, createDataSource(defaultDataSource, multiTenancyDataSource)); + }); + } + } + + private DataSource createDataSource(DataSource defaultDataSource, MultiTenancyDataSource multiTenancyDataSource) { + if (defaultDataSource instanceof HikariDataSource) { + HikariDataSource defaultHikariDataSource = (HikariDataSource) defaultDataSource; + Properties defaultDataSourceProperties = defaultHikariDataSource.getDataSourceProperties(); + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDriverClassName(multiTenancyDataSource.getDriverClassName()); + hikariConfig.setJdbcUrl(multiTenancyDataSource.getUrl()); + hikariConfig.setUsername(multiTenancyDataSource.getUsername()); + hikariConfig.setPassword(multiTenancyDataSource.getPassword()); + + if (ObjectUtils.isNotEmpty(defaultDataSource)) { + defaultDataSourceProperties.forEach((key, value) -> hikariConfig.addDataSourceProperty(String.valueOf(key), value)); + } + + return new HikariDataSource(hikariConfig); + } else { + return DataSourceBuilder.create() + .type(HikariDataSource.class) + .url(multiTenancyDataSource.getUrl()) + .driverClassName(multiTenancyDataSource.getDriverClassName()) + .username(multiTenancyDataSource.getUsername()) + .password(multiTenancyDataSource.getPassword()) + .build(); + } + } +} diff --git a/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyIdentifierResolver.java b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyIdentifierResolver.java new file mode 100644 index 0000000..57a1210 --- /dev/null +++ b/engine-data/data-sdk-jpa/src/main/java/com/liuhung/engine/data/jpa/tenancy/MultiTenancyIdentifierResolver.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.jpa.tenancy; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.context.TenantContextHolder; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.context.spi.CurrentTenantIdentifierResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Description: 租户选择器

+ * + * 选择具体使用哪个租户 + * + * @author : liuh + * @date : 2022/9/8 18:14 + */ +public class MultiTenancyIdentifierResolver implements CurrentTenantIdentifierResolver { + + private static final Logger log = LoggerFactory.getLogger(MultiTenancyIdentifierResolver.class); + + @Override + public String resolveCurrentTenantIdentifier() { + String currentTenantId = TenantContextHolder.getTenantId(); + String result = StringUtils.isNotBlank(currentTenantId) ? currentTenantId : BaseConstants.DEFAULT_TENANT_ID; + log.trace("[Quafer] |- Resolve Current Tenant Identifier is : [{}]", result); + return result; + } + + @Override + public boolean validateExistingCurrentSessions() { + return true; + } +} diff --git a/engine-data/data-sdk-mybatis-plus/pom.xml b/engine-data/data-sdk-mybatis-plus/pom.xml new file mode 100644 index 0000000..da5bc06 --- /dev/null +++ b/engine-data/data-sdk-mybatis-plus/pom.xml @@ -0,0 +1,63 @@ + + + + + 4.0.0 + + + engine-data + com.liuhung.engine + 2.7.8.0 + + + data-sdk-mybatis-plus + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + data-core + + + com.baomidou + mybatis-plus-boot-starter + + + com.baomidou + mybatis-plus-generator + + + org.apache.velocity + velocity-engine-core + + + + \ No newline at end of file diff --git a/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/annotation/EnableQuaferMybatisPlus.java b/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/annotation/EnableQuaferMybatisPlus.java new file mode 100644 index 0000000..67ffb50 --- /dev/null +++ b/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/annotation/EnableQuaferMybatisPlus.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.mybatis.plus.annotation; + +import com.liuhung.engine.data.mybatis.plus.configuration.MybatisPlusConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 手动开启 Mybatis Plus 注入

+ * + * @author : liuh + * @date : 2022/1/19 19:01 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(MybatisPlusConfiguration.class) +public @interface EnableQuaferMybatisPlus { +} diff --git a/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/configuration/MybatisPlusConfiguration.java b/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/configuration/MybatisPlusConfiguration.java new file mode 100644 index 0000000..47d9d3e --- /dev/null +++ b/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/configuration/MybatisPlusConfiguration.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.mybatis.plus.configuration; + +import com.liuhung.engine.data.core.constants.DataConstants; +import com.liuhung.engine.data.mybatis.plus.enhance.QuaferIdentifierGenerator; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Mybatis Plus 配置

+ * + * @author : liuh + * @date : 2021/8/28 11:48 + */ +@Configuration(proxyBeanMethods = false) +public class MybatisPlusConfiguration { + + private static final Logger log = LoggerFactory.getLogger(MybatisPlusConfiguration.class); + + @Value(DataConstants.ANNOTATION_SQL_INIT_PLATFORM) + private String platform; + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Data Mybatis Plus] Auto Configure."); + } + + private DbType parseDbType() { + if (StringUtils.isNotBlank(platform)) { + DbType type = DbType.getDbType(platform); + if (ObjectUtils.isNotEmpty(type)) { + return type; + } + } + + return DbType.POSTGRE_SQL; + } + + /** + * 防止 修改与删除时对全表进行操作 + * @return {@link MybatisPlusInterceptor} + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); + mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(parseDbType())); + log.trace("[Quafer] |- Bean [Mybatis Plus Interceptor] Auto Configure."); + return mybatisPlusInterceptor; + } + + @Bean + public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { + BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor(); + log.trace("[Quafer] |- Bean [Block Attack Inner Interceptor] Auto Configure."); + return blockAttackInnerInterceptor; + } + + @Bean + public IdentifierGenerator identifierGenerator() { + QuaferIdentifierGenerator quaferIdentifierGenerator = new QuaferIdentifierGenerator(); + log.trace("[Quafer] |- Bean [Quafer Identifier Generator] Auto Configure."); + return quaferIdentifierGenerator; + } +} diff --git a/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/enhance/QuaferIdentifierGenerator.java b/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/enhance/QuaferIdentifierGenerator.java new file mode 100644 index 0000000..334ed0b --- /dev/null +++ b/engine-data/data-sdk-mybatis-plus/src/main/java/com/liuhung/engine/data/mybatis/plus/enhance/QuaferIdentifierGenerator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.mybatis.plus.enhance; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import org.springframework.stereotype.Component; + +/** + *

Description: 自定义Mybatis Plus ID 生成器

+ * + * @author : liuh + * @date : 2021/8/28 15:52 + */ +@Component +public class QuaferIdentifierGenerator implements IdentifierGenerator { + + @Override + public Number nextId(Object entity) { + // 采用雪花算法获取id,时间回拨会存在重复,这里用随机数来减少重复的概率 + final Snowflake snowflake = IdUtil.getSnowflake(1, (int) (Math.random() * 20 + 3)); + return snowflake.nextId(); + } +} diff --git a/engine-data/data-sdk-p6spy/pom.xml b/engine-data/data-sdk-p6spy/pom.xml new file mode 100644 index 0000000..d41d172 --- /dev/null +++ b/engine-data/data-sdk-p6spy/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + + engine-data + com.liuhung.engine + 2.7.8.0 + + + data-sdk-p6spy + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + data-core + + + + p6spy + p6spy + + + + \ No newline at end of file diff --git a/engine-data/data-sdk-p6spy/src/main/java/com/liuhung/engine/data/p6spy/P6SpyMessageFormatting.java b/engine-data/data-sdk-p6spy/src/main/java/com/liuhung/engine/data/p6spy/P6SpyMessageFormatting.java new file mode 100644 index 0000000..b3fd92b --- /dev/null +++ b/engine-data/data-sdk-p6spy/src/main/java/com/liuhung/engine/data/p6spy/P6SpyMessageFormatting.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.p6spy; + +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; +import org.apache.commons.lang3.StringUtils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + *

Description: P6Spy自定义格式化

+ * + * @author liuh + * @date 2019/1/20 + */ +public class P6SpyMessageFormatting implements MessageFormattingStrategy { + + private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); + + @Override + public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { + + /** + * + * StringBuilder 是为了避免字符串拼接过程中产生很多不必要的字符串对象。 + * 经过编译器优化,多个字符串相‘+’,优化后,与StringBuilder等价 + * + * 关注idea的“'StringBuilder builder' can be replaced with 'String'”提示 + */ + String builder = this.format.format(new Date()) + " | took " + + elapsed + + "ms | " + + category + + " | connection " + + connectionId + + " | url " + + url + + "\n----------------------------------------------| " + + sql + + "\n----------------------------------------------| " + + ";"; + return StringUtils.isNotEmpty(sql.trim()) ? String.valueOf(builder) : ""; + } +} diff --git a/engine-data/data-sdk-p6spy/src/main/resources/spy.properties b/engine-data/data-sdk-p6spy/src/main/resources/spy.properties new file mode 100644 index 0000000..ca52375 --- /dev/null +++ b/engine-data/data-sdk-p6spy/src/main/resources/spy.properties @@ -0,0 +1,286 @@ +# +# Copyright (c) 2020-2030 ZHENGGENGWEI(\u7801\u5320\u541B) +# +# quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# quafer Engine \u91C7\u7528APACHE LICENSE 2.0\u5F00\u6E90\u534F\u8BAE\uFF0C\u60A8\u5728\u4F7F\u7528\u8FC7\u7A0B\u4E2D\uFF0C\u9700\u8981\u6CE8\u610F\u4EE5\u4E0B\u51E0\u70B9\uFF1A +# +# 1.\u8BF7\u4E0D\u8981\u5220\u9664\u548C\u4FEE\u6539\u6839\u76EE\u5F55\u4E0B\u7684LICENSE\u6587\u4EF6\u3002 +# 2.\u8BF7\u4E0D\u8981\u5220\u9664\u548C\u4FEE\u6539 quafer Engine \u6E90\u7801\u5934\u90E8\u7684\u7248\u6743\u58F0\u660E\u3002 +# 3.\u8BF7\u4FDD\u7559\u6E90\u7801\u548C\u76F8\u5173\u63CF\u8FF0\u6587\u4EF6\u7684\u9879\u76EE\u51FA\u5904\uFF0C\u4F5C\u8005\u58F0\u660E\u7B49\u3002 +# 4.\u5206\u53D1\u6E90\u7801\u65F6\u5019\uFF0C\u8BF7\u6CE8\u660E\u8F6F\u4EF6\u51FA\u5904 https://git.liuhung.com/gz/quafer-engine +# 5.\u5728\u4FEE\u6539\u5305\u540D\uFF0C\u6A21\u5757\u540D\u79F0\uFF0C\u9879\u76EE\u4EE3\u7801\u7B49\u65F6\uFF0C\u8BF7\u6CE8\u660E\u8F6F\u4EF6\u51FA\u5904 https://git.liuhung.com/gz/quafer-engine +# 6.\u82E5\u60A8\u7684\u9879\u76EE\u65E0\u6CD5\u6EE1\u8DB3\u4EE5\u4E0A\u51E0\u70B9\uFF0C\u53EF\u7533\u8BF7\u5546\u4E1A\u6388\u6743 +# + + +################################################################# +# P6Spy Options File # +# See documentation for detailed instructions # +# http://p6spy.github.io/p6spy/2.0/configandusage.html # +################################################################# + +################################################################# +# MODULES # +# # +# Module list adapts the modular functionality of P6Spy. # +# Only modules listed are active. # +# (default is com.p6spy.engine.logging.P6LogFactory and # +# com.p6spy.engine.spy.P6SpyFactory) # +# Please note that the core module (P6SpyFactory) can't be # +# deactivated. # +# Unlike the other properties, activation of the changes on # +# this one requires reload. # +################################################################# +#modulelist=com.p6spy.engine.spy.P6SpyFactory,com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory + +################################################################ +# CORE (P6SPY) PROPERTIES # +################################################################ + +# A comma separated list of JDBC drivers to load and register. +# (default is empty) +# +# Note: This is normally only needed when using P6Spy in an +# application server environment with a JNDI data source or when +# using a JDBC driver that does not implement the JDBC 4.0 API +# (specifically automatic registration). + +##### \u914D\u7F6E\u6570\u636E\u5E93\u9A71\u52A8\uFF0C\u6839\u636E\u81EA\u5DF1\u7684\u6570\u636E\u5E93\u9009\u62E9 +# driverlist=com.mysql.cj.jdbc.Driver # mysql.driver v8 +driverlist=com.mysql.cj.jdbc.Driver,org.postgresql.Driver + +# for flushing per statement +# (default is false) +#autoflush=false + +# sets the date format using Java's SimpleDateFormat routine. +# In case property is not set, milliseconds since 1.1.1970 (unix time) is used (default is empty) +#dateformat= + +# prints a stack trace for every statement logged +#stacktrace=false +# if stacktrace=true, specifies the stack trace to print +#stacktraceclass= + +# determines if property file should be reloaded +# Please note: reload means forgetting all the previously set +# settings (even those set during runtime - via JMX) +# and starting with the clean table +# (default is false) +reloadproperties=true + +# determines how often should be reloaded in seconds +# (default is 60) +#reloadpropertiesinterval=60 + +# specifies the appender to use for logging +# Please note: reload means forgetting all the previously set +# settings (even those set during runtime - via JMX) +# and starting with the clean table +# (only the properties read from the configuration file) +# (default is com.p6spy.engine.spy.appender.FileLogger) + +##### \u9009\u62E9sql\u65E5\u5FD7\u8F93\u51FA\u7684\u95EE\u9898 slf4jLogger\u8F93\u51FA\u5230\u63A7\u5236\u53F0\uFF0CfileLogger\u8F93\u51FA\u5230\u6587\u4EF6\u4E2D +#appender=com.p6spy.engine.spy.appender.Slf4JLogger +appender=com.p6spy.engine.spy.appender.StdoutLogger +#appender=com.p6spy.engine.spy.appender.FileLogger + +# name of logfile to use, note Windows sysUserRoles should make sure to use forward slashes in their pathname (e:/test/spy.log) +# (used for com.p6spy.engine.spy.appender.FileLogger only) +# (default is spy.log) + +##### \u5982\u679C\u9009\u62E9\u8F93\u51FA\u5230\u6587\u4EF6\u4E2D\u81EA\u5B9A\u4E49\u6587\u4EF6\u7684\u8F93\u51FA\u8DEF\u5F84 +#logfile=infos/logs/spy.log + +# append to the p6spy log file. if this is set to false the +# log file is truncated every time. (file logger only) +# (default is true) +#append=true + +# class to use for formatting log messages (default is: com.p6spy.engine.spy.appender.SingleLineFormat) +#logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat + +##### \u81EA\u5B9A\u4E49\u65E5\u5FD7\u683C\u5F0F\uFF0C\u5728\u7C7B\u4E2D\u5B9A\u4E49\uFF0C\u56E0\u4E3A\u539F\u751F\u7684p6spy\u65E5\u5FD7\u683C\u5F0F\u867D\u7136\u53D6\u4EE3\u4E86\u201C\uFF1F\u201D\u4F46\u662F\u5168\u90E8\u8F93\u51FA\u5230\u4E00\u884C\uFF0C\u53EF\u8BFB\u6027\u5DEE\uFF0C\u8FD8\u662F\u5EFA\u8BAE\u81EA\u5B9A\u4E49\u4E00\u4E0B +logMessageFormat=com.liuhung.engine.data.p6spy.P6SpyMessageFormatting + +# Custom log message format used ONLY IF logMessageFormat is set to com.p6spy.engine.spy.appender.CustomLineFormat +# default is %(currentTime)|%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) +# Available placeholders are: +# %(connectionId) the id of the connection +# %(currentTime) the current time expressing in milliseconds +# %(executionTime) the time in milliseconds that the operation took to complete +# %(category) the category of the operation +# %(effectiveSql) the SQL statement as submitted to the driver +# %(effectiveSqlSingleLine) the SQL statement as submitted to the driver, with all new lines removed +# %(sql) the SQL statement with all bind variables replaced with actual values +# %(sqlSingleLine) the SQL statement with all bind variables replaced with actual values, with all new lines removed +#customLogMessageFormat=%(currentTime)|%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) + +# format that is used for logging of the java.util.Date implementations (has to be compatible with java.text.SimpleDateFormat) +# (default is yyyy-MM-dd'T'HH:mm:ss.SSSZ) +#databaseDialectDateFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ + +##### \u8F93\u51FA\u65F6\u95F4\u683C\u5F0F +databaseDialectDateFormat=yyyy-MM-dd HH:mm:ss + +# format that is used for logging of the java.sql.Timestamp implementations (has to be compatible with java.text.SimpleDateFormat) +# (default is yyyy-MM-dd'T'HH:mm:ss.SSSZ) +#databaseDialectTimestampFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ + +# format that is used for logging booleans, possible values: boolean, numeric +# (default is boolean) +#databaseDialectBooleanFormat=boolean + +# whether to expose options via JMX or not +# (default is true) +#jmx=true + +# if exposing options via jmx (see option: jmx), what should be the prefix used? +# jmx naming pattern constructed is: com.p6spy(.)?:name= +# please note, if there is already such a name in use it would be unregistered first (the last registered wins) +# (default is none) +#jmxPrefix= + +# if set to true, the execution time will be measured in nanoseconds as opposed to milliseconds +# (default is false) +#useNanoTime=false + +################################################################# +# DataSource replacement # +# # +# Replace the real DataSource class in your application server # +# configuration with the name com.p6spy.engine.spy.P6DataSource # +# (that provides also connection pooling and xa support). # +# then add the JNDI name and class name of the real # +# DataSource here # +# # +# Values set in this item cannot be reloaded using the # +# reloadproperties variable. Once it is loaded, it remains # +# in memory until the application is restarted. # +# # +################################################################# +#realdatasource=/RealMySqlDS +#realdatasourceclass=com.mysql.jdbc.jdbc2.optional.MysqlDataSource + +################################################################# +# DataSource properties # +# # +# If you are using the DataSource support to intercept calls # +# to a DataSource that requires properties for proper setup, # +# define those properties here. Use name value pairs, separate # +# the name and value with a semicolon, and separate the # +# pairs with commas. # +# # +# The example shown here is for mysql # +# # +################################################################# +#realdatasourceproperties=port;3306,serverName;myhost,databaseName;jbossdb,foo;bar + +################################################################# +# JNDI DataSource lookup # +# # +# If you are using the DataSource support outside of an app # +# server, you will probably need to define the JNDI Context # +# environment. # +# # +# If the P6Spy code will be executing inside an app server then # +# do not use these properties, and the DataSource lookup will # +# use the naming context defined by the app server. # +# # +# The two standard elements of the naming environment are # +# jndicontextfactory and jndicontextproviderurl. If you need # +# additional elements, use the jndicontextcustom property. # +# You can define multiple properties in jndicontextcustom, # +# in name value pairs. Separate the name and value with a # +# semicolon, and separate the pairs with commas. # +# # +# The example shown here is for a standalone program running on # +# a machine that is also running JBoss, so the JNDI context # +# is configured for JBoss (3.0.4). # +# # +# (by default all these are empty) # +################################################################# +#jndicontextfactory=org.jnp.interfaces.NamingContextFactory +#jndicontextproviderurl=localhost:1099 +#jndicontextcustom=java.naming.factory.url.pkgs;org.jboss.naming:org.jnp.interfaces + +#jndicontextfactory=com.ibm.websphere.naming.WsnInitialContextFactory +#jndicontextproviderurl=iiop://localhost:900 + +################################################################ +# P6 LOGGING SPECIFIC PROPERTIES # +################################################################ + +# filter what is logged +# please note this is a precondition for usage of: include/exclude/sqlexpression +# (default is false) +#filter=false + +# comma separated list of strings to include +# please note that special characters escaping (used in java) has to be done for the provided regular expression +# (default is empty) +#include= +# comma separated list of strings to exclude +# (default is empty) +#exclude= + +# sql expression to evaluate if using regex +# please note that special characters escaping (used in java) has to be done for the provided regular expression +# (default is empty) +#sqlexpression= + +#list of categories to exclude: error, info, batch, debug, statement, +#commit, rollback, result and resultset are valid values +# (default is info,debug,result,resultset,batch) +#excludecategories=info,debug,result,resultset,batch + +#whether the binary values (passed to DB or retrieved ones) should be logged with placeholder: [binary] or not. +# (default is false) +#excludebinary=false + +# Execution threshold applies to the standard logging of P6Spy. +# While the standard logging logs out every statement +# regardless of its execution time, this feature puts a time +# condition on that logging. Only statements that have taken +# longer than the time specified (in milliseconds) will be +# logged. This way it is possible to see only statements that +# have exceeded some high water mark. +# This time is reloadable. +# +# executionThreshold=integer time (milliseconds) +# (default is 0) +#executionThreshold= + +################################################################ +# P6 OUTAGE SPECIFIC PROPERTIES # +################################################################ +# Outage Detection +# +# This feature detects long-running statements that may be indicative of +# a database outage problem. If this feature is turned on, it will log any +# statement that surpasses the configurable time boundary during its execution. +# When this feature is enabled, no other statements are logged except the long +# running statements. The interval property is the boundary time set in seconds. +# For example, if this is set to 2, then any statement requiring at least 2 +# seconds will be logged. Note that the same statement will continue to be logged +# for as long as it executes. So if the interval is set to 2, and the query takes +# 11 seconds, it will be logged 5 times (at the 2, 4, 6, 8, 10 second intervals). +# +# outagedetection=true|false +# outagedetectioninterval=integer time (seconds) +# +# (default is false) +#outagedetection=false +# (default is 60) +#outagedetectioninterval=30 \ No newline at end of file diff --git a/engine-data/data-spring-boot-starter/pom.xml b/engine-data/data-spring-boot-starter/pom.xml new file mode 100644 index 0000000..5ba7a44 --- /dev/null +++ b/engine-data/data-spring-boot-starter/pom.xml @@ -0,0 +1,63 @@ + + + + + + engine-data + com.liuhung.engine + 2.7.8.0 + + 4.0.0 + + data-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + data-sdk-jpa + + + com.liuhung.engine + data-sdk-mybatis-plus + + + com.liuhung.engine + data-sdk-p6spy + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + \ No newline at end of file diff --git a/engine-data/data-spring-boot-starter/src/main/java/com/liuhung/engine/data/autoconfigure/AutoConfiguration.java b/engine-data/data-spring-boot-starter/src/main/java/com/liuhung/engine/data/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..fd0a9bf --- /dev/null +++ b/engine-data/data-spring-boot-starter/src/main/java/com/liuhung/engine/data/autoconfigure/AutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.data.autoconfigure; + +import com.liuhung.engine.data.mybatis.plus.configuration.MybatisPlusConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: Data组件自动注入

+ * + * @author : liuh + * @date : 2022/1/19 19:03 + */ +@Configuration(proxyBeanMethods = false) +@Import({ + MybatisPlusConfiguration.class +}) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Data Starter] Auto Configure."); + } +} diff --git a/engine-data/data-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-data/data-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..5777123 --- /dev/null +++ b/engine-data/data-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.liuhung.engine.data.autoconfigure.AutoConfiguration +com.liuhung.engine.data.jpa.configuration.DataJpaConfiguration \ No newline at end of file diff --git a/engine-data/pom.xml b/engine-data/pom.xml new file mode 100644 index 0000000..74ebf0b --- /dev/null +++ b/engine-data/pom.xml @@ -0,0 +1,48 @@ + + + + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + 4.0.0 + + engine-data + pom + 2.7.8.0 + + + data-core + data-sdk-jpa + data-sdk-mybatis-plus + data-sdk-p6spy + data-spring-boot-starter + + \ No newline at end of file diff --git a/engine-event/event-core/pom.xml b/engine-event/event-core/pom.xml new file mode 100644 index 0000000..785e466 --- /dev/null +++ b/engine-event/event-core/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-event + com.liuhung.engine + 2.7.8.0 + + + event-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + web-core + + + + \ No newline at end of file diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/constants/EventConstants.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/constants/EventConstants.java new file mode 100644 index 0000000..9900d67 --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/constants/EventConstants.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: Event 模块常量

+ * + * @author : liuh + * @date : 2022/3/14 16:24 + */ +public interface EventConstants extends BaseConstants { + + String PROPERTY_PREFIX_KAFKA = PROPERTY_PREFIX_EVENT + ".kafka"; + + String ITEM_KAFKA_ENABLED = PROPERTY_PREFIX_KAFKA + PROPERTY_ENABLED; +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/AbstractCollectionChangeListener.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/AbstractCollectionChangeListener.java new file mode 100644 index 0000000..a672373 --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/AbstractCollectionChangeListener.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.definition; + +import org.apache.commons.collections4.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Description: 实体集合属性变更监听器

+ * + * @author : liuh + * @date : 2021/8/11 18:12 + */ +public abstract class AbstractCollectionChangeListener extends AbstractEntityListener { + + private List before; + private List after; + + public void setBefore(List before) { + this.before = before; + } + + public void setAfter(List after) { + this.after = after; + } + + protected List getChangedItems() { + if (CollectionUtils.isNotEmpty(this.before) && CollectionUtils.isNotEmpty(this.after)) { + return new ArrayList<>(CollectionUtils.disjunction(this.before, this.after)); + } + + if (CollectionUtils.isNotEmpty(this.before) && CollectionUtils.isEmpty(this.after)) { + return this.before; + } + + if (CollectionUtils.isEmpty(this.before) && CollectionUtils.isNotEmpty(this.after)) { + return this.after; + } + + return new ArrayList<>(); + } +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/AbstractEntityListener.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/AbstractEntityListener.java new file mode 100644 index 0000000..eff107e --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/AbstractEntityListener.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.definition; + +import com.liuhung.engine.web.core.context.ServiceContext; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; + +/** + *

Description: 抽象 JPA 实体变更 Listener

+ * + * @author : liuh + * @date : 2021/8/11 18:12 + */ +public abstract class AbstractEntityListener implements ApplicationContextAware { + + private ApplicationContext applicationContext; + + protected ApplicationContext getApplicationContext() { + if (ObjectUtils.isEmpty(applicationContext)) { + return ServiceContext.getInstance().getApplicationContext(); + } + return applicationContext; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + protected void publishEvent(ApplicationEvent event) { + this.getApplicationContext().publishEvent(event); + } +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/LocalApplicationEvent.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/LocalApplicationEvent.java new file mode 100644 index 0000000..d912ea0 --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/definition/LocalApplicationEvent.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.definition; + +import org.springframework.context.ApplicationEvent; + +import java.time.Clock; + +/** + *

Description: 自定义 Application Event 基础类

+ * + * @author : liuh + * @date : 2022/2/4 15:14 + */ +public class LocalApplicationEvent extends ApplicationEvent { + + private final T data; + + public LocalApplicationEvent(T data) { + super(data); + this.data = data; + } + + public LocalApplicationEvent(T data, Clock clock) { + super(data, clock); + this.data = data; + } + + public T getData() { + return data; + } +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalChangeUserStatusEvent.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalChangeUserStatusEvent.java new file mode 100644 index 0000000..d0e54d3 --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalChangeUserStatusEvent.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.local; + +import com.liuhung.engine.event.core.definition.LocalApplicationEvent; +import com.liuhung.engine.web.core.domain.UserStatus; + +import java.time.Clock; + +/** + *

Description: 本地用户状态变更事件

+ * + * @author : liuh + * @date : 2022/7/10 16:15 + */ +public class LocalChangeUserStatusEvent extends LocalApplicationEvent { + + public LocalChangeUserStatusEvent(UserStatus data) { + super(data); + } + + public LocalChangeUserStatusEvent(UserStatus data, Clock clock) { + super(data, clock); + } +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalPaymentNotifyEvent.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalPaymentNotifyEvent.java new file mode 100644 index 0000000..391f24c --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalPaymentNotifyEvent.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.local; + +import com.liuhung.engine.event.core.definition.LocalApplicationEvent; + +import java.time.Clock; +import java.util.Map; + +/** + *

Description: 单体模式下本地通知事件

+ * + * @author : liuh + * @date : 2022/1/10 21:05 + */ +public class LocalPaymentNotifyEvent extends LocalApplicationEvent> { + + public LocalPaymentNotifyEvent(Map data) { + super(data); + } + + public LocalPaymentNotifyEvent(Map data, Clock clock) { + super(data, clock); + } +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalPaymentReturnEvent.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalPaymentReturnEvent.java new file mode 100644 index 0000000..8547a74 --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalPaymentReturnEvent.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.local; + +import com.liuhung.engine.event.core.definition.LocalApplicationEvent; + +import java.time.Clock; +import java.util.Map; + +/** + *

Description: 单体模式下本地返回事件

+ * + * @author : liuh + * @date : 2022/1/10 22:09 + */ +public class LocalPaymentReturnEvent extends LocalApplicationEvent> { + + public LocalPaymentReturnEvent(Map data) { + super(data); + } + + public LocalPaymentReturnEvent(Map data, Clock clock) { + super(data, clock); + } +} diff --git a/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalRequestMappingGatherEvent.java b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalRequestMappingGatherEvent.java new file mode 100644 index 0000000..03bcefe --- /dev/null +++ b/engine-event/event-core/src/main/java/com/liuhung/engine/event/core/local/LocalRequestMappingGatherEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.core.local; + +import com.liuhung.engine.event.core.definition.LocalApplicationEvent; +import com.liuhung.engine.web.core.domain.RequestMapping; + +import java.time.Clock; +import java.util.List; + +/** + *

Description: 本地RequestMapping收集事件

+ * + * @author : liuh + * @date : 2021/8/8 21:55 + */ +public class LocalRequestMappingGatherEvent extends LocalApplicationEvent> { + + public LocalRequestMappingGatherEvent(List data) { + super(data); + } + + public LocalRequestMappingGatherEvent(List data, Clock clock) { + super(data, clock); + } +} diff --git a/engine-event/event-message-spring-boot-starter/pom.xml b/engine-event/event-message-spring-boot-starter/pom.xml new file mode 100644 index 0000000..fdd8fd8 --- /dev/null +++ b/engine-event/event-message-spring-boot-starter/pom.xml @@ -0,0 +1,52 @@ + + + + + 4.0.0 + + com.liuhung.engine + engine-event + 2.7.8.0 + + + event-message-spring-boot-starter + 2.7.8.0 + jar + + + + com.liuhung.engine + event-core + + + org.springframework.cloud + spring-cloud-starter-bus-kafka + + + + \ No newline at end of file diff --git a/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/annotation/ConditionalOnKafkaEnabled.java b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/annotation/ConditionalOnKafkaEnabled.java new file mode 100644 index 0000000..50e088b --- /dev/null +++ b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/annotation/ConditionalOnKafkaEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.message.annotation; + +import com.liuhung.engine.event.core.constants.EventConstants; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.*; + +/** + *

Description: 开启传统kafka使用方式支持

+ * + * @author : liuh + * @date : 2021/8/11 20:32 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@ConditionalOnProperty(value = EventConstants.ITEM_KAFKA_ENABLED) +public @interface ConditionalOnKafkaEnabled { +} diff --git a/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/autoconfigure/AutoConfiguration.java b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..c753b03 --- /dev/null +++ b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/autoconfigure/AutoConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.message.autoconfigure; + +import com.liuhung.engine.event.message.configuration.KafkaConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: Pay Event 自动注入配置

+ * + * @author : liuh + * @date : 2022/1/20 19:07 + */ +@Configuration(proxyBeanMethods = false) +@Import({KafkaConfiguration.class}) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Event Message Starter] Auto Configure."); + } +} diff --git a/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/configuration/KafkaConfiguration.java b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/configuration/KafkaConfiguration.java new file mode 100644 index 0000000..6446201 --- /dev/null +++ b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/configuration/KafkaConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.message.configuration; + +import com.liuhung.engine.event.message.annotation.ConditionalOnKafkaEnabled; +import com.liuhung.engine.event.message.properties.KafkaProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; + +import javax.annotation.PostConstruct; + +/** + *

Description: Kafka 配置

+ * + * @author : liuh + * @date : 2021/10/23 17:34 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnKafkaEnabled +@EnableConfigurationProperties({ + KafkaProperties.class +}) +public class KafkaConfiguration { + + private static final Logger log = LoggerFactory.getLogger(KafkaConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Event Message Kafka] Auto Configure."); + } + + @Bean + @ConditionalOnMissingBean(ConcurrentKafkaListenerContainerFactory.class) + public ConcurrentKafkaListenerContainerFactory concurrentKafkaListenerContainerFactory(KafkaProperties kafkaProperties, ConsumerFactory consumerFactory) { + ConcurrentKafkaListenerContainerFactory concurrentKafkaListenerContainerFactory = new ConcurrentKafkaListenerContainerFactory<>(); + concurrentKafkaListenerContainerFactory.setConsumerFactory(consumerFactory); + concurrentKafkaListenerContainerFactory.setAutoStartup(kafkaProperties.getEnabled()); + log.trace("[Quafer] |- Bean [Concurrent Kafka Listener ContainerFactory] Auto Configure."); + return concurrentKafkaListenerContainerFactory; + } +} diff --git a/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/properties/KafkaProperties.java b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/properties/KafkaProperties.java new file mode 100644 index 0000000..db629f5 --- /dev/null +++ b/engine-event/event-message-spring-boot-starter/src/main/java/com/liuhung/engine/event/message/properties/KafkaProperties.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.message.properties; + +import com.liuhung.engine.event.core.constants.EventConstants; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

Description: 消息队列配置

+ * + * @author : liuh + * @date : 2021/8/7 23:55 + */ +@ConfigurationProperties(prefix = EventConstants.PROPERTY_PREFIX_KAFKA) +public class KafkaProperties { + + /** + * Kakfa监听是否自动启动 + */ + private Boolean enabled = false; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } +} diff --git a/engine-event/event-message-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-event/event-message-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..5897002 --- /dev/null +++ b/engine-event/event-message-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.liuhung.engine.event.message.autoconfigure.AutoConfiguration \ No newline at end of file diff --git a/engine-event/event-pay-spring-boot-starter/pom.xml b/engine-event/event-pay-spring-boot-starter/pom.xml new file mode 100644 index 0000000..faf0eb1 --- /dev/null +++ b/engine-event/event-pay-spring-boot-starter/pom.xml @@ -0,0 +1,55 @@ + + + + + 4.0.0 + + + engine-event + com.liuhung.engine + 2.7.8.0 + + + event-pay-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + event-core + + + org.springframework.cloud + spring-cloud-bus + + + + \ No newline at end of file diff --git a/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/autoconfigure/AutoConfiguration.java b/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..2577022 --- /dev/null +++ b/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/autoconfigure/AutoConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.pay.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Pay Event 自动注入配置

+ * + * @author : liuh + * @date : 2022/1/20 19:07 + */ +@Configuration(proxyBeanMethods = false) +@RemoteApplicationEventScan({ + "com.liuhung.engine.event.pay.remote" +}) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Event Pay Starter] Auto Configure."); + } +} diff --git a/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/remote/RemotePaymentNotifyEvent.java b/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/remote/RemotePaymentNotifyEvent.java new file mode 100644 index 0000000..8bf0222 --- /dev/null +++ b/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/remote/RemotePaymentNotifyEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.pay.remote; + +import org.springframework.cloud.bus.event.RemoteApplicationEvent; + +/** + *

Description: 远程支付通知事件

+ * + * @author : liuh + * @date : 2022/1/10 14:44 + */ +public class RemotePaymentNotifyEvent extends RemoteApplicationEvent { + + private String data; + + public RemotePaymentNotifyEvent() { + super(); + } + + public RemotePaymentNotifyEvent(String data, String originService, String destinationService) { + super(data, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService)); + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/remote/RemotePaymentReturnEvent.java b/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/remote/RemotePaymentReturnEvent.java new file mode 100644 index 0000000..010dfa5 --- /dev/null +++ b/engine-event/event-pay-spring-boot-starter/src/main/java/com/liuhung/engine/event/pay/remote/RemotePaymentReturnEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.pay.remote; + +import org.springframework.cloud.bus.event.RemoteApplicationEvent; + +/** + *

Description: 远程支付返回事件

+ * + * @author : liuh + * @date : 2022/1/10 22:19 + */ +public class RemotePaymentReturnEvent extends RemoteApplicationEvent { + + private String data; + + public RemotePaymentReturnEvent() { + super(); + } + + public RemotePaymentReturnEvent(String data, String originService, String destinationService) { + super(data, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService)); + this.data = data; + } + + public String getData() { + return data; + } +} \ No newline at end of file diff --git a/engine-event/event-pay-spring-boot-starter/src/main/resources/META-INF/spring.factories b/engine-event/event-pay-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..15ce6db --- /dev/null +++ b/engine-event/event-pay-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.liuhung.engine.event.pay.autoconfigure.AutoConfiguration \ No newline at end of file diff --git a/engine-event/event-security-spring-boot-starter/pom.xml b/engine-event/event-security-spring-boot-starter/pom.xml new file mode 100644 index 0000000..1f4b9f5 --- /dev/null +++ b/engine-event/event-security-spring-boot-starter/pom.xml @@ -0,0 +1,55 @@ + + + + + 4.0.0 + + + engine-event + com.liuhung.engine + 2.7.8.0 + + + event-security-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + event-core + + + org.springframework.cloud + spring-cloud-bus + + + + \ No newline at end of file diff --git a/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/autoconfigure/AutoConfiguration.java b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..72385d0 --- /dev/null +++ b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/autoconfigure/AutoConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.security.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Security Event 自动配置

+ * + * @author : liuh + * @date : 2022/1/23 12:13 + */ +@Configuration(proxyBeanMethods = false) +@RemoteApplicationEventScan({ + "com.liuhung.engine.event.security.remote" +}) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Event Security Starter] Auto Configure."); + } +} diff --git a/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteChangeUserStatusEvent.java b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteChangeUserStatusEvent.java new file mode 100644 index 0000000..862b491 --- /dev/null +++ b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteChangeUserStatusEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.security.remote; + +import org.springframework.cloud.bus.event.RemoteApplicationEvent; + +/** + *

Description: 修改用户状态远程事件

+ * + * @author : liuh + * @date : 2022/7/10 16:13 + */ +public class RemoteChangeUserStatusEvent extends RemoteApplicationEvent { + + private String data; + + public RemoteChangeUserStatusEvent() { + super(); + } + + public RemoteChangeUserStatusEvent(String data, String originService, String destinationService) { + super(data, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService)); + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteRequestMappingGatherEvent.java b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteRequestMappingGatherEvent.java new file mode 100644 index 0000000..b9073be --- /dev/null +++ b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteRequestMappingGatherEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.security.remote; + +import org.springframework.cloud.bus.event.RemoteApplicationEvent; + +/** + *

Description: Request Mapping 收集远程事件

+ * + * @author : liuh + * @date : 2021/8/6 11:23 + */ +public class RemoteRequestMappingGatherEvent extends RemoteApplicationEvent { + + private String data; + + public RemoteRequestMappingGatherEvent() { + super(); + } + + public RemoteRequestMappingGatherEvent(String data, String originService, String destinationService) { + super(data, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService)); + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteSecurityMetadataSyncEvent.java b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteSecurityMetadataSyncEvent.java new file mode 100644 index 0000000..11c4a91 --- /dev/null +++ b/engine-event/event-security-spring-boot-starter/src/main/java/com/liuhung/engine/event/security/remote/RemoteSecurityMetadataSyncEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.event.security.remote; + +import org.springframework.cloud.bus.event.RemoteApplicationEvent; + +/** + *

Description: Security Metadata 远程刷新事件

+ * + * @author : liuh + * @date : 2021/8/6 11:24 + */ +public class RemoteSecurityMetadataSyncEvent extends RemoteApplicationEvent { + + private String data; + + public RemoteSecurityMetadataSyncEvent() { + super(); + } + + public RemoteSecurityMetadataSyncEvent(String data, String originService, String destinationService) { + super(data, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService)); + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/engine-event/event-security-spring-boot-starter/src/main/resources/META-INF/spring.factories b/engine-event/event-security-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..2e1b193 --- /dev/null +++ b/engine-event/event-security-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.liuhung.engine.event.security.autoconfigure.AutoConfiguration \ No newline at end of file diff --git a/engine-event/pom.xml b/engine-event/pom.xml new file mode 100644 index 0000000..117771e --- /dev/null +++ b/engine-event/pom.xml @@ -0,0 +1,49 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + engine-event + 2.7.8.0 + pom + + + event-core + event-security-spring-boot-starter + event-pay-spring-boot-starter + event-message-spring-boot-starter + + + \ No newline at end of file diff --git a/engine-facility/README.md b/engine-facility/README.md new file mode 100644 index 0000000..c8aee56 --- /dev/null +++ b/engine-facility/README.md @@ -0,0 +1 @@ +## 微服务基础设施模块 \ No newline at end of file diff --git a/engine-facility/facility-core/README.md b/engine-facility/facility-core/README.md new file mode 100644 index 0000000..3b39bd8 --- /dev/null +++ b/engine-facility/facility-core/README.md @@ -0,0 +1 @@ +## 服务基础共性依赖代码模块组件 \ No newline at end of file diff --git a/engine-facility/facility-core/pom.xml b/engine-facility/facility-core/pom.xml new file mode 100644 index 0000000..505d0cb --- /dev/null +++ b/engine-facility/facility-core/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-facility + com.liuhung.engine + 2.7.8.0 + + + facility-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + assistant-core + + + + \ No newline at end of file diff --git a/engine-facility/facility-core/src/main/java/com/liuhung/engine/facility/core/constants/FacilityConstants.java b/engine-facility/facility-core/src/main/java/com/liuhung/engine/facility/core/constants/FacilityConstants.java new file mode 100644 index 0000000..ace279f --- /dev/null +++ b/engine-facility/facility-core/src/main/java/com/liuhung/engine/facility/core/constants/FacilityConstants.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: 基础设施常量

+ * + * @author : liuh + * @date : 2022/2/5 17:33 + */ +public interface FacilityConstants extends BaseConstants { + + String PROPERTY_PREFIX_LOG_CENTER = PROPERTY_PREFIX_HERODOTUS + ".log-center"; + + String ITEM_LOG_CENTER_ENABLED = PROPERTY_PREFIX_LOG_CENTER + ".server-addr"; +} diff --git a/engine-facility/facility-sdk-log/README.md b/engine-facility/facility-sdk-log/README.md new file mode 100644 index 0000000..de58384 --- /dev/null +++ b/engine-facility/facility-sdk-log/README.md @@ -0,0 +1 @@ +> 日志中心模块组件 \ No newline at end of file diff --git a/engine-facility/facility-sdk-log/pom.xml b/engine-facility/facility-sdk-log/pom.xml new file mode 100644 index 0000000..ae1c791 --- /dev/null +++ b/engine-facility/facility-sdk-log/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + + engine-facility + com.liuhung.engine + 2.7.8.0 + + + facility-sdk-log + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + facility-core + + + net.logstash.logback + logstash-logback-encoder + + + + + \ No newline at end of file diff --git a/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/annotation/ConditionalOnLogEnabled.java b/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/annotation/ConditionalOnLogEnabled.java new file mode 100644 index 0000000..4b75669 --- /dev/null +++ b/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/annotation/ConditionalOnLogEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.log.annotation; + +import com.liuhung.engine.facility.core.constants.FacilityConstants; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.*; + +/** + *

Description: 日志中心是否开启条件注解

+ * + * @author : liuh + * @date : 2021/8/8 9:54 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@ConditionalOnProperty(name = FacilityConstants.ITEM_LOG_CENTER_ENABLED) +public @interface ConditionalOnLogEnabled { +} diff --git a/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/autoconfigure/FacilityLogAutoConfiguration.java b/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/autoconfigure/FacilityLogAutoConfiguration.java new file mode 100644 index 0000000..abffdf3 --- /dev/null +++ b/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/autoconfigure/FacilityLogAutoConfiguration.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.log.autoconfigure; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import com.liuhung.engine.assistant.core.json.jackson2.utils.JacksonUtils; +import com.liuhung.engine.facility.core.constants.FacilityConstants; +import com.liuhung.engine.facility.log.annotation.ConditionalOnLogEnabled; +import com.liuhung.engine.facility.log.properties.LogProperties; +import com.google.common.base.MoreObjects; +import net.logstash.logback.appender.LogstashTcpSocketAppender; +import net.logstash.logback.encoder.LogstashEncoder; +import org.apache.skywalking.apm.toolkit.log.logback.v1.x.logstash.TraceIdJsonProvider; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.logging.LogLevel; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.util.Map; + +/** + *

Description: 基础设置日志配置

+ * + * @author : liuh + * @date : 2022/2/5 19:01 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnLogEnabled +@EnableConfigurationProperties({LogProperties.class}) +public class FacilityLogAutoConfiguration { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(FacilityLogAutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Facility Log] Auto Configure."); + } + + + @Value(FacilityConstants.ANNOTATION_APPLICATION_NAME) + private String serviceName; + + @Autowired + private LogProperties logProperties; + + @PostConstruct + public void init() { + + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + LoggerContext loggerContext = rootLogger.getLoggerContext(); + + LogstashTcpSocketAppender logstashTcpSocketAppender = new LogstashTcpSocketAppender(); + logstashTcpSocketAppender.setName("LOGSTASH"); + logstashTcpSocketAppender.addDestination(logProperties.getServerAddr()); + logstashTcpSocketAppender.setKeepAliveDuration(logProperties.getKeepAliveDuration()); + logstashTcpSocketAppender.setReconnectionDelay(logProperties.getReconnectionDelay()); + logstashTcpSocketAppender.setWriteTimeout(logProperties.getWriteTimeout()); + logstashTcpSocketAppender.setContext(loggerContext); + + TraceIdJsonProvider traceIdJsonProvider = new TraceIdJsonProvider(); + traceIdJsonProvider.setContext(loggerContext); + + CustomFields customFields = new CustomFields(); + customFields.setService(serviceName); + + LogstashEncoder logstashEncoder = new LogstashEncoder(); + logstashEncoder.setCustomFields(JacksonUtils.toJson(customFields)); + logstashEncoder.addProvider(traceIdJsonProvider); + + logstashTcpSocketAppender.setEncoder(logstashEncoder); + logstashTcpSocketAppender.start(); + + rootLogger.addAppender(logstashTcpSocketAppender); + rootLogger.setLevel(Level.toLevel(logProperties.getLogLevel().name(), Level.INFO)); + + Map loggers = logProperties.getLoggers(); + loggers.forEach((key, value) -> { + Logger logger = (Logger) LoggerFactory.getLogger(key); + logger.setLevel(Level.toLevel(value.name())); + }); + } + + private static class CustomFields { + + private String service; + + public String getService() { + return service; + } + + public void setService(String service) { + this.service = service; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("service", service) + .toString(); + } + } +} diff --git a/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/properties/LogProperties.java b/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/properties/LogProperties.java new file mode 100644 index 0000000..cea69cf --- /dev/null +++ b/engine-facility/facility-sdk-log/src/main/java/com/liuhung/engine/facility/log/properties/LogProperties.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.log.properties; + +import ch.qos.logback.core.util.Duration; +import com.liuhung.engine.facility.core.constants.FacilityConstants; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.logging.LogLevel; + +import java.util.HashMap; +import java.util.Map; + +/** + *

Description: 日志中心配置

+ * + * @author : liuh + * @date : 2022/2/5 17:48 + */ +@ConfigurationProperties(prefix = FacilityConstants.PROPERTY_PREFIX_LOG_CENTER) +public class LogProperties { + + /** + * 日志中心的logstash地址。 + */ + private String serverAddr = "127.0.0.1:5044"; + /** + * 日志级别,默认为INFO + */ + private LogLevel logLevel = LogLevel.INFO; + /** + * 保持活动持续时间,默认5分钟,单位:分钟 + */ + private Duration keepAliveDuration = Duration.buildByMinutes(5); + + /** + * 尝试连接到目标间隔时间,默认30秒, 单位:秒 + */ + private Duration reconnectionDelay = Duration.buildBySeconds(30); + /** + * 日志写入超时时间,默认1分钟,单位:分钟 + */ + private Duration writeTimeout = Duration.buildByMinutes(1); + /** + * 日志级别配置 + */ + private Map loggers = new HashMap<>(); + + public String getServerAddr() { + return serverAddr; + } + + public void setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + } + + public LogLevel getLogLevel() { + return logLevel; + } + + public void setLogLevel(LogLevel logLevel) { + this.logLevel = logLevel; + } + + public Map getLoggers() { + return loggers; + } + + public void setLoggers(Map loggers) { + this.loggers = loggers; + } + + public Duration getKeepAliveDuration() { + return keepAliveDuration; + } + + public void setKeepAliveDuration(Duration keepAliveDuration) { + this.keepAliveDuration = keepAliveDuration; + } + + public Duration getReconnectionDelay() { + return reconnectionDelay; + } + + public void setReconnectionDelay(Duration reconnectionDelay) { + this.reconnectionDelay = reconnectionDelay; + } + + public Duration getWriteTimeout() { + return writeTimeout; + } + + public void setWriteTimeout(Duration writeTimeout) { + this.writeTimeout = writeTimeout; + } +} diff --git a/engine-facility/facility-sdk-sentinel/README.md b/engine-facility/facility-sdk-sentinel/README.md new file mode 100644 index 0000000..3db6950 --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/README.md @@ -0,0 +1,7 @@ +> Sentinel扩展集成模块组件 + +**包含以下内容:** + +1. Sentinel 统一 Fallback 拓展 +2. Sentinel 与 Feign 扩展集成 +3. Feign 自动降级处理 \ No newline at end of file diff --git a/engine-facility/facility-sdk-sentinel/pom.xml b/engine-facility/facility-sdk-sentinel/pom.xml new file mode 100644 index 0000000..d74bbc8 --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + engine-facility + com.liuhung.engine + 2.7.8.0 + + + facility-sdk-sentinel + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + facility-core + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + com.alibaba.csp + sentinel-datasource-nacos + + + org.springframework.boot + spring-boot-starter-web + compile + true + + + org.springframework.boot + spring-boot-starter-webflux + compile + true + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + + + \ No newline at end of file diff --git a/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/autoconfigure/FacilitySentinelAutoConfiguration.java b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/autoconfigure/FacilitySentinelAutoConfiguration.java new file mode 100644 index 0000000..4083f49 --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/autoconfigure/FacilitySentinelAutoConfiguration.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.sentinel.autoconfigure; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.json.jackson2.utils.JacksonUtils; +import com.liuhung.engine.facility.sentinel.enhance.QuaferSentinelFeign; +import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.BlockRequestHandler; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; +import feign.Feign; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.ServerResponse; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +/** + *

Description: 基础设施 Sentinel 配置

+ * + * @author : liuh + * @date : 2022/2/5 17:57 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ SphU.class, Feign.class }) +@AutoConfigureBefore(SentinelFeignAutoConfiguration.class) +public class FacilitySentinelAutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(FacilitySentinelAutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Facility Sentinel] Auto Configure."); + } + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "feign.sentinel.enabled") + public Feign.Builder feignSentinelBuilder() { + return QuaferSentinelFeign.builder(); + } + + /** + * 限流、熔断统一处理类 + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(HttpServletRequest.class) + public static class WebmvcHandler { + @Bean + public BlockExceptionHandler webmvcBlockExceptionHandler() { + return (request, response, e) -> { + response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + Result result = Result.failure("Too many request, please retry later."); + response.getWriter().print(JacksonUtils.toJson(result)); + }; + } + + } + + /** + * 限流、熔断统一处理类 + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ServerResponse.class) + public static class WebfluxHandler { + @Bean + public BlockRequestHandler webfluxBlockExceptionHandler() { + return (exchange, t) -> + ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(Result.failure(t.getMessage()))); + } + } +} diff --git a/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferFallback.java b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferFallback.java new file mode 100644 index 0000000..6b7bc55 --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferFallback.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.sentinel.enhance; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.exception.GlobalExceptionHandler; +import com.google.common.base.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; + +import java.lang.reflect.Method; + +/** + *

Description: 统一 fallback 实体

+ * + * @author : liuh + * @date : 2022/5/30 15:12 + */ +public class QuaferFallback implements MethodInterceptor { + + private static final Logger log = LoggerFactory.getLogger(QuaferFallback.class); + + private final Class targetType; + private final String targetName; + private final Throwable cause; + + public QuaferFallback(Class targetType, String targetName, Throwable cause) { + this.targetType = targetType; + this.targetName = targetName; + this.cause = cause; + } + + @Override + public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { + + String errorMessage = cause.getMessage(); + String path = targetType.getName() + SymbolConstants.FORWARD_SLASH + method.getName(); + + Result result = GlobalExceptionHandler.resolveException((Exception) cause, path); + log.error("[Quafer] |- Feign remote call fallback : [{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuaferFallback that = (QuaferFallback) o; + return Objects.equal(targetType, that.targetType); + } + + @Override + public int hashCode() { + return Objects.hashCode(targetType); + } +} diff --git a/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferFallbackFactory.java b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferFallbackFactory.java new file mode 100644 index 0000000..acd3b9e --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferFallbackFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.sentinel.enhance; + +import feign.Target; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cloud.openfeign.FallbackFactory; + +/** + *

Description: Feign 统一 Fallback 工厂

+ * + * @author : liuh + * @date : 2022/5/30 15:09 + */ +public class QuaferFallbackFactory implements FallbackFactory { + + private final Target target; + + public QuaferFallbackFactory(Target target) { + this.target = target; + } + + @Override + public Object create(Throwable cause) { + final Class targetType = target.type(); + final String targetName = target.name(); + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(targetType); + enhancer.setUseCache(true); + enhancer.setCallback(new QuaferFallback<>(targetType, targetName, cause)); + return (T) enhancer.create(); + } +} diff --git a/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferSentinelFeign.java b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferSentinelFeign.java new file mode 100644 index 0000000..3510df5 --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferSentinelFeign.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.sentinel.enhance; + +import com.alibaba.cloud.sentinel.feign.SentinelContractHolder; +import feign.Contract; +import feign.Feign; +import feign.InvocationHandlerFactory; +import feign.Target; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.cloud.openfeign.FeignClientFactoryBean; +import org.springframework.cloud.openfeign.FeignContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; + +/** + *

Description: 复制原有代码,扩展支持自定义 SentinelInvocationHandler

+ * + * @author : liuh + * @date : 2022/5/30 16:06 + */ +public class QuaferSentinelFeign { + + private QuaferSentinelFeign() { + + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends Feign.Builder + implements ApplicationContextAware { + + private Contract contract = new Contract.Default(); + + private ApplicationContext applicationContext; + + private FeignContext feignContext; + + @Override + public Feign.Builder invocationHandlerFactory( + InvocationHandlerFactory invocationHandlerFactory) { + throw new UnsupportedOperationException(); + } + + @Override + public Builder contract(Contract contract) { + this.contract = contract; + return this; + } + + @Override + public Feign build() { + super.invocationHandlerFactory(new InvocationHandlerFactory() { + @Override + public InvocationHandler create(Target target, + Map dispatch) { + GenericApplicationContext gctx = (GenericApplicationContext) Builder.this.applicationContext; + BeanDefinition def = gctx.getBeanDefinition(target.type().getName()); + + /* + * Due to the change of the initialization sequence, + * BeanFactory.getBean will cause a circular dependency. So + * FeignClientFactoryBean can only be obtained from BeanDefinition + */ + FeignClientFactoryBean feignClientFactoryBean = (FeignClientFactoryBean) def + .getAttribute("feignClientsRegistrarFactoryBean"); + + Class fallback = feignClientFactoryBean.getFallback(); + Class fallbackFactory = feignClientFactoryBean.getFallbackFactory(); + String beanName = feignClientFactoryBean.getContextId(); + if (!StringUtils.hasText(beanName)) { + beanName = (String) getFieldValue(feignClientFactoryBean, "name"); + } + + Object fallbackInstance; + FallbackFactory fallbackFactoryInstance; + // check fallback and fallbackFactory properties + if (void.class != fallback) { + fallbackInstance = getFromContext(beanName, "fallback", fallback, + target.type()); + return new QuaferSentinelInvocationHandler(target, dispatch, + new FallbackFactory.Default(fallbackInstance)); + } + if (void.class != fallbackFactory) { + fallbackFactoryInstance = (FallbackFactory) getFromContext( + beanName, "fallbackFactory", fallbackFactory, + FallbackFactory.class); + return new QuaferSentinelInvocationHandler(target, dispatch, + fallbackFactoryInstance); + } + + QuaferFallbackFactory quaferFallbackFactory = new QuaferFallbackFactory(target); + return new QuaferSentinelInvocationHandler(target, dispatch, quaferFallbackFactory); + } + + private Object getFromContext(String name, String type, + Class fallbackType, Class targetType) { + Object fallbackInstance = feignContext.getInstance(name, + fallbackType); + if (fallbackInstance == null) { + throw new IllegalStateException(String.format( + "No %s instance of type %s found for feign client %s", + type, fallbackType, name)); + } + + if (!targetType.isAssignableFrom(fallbackType)) { + throw new IllegalStateException(String.format( + "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", + type, fallbackType, targetType, name)); + } + return fallbackInstance; + } + }); + + super.contract(new SentinelContractHolder(contract)); + return super.build(); + } + + private Object getFieldValue(Object instance, String fieldName) { + Field field = ReflectionUtils.findField(instance.getClass(), fieldName); + field.setAccessible(true); + try { + return field.get(instance); + } catch (IllegalAccessException e) { + // ignore + } + return null; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + feignContext = this.applicationContext.getBean(FeignContext.class); + } + } + +} diff --git a/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferSentinelInvocationHandler.java b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferSentinelInvocationHandler.java new file mode 100644 index 0000000..313df7e --- /dev/null +++ b/engine-facility/facility-sdk-sentinel/src/main/java/com/liuhung/engine/facility/sentinel/enhance/QuaferSentinelInvocationHandler.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.sentinel.enhance; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.alibaba.cloud.sentinel.feign.SentinelContractHolder; +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import feign.Feign; +import feign.InvocationHandlerFactory.MethodHandler; +import feign.MethodMetadata; +import feign.Target; +import feign.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + *

Description: 复制原有代码,扩展支持统一 fallback 工厂

+ * + * @author : liuh + * @date : 2022/5/30 15:03 + */ +public class QuaferSentinelInvocationHandler implements InvocationHandler { + + private static final Logger log = LoggerFactory.getLogger(QuaferSentinelInvocationHandler.class); + + private final Target target; + + private final Map dispatch; + + private FallbackFactory fallbackFactory; + + private Map fallbackMethodMap; + + QuaferSentinelInvocationHandler(Target target, Map dispatch, + FallbackFactory fallbackFactory) { + this.target = Util.checkNotNull(target, "target"); + this.dispatch = Util.checkNotNull(dispatch, "dispatch"); + this.fallbackFactory = fallbackFactory; + this.fallbackMethodMap = toFallbackMethod(dispatch); + } + + QuaferSentinelInvocationHandler(Target target, Map dispatch) { + this.target = Util.checkNotNull(target, "target"); + this.dispatch = Util.checkNotNull(dispatch, "dispatch"); + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + if ("equals".equals(method.getName())) { + try { + Object otherHandler = args.length > 0 && args[0] != null + ? Proxy.getInvocationHandler(args[0]) + : null; + return equals(otherHandler); + } catch (IllegalArgumentException e) { + return false; + } + } else if ("hashCode".equals(method.getName())) { + return hashCode(); + } else if ("toString".equals(method.getName())) { + return toString(); + } + + Object result; + MethodHandler methodHandler = this.dispatch.get(method); + // only handle by HardCodedTarget + if (target instanceof Target.HardCodedTarget) { + Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target; + MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP + .get(hardCodedTarget.type().getName() + + Feign.configKey(hardCodedTarget.type(), method)); + // resource default is HttpMethod:protocol://url + if (methodMetadata == null) { + result = methodHandler.invoke(args); + } else { + String resourceName = methodMetadata.template().method().toUpperCase() + + ":" + hardCodedTarget.url() + methodMetadata.template().path(); + Entry entry = null; + try { + ContextUtil.enter(resourceName); + entry = SphU.entry(resourceName, EntryType.OUT, 1, args); + result = methodHandler.invoke(args); + } catch (Throwable ex) { + // fallback handle + if (!BlockException.isBlockException(ex)) { + Tracer.traceEntry(ex, entry); + } + if (fallbackFactory != null) { + try { + Object fallbackResult = fallbackMethodMap.get(method) + .invoke(fallbackFactory.create(ex), args); + return fallbackResult; + } catch (IllegalAccessException e) { + // shouldn't happen as method is public due to being an + // interface + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e.getCause()); + } + } else { + // throw exception if fallbackFactory is null + // 若是R类型 执行自动降级返回R + if (Result.class == method.getReturnType()) { + log.error("[Quafer] |- Feign service call exception", ex); + return Result.failure(ex.getLocalizedMessage()); + } else { + throw ex; + } + } + } finally { + if (entry != null) { + entry.exit(1, args); + } + ContextUtil.exit(); + } + } + } else { + // other target type using default strategy + result = methodHandler.invoke(args); + } + + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof QuaferSentinelInvocationHandler) { + QuaferSentinelInvocationHandler other = (QuaferSentinelInvocationHandler) obj; + return target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return target.toString(); + } + + static Map toFallbackMethod(Map dispatch) { + Map result = new LinkedHashMap<>(); + for (Method method : dispatch.keySet()) { + method.setAccessible(true); + result.put(method, method); + } + return result; + } +} diff --git a/engine-facility/facility-spring-boot-starter/README.md b/engine-facility/facility-spring-boot-starter/README.md new file mode 100644 index 0000000..451e874 --- /dev/null +++ b/engine-facility/facility-spring-boot-starter/README.md @@ -0,0 +1,5 @@ +## Facility Starter 模块组件 + +**包含以下内容:** + +1. 包含微服务必须基础设施依赖。 diff --git a/engine-facility/facility-spring-boot-starter/pom.xml b/engine-facility/facility-spring-boot-starter/pom.xml new file mode 100644 index 0000000..15bfa93 --- /dev/null +++ b/engine-facility/facility-spring-boot-starter/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + + engine-facility + com.liuhung.engine + 2.7.8.0 + + + facility-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + facility-sdk-log + + + com.liuhung.engine + facility-sdk-sentinel + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + org.springframework.cloud + spring-cloud-context + + + com.alibaba.nacos + nacos-client + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + org.springframework.cloud + spring-cloud-context + + + com.alibaba.nacos + nacos-client + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + + com.alibaba.nacos + nacos-client + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + + + + \ No newline at end of file diff --git a/engine-facility/facility-spring-boot-starter/src/main/java/com/liuhung/engine/facility/autoconfigure/AutoConfiguration.java b/engine-facility/facility-spring-boot-starter/src/main/java/com/liuhung/engine/facility/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..f08bc75 --- /dev/null +++ b/engine-facility/facility-spring-boot-starter/src/main/java/com/liuhung/engine/facility/autoconfigure/AutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.facility.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: 基础设置自动配置

+ * + * @author : liuh + * @date : 2022/2/5 19:09 + */ +@Configuration(proxyBeanMethods = false) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Facility Starter] Auto Configure."); + } +} diff --git a/engine-facility/facility-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-facility/facility-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..4524063 --- /dev/null +++ b/engine-facility/facility-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.liuhung.engine.facility.autoconfigure.AutoConfiguration +com.liuhung.engine.facility.sentinel.autoconfigure.FacilitySentinelAutoConfiguration +com.liuhung.engine.facility.log.autoconfigure.FacilityLogAutoConfiguration \ No newline at end of file diff --git a/engine-facility/pom.xml b/engine-facility/pom.xml new file mode 100644 index 0000000..69dc5f7 --- /dev/null +++ b/engine-facility/pom.xml @@ -0,0 +1,49 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + engine-facility + 2.7.8.0 + pom + + + facility-core + facility-sdk-sentinel + facility-sdk-log + facility-spring-boot-starter + + + \ No newline at end of file diff --git a/engine-message/message-core/pom.xml b/engine-message/message-core/pom.xml new file mode 100644 index 0000000..6e500ec --- /dev/null +++ b/engine-message/message-core/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-message + com.liuhung.engine + 2.7.8.0 + + + message-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + assistant-core + + + + \ No newline at end of file diff --git a/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/constants/MessageConstants.java b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/constants/MessageConstants.java new file mode 100644 index 0000000..2b9cf36 --- /dev/null +++ b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/constants/MessageConstants.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: 消息模块常量

+ * + * @author : liuh + * @date : 2022/2/4 17:06 + */ +public interface MessageConstants extends BaseConstants { + + String MSG_AREA_PREFIX = AREA_PREFIX + "msg:"; + + String REDIS_CURRENT_ONLINE_USER = MSG_AREA_PREFIX + "online:user"; + String REGION_MESSAGE_ANNOUNCEMENT = MSG_AREA_PREFIX + "system_announcement"; + String REGION_MESSAGE_DIALOGUE_CONTACT = MSG_AREA_PREFIX + "personal:contact"; + String REGION_MESSAGE_DIALOGUE = MSG_AREA_PREFIX + "personal:dialogue"; + String REGION_MESSAGE_DIALOGUE_DETAIL = MSG_AREA_PREFIX + "personal:dialogue:detail"; + String REGION_MESSAGE_NOTIFICATION = MSG_AREA_PREFIX + "notification_queue"; + String REGION_MESSAGE_PULL_STAMP = MSG_AREA_PREFIX + "pull_stamp"; + + String PROPERTY_PREFIX_MESSAGE = PROPERTY_PREFIX_HERODOTUS + ".message"; + + String PROPERTY_PREFIX_ENGINE_IO = PROPERTY_PREFIX_MESSAGE + ".engineio"; + String PROPERTY_PREFIX_WEBSOCKET = PROPERTY_PREFIX_MESSAGE + ".websocket"; + + String WEBSOCKET_CHANNEL_PROXY_BROADCAST = "/broadcast"; + String WEBSOCKET_CHANNEL_PROXY_PERSONAL = "/personal"; + String WEBSOCKET_DESTINATION_BROADCAST_NOTICE = WEBSOCKET_CHANNEL_PROXY_BROADCAST + "/notice"; + String WEBSOCKET_DESTINATION_BROADCAST_ONLINE = WEBSOCKET_CHANNEL_PROXY_BROADCAST + "/online"; + String WEBSOCKET_DESTINATION_PERSONAL_MESSAGE = WEBSOCKET_CHANNEL_PROXY_PERSONAL + "/message"; + + String ENGINE_IO_INTERCEPTOR_PATH = "/engine.io/"; + String ENGINE_IO_ATTRIBUTE_BRIDGE = "engineIo.bridge"; + String ENGINE_IO_ATTRIBUTE_QUERY = "engineIo.query"; + String ENGINE_IO_ATTRIBUTE_HEADERS = "engineIo.headers"; +} diff --git a/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/constants/MessageErrorCode.java b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/constants/MessageErrorCode.java new file mode 100644 index 0000000..0a230cb --- /dev/null +++ b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/constants/MessageErrorCode.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.ErrorCode; + +/** + *

Description: WebSocket 统一错误代码定义

+ * + * @author : liuh + * @date : 2022/12/29 15:57 + */ +public interface MessageErrorCode extends ErrorCode { + + int ILLEGAL_CHANNEL = MESSAGE_MODULE_406_BEGIN + 1; + int PRINCIPAL_NOT_FOUND = ILLEGAL_CHANNEL + 1; +} diff --git a/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/enums/NotificationCategory.java b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/enums/NotificationCategory.java new file mode 100644 index 0000000..9f01359 --- /dev/null +++ b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/enums/NotificationCategory.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 通知类别

+ * + * @author : liuh + * @date : 2022/12/24 20:34 + */ +@Schema(title = "通知类别") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum NotificationCategory implements BaseUiEnum { + + /** + * enum + */ + ANNOUNCEMENT(0, "系统公告"), + DIALOGUE(1, "私信"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(title = "说明") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (NotificationCategory notificationCategory : NotificationCategory.values()) { + INDEX_MAP.put(notificationCategory.getValue(), notificationCategory); + JSON_STRUCTURE.add(notificationCategory.getValue(), + ImmutableMap.builder() + .put("value", notificationCategory.getValue()) + .put("key", notificationCategory.name()) + .put("text", notificationCategory.getDescription()) + .put("index", notificationCategory.getValue()) + .build()); + } + } + + NotificationCategory(Integer value, String description) { + this.value = value; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum索引 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDescription() { + return description; + } + + public static NotificationCategory get(Integer index) { + return INDEX_MAP.getOrDefault(index, null); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/exception/IllegalChannelException.java b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/exception/IllegalChannelException.java new file mode 100644 index 0000000..0054e0a --- /dev/null +++ b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/exception/IllegalChannelException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.message.core.constants.MessageErrorCode; +import org.apache.http.HttpStatus; + +/** + *

Description: Web Socket Channel 错误

+ * + * @author : liuh + * @date : 2021/10/24 18:45 + */ +public class IllegalChannelException extends PlatformException { + + public IllegalChannelException() { + super(); + } + + public IllegalChannelException(String message) { + super(message); + } + + public IllegalChannelException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalChannelException(Throwable cause) { + super(cause); + } + + protected IllegalChannelException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(MessageErrorCode.ILLEGAL_CHANNEL, "WebSocket Channel 设置错误", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/exception/PrincipalNotFoundException.java b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/exception/PrincipalNotFoundException.java new file mode 100644 index 0000000..c1f12b6 --- /dev/null +++ b/engine-message/message-core/src/main/java/com/liuhung/engine/message/core/exception/PrincipalNotFoundException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.message.core.constants.MessageErrorCode; +import org.apache.http.HttpStatus; + +/** + *

Description: 无法找到 Principal 错误

+ * + * @author : liuh + * @date : 2021/10/24 18:45 + */ +public class PrincipalNotFoundException extends PlatformException { + + public PrincipalNotFoundException() { + super(); + } + + public PrincipalNotFoundException(String message) { + super(message); + } + + public PrincipalNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public PrincipalNotFoundException(Throwable cause) { + super(cause); + } + + protected PrincipalNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public Feedback getFeedback() { + return new Feedback(MessageErrorCode.PRINCIPAL_NOT_FOUND, "WebSocket 无法获取用户身份信息", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-message/message-sdk-mailing/pom.xml b/engine-message/message-sdk-mailing/pom.xml new file mode 100644 index 0000000..269d87b --- /dev/null +++ b/engine-message/message-sdk-mailing/pom.xml @@ -0,0 +1,53 @@ + + + + + 4.0.0 + + com.liuhung.engine + engine-message + 2.7.8.0 + + + message-sdk-mailing + 2.7.8.0 + jar + + + + com.liuhung.engine + message-core + + + + com.liuhung.engine + rest-core + + + + \ No newline at end of file diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/base/BaseSenderEntity.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/base/BaseSenderEntity.java new file mode 100644 index 0000000..106a53f --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/base/BaseSenderEntity.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.base; + +import com.liuhung.engine.data.core.entity.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; + +/** + *

Description: 基础发送者实体

+ * + * @author : liuh + * @date : 2022/12/16 22:30 + */ +@MappedSuperclass +public abstract class BaseSenderEntity extends BaseEntity { + + @Schema(name = "发送人ID") + @Column(name = "sender_id", length = 64) + private String senderId; + + @Schema(name = "发送人名称", title = "冗余信息,增加该字段减少重复查询") + @Column(name = "sender_name", length = 50) + private String senderName; + + @Schema(name = "发送人头像") + @Column(name = "sender_avatar", length = 1000) + private String senderAvatar; + + public String getSenderId() { + return senderId; + } + + public void setSenderId(String senderId) { + this.senderId = senderId; + } + + public String getSenderName() { + return senderName; + } + + public void setSenderName(String senderName) { + this.senderName = senderName; + } + + public String getSenderAvatar() { + return senderAvatar; + } + + public void setSenderAvatar(String senderAvatar) { + this.senderAvatar = senderAvatar; + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/configuration/MessageMailingConfiguration.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/configuration/MessageMailingConfiguration.java new file mode 100644 index 0000000..7693d1c --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/configuration/MessageMailingConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import javax.annotation.PostConstruct; + +/** + *

Description: 消息互动

+ * + * @author : liuh + * @date : 2022/12/6 21:29 + */ +@Configuration(proxyBeanMethods = false) +@EntityScan(basePackages = { + "com.liuhung.engine.message.mailing.entity" +}) +@EnableJpaRepositories(basePackages = { + "com.liuhung.engine.message.mailing.repository", +}) +@ComponentScan(basePackages = { + "com.liuhung.engine.message.mailing.service", + "com.liuhung.engine.message.mailing.controller", +}) +public class MessageMailingConfiguration { + + private static final Logger log = LoggerFactory.getLogger(MessageMailingConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Message Mailing] Auto Configure."); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/DialogueContactController.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/DialogueContactController.java new file mode 100644 index 0000000..721ea6b --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/DialogueContactController.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.data.core.service.WriteableService; +import com.liuhung.engine.message.mailing.entity.DialogueContact; +import com.liuhung.engine.message.mailing.service.DialogueContactService; +import com.liuhung.engine.rest.core.controller.BaseWriteableRestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + *

Description: DialogueContactController

+ * + * @author : liuh + * @date : 2022/12/17 0:05 + */ +@RestController +@RequestMapping("/dialogue/contact") +@Tags({ + @Tag(name = "消息管理接口"), + @Tag(name = "私信管理接口"), + @Tag(name = "私信联系人管理接口") +}) +public class DialogueContactController extends BaseWriteableRestController { + + private final DialogueContactService dialogueContactService; + + public DialogueContactController(DialogueContactService dialogueContactService) { + this.dialogueContactService = dialogueContactService; + } + + @Override + public WriteableService getWriteableService() { + return dialogueContactService; + } + + @Operation(summary = "条件查询私信联系人分页数据", description = "根据输入的字段条件查询联系人信息", + responses = {@ApiResponse(description = "联系人列表", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class)))}) + @Parameters({ + @Parameter(name = "pageNumber", required = true, description = "当前页码", schema = @Schema(type = "integer")), + @Parameter(name = "pageSize", required = true, description = "每页显示数量", schema = @Schema(type = "integer")), + @Parameter(name = "receiverId", required = true, description = "收信人ID,即当前用户ID"), + }) + @GetMapping("/condition") + public Result> findByCondition(@NotNull @RequestParam("pageNumber") Integer pageNumber, + @NotNull @RequestParam("pageSize") Integer pageSize, + @NotNull @RequestParam("receiverId") String receiverId) { + Page pages = dialogueContactService.findByCondition(pageNumber, pageSize, receiverId); + return result(pages); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/DialogueDetailController.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/DialogueDetailController.java new file mode 100644 index 0000000..3dc5c08 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/DialogueDetailController.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.data.core.service.WriteableService; +import com.liuhung.engine.message.mailing.entity.DialogueDetail; +import com.liuhung.engine.message.mailing.service.DialogueDetailService; +import com.liuhung.engine.rest.core.annotation.Idempotent; +import com.liuhung.engine.rest.core.controller.BaseWriteableRestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + *

Description: DialogueDetailController

+ * + * @author : liuh + * @date : 2022/12/17 12:49 + */ +@RestController +@RequestMapping("/dialogue/detail") +@Tags({ + @Tag(name = "消息管理接口"), + @Tag(name = "私信管理接口"), + @Tag(name = "私信详情管理接口") +}) +public class DialogueDetailController extends BaseWriteableRestController { + + private final DialogueDetailService dialogueDetailService; + + public DialogueDetailController(DialogueDetailService dialogueDetailService) { + this.dialogueDetailService = dialogueDetailService; + } + + @Override + public WriteableService getWriteableService() { + return dialogueDetailService; + } + + @Operation(summary = "条件查询私信详情分页数据", description = "根据输入的字段条件查询详情信息", + responses = {@ApiResponse(description = "详情列表", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class)))}) + @Parameters({ + @Parameter(name = "pageNumber", required = true, description = "当前页码", schema = @Schema(type = "integer")), + @Parameter(name = "pageSize", required = true, description = "每页显示数量", schema = @Schema(type = "integer")), + @Parameter(name = "dialogueId", required = true, description = "对话ID"), + }) + @GetMapping("/condition") + public Result> findByCondition(@NotNull @RequestParam("pageNumber") Integer pageNumber, + @NotNull @RequestParam("pageSize") Integer pageSize, + @NotNull @RequestParam("dialogueId") String dialogueId) { + Page pages = dialogueDetailService.findByCondition(pageNumber, pageSize, dialogueId); + return result(pages); + } + + @Idempotent + @Operation(summary = "根据dialogueId删除私信整个对话", description = "根据实体dialogueId删除私信整个对话,包括相关联的关联数据", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = "application/json")), + responses = {@ApiResponse(description = "操作消息", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "id", required = true, in = ParameterIn.PATH, description = "DialogueId 关联私信联系人和私信详情的ID") + }) + @DeleteMapping("/dialogue/{id}") + public Result deleteDialogueById(@PathVariable String id) { + dialogueDetailService.deleteDialogueById(id); + return Result.success("删除成功"); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/NotificationController.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/NotificationController.java new file mode 100644 index 0000000..7514e69 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/controller/NotificationController.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.data.core.service.WriteableService; +import com.liuhung.engine.message.core.enums.NotificationCategory; +import com.liuhung.engine.message.mailing.entity.Notification; +import com.liuhung.engine.message.mailing.service.NotificationService; +import com.liuhung.engine.rest.core.controller.BaseWriteableRestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + *

Description: 系统通知

+ * + * @author : liuh + * @date : 2022/12/17 14:19 + */ +@RestController +@RequestMapping("/notification") +@Tags({ + @Tag(name = "消息管理接口"), + @Tag(name = "通知管理接口") +}) +public class NotificationController extends BaseWriteableRestController { + + private final NotificationService notificationService; + + public NotificationController(NotificationService notificationService) { + this.notificationService = notificationService; + } + + @Override + public WriteableService getWriteableService() { + return notificationService; + } + + @Operation(summary = "条件查询通知信息分页数据", description = "根据输入的字段条件查询通知信息", + responses = {@ApiResponse(description = "详情列表", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class)))}) + @Parameters({ + @Parameter(name = "pageNumber", required = true, description = "当前页码", schema = @Schema(type = "integer")), + @Parameter(name = "pageSize", required = true, description = "每页显示数量", schema = @Schema(type = "integer")), + @Parameter(name = "userId", required = true, description = "用户ID"), + @Parameter(name = "category", description = "通知类别", schema = @Schema(type = "integer")), + @Parameter(name = "read", description = "是否已读", schema = @Schema(type = "boolean")), + }) + @GetMapping("/condition") + public Result> findByCondition(@NotNull @RequestParam(value = "pageNumber") Integer pageNumber, + @NotNull @RequestParam(value = "pageSize") Integer pageSize, + @NotNull @RequestParam(value = "userId") String userId, + @RequestParam(value = "category", required = false) Integer category, + @RequestParam(value = "read", required = false) Boolean read) { + NotificationCategory notificationCategory = NotificationCategory.get(category); + if (ObjectUtils.isNotEmpty(notificationCategory)) { + if (notificationCategory == NotificationCategory.ANNOUNCEMENT) { + notificationService.pullAnnouncements(userId); + } + } + + Page pages = notificationService.findByCondition(pageNumber, pageSize, userId, notificationCategory, read); + return result(pages); + } + + @Operation(summary = "全部通知已读", description = "根据用户ID设置该用户的全部通知为已读", + responses = {@ApiResponse(description = "影响数据条目数", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "userId", description = "用户ID") + }) + @PutMapping("/all-read") + public Result setAllRead(@RequestParam("userId") String userId) { + Integer result = notificationService.setAllRead(userId); + return Result.success("操作成功", result); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Announcement.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Announcement.java new file mode 100644 index 0000000..d4f5541 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Announcement.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.entity; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.mailing.base.BaseSenderEntity; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + +/** + *

Description: 站内通知

+ * + * @author : liuh + * @date : 2022/12/6 21:33 + */ +@Schema(name = "系统公告") +@Entity +@Table(name = "msg_announcement", indexes = {@Index(name = "msg_announcement_id_idx", columnList = "announcement_id")}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_ANNOUNCEMENT) +public class Announcement extends BaseSenderEntity { + + @Schema(name = "公告ID") + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "announcement_id", length = 64) + private String announcementId; + + @Schema(name = "公告标题") + @Column(name = "title", length = 128) + private String title; + + @Schema(name = "公告内容") + @Column(name = "content", columnDefinition = "TEXT") + private String content; + + public String getAnnouncementId() { + return announcementId; + } + + public void setAnnouncementId(String announcementId) { + this.announcementId = announcementId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("announcementId", announcementId) + .add("title", title) + .add("content", content) + .toString(); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Dialogue.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Dialogue.java new file mode 100644 index 0000000..a9aa435 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Dialogue.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.entity; + +import com.liuhung.engine.data.core.entity.BaseEntity; +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + +/** + *

Description: 私信对话

+ *

+ * 本质是一张冗余表,作为中间桥梁连接私信联系和私信对话详情。同时保存对话的最新一条信息,方便展示。 + * + * @author : liuh + * @date : 2022/12/7 11:01 + */ +@Schema(name = "私信对话") +@Entity +@Table(name = "msg_dialogue", indexes = {@Index(name = "msg_dialogue_id_idx", columnList = "dialogue_id")}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_DIALOGUE) +public class Dialogue extends BaseEntity { + + @Schema(name = "对话ID") + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "dialogue_id", length = 64) + private String dialogueId; + + @Schema(name = "最新内容") + @Column(name = "latest_news", columnDefinition = "TEXT") + private String latestNews; + + public String getDialogueId() { + return dialogueId; + } + + public void setDialogueId(String dialogueId) { + this.dialogueId = dialogueId; + } + + public String getLatestNews() { + return latestNews; + } + + public void setLatestNews(String latestNews) { + this.latestNews = latestNews; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("dialogueId", dialogueId) + .add("latestNews", latestNews) + .toString(); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/DialogueContact.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/DialogueContact.java new file mode 100644 index 0000000..4410e1d --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/DialogueContact.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.entity; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.mailing.base.BaseSenderEntity; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + +/** + *

Description: 私信联系

+ *

+ * 私信双相关系存储。 + * + * @author : liuh + * @date : 2022/12/7 11:03 + */ +@Schema(name = "私信联系") +@Entity +@Table(name = "msg_dialogue_contact", indexes = { + @Index(name = "msg_dialogue_contact_id_idx", columnList = "contact_id"), + @Index(name = "msg_dialogue_contact_sid_idx", columnList = "sender_id"), +}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_DIALOGUE_CONTACT) +public class DialogueContact extends BaseSenderEntity { + + @Schema(name = "联系ID") + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "contact_id", length = 64) + private String contactId; + + @Schema(name = "接收人ID") + @Column(name = "receiver_id", length = 64) + private String receiverId; + + @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_DIALOGUE) + @Schema(title = "对话ID") + @ManyToOne + @JoinColumn(name = "dialogue_id", nullable = false) + private Dialogue dialogue; + + public String getContactId() { + return contactId; + } + + public void setContactId(String contactId) { + this.contactId = contactId; + } + + public String getReceiverId() { + return receiverId; + } + + public void setReceiverId(String receiverId) { + this.receiverId = receiverId; + } + + public Dialogue getDialogue() { + return dialogue; + } + + public void setDialogue(Dialogue dialogue) { + this.dialogue = dialogue; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("contactId", contactId) + .add("receiverId", receiverId) + .add("dialogue", dialogue) + .toString(); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/DialogueDetail.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/DialogueDetail.java new file mode 100644 index 0000000..f57cdaf --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/DialogueDetail.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.entity; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.mailing.base.BaseSenderEntity; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + + +/** + *

Description: 私信对话详情

+ * + * @author : liuh + * @date : 2022/12/7 11:03 + */ +@Schema(name = "私信对话详情") +@Entity +@Table(name = "msg_dialogue_detail", indexes = { + @Index(name = "msg_dialogue_detail_id_idx", columnList = "detail_id"), + @Index(name = "msg_dialogue_detail_sid_idx", columnList = "sender_id"), + @Index(name = "msg_dialogue_detail_rid_idx", columnList = "receiver_id"), + @Index(name = "msg_dialogue_detail_did_idx", columnList = "dialogue_id") +}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_DIALOGUE_DETAIL) +public class DialogueDetail extends BaseSenderEntity { + + @Schema(name = "对话详情ID") + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "detail_id", length = 64) + private String detailId; + + @Schema(name = "接收人ID") + @Column(name = "receiver_id", length = 64) + private String receiverId; + + @Schema(name = "接收人名称", title = "冗余信息,增加该字段减少重复查询") + @Column(name = "receiver_name", length = 50) + private String receiverName; + + @Schema(name = "发送人头像") + @Column(name = "receiver_avatar", length = 1000) + private String receiverAvatar; + + @Schema(name = "公告内容") + @Column(name = "content", columnDefinition = "TEXT") + private String content; + + @Schema(name = "对话ID") + @Column(name = "dialogue_id", length = 64) + private String dialogueId; + + public String getDetailId() { + return detailId; + } + + public void setDetailId(String detailId) { + this.detailId = detailId; + } + + public String getReceiverId() { + return receiverId; + } + + public void setReceiverId(String receiverId) { + this.receiverId = receiverId; + } + + public String getReceiverName() { + return receiverName; + } + + public void setReceiverName(String receiverName) { + this.receiverName = receiverName; + } + + public String getReceiverAvatar() { + return receiverAvatar; + } + + public void setReceiverAvatar(String receiverAvatar) { + this.receiverAvatar = receiverAvatar; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getDialogueId() { + return dialogueId; + } + + public void setDialogueId(String dialogueId) { + this.dialogueId = dialogueId; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("detailId", detailId) + .add("receiverId", receiverId) + .add("receiverName", receiverName) + .add("receiverAvatar", receiverAvatar) + .add("content", content) + .add("dialogueId", dialogueId) + .toString(); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Notification.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Notification.java new file mode 100644 index 0000000..e0ed5fc --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/Notification.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.entity; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.core.enums.NotificationCategory; +import com.liuhung.engine.message.mailing.base.BaseSenderEntity; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + + +/** + *

Description: 通知队列

+ * + * @author : liuh + * @date : 2022/12/7 18:19 + */ +@Schema(name = "通知队列") +@Entity +@Table(name = "msg_notification", indexes = { + @Index(name = "msg_notification_id_idx", columnList = "queue_id"), + @Index(name = "msg_notification_sid_idx", columnList = "user_id") +}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_NOTIFICATION) +public class Notification extends BaseSenderEntity { + + @Schema(name = "队列ID") + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "queue_id", length = 64) + private String queueId; + + @Schema(name = "是否已经读取", title = "false 未读,true 已读") + @Column(name = "is_read") + private Boolean read = false; + + @Schema(name = "用户ID") + @Column(name = "user_id", length = 64) + private String userId; + + @Schema(name = "公告内容") + @Column(name = "content", columnDefinition = "TEXT") + private String content; + + @Schema(name = "通知类别", title = "1. 公告,2.私信") + @Column(name = "category") + @Enumerated(EnumType.ORDINAL) + private NotificationCategory category = NotificationCategory.ANNOUNCEMENT; + + public String getQueueId() { + return queueId; + } + + public void setQueueId(String queueId) { + this.queueId = queueId; + } + + public Boolean getRead() { + return read; + } + + public void setRead(Boolean read) { + this.read = read; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public NotificationCategory getCategory() { + return category; + } + + public void setCategory(NotificationCategory category) { + this.category = category; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("queueId", queueId) + .add("read", read) + .add("userId", userId) + .add("content", content) + .add("category", category) + .toString(); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/PullStamp.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/PullStamp.java new file mode 100644 index 0000000..d70a7dc --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/entity/PullStamp.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.entity; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractEntity; +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; +import java.util.Date; + +/** + *

Description: 信息拉取标记

+ * + * @author : liuh + * @date : 2022/12/6 21:34 + */ +@Schema(name = "拉取标记") +@Entity +@Table(name = "msg_pull_stamp", indexes = { + @Index(name = "msg_pull_stamp_id_idx", columnList = "stamp_id"), + @Index(name = "msg_pull_stamp_sid_idx", columnList = "user_id") +}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = MessageConstants.REGION_MESSAGE_PULL_STAMP) +public class PullStamp extends AbstractEntity { + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "stamp_id", length = 64) + private String stampId; + + @Schema(name = "用户ID") + @Column(name = "user_id", length = 64) + private String userId; + + @Schema(name = "来源", title = "预留字段,以备支持不同端的情况") + @Column(name = "source", length = 50) + private String source; + + @Schema(title = "上次拉取时间") + @Column(name = "latest_pull_time", updatable = false) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date latestPullTime = new Date(); + + public String getStampId() { + return stampId; + } + + public void setStampId(String stampId) { + this.stampId = stampId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Date getLatestPullTime() { + return latestPullTime; + } + + public void setLatestPullTime(Date latestPullTime) { + this.latestPullTime = latestPullTime; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("stampId", stampId) + .add("userId", userId) + .add("source", source) + .add("latestPullTime", latestPullTime) + .toString(); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/AnnouncementRepository.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/AnnouncementRepository.java new file mode 100644 index 0000000..d49a15c --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/AnnouncementRepository.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.message.mailing.entity.Announcement; +import org.springframework.data.jpa.repository.QueryHints; + +import javax.persistence.QueryHint; +import java.util.Date; +import java.util.List; + +/** + *

Description: SystemAnnouncementRepository

+ * + * @author : liuh + * @date : 2022/12/7 22:07 + */ +public interface AnnouncementRepository extends BaseRepository { + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + List findAllByCreateTimeAfter(Date stamp); +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueContactRepository.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueContactRepository.java new file mode 100644 index 0000000..b2552f3 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueContactRepository.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.repository; + +import com.liuhung.engine.assistant.core.exception.transaction.TransactionalRollbackException; +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.message.mailing.entity.DialogueContact; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.QueryHint; +import java.util.Optional; + +/** + *

Description: PersonalContactRepository

+ * + * @author : liuh + * @date : 2022/12/7 22:05 + */ +public interface DialogueContactRepository extends BaseRepository { + + @Transactional(rollbackFor = TransactionalRollbackException.class) + @Modifying + @Query("delete from DialogueContact c where c.dialogue.dialogueId = :id") + void deleteAllByDialogueId(@Param("id") String dialogueId); + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + Optional findBySenderIdAndReceiverId(String senderId, String receiverId); +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueDetailRepository.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueDetailRepository.java new file mode 100644 index 0000000..0515297 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueDetailRepository.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.message.mailing.entity.DialogueDetail; + +/** + *

Description: PersonalDialogueDetailRepository

+ * + * @author : liuh + * @date : 2022/12/7 22:06 + */ +public interface DialogueDetailRepository extends BaseRepository { + + void deleteAllByDialogueId(String dialogueId); +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueRepository.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueRepository.java new file mode 100644 index 0000000..afdef35 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/DialogueRepository.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.message.mailing.entity.Dialogue; + +/** + *

Description: PersonalDialogueRepository

+ * + * @author : liuh + * @date : 2022/12/7 22:06 + */ +public interface DialogueRepository extends BaseRepository { + +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/NotificationRepository.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/NotificationRepository.java new file mode 100644 index 0000000..600b546 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/NotificationRepository.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.repository; + +import com.liuhung.engine.assistant.core.exception.transaction.TransactionalRollbackException; +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.message.mailing.entity.Notification; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + *

Description: NotificationQueueRepository

+ * + * @author : liuh + * @date : 2022/12/7 22:03 + */ +public interface NotificationRepository extends BaseRepository { + + @Transactional(rollbackFor = TransactionalRollbackException.class) + @Modifying + @Query("update Notification n set n.read = true where n.userId = :userId") + int updateAllRead(@Param("userId") String userId); +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/PullStampRepository.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/PullStampRepository.java new file mode 100644 index 0000000..8f6d705 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/repository/PullStampRepository.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.message.mailing.entity.PullStamp; +import org.springframework.data.jpa.repository.QueryHints; + +import javax.persistence.QueryHint; +import java.util.Optional; + +/** + *

Description: PullStampRepository

+ * + * @author : liuh + * @date : 2022/12/6 21:56 + */ +public interface PullStampRepository extends BaseRepository { + + @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true")) + Optional findByUserId(String userId); +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/AnnouncementService.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/AnnouncementService.java new file mode 100644 index 0000000..32c72bb --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/AnnouncementService.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.message.mailing.entity.Announcement; +import com.liuhung.engine.message.mailing.repository.AnnouncementRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** + *

Description: SystemAnnouncementService

+ * + * @author : liuh + * @date : 2022/12/7 22:11 + */ +@Service +public class AnnouncementService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(AnnouncementService.class); + + private final AnnouncementRepository announcementRepository; + + public AnnouncementService(AnnouncementRepository announcementRepository) { + this.announcementRepository = announcementRepository; + } + + @Override + public BaseRepository getRepository() { + return announcementRepository; + } + + public List pullAnnouncements(Date stamp) { + List announcements = announcementRepository.findAllByCreateTimeAfter(stamp); + log.debug("[Quafer] |- Announcement Service pullAnnouncements."); + return announcements; + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueContactService.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueContactService.java new file mode 100644 index 0000000..5ddcfc1 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueContactService.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.message.mailing.entity.Dialogue; +import com.liuhung.engine.message.mailing.entity.DialogueContact; +import com.liuhung.engine.message.mailing.entity.DialogueDetail; +import com.liuhung.engine.message.mailing.repository.DialogueContactRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; + +import javax.persistence.criteria.Predicate; +import java.util.ArrayList; +import java.util.List; + +/** + *

Description: PersonalContactService

+ * + * @author : liuh + * @date : 2022/12/7 22:09 + */ +@Service +public class DialogueContactService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(DialogueContactService.class); + + private final DialogueContactRepository dialogueContactRepository; + + public DialogueContactService(DialogueContactRepository dialogueContactRepository) { + this.dialogueContactRepository = dialogueContactRepository; + } + + @Override + public BaseRepository getRepository() { + return dialogueContactRepository; + } + + public List createContact(Dialogue dialogue, DialogueDetail dialogueDetail) { + DialogueContact contact = new DialogueContact(); + contact.setDialogue(dialogue); + contact.setReceiverId(dialogueDetail.getReceiverId()); + contact.setSenderId(dialogueDetail.getSenderId()); + contact.setSenderName(dialogueDetail.getSenderName()); + contact.setSenderAvatar(dialogueDetail.getSenderAvatar()); + + DialogueContact reverseContext = new DialogueContact(); + reverseContext.setDialogue(dialogue); + reverseContext.setReceiverId(dialogueDetail.getSenderId()); + reverseContext.setSenderId(dialogueDetail.getReceiverId()); + reverseContext.setSenderName(dialogueDetail.getReceiverName()); + reverseContext.setSenderAvatar(dialogueDetail.getReceiverAvatar()); + + List personalContacts = new ArrayList<>(); + personalContacts.add(contact); + personalContacts.add(reverseContext); + + log.debug("[Quafer] |- DialogueContact Service createContact."); + return this.saveAll(personalContacts); + } + + public Page findByCondition(int pageNumber, int pageSize, String receiverId) { + Pageable pageable = PageRequest.of(pageNumber, pageSize); + + Specification specification = (root, criteriaQuery, criteriaBuilder) -> { + + List predicates = new ArrayList<>(); + + predicates.add(criteriaBuilder.equal(root.get("receiverId"), receiverId)); + + Predicate[] predicateArray = new Predicate[predicates.size()]; + criteriaQuery.where(criteriaBuilder.and(predicates.toArray(predicateArray))); + criteriaQuery.orderBy(criteriaBuilder.desc(root.get("createTime"))); + return criteriaQuery.getRestriction(); + }; + + log.debug("[Quafer] |- DialogueContact Service findByCondition."); + return this.findByPage(specification, pageable); + } + + public void deleteByDialogueId(String dialogueId) { + dialogueContactRepository.deleteAllByDialogueId(dialogueId); + } + + public DialogueContact findBySenderIdAndReceiverId(String senderId, String receiverId) { + return dialogueContactRepository.findBySenderIdAndReceiverId(senderId, receiverId).orElse(null); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueDetailService.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueDetailService.java new file mode 100644 index 0000000..e721081 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueDetailService.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.message.core.enums.NotificationCategory; +import com.liuhung.engine.message.mailing.entity.Dialogue; +import com.liuhung.engine.message.mailing.entity.DialogueContact; +import com.liuhung.engine.message.mailing.entity.DialogueDetail; +import com.liuhung.engine.message.mailing.entity.Notification; +import com.liuhung.engine.message.mailing.repository.DialogueDetailRepository; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.criteria.Predicate; +import java.util.ArrayList; +import java.util.List; + +/** + *

Description: PersonalDialogueDetailService

+ * + * @author : liuh + * @date : 2022/12/7 22:10 + */ +@Service +public class DialogueDetailService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(DialogueDetailService.class); + + private final DialogueDetailRepository dialogueDetailRepository; + private final DialogueContactService dialogueContactService; + private final DialogueService dialogueService; + private final NotificationService notificationService; + + public DialogueDetailService(DialogueDetailRepository dialogueDetailRepository, DialogueContactService dialogueContactService, DialogueService dialogueService, NotificationService notificationService) { + this.dialogueDetailRepository = dialogueDetailRepository; + this.dialogueContactService = dialogueContactService; + this.dialogueService = dialogueService; + this.notificationService = notificationService; + } + + @Override + public BaseRepository getRepository() { + return dialogueDetailRepository; + } + + private Notification convertDialogueDetailToNotification(DialogueDetail dialogueDetail) { + Notification notification = new Notification(); + notification.setUserId(dialogueDetail.getReceiverId()); + notification.setContent(dialogueDetail.getContent()); + notification.setSenderId(dialogueDetail.getSenderId()); + notification.setSenderName(dialogueDetail.getSenderName()); + notification.setSenderAvatar(dialogueDetail.getSenderAvatar()); + notification.setCategory(NotificationCategory.DIALOGUE); + return notification; + } + + /** + * 借鉴 Gitee 的私信设计 + * 1. 每个人都可以查看与自己有过私信往来的用户列表。自己可以查看与自己有过联系的人,对方也可以查看与自己有过联系的人 + * 2. 私信往来用户列表中,显示最新一条对话的内容 + * 3. 点开某一个用户,可以查看具体的对话详情。自己和私信对话用户看到的内容一致。 + *

+ * PersonalContact 存储私信双方的关系,存储两条。以及和对话的关联 + * PersonalDialogue 是一个桥梁连接 PersonalContact 和 PersonalDialogueDetail,同时存储一份最新对话副本 + *

+ * 本处的逻辑: + * 发送私信时,首先要判断是否已经创建了 Dialogue + * 1. 如果没有创建 Dialogue,就是私信双方第一对话,那么要先创建 Dialogue,同时要建立私信双方的联系 Contact。保存的私信与将生成好的 DialogueId进行关联。 + * 2. 如果已经有Dialogue,那么就直接保存私信对话,同时更新 Dialogue 中的最新信息。 + * + * @param domain 数据对应实体 + * @return + */ + @Transactional + @Override + public DialogueDetail save(DialogueDetail domain) { + + if (StringUtils.isBlank(domain.getDialogueId())) { + DialogueContact dialogueContact = dialogueContactService.findBySenderIdAndReceiverId(domain.getSenderId(), domain.getReceiverId()); + if (ObjectUtils.isNotEmpty(dialogueContact) && ObjectUtils.isNotEmpty(dialogueContact.getDialogue())) { + String dialogueId = dialogueContact.getDialogue().getDialogueId(); + domain.setDialogueId(dialogueId); + dialogueService.updateDialogue(dialogueId, domain.getContent()); + } else { + Dialogue dialogue = dialogueService.createDialogue(domain.getContent()); + domain.setDialogueId(dialogue.getDialogueId()); + dialogueContactService.createContact(dialogue, domain); + } + } else { + dialogueService.updateDialogue(domain.getDialogueId(), domain.getContent()); + } + + notificationService.save(convertDialogueDetailToNotification(domain)); + + return super.save(domain); + } + + @Transactional + public void deleteDialogueById(String dialogueId) { + dialogueContactService.deleteByDialogueId(dialogueId); + dialogueService.deleteById(dialogueId); + dialogueDetailRepository.deleteAllByDialogueId(dialogueId); + log.debug("[Quafer] |- DialogueDetail Service deleteAllByDialogueId."); + } + + public Page findByCondition(int pageNumber, int pageSize, String dialogueId) { + Pageable pageable = PageRequest.of(pageNumber, pageSize); + + Specification specification = (root, criteriaQuery, criteriaBuilder) -> { + + List predicates = new ArrayList<>(); + + predicates.add(criteriaBuilder.equal(root.get("dialogueId"), dialogueId)); + + Predicate[] predicateArray = new Predicate[predicates.size()]; + criteriaQuery.where(criteriaBuilder.and(predicates.toArray(predicateArray))); + criteriaQuery.orderBy(criteriaBuilder.desc(root.get("createTime"))); + return criteriaQuery.getRestriction(); + }; + + log.debug("[Quafer] |- DialogueDetail Service findByCondition."); + return this.findByPage(specification, pageable); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueService.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueService.java new file mode 100644 index 0000000..3e16f17 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/DialogueService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.message.mailing.entity.Dialogue; +import com.liuhung.engine.message.mailing.repository.DialogueRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + *

Description: PersonalDialogueService

+ * + * @author : liuh + * @date : 2022/12/7 22:09 + */ +@Service +public class DialogueService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(DialogueService.class); + + private final DialogueRepository dialogueRepository; + + public DialogueService(DialogueRepository dialogueRepository) { + this.dialogueRepository = dialogueRepository; + } + + @Override + public BaseRepository getRepository() { + return dialogueRepository; + } + + public Dialogue createDialogue(String content) { + Dialogue dialogue = new Dialogue(); + dialogue.setLatestNews(content); + + log.debug("[Quafer] |- Dialogue Service createDialogue."); + return this.save(dialogue); + } + + public Dialogue updateDialogue(String dialogueId, String content) { + Dialogue dialogue = this.findById(dialogueId); + dialogue.setLatestNews(content); + log.debug("[Quafer] |- updateDialogue Service createDialog."); + return this.save(dialogue); + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/NotificationService.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/NotificationService.java new file mode 100644 index 0000000..d5e5489 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/NotificationService.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.message.core.enums.NotificationCategory; +import com.liuhung.engine.message.mailing.entity.Announcement; +import com.liuhung.engine.message.mailing.entity.Notification; +import com.liuhung.engine.message.mailing.entity.PullStamp; +import com.liuhung.engine.message.mailing.repository.NotificationRepository; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; + +import javax.persistence.criteria.Predicate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

Description: NotificationQueueService

+ * + * @author : liuh + * @date : 2022/12/7 22:08 + */ +@Service +public class NotificationService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(NotificationService.class); + + private final NotificationRepository notificationRepository; + private final PullStampService pullStampService; + private final AnnouncementService announcementService; + + public NotificationService(NotificationRepository notificationRepository, PullStampService pullStampService, AnnouncementService announcementService) { + this.notificationRepository = notificationRepository; + this.pullStampService = pullStampService; + this.announcementService = announcementService; + } + + @Override + public BaseRepository getRepository() { + return notificationRepository; + } + + public void pullAnnouncements(String userId) { + PullStamp pullStamp = pullStampService.getPullStamp(userId); + List systemAnnouncements = announcementService.pullAnnouncements(pullStamp.getLatestPullTime()); + if (CollectionUtils.isNotEmpty(systemAnnouncements)) { + List notificationQueues = convertAnnouncementsToNotifications(userId, systemAnnouncements); + this.saveAll(notificationQueues); + } + } + + public Page findByCondition(int pageNumber, int pageSize, String userId, NotificationCategory category, Boolean read) { + + Pageable pageable = PageRequest.of(pageNumber, pageSize); + + Specification specification = (root, criteriaQuery, criteriaBuilder) -> { + + List predicates = new ArrayList<>(); + + predicates.add(criteriaBuilder.equal(root.get("userId"), userId)); + + if (ObjectUtils.isNotEmpty(category)) { + predicates.add(criteriaBuilder.equal(root.get("category"), category)); + } + + if (ObjectUtils.isNotEmpty(read)) { + predicates.add(criteriaBuilder.equal(root.get("read"), read)); + } + + Predicate[] predicateArray = new Predicate[predicates.size()]; + criteriaQuery.where(criteriaBuilder.and(predicates.toArray(predicateArray))); + criteriaQuery.orderBy(criteriaBuilder.desc(root.get("createTime"))); + return criteriaQuery.getRestriction(); + }; + + log.debug("[Quafer] |- Notification Service findByCondition."); + return this.findByPage(specification, pageable); + } + + private List convertAnnouncementsToNotifications(String userId, List announcements) { + return announcements.stream().map(announcement -> convertAnnouncementToNotification(userId, announcement)).collect(Collectors.toList()); + } + + private Notification convertAnnouncementToNotification(String userId, Announcement announcement) { + Notification notification = new Notification(); + notification.setUserId(userId); + notification.setContent(announcement.getContent()); + notification.setSenderId(announcement.getSenderId()); + notification.setSenderName(announcement.getSenderName()); + notification.setSenderAvatar(announcement.getSenderAvatar()); + notification.setCategory(NotificationCategory.ANNOUNCEMENT); + return notification; + } + + public int setAllRead(String userId) { + int result = notificationRepository.updateAllRead(userId); + log.debug("[Quafer] |- Notification Service setAllRead."); + return result; + } +} diff --git a/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/PullStampService.java b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/PullStampService.java new file mode 100644 index 0000000..98283e3 --- /dev/null +++ b/engine-message/message-sdk-mailing/src/main/java/com/liuhung/engine/message/mailing/service/PullStampService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.mailing.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.message.mailing.entity.PullStamp; +import com.liuhung.engine.message.mailing.repository.PullStampRepository; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Service; + +import java.util.Date; + +/** + *

Description: MessagePullStampService

+ * + * @author : liuh + * @date : 2022/12/7 22:10 + */ +@Service +public class PullStampService extends BaseLayeredService { + + private final PullStampRepository pullStampRepository; + + public PullStampService(PullStampRepository pullStampRepository) { + this.pullStampRepository = pullStampRepository; + } + + @Override + public BaseRepository getRepository() { + return pullStampRepository; + } + + public PullStamp findByUserId(String userId) { + return pullStampRepository.findByUserId(userId).orElse(null); + } + + public PullStamp getPullStamp(String userId) { + + PullStamp stamp = findByUserId(userId); + if (ObjectUtils.isEmpty(stamp)) { + stamp = new PullStamp(); + stamp.setUserId(userId); + } + stamp.setLatestPullTime(new Date()); + + return this.save(stamp); + } +} diff --git a/engine-message/message-sdk-websocket/pom.xml b/engine-message/message-sdk-websocket/pom.xml new file mode 100644 index 0000000..5441073 --- /dev/null +++ b/engine-message/message-sdk-websocket/pom.xml @@ -0,0 +1,74 @@ + + + + + 4.0.0 + + com.liuhung.engine + engine-message + 2.7.8.0 + + + message-sdk-websocket + 2.7.8.0 + jar + + + + com.liuhung.engine + message-sdk-mailing + + + + com.liuhung.engine + cache-sdk-redis + + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + org.springframework.session + spring-session-data-redis + + + + + \ No newline at end of file diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/annotation/EnableQuaferWebSocket.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/annotation/EnableQuaferWebSocket.java new file mode 100644 index 0000000..8b0d3a9 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/annotation/EnableQuaferWebSocket.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.annotation; + +import com.liuhung.engine.cache.redis.annotation.EnableQuaferRedis; +import com.liuhung.engine.message.websocket.configuration.MessageWebSocketConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 开启 WebSocket

+ * + * @author : liuh + * @date : 2021/10/24 18:56 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EnableQuaferRedis +@Import(MessageWebSocketConfiguration.class) +public @interface EnableQuaferWebSocket { +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/MessageWebSocketConfiguration.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/MessageWebSocketConfiguration.java new file mode 100644 index 0000000..8b8cb70 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/MessageWebSocketConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.configuration; + +import com.liuhung.engine.message.websocket.properties.WebSocketProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Import; + +import javax.annotation.PostConstruct; + +/** + *

Description: Web Socket 核心配置

+ * + * @author : liuh + * @date : 2021/10/24 18:54 + */ +@AutoConfiguration +@EnableConfigurationProperties({WebSocketProperties.class}) +@Import({ + WebSocketProcessorConfiguration.class, + WebSocketMessageBrokerConfiguration.class, + WebSocketLogicConfiguration.class +}) +public class MessageWebSocketConfiguration { + + private static final Logger log = LoggerFactory.getLogger(MessageWebSocketConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [Message WebSocket] Auto Configure."); + } + +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketLogicConfiguration.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketLogicConfiguration.java new file mode 100644 index 0000000..f6b671b --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketLogicConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: WebSocket 接口配置

+ * + * @author : liuh + * @date : 2022/12/28 22:17 + */ +@Configuration(proxyBeanMethods = false) +@ComponentScan(basePackages = { + "com.liuhung.engine.message.websocket.service", + "com.liuhung.engine.message.websocket.controller", + "com.liuhung.engine.message.websocket.listener", +}) +public class WebSocketLogicConfiguration { + + private static final Logger log = LoggerFactory.getLogger(WebSocketLogicConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [WebSocket Logic] Auto Configure."); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketMessageBrokerConfiguration.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketMessageBrokerConfiguration.java new file mode 100644 index 0000000..134cc42 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketMessageBrokerConfiguration.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.configuration; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.websocket.interceptor.WebSocketChannelInterceptor; +import com.liuhung.engine.message.websocket.interceptor.WebSocketPrincipalHandshakeHandler; +import com.liuhung.engine.message.websocket.interceptor.WebSocketSessionHandshakeInterceptor; +import com.liuhung.engine.message.websocket.properties.WebSocketProperties; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.messaging.simp.config.ChannelRegistration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.session.Session; +import org.springframework.session.web.socket.config.annotation.AbstractSessionWebSocketMessageBrokerConfigurer; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; + +import javax.annotation.PostConstruct; + +/** + *

Description: WebSocketMessageBrokerConfigurer

+ * + * @author : liuh + * @date : 2022/12/4 19:19 + */ +@AutoConfiguration(after = {WebSocketSessionHandshakeInterceptor.class}) +@EnableScheduling +@EnableWebSocketMessageBroker +public class WebSocketMessageBrokerConfiguration extends AbstractSessionWebSocketMessageBrokerConfigurer { + + private static final Logger log = LoggerFactory.getLogger(WebSocketMessageBrokerConfiguration.class); + + private final WebSocketProperties webSocketProperties; + private final WebSocketChannelInterceptor webSocketChannelInterceptor; + private final WebSocketSessionHandshakeInterceptor webSocketSessionHandshakeInterceptor; + + public WebSocketMessageBrokerConfiguration(WebSocketProperties webSocketProperties, WebSocketChannelInterceptor webSocketChannelInterceptor, WebSocketSessionHandshakeInterceptor webSocketSessionHandshakeInterceptor) { + this.webSocketProperties = webSocketProperties; + this.webSocketChannelInterceptor = webSocketChannelInterceptor; + this.webSocketSessionHandshakeInterceptor = webSocketSessionHandshakeInterceptor; + } + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [WebSocket Message Broker] Auto Configure."); + } + + /** + * 添加 Stomp Endpoint,创建配置客户端尝试连接地址,并对外暴露该接口,这样就可以通过websocket连接上服务 + * + * @param registry {@link StompEndpointRegistry} + */ + @Override + protected void configureStompEndpoints(StompEndpointRegistry registry) { + WebSocketPrincipalHandshakeHandler principalHandshakeHandler = new WebSocketPrincipalHandshakeHandler(); + + /* + * 1. 将 /serviceName/stomp路径注册为Stomp的端点, + * 用户连接了这个端点后就可以进行websocket通讯,支持socketJs + * 2. setAllowedOriginPatterns("*")表示可以跨域 + * 3. withSockJS()表示支持socktJS访问 + * 4. 添加自定义拦截器,这个拦截器是上一个demo自己定义的获取httpsession的拦截器 + */ + registry.addEndpoint(webSocketProperties.getEndpoint()) + .setAllowedOrigins("*") + .addInterceptors(webSocketSessionHandshakeInterceptor) + .setHandshakeHandler(principalHandshakeHandler) + .withSockJS(); + + registry.addEndpoint(webSocketProperties.getEndpoint()) + .setAllowedOrigins("*") + .addInterceptors(webSocketSessionHandshakeInterceptor) + .setHandshakeHandler(principalHandshakeHandler); + } + + /** + * 配置消息代理 + * + * @param registry {@link MessageBrokerRegistry} + */ + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + /* + * enableStompBrokerRelay 配置外部的STOMP服务,需要安装额外的支持 比如rabbitmq或activemq + * 1. 配置代理域,可以配置多个,此段代码配置代理目的地的前缀为 /topicTest 或者 /userTest + * 我们就可以在配置的域上向客户端推送消息 + * 3. 可以通过 setRelayHost 配置代理监听的host,默认为localhost + * 4. 可以通过 setRelayPort 配置代理监听的端口,默认为61613 + * 5. 可以通过 setClientLogin 和 setClientPasscode 配置账号和密码 + * 6. setxxx这种设置方法是可选的,根据业务需要自行配置,也可以使用默认配置 + */ +// registry.enableStompBrokerRelay("/topicTest", "/userTest") +// .setRelayHost("rabbit.someotherserver") +// .setRelayPort(62623); +// .setClientLogin("userName") +// .setClientPasscode("password"); + + // 自定义调度器,用于控制心跳线程 + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + // 线程池线程数,心跳连接开线程 + taskScheduler.setPoolSize(1); + // 线程名前缀 + taskScheduler.setThreadNamePrefix("quafer-websocket-heartbeat-thread-"); + // 初始化 + taskScheduler.initialize(); + + /* + * spring 内置broker对象 广播式应配置一个/topic消息代理,点对点应配置一个/user消息代理, + * 1. 配置代理域,可以配置多个,此段代码配置代理目的地的前缀为 /topicTest 或者 /userTest 我们就可以在配置的域上向客户端推送消息 + * 2. 进行心跳设置,第一值表示server最小能保证发的心跳间隔毫秒数, 第二个值代码server希望client发的心跳间隔毫秒数 + * 3. 可以配置心跳线程调度器 setHeartbeatValue这个不能单独设置,不然不起作用,要配合setTaskScheduler才可以生效 + * 调度器我们可以自己写一个,也可以自己使用默认的调度器 new DefaultManagedTaskScheduler() + */ + registry.enableSimpleBroker(MessageConstants.WEBSOCKET_CHANNEL_PROXY_BROADCAST, MessageConstants.WEBSOCKET_CHANNEL_PROXY_PERSONAL) + .setHeartbeatValue(new long[]{10000, 10000}) + .setTaskScheduler(taskScheduler); + + /* + * 全局使用的消息前缀(客户端订阅路径上会体现出来) + * "/app" 为配置应用服务器的地址前缀,表示所有以/app 开头的客户端消息或请求 + * 都会路由到带有@MessageMapping 注解的方法中 + */ + String[] applicationDestinationPrefixes = webSocketProperties.getApplicationPrefixes(); + if (ArrayUtils.isNotEmpty(applicationDestinationPrefixes)) { + registry.setApplicationDestinationPrefixes(applicationDestinationPrefixes); + } + + /* + * 点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/ + * 1. 配置一对一消息前缀, 客户端接收一对一消息需要配置的前缀 如“'/user/'+userid + '/message'”, + * 是客户端订阅一对一消息的地址 stompClient.subscribe js方法调用的地址 + * 2. 使用@SendToUser发送私信的规则不是这个参数设定,在框架内部是用UserDestinationMessageHandler处理, + * 而不是 AnnotationMethodMessageHandler 或 SimpleBrokerMessageHandler + * or StompBrokerRelayMessageHandler,是在@SendToUser的URL前加“user+sessionId"组成 + */ + if (StringUtils.isNotBlank(webSocketProperties.getUserDestinationPrefix())) { + registry.setUserDestinationPrefix(webSocketProperties.getUserDestinationPrefix()); + } + + /* + * 自定义路径分割符 + * 注释掉的这段代码添加的分割符为. 分割是类级别的@messageMapping和方法级别的@messageMapping的路径 + * 例如类注解路径为 “topic”,方法注解路径为“hello”,那么客户端JS stompClient.send 方法调用的路径为“/app/topic.hello” + * 注释掉此段代码后,类注解路径“/topic”,方法注解路径“/hello”,JS调用的路径为“/app/topic/hello” + */ + //registry.setPathMatcher(new AntPathMatcher(".")); + } + + /** + * 配置发送与接收的消息参数,可以指定消息字节大小,缓存大小,发送超时时间 + * + * @param registration {@link WebSocketTransportRegistration} + */ + @Override + public void configureWebSocketTransport(WebSocketTransportRegistration registration) { + /* + * 1. setMessageSizeLimit 设置消息缓存的字节数大小 字节 + * 2. setSendBufferSizeLimit 设置websocket会话时,缓存的大小 字节 + * 3. setSendTimeLimit 设置消息发送会话超时时间,毫秒 + */ + registration.setMessageSizeLimit(10240) + .setSendBufferSizeLimit(10240) + .setSendTimeLimit(10000); + super.configureWebSocketTransport(registration); + } + + /** + * 采用自定义拦截器,获取connect时候传递的参数 + * 设置输入消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间 + * + * @param registration {@link ChannelRegistration} + */ + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + /* + * 配置消息线程池 + * 1. corePoolSize 配置核心线程池,当线程数小于此配置时,不管线程中有无空闲的线程,都会产生新线程处理任务 + * 2. maxPoolSize 配置线程池最大数,当线程池数等于此配置时,不会产生新线程 + * 3. keepAliveSeconds 线程池维护线程所允许的空闲时间,单位秒 + */ + registration.taskExecutor() + .corePoolSize(10) + .maxPoolSize(20) + .keepAliveSeconds(60); + + /* + * 添加stomp自定义拦截器,可以根据业务做一些处理 + * 消息拦截器,实现ChannelInterceptor接口 + */ + registration.interceptors(webSocketChannelInterceptor); + super.configureClientInboundChannel(registration); + } + + /** + * 设置输出消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间 + * + * @param registration {@link ChannelRegistration} + */ + @Override + public void configureClientOutboundChannel(ChannelRegistration registration) { + registration.taskExecutor().corePoolSize(10) + .maxPoolSize(20) + .keepAliveSeconds(60); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketProcessorConfiguration.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketProcessorConfiguration.java new file mode 100644 index 0000000..1b2ee83 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/configuration/WebSocketProcessorConfiguration.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.configuration; + +import com.liuhung.engine.assistant.core.definition.BearerTokenResolver; +import com.liuhung.engine.message.websocket.interceptor.WebSocketChannelInterceptor; +import com.liuhung.engine.message.websocket.interceptor.WebSocketSessionHandshakeInterceptor; +import com.liuhung.engine.message.websocket.processor.WebSocketBearerTokenResolver; +import com.liuhung.engine.message.websocket.processor.WebSocketMessageSender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.messaging.simp.user.SimpUserRegistry; + +import javax.annotation.PostConstruct; + +/** + *

Description: WebSocket 处理器相关配置

+ * + * @author : liuh + * @date : 2022/12/29 15:52 + */ +@Configuration(proxyBeanMethods = false) +public class WebSocketProcessorConfiguration { + + private static final Logger log = LoggerFactory.getLogger(WebSocketProcessorConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [WebSocket Processor] Auto Configure."); + } + + @Bean + @ConditionalOnMissingBean + public BearerTokenResolver principalResolver() { + WebSocketBearerTokenResolver webSocketPrincipalResolver = new WebSocketBearerTokenResolver(); + log.trace("[Quafer] |- Bean [WebSocket Principal Resolver] Auto Configure."); + return webSocketPrincipalResolver; + } + + @Bean + @ConditionalOnBean(BearerTokenResolver.class) + public WebSocketSessionHandshakeInterceptor webSocketSessionHandshakeInterceptor(BearerTokenResolver bearerTokenResolver) { + WebSocketSessionHandshakeInterceptor webSocketSessionHandshakeInterceptor = new WebSocketSessionHandshakeInterceptor(bearerTokenResolver); + log.trace("[Quafer] |- Bean [Web Socket Session Handshake Interceptor] Auto Configure."); + return webSocketSessionHandshakeInterceptor; + } + + @Bean + public WebSocketMessageSender webSocketMessageSender(SimpMessagingTemplate simpMessagingTemplate, SimpUserRegistry simpUserRegistry) { + WebSocketMessageSender webSocketMessageSender = new WebSocketMessageSender(); + webSocketMessageSender.setSimpMessagingTemplate(simpMessagingTemplate); + webSocketMessageSender.setSimpUserRegistry(simpUserRegistry); + log.trace("[Quafer] |- Bean [Web Socket Message Sender] Auto Configure."); + return webSocketMessageSender; + } + + @Bean + @ConditionalOnMissingBean + public WebSocketChannelInterceptor webSocketChannelInterceptor() { + WebSocketChannelInterceptor webSocketChannelInterceptor = new WebSocketChannelInterceptor(); + log.trace("[Quafer] |- Bean [Web Socket Channel Interceptor] Auto Configure."); + return webSocketChannelInterceptor; + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/controller/WebSocketMessageController.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/controller/WebSocketMessageController.java new file mode 100644 index 0000000..de84842 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/controller/WebSocketMessageController.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.message.websocket.processor.WebSocketMessageSender; +import com.liuhung.engine.message.websocket.service.WebSocketDisplayService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + *

Description: WebSocket 消息接口

+ * + * @author : liuh + * @date : 2022/11/18 14:06 + */ +@RestController +@RequestMapping("/message/websocket") +@Tags({ + @Tag(name = "消息接口"), + @Tag(name = "WebSocket消息接口") +}) +public class WebSocketMessageController { + + private static final Logger log = LoggerFactory.getLogger(WebSocketMessageController.class); + + private final WebSocketMessageSender webSocketMessageSender; + private final WebSocketDisplayService webSocketDisplayService; + + public WebSocketMessageController(WebSocketMessageSender webSocketMessageSender, WebSocketDisplayService webSocketDisplayService) { + this.webSocketMessageSender = webSocketMessageSender; + this.webSocketDisplayService = webSocketDisplayService; + } + @Operation(summary = "后端发送通知", description = "后端发送 WebSocket 广播通知接口", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = "application/json")), + responses = {@ApiResponse(description = "是否成功", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "message", required = true, description = "消息实体") + }) + @PostMapping("/send/notice") + public Result sendNotice(@RequestBody String message) { + + if (StringUtils.isNotBlank(message)) { + webSocketMessageSender.sendNoticeToAll(message); + } + + return Result.success(message); + } + + @Operation(summary = "获取统计信息", description = "获取WebSocket相关的统计信息") + @GetMapping(value = "/stat") + public Result> findAllStat() { + Result> result = new Result<>(); + Map stat = webSocketDisplayService.findAllStat(); + if (MapUtils.isNotEmpty(stat)) { + return Result.success("获取统计信息成功", stat); + } else { + return Result.failure("获取统计信息失败"); + } + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/controller/WebSocketPublishMessageController.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/controller/WebSocketPublishMessageController.java new file mode 100644 index 0000000..a0bad96 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/controller/WebSocketPublishMessageController.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.controller; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.mailing.entity.DialogueDetail; +import com.liuhung.engine.message.mailing.service.DialogueDetailService; +import com.liuhung.engine.message.websocket.domain.WebSocketMessage; +import com.liuhung.engine.message.websocket.domain.WebSocketPrincipal; +import com.liuhung.engine.message.websocket.processor.WebSocketMessageSender; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.web.bind.annotation.RestController; + +/** + *

Description: 前端使用的 publish 响应接口

+ * + * @author : liuh + * @date : 2022/12/5 17:49 + */ +@RestController +public class WebSocketPublishMessageController { + + private static final Logger log = LoggerFactory.getLogger(WebSocketPublishMessageController.class); + + private final WebSocketMessageSender webSocketMessageSender; + + private final DialogueDetailService dialogueDetailService; + + public WebSocketPublishMessageController(WebSocketMessageSender webSocketMessageSender, DialogueDetailService dialogueDetailService) { + this.webSocketMessageSender = webSocketMessageSender; + this.dialogueDetailService = dialogueDetailService; + } + + @MessageMapping("/public/notice") + @SendTo(MessageConstants.WEBSOCKET_DESTINATION_BROADCAST_NOTICE) + public String notice(String message, StompHeaderAccessor headerAccessor) { + System.out.println("---message---" + message); + if (ObjectUtils.isNotEmpty(headerAccessor)) { + System.out.println("---id---" + headerAccessor.getUser().getName()); + } + + return message; + } + + /** + * 发送私信消息。 + * + * @param detail 前端数据 {@link DialogueDetail} + * @param headerAccessor 在WebSocketChannelInterceptor拦截器中绑定上的对象 + */ + @MessageMapping("/private/message") + public void sendPrivateMessage(@Payload DialogueDetail detail, StompHeaderAccessor headerAccessor) { + + WebSocketMessage response = new WebSocketMessage<>(); + response.setTo(detail.getReceiverId()); + response.setChannel(MessageConstants.WEBSOCKET_DESTINATION_PERSONAL_MESSAGE); + + if (StringUtils.isNotBlank(detail.getReceiverId()) && StringUtils.isNotBlank(detail.getReceiverName())) { + if (StringUtils.isBlank(detail.getSenderId()) && StringUtils.isBlank(detail.getSenderName())) { + WebSocketPrincipal sender = (WebSocketPrincipal) headerAccessor.getUser(); + detail.setSenderId(sender.getUserId()); + detail.setSenderName(sender.getUserName()); + detail.setSenderAvatar(sender.getAvatar()); + } + + DialogueDetail result = dialogueDetailService.save(detail); + + + if (ObjectUtils.isNotEmpty(result)) { + response.setPayload("私信发送成功"); + + } else { + response.setPayload("私信发送失败"); + } + } else { + response.setPayload("私信发送失败,参数错误"); + } + + webSocketMessageSender.toUser(response); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/definition/AbstractWebSocketListener.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/definition/AbstractWebSocketListener.java new file mode 100644 index 0000000..8c6e229 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/definition/AbstractWebSocketListener.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.definition; + +import com.liuhung.engine.cache.redis.utils.RedisBitMapUtils; +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.websocket.processor.WebSocketMessageSender; +import com.liuhung.engine.message.websocket.utils.WebSocketUtils; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + +/** + *

Description: 公共 WebSocketUserListener

+ * + * @author : liuh + * @date : 2022/12/29 22:20 + */ +public abstract class AbstractWebSocketListener implements ApplicationListener { + + private final WebSocketMessageSender webSocketMessageSender; + + public AbstractWebSocketListener(WebSocketMessageSender webSocketMessageSender) { + this.webSocketMessageSender = webSocketMessageSender; + } + + private int getOnlineCount() { + Long count = RedisBitMapUtils.bitCount(MessageConstants.REDIS_CURRENT_ONLINE_USER); + return count.intValue(); + } + + protected void syncUserCountToAll() { + int count = WebSocketUtils.getOnlineCount(); + webSocketMessageSender.sendOnlineToAll(count); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketChannel.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketChannel.java new file mode 100644 index 0000000..5aac9c1 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketChannel.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.domain; + +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: WebSocket通道

+ * + * @author : liuh + * @date : 2021/10/24 18:41 + */ +public enum WebSocketChannel { + /** + * 个人通知 + */ + NOTICE("/notice", "个人通知"); + + @Schema(title = "消息端点") + private final String destination; + @Schema(title = "说明") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCT = new ArrayList<>(); + + static { + for (WebSocketChannel webSocketChannel : WebSocketChannel.values()) { + INDEX_MAP.put(webSocketChannel.name(), webSocketChannel); + JSON_STRUCT.add(webSocketChannel.ordinal(), + ImmutableMap.builder() + .put("value", webSocketChannel.ordinal()) + .put("key", webSocketChannel.name()) + .put("text", webSocketChannel.getDescription()) + .build()); + } + } + + WebSocketChannel(String destination, String description) { + this.destination = destination; + this.description = description; + } + + public String getDestination() { + return destination; + } + + public String getDescription() { + return description; + } + + public static WebSocketChannel getWebSocketChannel(String code) { + return INDEX_MAP.get(code); + } + + public static List> getToJsonStruct() { + return JSON_STRUCT; + } +} \ No newline at end of file diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketMessage.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketMessage.java new file mode 100644 index 0000000..3ec065a --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketMessage.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.domain; + +import java.io.Serializable; + +/** + *

Description: WebSocket发送消息参数实体

+ * + * @author : liuh + * @date : 2021/10/24 18:42 + */ +public class WebSocketMessage implements Serializable { + + private String to; + + private String channel; + + private T payload; + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public T getPayload() { + return payload; + } + + public void setPayload(T payload) { + this.payload = payload; + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketPrincipal.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketPrincipal.java new file mode 100644 index 0000000..88c67b2 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/domain/WebSocketPrincipal.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.domain; + +import com.liuhung.engine.assistant.core.domain.PrincipalDetails; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import java.security.Principal; +import java.util.Set; + +/** + *

Description: Websocket登录连接对象

+ *

+ * 用于保存websocket连接过程中需要存储的业务参数 + * + * @author : liuh + * @date : 2021/10/24 18:43 + */ +public class WebSocketPrincipal implements Principal { + + private String userId; + private String userName; + + private String employeeId; + + private String avatar; + + private Set roles; + + public WebSocketPrincipal(PrincipalDetails details) { + this.userId = details.getOpenId(); + this.userName = details.getUserName(); + this.employeeId = details.getEmployeeId(); + this.avatar = details.getAvatar(); + this.roles = details.getRoles(); + } + + public WebSocketPrincipal(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + /** + * 这里的 Name 是发送信息目标的标识。 + *

+ * 使用 UserName 可控度不高,使用也不方便。直接用ID + * + * @return WebSocket 用户的唯一标识 + */ + @Override + public String getName() { + return this.userId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getEmployeeId() { + return employeeId; + } + + public void setEmployeeId(String employeeId) { + this.employeeId = employeeId; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WebSocketPrincipal that = (WebSocketPrincipal) o; + return Objects.equal(userId, that.userId); + } + + @Override + public int hashCode() { + return Objects.hashCode(userId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("userId", userId) + .add("userName", userName) + .add("employeeId", employeeId) + .add("avatar", avatar) + .toString(); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketChannelInterceptor.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketChannelInterceptor.java new file mode 100644 index 0000000..5a0b731 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketChannelInterceptor.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.interceptor; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.message.websocket.domain.WebSocketPrincipal; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; + +import java.util.List; + +/** + *

Description: Websocket消息监听

+ *

+ * 用于监听websocket用户连接情况 + * + * @author : liuh + * @date : 2021/10/24 18:50 + */ +public class WebSocketChannelInterceptor implements ChannelInterceptor { + + private static final Logger log = LoggerFactory.getLogger(WebSocketChannelInterceptor.class); + + /** + * 在消息发送之前调用,方法中可以对消息进行修改,如果此方法返回值为空,则不会发生实际的消息发送调用 + * + * @param message {@link Message} + * @param channel {@link MessageChannel} + * @return {@link Message} + */ + @Override + public Message preSend(Message message, MessageChannel channel) { + + StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + + WebSocketPrincipal principal = (WebSocketPrincipal) accessor.getUser(); + + if (ObjectUtils.isEmpty(principal)) { + log.warn("[Quafer] |- WebSocket channel cannot fetch user principal."); + return null; + } + + StompCommand command = accessor.getCommand(); + if (ObjectUtils.isNotEmpty(command)) { + switch (command) { + /* + * 判断是否为首次连接请求,如果已经连接过,直接返回message + */ + case CONNECT: + /* + * 1. 如果是老版本 stomp-client,这里可以获取到 stompClient.connect(headers, function (frame){.......}) 中header的信息 + * 2. 如果是新版本 @stomp/stompjs, 这里可以获取到 connectHeaders : {} 中的 header + */ + List tokenHeaders = accessor.getNativeHeader(HttpHeaders.AUTHORIZATION); + String token = null; + if (CollectionUtils.isNotEmpty(tokenHeaders)) { + String temp = tokenHeaders.get(0); + if (StringUtils.isNotBlank(temp) && StringUtils.startsWith(temp, BaseConstants.BEARER_TOKEN)) { + token = StringUtils.removeStartIgnoreCase(temp, BaseConstants.BEARER_TOKEN); + } + } + + /* + *1. 这里直接封装到StompHeaderAccessor 中,可以根据自身业务进行改变 + * 2. 封装StompHeaderAccessor中后,可以在@Controller / @MessageMapping注解的方法中直接带上StompHeaderAccessor 就可以通过方法提供的 getUser()方法获取到这里封装user对象 + * 3. 例如可以在这里拿到前端的信息进行登录鉴权 + */ + WebSocketPrincipal user = (WebSocketPrincipal) accessor.getUser(); + + log.debug("[Quafer] |- Authentication user [{}] transmit token [{}] from frontend.", user.getName(), token); + break; + case DISCONNECT : + break; + default : + break; + } + } + + return message; + } + + /** + * 在消息发送后立刻调用 + * + * @param message {@link Message} + * @param channel {@link MessageChannel} + * @param sent boolean值参数表示该调用的返回值 + */ + @Override + public void postSend(Message message, MessageChannel channel, boolean sent) { + + /* + * 拿到消息头对象后,我们可以做一系列业务操作 + * 1. 通过getSessionAttributes()方法获取到websocketSession, 就可以取到我们在WebSocketHandshakeInterceptor拦截器中存在session中的信息 + * 2. 我们也可以获取到当前连接的状态,做一些统计,例如统计在线人数,或者缓存在线人数对应的令牌,方便后续业务调用 + */ + } + + /** + * 1. 在消息发送完成后调用,而不管消息发送是否产生异常,在此方法中,我们可以做一些资源释放清理的工作 + * 2. 此方法的触发必须是preSend方法执行成功,且返回值不为null,发生了实际的消息推送,才会触发 + * + * @param message {@link Message} + * @param channel {@link MessageChannel} + * @param sent boolean值参数表示该调用的返回值 + * @param ex 失败时抛出的 QuaferException + */ + @Override + public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, Exception ex) { + + } + + /** + * 在消息被实际检索之前调用, 只适用于(PollableChannels, 轮询场景),在websocket的场景中用不到 + * + * @param channel channel {@link MessageChannel} + * @return 如果返回false, 则不会对检索任何消息 + */ + @Override + public boolean preReceive(MessageChannel channel) { + return true; + } + + /** + * 在检索到消息之后,返回调用方之前调用,可以进行信息修改。适用于PollableChannels,轮询场景 + * + * @param message {@link Message} + * @param channel {@link MessageChannel} + * @return 如果返回null, 就不会进行下一步操作 + */ + @Override + public Message postReceive(Message message, MessageChannel channel) { + return message; + } + + /** + * 1. 在消息接收完成之后调用,不管发生什么异常,可以用于消息发送后的资源清理 + * 2. 只有当preReceive 执行成功,并返回true才会调用此方法 + * 3. 适用于PollableChannels,轮询场景 + * + * @param message {@link Message} + * @param channel {@link MessageChannel} + * @param ex 失败时抛出的 QuaferException + */ + @Override + public void afterReceiveCompletion(Message message, MessageChannel channel, Exception ex) { + + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketPrincipalHandshakeHandler.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketPrincipalHandshakeHandler.java new file mode 100644 index 0000000..d9fdb57 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketPrincipalHandshakeHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.interceptor; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.domain.PrincipalDetails; +import com.liuhung.engine.message.websocket.domain.WebSocketPrincipal; +import com.liuhung.engine.message.websocket.utils.WebSocketUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.support.DefaultHandshakeHandler; + +import javax.servlet.http.HttpServletRequest; +import java.security.Principal; +import java.util.Map; + +/** + *

Description: 设置认证用户信息

+ * + * @author : liuh + * @date : 2021/10/24 18:52 + */ +public class WebSocketPrincipalHandshakeHandler extends DefaultHandshakeHandler { + + private static final Logger log = LoggerFactory.getLogger(WebSocketPrincipalHandshakeHandler.class); + + @Override + protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map attributes) { + + HttpServletRequest httpServletRequest = WebSocketUtils.getHttpServletRequest(request); + if (ObjectUtils.isNotEmpty(httpServletRequest)) { + Object object = attributes.get(BaseConstants.PRINCIPAL); + if (ObjectUtils.isNotEmpty(object) && object instanceof PrincipalDetails) { + PrincipalDetails details = (PrincipalDetails) object; + WebSocketPrincipal webSocketPrincipal = new WebSocketPrincipal(details); + log.debug("[Quafer] |- Determine user by request parameter, userId is [{}].", webSocketPrincipal.getUserId()); + return webSocketPrincipal; + } + + String userId = httpServletRequest.getParameter(BaseConstants.OPEN_ID); + if (StringUtils.isNotBlank(userId)) { + WebSocketPrincipal webSocketPrincipal = new WebSocketPrincipal(userId); + log.debug("[Quafer] |- Determine user by request parameter, userId is [{}].", userId); + return webSocketPrincipal; + } + } + + Principal principal = request.getPrincipal(); + if (ObjectUtils.isNotEmpty(principal)) { + log.debug("[Quafer] |- Determine user from request, value is [{}].", principal.getName()); + return new WebSocketPrincipal(principal.getName()); + } + + log.warn("[Quafer] |- Can not determine user from request."); + return null; + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketSessionHandshakeInterceptor.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketSessionHandshakeInterceptor.java new file mode 100644 index 0000000..7d871ea --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/interceptor/WebSocketSessionHandshakeInterceptor.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.interceptor; + +import com.liuhung.engine.assistant.core.definition.BearerTokenResolver; +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.domain.PrincipalDetails; +import com.liuhung.engine.message.websocket.utils.WebSocketUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.Map; + +/** + *

Description: WebSocketSessionHandshakeInterceptor

+ *

+ * 不是开启websocket的必要步骤,根据自身的业务逻辑决定是否添加拦截器 + * + * 当前主要处理 Token 获取,以及 Token 的验证。如果验证成功,使用返回的用户名进行下一步,如果验证失败返回 false 终止握手。 + * + * @author : liuh + * @date : 2022/12/4 21:34 + */ +public class WebSocketSessionHandshakeInterceptor extends HttpSessionHandshakeInterceptor { + + private static final Logger log = LoggerFactory.getLogger(WebSocketSessionHandshakeInterceptor.class); + + private static final String SEC_WEBSOCKET_PROTOCOL = com.google.common.net.HttpHeaders.SEC_WEBSOCKET_PROTOCOL; + + private final BearerTokenResolver bearerTokenResolver; + + public WebSocketSessionHandshakeInterceptor(BearerTokenResolver bearerTokenResolver) { + this.bearerTokenResolver = bearerTokenResolver; + } + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + + HttpServletRequest httpServletRequest = WebSocketUtils.getHttpServletRequest(request); + + if (ObjectUtils.isNotEmpty(httpServletRequest)) { + + String protocol = httpServletRequest.getHeader(SEC_WEBSOCKET_PROTOCOL); + + String token = determineToken(protocol); + + if (StringUtils.isNotBlank(token)) { + log.debug("[Quafer] |- WebSocket fetch the token is [{}].", token); + + PrincipalDetails details = bearerTokenResolver.resolve(token); + if (ObjectUtils.isNotEmpty(details)) { + attributes.put(BaseConstants.PRINCIPAL, details); + + HttpSession session = httpServletRequest.getSession(); + if (ObjectUtils.isNotEmpty(session)) { + SessionRepositoryMessageInterceptor.setSessionId(attributes, session.getId()); + } + } else { + response.setStatusCode(HttpStatus.UNAUTHORIZED); + log.info("[Quafer] |- Token is invalid for WebSocket, stop handshake."); + return false; + } + } + } + + // 调用父类方法 + return super.beforeHandshake(request, response, wsHandler, attributes); + } + + private String determineToken(String protocol) { + if (StringUtils.contains(protocol, SymbolConstants.COMMA)) { + String[] protocols = StringUtils.split(protocol, SymbolConstants.COMMA); + for (String item : protocols) { + if (!StringUtils.endsWith(item, ".stomp")) { + return item; + } + } + } + return null; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { + + HttpServletRequest httpServletRequest = WebSocketUtils.getHttpServletRequest(request); + HttpServletResponse httpServletResponse = WebSocketUtils.getHttpServletResponse(response); + + if (ObjectUtils.isNotEmpty(httpServletRequest) && ObjectUtils.isNotEmpty(httpServletResponse)) { + httpServletResponse.setHeader(SEC_WEBSOCKET_PROTOCOL, "v10.stomp"); + } + + log.info("[Quafer] |- WebSocket handshake success!"); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/listener/WebSocketConnectedListener.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/listener/WebSocketConnectedListener.java new file mode 100644 index 0000000..a71b67e --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/listener/WebSocketConnectedListener.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.listener; + +import com.liuhung.engine.cache.redis.utils.RedisBitMapUtils; +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.websocket.definition.AbstractWebSocketListener; +import com.liuhung.engine.message.websocket.domain.WebSocketPrincipal; +import com.liuhung.engine.message.websocket.processor.WebSocketMessageSender; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionConnectedEvent; + +/** + *

Description: WebSocketUserOnlineListener

+ * + * 使用监听方式,主要为了将 WebSocket 基本配置,与应用操作解耦。避免产生注入循环。 + * + * @author : liuh + * @date : 2022/12/29 22:17 + */ +@Component +public class WebSocketConnectedListener extends AbstractWebSocketListener { + + private static final Logger log = LoggerFactory.getLogger(WebSocketConnectedListener.class); + + public WebSocketConnectedListener(WebSocketMessageSender webSocketMessageSender) { + super(webSocketMessageSender); + } + + @Override + public void onApplicationEvent(SessionConnectedEvent event) { + + WebSocketPrincipal principal = (WebSocketPrincipal) event.getUser(); + + if(ObjectUtils.isNotEmpty(principal)) { + + RedisBitMapUtils.setBit(MessageConstants.REDIS_CURRENT_ONLINE_USER, principal.getName(), true); + + log.debug("[Quafer] |- WebSocket user [{}] Offline.", principal); + + this.syncUserCountToAll(); + } + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/listener/WebSocketDisconnectListener.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/listener/WebSocketDisconnectListener.java new file mode 100644 index 0000000..1af96ef --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/listener/WebSocketDisconnectListener.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.listener; + +import com.liuhung.engine.cache.redis.utils.RedisBitMapUtils; +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.websocket.definition.AbstractWebSocketListener; +import com.liuhung.engine.message.websocket.domain.WebSocketPrincipal; +import com.liuhung.engine.message.websocket.processor.WebSocketMessageSender; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +/** + *

Description: WebSocketUserDisconnectListener

+ * + * 使用监听方式,主要为了将 WebSocket 基本配置,与应用操作解耦。避免产生注入循环。 + * + * @author : liuh + * @date : 2022/12/29 22:30 + */ +@Component +public class WebSocketDisconnectListener extends AbstractWebSocketListener { + + private static final Logger log = LoggerFactory.getLogger(WebSocketDisconnectListener.class); + + public WebSocketDisconnectListener(WebSocketMessageSender webSocketMessageSender) { + super(webSocketMessageSender); + } + + @Override + public void onApplicationEvent(SessionDisconnectEvent event) { + WebSocketPrincipal principal = (WebSocketPrincipal) event.getUser(); + + if(ObjectUtils.isNotEmpty(principal)) { + + RedisBitMapUtils.setBit(MessageConstants.REDIS_CURRENT_ONLINE_USER, principal.getName(), false); + + log.debug("[Quafer] |- WebSocket user [{}] Offline.", principal); + + this.syncUserCountToAll(); + } + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/processor/WebSocketBearerTokenResolver.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/processor/WebSocketBearerTokenResolver.java new file mode 100644 index 0000000..778572b --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/processor/WebSocketBearerTokenResolver.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.processor; + +import com.liuhung.engine.assistant.core.definition.BearerTokenResolver; +import com.liuhung.engine.assistant.core.domain.PrincipalDetails; + +/** + *

Description: WebSocket 默认 BearerTokenResolver

+ * + * BearerTokenResolver 为必要 Bean,增加一个默认的 Bean 防止引用该包没有Bean 的时候出错。 + * + * @author : liuh + * @date : 2022/12/28 0:12 + */ +public class WebSocketBearerTokenResolver implements BearerTokenResolver { + + @Override + public PrincipalDetails resolve(String token){ + PrincipalDetails details = new PrincipalDetails(); + details.setOpenId(token); + return details; + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/processor/WebSocketMessageSender.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/processor/WebSocketMessageSender.java new file mode 100644 index 0000000..c0a951e --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/processor/WebSocketMessageSender.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.processor; + +import com.liuhung.engine.message.core.constants.MessageConstants; +import com.liuhung.engine.message.core.exception.IllegalChannelException; +import com.liuhung.engine.message.core.exception.PrincipalNotFoundException; +import com.liuhung.engine.message.websocket.domain.WebSocketMessage; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.messaging.simp.user.SimpUser; +import org.springframework.messaging.simp.user.SimpUserRegistry; + +/** + *

Description: Web Socket 服务端消息发送

+ * + * @author : liuh + * @date : 2021/10/24 18:47 + */ +public class WebSocketMessageSender { + + private static final Logger log = LoggerFactory.getLogger(WebSocketMessageSender.class); + + private SimpMessagingTemplate simpMessagingTemplate; + private SimpUserRegistry simpUserRegistry; + + public void setSimpMessagingTemplate(SimpMessagingTemplate simpMessagingTemplate) { + this.simpMessagingTemplate = simpMessagingTemplate; + } + + public void setSimpUserRegistry(SimpUserRegistry simpUserRegistry) { + this.simpUserRegistry = simpUserRegistry; + } + + /** + * 发送给指定用户信息。 + * + * @param webSocketMessage 发送内容参数实体 {@link WebSocketMessage} + * @param 指定 payload 类型 + * @throws IllegalChannelException Web Socket 通道设置错误 + * @throws PrincipalNotFoundException 该服务中无法找到与 identity 对应的用户 Principal + */ + public void toUser(WebSocketMessage webSocketMessage) throws IllegalChannelException, PrincipalNotFoundException { + SimpUser simpUser = simpUserRegistry.getUser(webSocketMessage.getTo()); + if (ObjectUtils.isEmpty(simpUser)) { + throw new PrincipalNotFoundException("Web socket user principal is not found!"); + } + + log.debug("[Quafer] |- Web socket send message to user [{}].", webSocketMessage.getTo()); + simpMessagingTemplate.convertAndSendToUser(webSocketMessage.getTo(), webSocketMessage.getChannel(), webSocketMessage.getPayload()); + } + + public void toAll(String channel, T payload) { + simpMessagingTemplate.convertAndSend(channel, payload); + } + + /** + * 广播 WebSocket 信息 + * + * @param payload 发送的内容 + * @param payload 类型 + */ + public void sendNoticeToAll(T payload) { + toAll(MessageConstants.WEBSOCKET_DESTINATION_BROADCAST_NOTICE, payload); + } + + /** + * 广播 WebSocket 信息 + * + * @param payload 发送的内容 + * @param payload 类型 + */ + public void sendOnlineToAll(T payload) { + toAll(MessageConstants.WEBSOCKET_DESTINATION_BROADCAST_ONLINE, payload); + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/properties/WebSocketProperties.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/properties/WebSocketProperties.java new file mode 100644 index 0000000..0bded44 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/properties/WebSocketProperties.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.properties; + +import com.liuhung.engine.assistant.core.definition.constants.HttpHeaders; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.message.core.constants.MessageConstants; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

Description: Web Socket 配置

+ * + * @author : liuh + * @date : 2021/10/24 18:38 + */ +@ConfigurationProperties(prefix = MessageConstants.PROPERTY_PREFIX_WEBSOCKET) +public class WebSocketProperties { + + /** + * 客户端尝试连接端点 + */ + private String endpoint = "stomp/ws"; + + /** + * 全局使用的消息前缀 + */ + private List applicationDestinationPrefixes = Collections.singletonList("/app"); + + /** + * 点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/ + */ + private String userDestinationPrefix = "/user"; + + /** + * 集群模式下,信息同步消息队列Topic + */ + private String topic = "ws"; + + /** + * 请求中传递的用户身份标识属性名 + */ + private String principalHeader = HttpHeaders.X_HERODOTUS_OPEN_ID; + + private String format(String endpoint) { + if (StringUtils.isNotBlank(endpoint) && !StringUtils.startsWith(endpoint, SymbolConstants.FORWARD_SLASH)) { + return SymbolConstants.FORWARD_SLASH + endpoint; + } else { + return endpoint; + } + } + + public String getEndpoint() { + return format(endpoint); + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public List getApplicationDestinationPrefixes() { + return applicationDestinationPrefixes; + } + + public void setApplicationDestinationPrefixes(List applicationDestinationPrefixes) { + this.applicationDestinationPrefixes = applicationDestinationPrefixes; + } + + public String[] getApplicationPrefixes() { + List prefixes = this.getApplicationDestinationPrefixes(); + if (CollectionUtils.isNotEmpty(prefixes)) { + List wellFormed = prefixes.stream().map(this::format).collect(Collectors.toList()); + String[] result = new String[wellFormed.size()]; + return wellFormed.toArray(result); + } else { + return new String[]{}; + } + } + + public String getUserDestinationPrefix() { + return format(userDestinationPrefix); + } + + public void setUserDestinationPrefix(String userDestinationPrefix) { + this.userDestinationPrefix = userDestinationPrefix; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getPrincipalHeader() { + return principalHeader; + } + + public void setPrincipalHeader(String principalHeader) { + this.principalHeader = principalHeader; + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/service/WebSocketDisplayService.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/service/WebSocketDisplayService.java new file mode 100644 index 0000000..4152915 --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/service/WebSocketDisplayService.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.service; + +import com.liuhung.engine.message.websocket.utils.WebSocketUtils; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + *

Description: WebSocket 数据展现相关内容服务

+ * + * @author : liuh + * @date : 2022/12/30 23:12 + */ +@Service +public class WebSocketDisplayService { + + public Map findAllStat() { + Map data = new HashMap<>(); + data.put("onlineCount", WebSocketUtils.getOnlineCount()); + return data; + } +} diff --git a/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/utils/WebSocketUtils.java b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/utils/WebSocketUtils.java new file mode 100644 index 0000000..78ba1ab --- /dev/null +++ b/engine-message/message-sdk-websocket/src/main/java/com/liuhung/engine/message/websocket/utils/WebSocketUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.websocket.utils; + +import com.liuhung.engine.cache.redis.utils.RedisBitMapUtils; +import com.liuhung.engine.message.core.constants.MessageConstants; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + *

Description: WebSocket 通用工具类

+ * + * @author : liuh + * @date : 2022/12/30 12:17 + */ +public class WebSocketUtils { + + public static HttpServletRequest getHttpServletRequest(ServerHttpRequest serverHttpRequest) { + if (serverHttpRequest instanceof ServletServerHttpRequest) { + ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest; + return request.getServletRequest(); + } + + return null; + } + + public static HttpServletResponse getHttpServletResponse(ServerHttpResponse serverHttpResponse) { + if (serverHttpResponse instanceof ServletServerHttpResponse) { + ServletServerHttpResponse response = (ServletServerHttpResponse) serverHttpResponse; + return response.getServletResponse(); + } + + return null; + } + + public static int getOnlineCount() { + Long count = RedisBitMapUtils.bitCount(MessageConstants.REDIS_CURRENT_ONLINE_USER); + return count.intValue(); + } +} diff --git a/engine-message/message-spring-boot-starter/pom.xml b/engine-message/message-spring-boot-starter/pom.xml new file mode 100644 index 0000000..cc06fc0 --- /dev/null +++ b/engine-message/message-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + engine-message + com.liuhung.engine + 2.7.8.0 + + + message-spring-boot-starter + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + message-sdk-websocket + + + + \ No newline at end of file diff --git a/engine-message/message-spring-boot-starter/src/main/java/com/liuhung/engine/message/autoconfigure/AutoConfiguration.java b/engine-message/message-spring-boot-starter/src/main/java/com/liuhung/engine/message/autoconfigure/AutoConfiguration.java new file mode 100644 index 0000000..43ad61b --- /dev/null +++ b/engine-message/message-spring-boot-starter/src/main/java/com/liuhung/engine/message/autoconfigure/AutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.message.autoconfigure; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: Message 模块自动注入配置

+ * + * @author : liuh + * @date : 2022/2/4 17:08 + */ +@Configuration(proxyBeanMethods = false) +public class AutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AutoConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.info("[Quafer] |- Starter [Message Starter] Auto Configure."); + } +} diff --git a/engine-message/message-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/engine-message/message-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..0e7c97e --- /dev/null +++ b/engine-message/message-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.liuhung.engine.message.autoconfigure.AutoConfiguration +com.liuhung.engine.message.websocket.configuration.MessageWebSocketConfiguration +com.liuhung.engine.message.mailing.configuration.MessageMailingConfiguration \ No newline at end of file diff --git a/engine-message/pom.xml b/engine-message/pom.xml new file mode 100644 index 0000000..aa582e4 --- /dev/null +++ b/engine-message/pom.xml @@ -0,0 +1,49 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + engine-message + 2.7.8.0 + pom + + + message-core + message-spring-boot-starter + message-sdk-mailing + message-sdk-websocket + + + \ No newline at end of file diff --git a/engine-nosql/nosql-core/pom.xml b/engine-nosql/nosql-core/pom.xml new file mode 100644 index 0000000..10f9150 --- /dev/null +++ b/engine-nosql/nosql-core/pom.xml @@ -0,0 +1,49 @@ + + + + + 4.0.0 + + + engine-nosql + com.liuhung.engine + 2.7.8.0 + + + nosql-core + jar + 2.7.8.0 + + + + com.liuhung.engine + assistant-core + + + + \ No newline at end of file diff --git a/engine-nosql/nosql-core/src/main/java/com/liuhung/engine/nosql/core/constants/NosqlConstants.java b/engine-nosql/nosql-core/src/main/java/com/liuhung/engine/nosql/core/constants/NosqlConstants.java new file mode 100644 index 0000000..dc49a76 --- /dev/null +++ b/engine-nosql/nosql-core/src/main/java/com/liuhung/engine/nosql/core/constants/NosqlConstants.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: Nosql 模块通用常量

+ * + * @author : liuh + * @date : 2022/8/26 16:00 + */ +public interface NosqlConstants extends BaseConstants { + + String PROPERTY_PREFIX_NOSQL = PROPERTY_PREFIX_HERODOTUS + ".nosql"; + + String PROPERTY_NOSQL_COUCHDB = PROPERTY_PREFIX_NOSQL + ".couchdb"; + String PROPERTY_NOSQL_INFLUXDB = PROPERTY_PREFIX_NOSQL + ".influxdb"; + + String ITEM_COUCHDB_ENDPOINT = PROPERTY_NOSQL_COUCHDB + ".endpoint"; + String ITEM_COUCHDB_USERNAME = PROPERTY_NOSQL_COUCHDB + ".username"; + String ITEM_COUCHDB_PASSWORD = PROPERTY_NOSQL_COUCHDB + ".password"; + + String ITEM_INFLUXDB_ENDPOINT = PROPERTY_NOSQL_INFLUXDB + ".endpoint"; + String ITEM_INFLUXDB_DATABASE = PROPERTY_NOSQL_INFLUXDB + ".database"; + String ITEM_INFLUXDB_USERNAME = PROPERTY_NOSQL_INFLUXDB + ".username"; + String ITEM_INFLUXDB_PASSWORD = PROPERTY_NOSQL_INFLUXDB + ".password"; +} diff --git a/engine-nosql/nosql-sdk-couchdb/pom.xml b/engine-nosql/nosql-sdk-couchdb/pom.xml new file mode 100644 index 0000000..8daf21b --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/pom.xml @@ -0,0 +1,54 @@ + + + + + 4.0.0 + + + engine-nosql + com.liuhung.engine + 2.7.8.0 + + + nosql-sdk-couchdb + jar + 2.7.8.0 + + + + + com.liuhung.engine + rest-core + + + + com.liuhung.engine + nosql-core + + + \ No newline at end of file diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/annotation/ConditionalOnCouchdbEnabled.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/annotation/ConditionalOnCouchdbEnabled.java new file mode 100644 index 0000000..42c4410 --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/annotation/ConditionalOnCouchdbEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.annotation; + +import com.liuhung.engine.nosql.couchdb.condition.CouchdbEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: Influxdb条件注解

+ * + * @author : liuh + * @date : 2021/11/18 10:12 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(CouchdbEnabledCondition.class) +public @interface ConditionalOnCouchdbEnabled { +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/annotation/EnableQuaferCouchdb.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/annotation/EnableQuaferCouchdb.java new file mode 100644 index 0000000..a76fa1c --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/annotation/EnableQuaferCouchdb.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.annotation; + +import com.liuhung.engine.nosql.couchdb.configuration.CouchdbConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 开启Influxdb支持

+ * + * @author : liuh + * @date : 2021/11/17 17:40 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(CouchdbConfiguration.class) +public @interface EnableQuaferCouchdb { + +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/condition/CouchdbEnabledCondition.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/condition/CouchdbEnabledCondition.java new file mode 100644 index 0000000..7bf8d3b --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/condition/CouchdbEnabledCondition.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.condition; + +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import com.liuhung.engine.nosql.core.constants.NosqlConstants; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: Couchdb 注入开启条件

+ * + * @author : liuh + * @date : 2021/11/17 18:06 + */ +public class CouchdbEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(CouchdbEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + String url = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_COUCHDB_ENDPOINT); + String username = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_COUCHDB_USERNAME); + String password = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_COUCHDB_PASSWORD); + boolean result = StringUtils.isNotBlank(url) && StringUtils.startsWith(url, "http") && StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password); + log.debug("[Quafer] |- Condition [Influxdb Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/configuration/CouchdbConfiguration.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/configuration/CouchdbConfiguration.java new file mode 100644 index 0000000..4bcd314 --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/configuration/CouchdbConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.configuration; + +import com.liuhung.engine.nosql.couchdb.properties.CouchdbProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + *

Description: CouchDB 配置

+ * + * @author : liuh + * @date : 2022/8/26 18:08 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties({CouchdbProperties.class}) +@ComponentScan(basePackages = { + "com.liuhung.engine.nosql.couchdb.service", + "com.liuhung.engine.nosql.couchdb.controller", +}) +public class CouchdbConfiguration { + + private static final Logger log = LoggerFactory.getLogger(CouchdbConfiguration.class); + + @PostConstruct + public void init() { + log.info("[Quafer] |- SDK [Nosql CouchDB] Auto Configure."); + } +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/controller/DatabaseController.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/controller/DatabaseController.java new file mode 100644 index 0000000..9afd462 --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/controller/DatabaseController.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.nosql.couchdb.domain.Response; +import com.liuhung.engine.nosql.couchdb.dto.Database; +import com.liuhung.engine.nosql.couchdb.service.DatabaseService; +import com.liuhung.engine.rest.core.controller.Controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + *

Description: CouchDB 数据库管理接口

+ * + * @author : liuh + * @date : 2022/8/27 16:59 + */ +@RestController +@RequestMapping("/couchdb/database") +@Tags({ + @Tag(name = "Nosql 管理接口"), + @Tag(name = "CouchDB 管理接口"), + @Tag(name = "CouchDB Database 接口"), +}) +public class DatabaseController implements Controller { + + private final DatabaseService databaseService; + + @Autowired + public DatabaseController(DatabaseService databaseService) { + this.databaseService = databaseService; + } + + @Operation(summary = "创建数据库", description = "创建 CouchDB 数据库名称", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = "application/json")), + responses = {@ApiResponse(description = "Result", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "database", required = true, description = "CouchDB 数据库名称", schema = @Schema(implementation = Database.class)) + }) + @PostMapping + public Result create(@Validated @RequestBody Database database) { + Response response = databaseService.create(database.getName()); + return result(response); + } +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/definition/AbstractCouchdbService.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/definition/AbstractCouchdbService.java new file mode 100644 index 0000000..c83f168 --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/definition/AbstractCouchdbService.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.definition; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.assistant.core.definition.AbstractRest; +import com.liuhung.engine.nosql.couchdb.properties.CouchdbProperties; +import cn.hutool.core.codec.Base64; +import org.apache.http.HttpHeaders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; + +import java.util.HashMap; +import java.util.Map; + +/** + *

Description: CouchDB 基础服务

+ * + * @author : liuh + * @date : 2022/8/27 15:33 + */ +public abstract class AbstractCouchdbService extends AbstractRest { + + private static final Logger log = LoggerFactory.getLogger(AbstractCouchdbService.class); + @Autowired + private CouchdbProperties couchdbProperties; + + @Override + protected String getBaseUrl() { + return couchdbProperties.getEndpoint(); + } + + private String getBasicToken() { + Assert.hasText(couchdbProperties.getUsername(), "username cannot be empty"); + Assert.hasText(couchdbProperties.getPassword(), "password cannot be empty"); + String content = couchdbProperties.getUsername() + SymbolConstants.COLON + couchdbProperties.getPassword(); + String token = BaseConstants.BASIC_TOKEN + Base64.encode(content); + log.debug("[Quafer] |- Create CouchDB Basic Authentication Token : [{}]", token); + return token; + } + + protected Map getBasicAuthentication() { + Map header = new HashMap<>(); + header.put(HttpHeaders.AUTHORIZATION, getBasicToken()); + return header; + } +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/domain/Response.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/domain/Response.java new file mode 100644 index 0000000..ac7e38a --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/domain/Response.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.domain; + +import com.liuhung.engine.assistant.core.definition.domain.Entity; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; + +/** + *

Description: CouchDB 基础响应实体

+ * + * @author : liuh + * @date : 2022/8/27 16:49 + */ +public class Response implements Entity { + + @JsonProperty("ok") + private Boolean success = false; + + private String error; + + private String reason; + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("success", success) + .add("error", error) + .add("reason", reason) + .toString(); + } +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/dto/Database.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/dto/Database.java new file mode 100644 index 0000000..728061c --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/dto/Database.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.dto; + +import com.liuhung.engine.rest.core.definition.dto.BaseDto; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + *

Description: 数据库操作请求实体

+ * + * @author : liuh + * @date : 2022/8/27 21:53 + */ +@Schema(name = "数据库操作请求实体") +public class Database extends BaseDto { + + @Schema(name = "数据库名称", title = "数据库名称只能是小写字母、数字和 _, $, (, ), +, -, and /.") + @NotBlank(message = "数据库名称不能为空") + @Pattern(regexp = "^[a-z][a-z0-9_$()+/-]*$.", message = "数据库名称只能是小写字母、数字和 _, $, (, ), +, -, and /.") + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/properties/CouchdbProperties.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/properties/CouchdbProperties.java new file mode 100644 index 0000000..1ce4483 --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/properties/CouchdbProperties.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.properties; + +import com.liuhung.engine.nosql.core.constants.NosqlConstants; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

Description: CouchDB 配置属性

+ * + * @author : liuh + * @date : 2022/8/26 18:09 + */ +@ConfigurationProperties(prefix = NosqlConstants.PROPERTY_NOSQL_COUCHDB) +public class CouchdbProperties { + + /** + * CouchDB API接口基础地址 + */ + private String endpoint; + /** + * CouchDB 管理员用户名 + */ + private String username; + /** + * CouchDB 管理员用户名 + */ + private String password; + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + 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/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/service/DatabaseService.java b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/service/DatabaseService.java new file mode 100644 index 0000000..8cec0e9 --- /dev/null +++ b/engine-nosql/nosql-sdk-couchdb/src/main/java/com/liuhung/engine/nosql/couchdb/service/DatabaseService.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.couchdb.service; + +import com.liuhung.engine.nosql.couchdb.definition.AbstractCouchdbService; +import com.liuhung.engine.nosql.couchdb.domain.Response; +import cn.zhxu.data.TypeRef; +import cn.zhxu.okhttps.OkHttps; +import org.springframework.stereotype.Service; + +/** + *

Description: CouchDB 数据库操作服务

+ * + * @author : liuh + * @date : 2022/8/27 16:12 + */ +@Service +public class DatabaseService extends AbstractCouchdbService { + + public Response create(String name) { + return this.http().sync("/{db}") + .bodyType(OkHttps.JSON) + .addHeader(this.getBasicAuthentication()) + .addPathPara("db", name) + .put() + .getBody() + .toBean(new TypeRef() { + }); + } +} diff --git a/engine-nosql/nosql-sdk-influxdb/pom.xml b/engine-nosql/nosql-sdk-influxdb/pom.xml new file mode 100644 index 0000000..f59aa89 --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/pom.xml @@ -0,0 +1,54 @@ + + + + + 4.0.0 + + + engine-nosql + com.liuhung.engine + 2.7.8.0 + + + nosql-sdk-influxdb + jar + 2.7.8.0 + + + + com.liuhung.engine + nosql-core + + + + org.influxdb + influxdb-java + + + + \ No newline at end of file diff --git a/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/annotation/ConditionalOnInfluxdbEnabled.java b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/annotation/ConditionalOnInfluxdbEnabled.java new file mode 100644 index 0000000..ddb0399 --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/annotation/ConditionalOnInfluxdbEnabled.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.influxdb.annotation; + +import com.liuhung.engine.nosql.influxdb.condition.InfluxdbEnabledCondition; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + *

Description: Influxdb条件注解

+ * + * @author : liuh + * @date : 2021/11/18 10:12 + */ +@Conditional(InfluxdbEnabledCondition.class) +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ConditionalOnInfluxdbEnabled { +} diff --git a/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/annotation/EnableQuaferInfluxdb.java b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/annotation/EnableQuaferInfluxdb.java new file mode 100644 index 0000000..f5e46f7 --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/annotation/EnableQuaferInfluxdb.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.influxdb.annotation; + +import com.liuhung.engine.nosql.influxdb.configuration.InfluxdbConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + *

Description: 开启Influxdb支持

+ * + * @author : liuh + * @date : 2021/11/17 17:40 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(InfluxdbConfiguration.class) +public @interface EnableQuaferInfluxdb { + +} diff --git a/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/condition/InfluxdbEnabledCondition.java b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/condition/InfluxdbEnabledCondition.java new file mode 100644 index 0000000..cc4e2d6 --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/condition/InfluxdbEnabledCondition.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.influxdb.condition; + +import com.liuhung.engine.assistant.core.context.PropertyResolver; +import com.liuhung.engine.nosql.core.constants.NosqlConstants; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + *

Description: Influxdb 注入开启条件

+ * + * @author : liuh + * @date : 2021/11/17 18:06 + */ +public class InfluxdbEnabledCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(InfluxdbEnabledCondition.class); + + @SuppressWarnings("NullableProblems") + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + String url = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_INFLUXDB_ENDPOINT); + String database = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_INFLUXDB_DATABASE); + String username = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_INFLUXDB_USERNAME); + String password = PropertyResolver.getProperty(conditionContext, NosqlConstants.ITEM_INFLUXDB_PASSWORD); + boolean result = StringUtils.isNotBlank(url) && StringUtils.isNotBlank(database) && StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password); + log.debug("[Quafer] |- Condition [Influxdb Enabled] value is [{}]", result); + return result; + } +} diff --git a/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/configuration/InfluxdbConfiguration.java b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/configuration/InfluxdbConfiguration.java new file mode 100644 index 0000000..a102a8a --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/configuration/InfluxdbConfiguration.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.influxdb.configuration; + +import com.liuhung.engine.nosql.influxdb.support.InfluxdbTemplate; +import com.liuhung.engine.nosql.influxdb.annotation.ConditionalOnInfluxdbEnabled; +import com.liuhung.engine.nosql.influxdb.properties.InfluxdbProperties; +import org.apache.commons.lang3.StringUtils; +import org.influxdb.InfluxDB; +import org.influxdb.InfluxDBFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.util.concurrent.TimeUnit; + +/** + *

Description: Influxdb 配置

+ * + * @author : liuh + * @date : 2021/11/17 17:39 + */ +@Configuration +@ConditionalOnInfluxdbEnabled +@EnableConfigurationProperties({InfluxdbProperties.class}) +public class InfluxdbConfiguration { + + private static final Logger log = LoggerFactory.getLogger(InfluxdbConfiguration.class); + + @PostConstruct + public void init() { + log.info("[Quafer] |- Plugin [Quafer Influxdb] Auto Configure."); + } + + @Bean + public InfluxDB influxdb(InfluxdbProperties influxdbProperties) { + + InfluxDB influxdb; + if (StringUtils.isNotBlank(influxdbProperties.getUsername()) && StringUtils.isNotBlank(influxdbProperties.getPassword())) { + influxdb = InfluxDBFactory.connect(influxdbProperties.getEndpoint(), influxdbProperties.getUsername(), influxdbProperties.getPassword()); + } else { + influxdb = InfluxDBFactory.connect(influxdbProperties.getEndpoint()); + } + + try { + /** + * 异步插入: + * enableBatch这里第一个是point的个数,第二个是时间,单位毫秒 + * point的个数和时间是联合使用的,如果满100条或者2000毫秒 + * 满足任何一个条件就会发送一次写的请求。 + */ + influxdb.setDatabase(influxdbProperties.getDatabase()).enableBatch(100, 2000, TimeUnit.MILLISECONDS); + } catch (Exception e) { + log.error("[Quafer] |- Influxdb set database catch error.", e); + } finally { + influxdb.setRetentionPolicy("autogen"); + } + influxdb.setLogLevel(InfluxDB.LogLevel.BASIC); + return influxdb; + } + + @Bean + @ConditionalOnBean(InfluxDB.class) + public InfluxdbTemplate influxdbTemplate(InfluxDB influxdb) { + InfluxdbTemplate influxdbTemplate = new InfluxdbTemplate(influxdb); + log.trace("[Quafer] |- Bean [Influxdb Template Auto Configure."); + return influxdbTemplate; + } +} diff --git a/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/properties/InfluxdbProperties.java b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/properties/InfluxdbProperties.java new file mode 100644 index 0000000..1942ac0 --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/properties/InfluxdbProperties.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.influxdb.properties; + +import com.liuhung.engine.nosql.core.constants.NosqlConstants; +import com.google.common.base.MoreObjects; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

Description: Influxdb 配置参数

+ * + * @author : liuh + * @date : 2021/11/17 17:40 + */ +@ConfigurationProperties(prefix = NosqlConstants.PROPERTY_NOSQL_INFLUXDB) +public class InfluxdbProperties { + + /** + * Influxdb 1.X 用户名 + */ + private String username; + /** + * Influxdb 1.X 密码 + */ + private String password; + /** + * Influxdb 1.X 连接url + */ + private String endpoint; + /** + * Influxdb 1.X database + */ + private String database; + + 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 getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("username", username) + .add("password", password) + .add("endpoint", endpoint) + .add("database", database) + .toString(); + } +} diff --git a/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/support/InfluxdbTemplate.java b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/support/InfluxdbTemplate.java new file mode 100644 index 0000000..94ac6f4 --- /dev/null +++ b/engine-nosql/nosql-sdk-influxdb/src/main/java/com/liuhung/engine/nosql/influxdb/support/InfluxdbTemplate.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Engine 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.nosql.influxdb.support; + +import org.influxdb.InfluxDB; +import org.influxdb.dto.Pong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + *

Description: Influxdb 操作模版

+ * + * @author : liuh + * @date : 2021/11/17 18:26 + */ +@Component +public class InfluxdbTemplate { + + private static final Logger log = LoggerFactory.getLogger(InfluxdbTemplate.class); + + private final InfluxDB influxdb; + + public InfluxdbTemplate(InfluxDB influxdb) { + this.influxdb = influxdb; + } + + /** + * 测试连接是否正常 + * + * @return true 正常 + */ + public boolean ping() { + boolean isConnected = false; + Pong pong; + try { + pong = influxdb.ping(); + if (pong != null) { + isConnected = true; + } + } catch (Exception e) { + log.error("[Quafer] |- Influxdb ping the connection error.", e); + } + return isConnected; + } +} diff --git a/engine-nosql/pom.xml b/engine-nosql/pom.xml new file mode 100644 index 0000000..0e370db --- /dev/null +++ b/engine-nosql/pom.xml @@ -0,0 +1,48 @@ + + + + + 4.0.0 + + + quafer-engine + com.liuhung.engine + 2.7.8.0 + + + engine-nosql + pom + 2.7.8.0 + + + nosql-core + nosql-sdk-couchdb + nosql-sdk-influxdb + + + \ No newline at end of file diff --git a/engine-oauth2/oauth2-core/README.md b/engine-oauth2/oauth2-core/README.md new file mode 100644 index 0000000..ee240c6 --- /dev/null +++ b/engine-oauth2/oauth2-core/README.md @@ -0,0 +1 @@ +## 缓存通用代码模块 diff --git a/engine-oauth2/oauth2-core/pom.xml b/engine-oauth2/oauth2-core/pom.xml new file mode 100644 index 0000000..edbdb11 --- /dev/null +++ b/engine-oauth2/oauth2-core/pom.xml @@ -0,0 +1,76 @@ + + + + + 4.0.0 + + + engine-oauth2 + com.liuhung.engine + 2.7.8.0 + + + oauth2-core + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + web-core + + + + com.liuhung.engine + data-core + + + + com.liuhung.engine + access-core + + + + org.springframework.security + spring-security-cas + + + + org.springframework.security + spring-security-oauth2-jose + + + + org.springframework.security + spring-security-oauth2-resource-server + + + + \ No newline at end of file diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/constants/OAuth2Constants.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/constants/OAuth2Constants.java new file mode 100644 index 0000000..7c32be6 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/constants/OAuth2Constants.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.constants; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; + +/** + *

Description: OAuth2 模块通用常量

+ * + * @author : liuh + * @date : 2022/2/25 9:44 + */ +public interface OAuth2Constants extends BaseConstants { + + String PROPERTY_PREFIX_OAUTH2 = PROPERTY_PREFIX_HERODOTUS + ".oauth2"; + String PROPERTY_OAUTH2_UI = PROPERTY_PREFIX_OAUTH2 + ".ui"; + String PROPERTY_OAUTH2_COMPLIANCE = PROPERTY_PREFIX_OAUTH2 + ".compliance"; + String ITEM_COMPLIANCE_AUTO_UNLOCK = PROPERTY_OAUTH2_COMPLIANCE + ".auto-unlock"; + + String REGION_OAUTH2_AUTHORIZATION = AREA_PREFIX + "oauth2:authorization"; + String REGION_OAUTH2_AUTHORIZATION_CONSENT = AREA_PREFIX + "oauth2:authorization:consent"; + String REGION_OAUTH2_REGISTERED_CLIENT = AREA_PREFIX + "oauth2:registered:client"; + String REGION_OAUTH2_APPLICATION = AREA_PREFIX + "oauth2:application"; + String REGION_OAUTH2_COMPLIANCE = AREA_PREFIX + "oauth2:compliance"; + String REGION_OAUTH2_AUTHORITY = AREA_PREFIX + "oauth2:authority"; + String REGION_OAUTH2_SCOPE = AREA_PREFIX + "oauth2:scope"; + String REGION_OAUTH2_APPLICATION_SCOPE = AREA_PREFIX + "oauth2:application:scope"; + + String CACHE_NAME_TOKEN_SIGN_IN_FAILURE_LIMITED = CACHE_TOKEN_BASE_PREFIX + "sign_in:failure_limited:"; + String CACHE_NAME_TOKEN_LOCKED_USER_DETAIL = CACHE_TOKEN_BASE_PREFIX + "locked:user_details:"; +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/constants/OAuth2ErrorCodes.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/constants/OAuth2ErrorCodes.java new file mode 100644 index 0000000..78a675a --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/constants/OAuth2ErrorCodes.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.constants; + +/** + *

Description: 扩展 OAuth2 错误代码

+ * + * @author : liuh + * @date : 2022/7/9 12:59 + */ +public interface OAuth2ErrorCodes extends org.springframework.security.oauth2.core.OAuth2ErrorCodes { + + String ACCOUNT_EXPIRED = "AccountExpiredException"; + String ACCOUNT_DISABLED = "DisabledException"; + String ACCOUNT_LOCKED = "LockedException"; + String ACCOUNT_ENDPOINT_LIMITED = "AccountEndpointLimitedException"; + String BAD_CREDENTIALS = "BadCredentialsException"; + String CREDENTIALS_EXPIRED = "CredentialsExpiredException"; + String USERNAME_NOT_FOUND = "UsernameNotFoundException"; + + String SESSION_EXPIRED = "SessionExpiredException"; + +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/QuaferGrantType.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/QuaferGrantType.java new file mode 100644 index 0000000..bd41664 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/QuaferGrantType.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +/** + *

Description: 自定义 Grant Type 类型

+ * + * @author : liuh + * @date : 2022/2/25 9:53 + */ +public interface QuaferGrantType { + + AuthorizationGrantType SOCIAL = new AuthorizationGrantType(BaseConstants.SOCIAL_CREDENTIALS); +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/Authority.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/Authority.java new file mode 100644 index 0000000..2f619c2 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/Authority.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +import com.google.common.base.MoreObjects; + +/** + *

Description: 权限对象

+ * + * @author : liuh + * @date : 2022/4/1 18:26 + */ +public class Authority { + + private String authorityId; + + private String authorityCode; + + private String serviceId; + + private String requestMethod; + + private String url; + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + + public String getAuthorityCode() { + return authorityCode; + } + + public void setAuthorityCode(String authorityCode) { + this.authorityCode = authorityCode; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public String getRequestMethod() { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) { + this.requestMethod = requestMethod; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("authorityId", authorityId) + .add("authorityCode", authorityCode) + .add("serviceId", serviceId) + .add("requestMethod", requestMethod) + .add("url", url) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/FormLoginWebAuthenticationDetails.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/FormLoginWebAuthenticationDetails.java new file mode 100644 index 0000000..ff87d40 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/FormLoginWebAuthenticationDetails.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +import com.liuhung.engine.oauth2.core.utils.SymmetricUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.web.authentication.WebAuthenticationDetails; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +/** + *

Description: 表单登录 Details 定义

+ * + * @author : liuh + * @date : 2022/4/12 10:32 + */ +public class FormLoginWebAuthenticationDetails extends WebAuthenticationDetails { + + /** + * 验证码是否关闭 + */ + private final Boolean closed; + /** + * 请求中,验证码对应的表单参数名。对应UI Properties 里面的 captcha parameter + */ + private final String parameterName; + /** + * 验证码分类 + */ + private final String category; + private String code = null; + private String identity = null; + + public FormLoginWebAuthenticationDetails(String remoteAddress, String sessionId, Boolean closed, String parameterName, String category, String code, String identity) { + super(remoteAddress, sessionId); + this.closed = closed; + this.parameterName = parameterName; + this.category = category; + this.code = code; + this.identity = identity; + } + + public FormLoginWebAuthenticationDetails(HttpServletRequest request, boolean closed, String parameterName, String category) { + super(request); + this.closed = closed; + this.parameterName = parameterName; + this.category = category; + this.init(request); + } + + private void init(HttpServletRequest request) { + String encryptedCode = request.getParameter(parameterName); + String key = request.getParameter("symmetric"); + + HttpSession session = request.getSession(); + this.identity = session.getId(); + + if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(encryptedCode)) { + byte[] byteKey = SymmetricUtils.getDecryptedSymmetricKey(key); + this.code = SymmetricUtils.decrypt(encryptedCode, byteKey); + } + } + + public Boolean getClosed() { + return closed; + } + + public String getParameterName() { + return parameterName; + } + + public String getCategory() { + return category; + } + + public String getCode() { + return code; + } + + public String getIdentity() { + return identity; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferGrantedAuthority.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferGrantedAuthority.java new file mode 100644 index 0000000..421e851 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferGrantedAuthority.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import org.springframework.security.core.GrantedAuthority; + +/** + *

Description: 自定义 GrantedAuthority

+ * + * @author : liuh + * @date : 2022/3/5 0:12 + */ +public class QuaferGrantedAuthority implements GrantedAuthority { + + public QuaferGrantedAuthority() { + } + + public QuaferGrantedAuthority(String authority) { + this.authority = authority; + } + + private String authority; + + @Override + public String getAuthority() { + return this.authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuaferGrantedAuthority that = (QuaferGrantedAuthority) o; + return Objects.equal(authority, that.authority); + } + + @Override + public int hashCode() { + return Objects.hashCode(authority); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("authority", authority) + .toString(); + } +} \ No newline at end of file diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferSecurityConfig.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferSecurityConfig.java new file mode 100644 index 0000000..8ff1419 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferSecurityConfig.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Description: 自定义SecurityConfig

+ *

+ * 自定义SecurityConfig,主要为了构建无参数构造函数,以解决序列化出错问题 + * + * @author : liuh + * @date : 2021/9/11 15:57 + */ +public class QuaferSecurityConfig implements ConfigAttribute { + + private String attrib; + + public QuaferSecurityConfig() { + } + + public QuaferSecurityConfig(String config) { + Assert.hasText(config, "You must provide a configuration attribute"); + this.attrib = config; + } + + @Override + public String getAttribute() { + return this.attrib; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuaferSecurityConfig that = (QuaferSecurityConfig) o; + return Objects.equal(attrib, that.attrib); + } + + @Override + public int hashCode() { + return Objects.hashCode(attrib); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("attrib", attrib) + .toString(); + } + + public static List createListFromCommaDelimitedString(String access) { + return createList(StringUtils.commaDelimitedListToStringArray(access)); + } + + public static List createList(String... attributeNames) { + Assert.notNull(attributeNames, "You must supply an array of attribute names"); + List attributes = new ArrayList<>(attributeNames.length); + for (String attribute : attributeNames) { + attributes.add(new QuaferSecurityConfig(attribute.trim())); + } + return attributes; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferUser.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferUser.java new file mode 100644 index 0000000..80b8e66 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/QuaferUser.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +import com.liuhung.engine.oauth2.core.jackson2.QuaferUserDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.security.core.CredentialsContainer; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.*; + +/** + * @author liuh + */ +@JsonDeserialize(using = QuaferUserDeserializer.class) +public class QuaferUser implements UserDetails, CredentialsContainer { + + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + private String userId; + + private String password; + + private String username; + + private Set authorities; + + private boolean accountNonExpired; + + private boolean accountNonLocked; + + private boolean credentialsNonExpired; + + private boolean enabled; + + private Set roles; + + private String employeeId; + + private String avatar; + + public QuaferUser() { + } + + /** + * Calls the more complex constructor with all boolean arguments set to {@code true}. + */ + public QuaferUser(String userId, String username, String password, Collection authorities) { + this(userId, username, password, authorities, null); + } + + /** + * Calls the more complex constructor with all boolean arguments set to {@code true}. + */ + public QuaferUser(String userId, String username, String password, Collection authorities, Set roles) { + this(userId, username, password, true, true, true, true, authorities, roles, null, null); + } + + /** + * Construct the User with the details required by + * org.springframework.security.authentication.dao.DaoAuthenticationProvider + * + * @param username the username presented to the + * DaoAuthenticationProvider + * @param password the password that should be presented to the + * DaoAuthenticationProvider + * @param enabled set to true if the user is enabled + * @param accountNonExpired set to true if the account has not expired + * @param credentialsNonExpired set to true if the credentials have not + * expired + * @param accountNonLocked set to true if the account is not locked + * @param authorities the authorities that should be granted to the caller if they + * presented the correct username and password and the user is enabled. Not null. + * @throws IllegalArgumentException if a null value was passed either as + * a parameter or as an element in the GrantedAuthority collection + */ + public QuaferUser(String userId, String username, String password, boolean enabled, boolean accountNonExpired, + boolean credentialsNonExpired, boolean accountNonLocked, + Collection authorities, Set roles, String employeeId, String avatar) { + Assert.isTrue(username != null && !"".equals(username) && password != null, + "Cannot pass null or empty values to constructor"); + this.userId = userId; + this.username = username; + this.password = password; + this.enabled = enabled; + this.accountNonExpired = accountNonExpired; + this.credentialsNonExpired = credentialsNonExpired; + this.accountNonLocked = accountNonLocked; + this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); + this.roles = CollectionUtils.isNotEmpty(roles) ? roles : new HashSet<>(); + this.employeeId = employeeId; + this.avatar = avatar; + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + @Override + public boolean isAccountNonExpired() { + return this.accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return this.accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return this.credentialsNonExpired; + } + + @Override + public void eraseCredentials() { + this.password = null; + } + + public String getUserId() { + return userId; + } + + public Set getRoles() { + return roles; + } + + public String getEmployeeId() { + return employeeId; + } + + public String getAvatar() { + return avatar; + } + + private static SortedSet sortAuthorities(Collection authorities) { + Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection"); + // Ensure array iteration order is predictable (as per + // UserDetails.getAuthorities() contract and SEC-717) + SortedSet sortedAuthorities = new TreeSet<>(new QuaferUser.AuthorityComparator()); + for (GrantedAuthority grantedAuthority : authorities) { + Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements"); + sortedAuthorities.add(grantedAuthority); + } + return sortedAuthorities; + } + + private static class AuthorityComparator implements Comparator, Serializable { + + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + @Override + public int compare(GrantedAuthority g1, GrantedAuthority g2) { + // Neither should ever be null as each entry is checked before adding it to + // the set. If the authority is null, it is a custom authority and should + // precede others. + if (g2.getAuthority() == null) { + return -1; + } + if (g1.getAuthority() == null) { + return 1; + } + return g1.getAuthority().compareTo(g2.getAuthority()); + } + + } + + /** + * Returns {@code true} if the supplied object is a {@code User} instance with the + * same {@code username} value. + *

+ * In other words, the objects are equal if they have the same username, representing + * the same principal. + */ + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuaferUser that = (QuaferUser) o; + return Objects.equal(userId, that.userId) && Objects.equal(username, that.username); + } + + /** + * Returns the hashcode of the {@code username}. + */ + @Override + public int hashCode() { + return Objects.hashCode(userId, username); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("userId", userId) + .add("password", "[PROTECTED]") + .add("username", username) + .add("accountNonExpired", accountNonExpired) + .add("accountNonLocked", accountNonLocked) + .add("credentialsNonExpired", credentialsNonExpired) + .add("enabled", enabled) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/SecurityAttribute.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/SecurityAttribute.java new file mode 100644 index 0000000..87a8f09 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/SecurityAttribute.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import java.io.Serializable; +import java.util.Set; + +/** + *

Description: Security Metadata 传输数据实体

+ * + * @author : liuh + * @date : 2021/8/8 15:51 + */ +public class SecurityAttribute implements Serializable { + + private String attributeId; + + private String attributeCode; + + private String attributeName; + + private String expression; + + private String manualSetting; + + private String ipAddress; + + private String url; + + private String requestMethod; + + private String serviceId; + + private Set roles; + + public SecurityAttribute() { + } + + public String getAttributeId() { + return attributeId; + } + + public void setAttributeId(String attributeId) { + this.attributeId = attributeId; + } + + public String getAttributeCode() { + return attributeCode; + } + + public void setAttributeCode(String attributeCode) { + this.attributeCode = attributeCode; + } + + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public String getManualSetting() { + return manualSetting; + } + + public void setManualSetting(String manualSetting) { + this.manualSetting = manualSetting; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getRequestMethod() { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) { + this.requestMethod = requestMethod; + } + + public String getServiceId() { + return serviceId; + } + + public String getAttributeName() { + return attributeName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SecurityAttribute that = (SecurityAttribute) o; + return Objects.equal(attributeId, that.attributeId); + } + + @Override + public int hashCode() { + return Objects.hashCode(attributeId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("attributeId", attributeId) + .add("attributeCode", attributeCode) + .add("attributeName", attributeName) + .add("expression", expression) + .add("manualSetting", manualSetting) + .add("ipAddress", ipAddress) + .add("url", url) + .add("requestMethod", requestMethod) + .add("serviceId", serviceId) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/SocialUserDetails.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/SocialUserDetails.java new file mode 100644 index 0000000..5ed49ba --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/SocialUserDetails.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +/** + *

Description: 社交登录用户信息详情

+ * + * @author : liuh + * @date : 2021/5/16 17:47 + */ +public interface SocialUserDetails { + + /** + * 获取社交登录唯一标识 + * + * @return String + */ + String getUuid(); + + /** + * 获取社交登录分类标识 + * + * @return String + */ + String getSource(); + + String getPhoneNumber(); + + String getAvatar(); + + String getUserName(); + + String getNickName(); +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/TokenEnhancer.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/TokenEnhancer.java new file mode 100644 index 0000000..edeb9ab --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/domain/TokenEnhancer.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.domain; + +/** + *

Description: TODO

+ * + * @author : liuh + * @date : 2022/7/14 11:35 + */ +public interface TokenEnhancer { +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/handler/AbstractSocialAuthenticationHandler.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/handler/AbstractSocialAuthenticationHandler.java new file mode 100644 index 0000000..4b21551 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/handler/AbstractSocialAuthenticationHandler.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.handler; + +import com.liuhung.engine.access.core.exception.AccessIdentityVerificationFailedException; +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.oauth2.core.exception.SocialCredentialsUserBindingFailedException; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import com.liuhung.engine.oauth2.core.definition.domain.SocialUserDetails; +import com.liuhung.engine.oauth2.core.exception.UsernameAlreadyExistsException; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.security.core.AuthenticationException; + +/** + *

Description: 抽象的社交登录处理器

+ *

+ * 实现社交登录和手机号码登录的主要流程逻辑 + * + * @author : liuh + * @date : 2021/5/16 17:38 + */ +public abstract class AbstractSocialAuthenticationHandler implements SocialAuthenticationHandler { + + /** + * 第三方登录以及手机号码验证的认证,认证成功返回对应系统中的返回信息 + * + * @param source 社交登录提供者分类 + * @param accessPrincipal 社交登录所需要的参数信息 + * @return 认证成功后返回的信息 {@link SocialUserDetails} + * @throws AccessIdentityVerificationFailedException 社交登录认证出错 + */ + public abstract SocialUserDetails identity(String source, AccessPrincipal accessPrincipal) throws AccessIdentityVerificationFailedException; + + /** + * 根据社交登录返回的用户信息,查询系统中是否有响应的信息 + * + * @param socialUserDetails 第三方系统或者手机号码认证成功后返回的信息 {@link SocialUserDetails} + * @return 系统UserDetails {@link SocialUserDetails} + */ + public abstract SocialUserDetails isUserExist(SocialUserDetails socialUserDetails); + + /** + * 系统用户注册 + *

+ * 根据社交用户提供的一些信息,进行系统用户的注册 + * + * @param socialUserDetails {@link SocialUserDetails} + * @return 系统用户 {@link QuaferUser} + * @throws UsernameAlreadyExistsException 用户名已经存在 + */ + public abstract QuaferUser register(SocialUserDetails socialUserDetails) throws UsernameAlreadyExistsException; + + /** + * 系统用户与社交用户绑定操作 + * + * @param socialUserDetails 第三方系统或者手机号码认证成功后返回的信息 {@link SocialUserDetails} + * @param userId 系统用户的ID。 + * @throws SocialCredentialsUserBindingFailedException 绑定出现错误Exception + */ + public abstract void binding(String userId, SocialUserDetails socialUserDetails) throws SocialCredentialsUserBindingFailedException; + + /** + * 随着系统业务复杂度的增加,系统用户注册成功之后,也许还会进行其它额外的操作,来补充新用户的相关信息。 + * 所以提供一个方法,方便进行新用户其它业务信息的操作。建议采用是异步操作。 + * + * @param QuaferUser 系统用户信息 {@link QuaferUser} + * @param socialUserDetails 社交登录过程中,第三方系统返回的新信息 + */ + public abstract void additionalRegisterOperation(QuaferUser QuaferUser, SocialUserDetails socialUserDetails); + + /** + * 系统用户注册 + *

+ * 根据社交用户提供的一些信息,进行系统用户的注册 + * + * @param socialUserDetails {@link SocialUserDetails} + * @return 系统用户 {@link QuaferUser} + */ + public abstract QuaferUser signIn(SocialUserDetails socialUserDetails); + + /** + * 社交用户登录后,附加的其它操作 + * + * @param QuaferUser 系统用户信息 {@link QuaferUser} + * @param newSocialUserDetails 社交登录过程中,第三方系统返回的新信息 {@link SocialUserDetails} + * @param oldSocialUserDetails 系统中已经存在的社交用户信息 + */ + public abstract void additionalSignInOperation(QuaferUser QuaferUser, SocialUserDetails newSocialUserDetails, SocialUserDetails oldSocialUserDetails); + + /** + * 社交登录 + *

+ * 1. 首先在第三方系统进行认证,或者手机号码、扫码认证。返回认证后的信息 + * 2. 根据认证返回的信息,在系统中查询是否有对应的用户信息。 + * 2.1. 如果有对应的信息,根据需要更新社交用户的信息,然后返回系统用户信息,进行登录。 + * 2.2. 如果没有对应信息,就先进行用户的注册,然后进行社交用户和系统用户的绑定。 + * + * @param source 社交登录提供者分类 + * @param accessPrincipal 社交登录所需要的信息 + */ + @Override + public QuaferUser authentication(String source, AccessPrincipal accessPrincipal) throws AuthenticationException { + SocialUserDetails newSocialUserDetails = this.identity(source, accessPrincipal); + SocialUserDetails oldSocialUserDetails = this.isUserExist(newSocialUserDetails); + + if (ObjectUtils.isEmpty(oldSocialUserDetails)) { + QuaferUser QuaferUser = this.register(newSocialUserDetails); + this.binding(QuaferUser.getUserId(), newSocialUserDetails); + this.additionalRegisterOperation(QuaferUser, newSocialUserDetails); + return QuaferUser; + } else { + QuaferUser QuaferUser = this.signIn(oldSocialUserDetails); + this.additionalSignInOperation(QuaferUser, newSocialUserDetails, oldSocialUserDetails); + return QuaferUser; + } + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/handler/SocialAuthenticationHandler.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/handler/SocialAuthenticationHandler.java new file mode 100644 index 0000000..42cd35f --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/handler/SocialAuthenticationHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.handler; + +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import org.springframework.security.core.AuthenticationException; + +/** + *

Description: 社交登录处理器

+ * + * @author : liuh + * @date : 2021/4/4 17:34 + */ +public interface SocialAuthenticationHandler { + + /** + * 社交登录 + *

+ * 1. 首先在第三方系统进行认证,或者手机号码、扫码认证。返回认证后的信息 + * 2. 根据认证返回的信息,在系统中查询是否有对应的用户信息。 + * 2.1. 如果有对应的信息,根据需要更新社交用户的信息,然后返回系统用户信息,进行登录。 + * 2.2. 如果没有对应信息,就先进行用户的注册,然后进行社交用户和系统用户的绑定。 + * + * @param source 社交登录提供者分类 + * @param accessPrincipal 社交登录所需要的信息 {@link AccessPrincipal} + * @return 系统用户 + * @throws AuthenticationException {@link AuthenticationException} 认证错误 + */ + QuaferUser authentication(String source, AccessPrincipal accessPrincipal) throws AuthenticationException; + +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/ClientDetailsService.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/ClientDetailsService.java new file mode 100644 index 0000000..044ea7e --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/ClientDetailsService.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.service; + +import com.liuhung.engine.oauth2.core.definition.domain.QuaferGrantedAuthority; + +import java.util.Set; + +/** + *

Description: 客户端操作基础接口

+ * + * @author : liuh + * @date : 2022/4/1 18:16 + */ +public interface ClientDetailsService { + + /** + * 根据客户端ID获取客户端权限 + * + * @param clientId 客户端ID + * @return 客户端权限集合 + */ + Set findAuthoritiesById(String clientId); +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/EnhanceClientDetailsService.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/EnhanceClientDetailsService.java new file mode 100644 index 0000000..d8aeb2e --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/EnhanceClientDetailsService.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.service; + +/** + *

Description: 自定义客户端详情接口

+ * + * @author : liuh + * @date : 2022/3/31 14:42 + */ +public interface EnhanceClientDetailsService extends ClientDetailsService { + +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/EnhanceUserDetailsService.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/EnhanceUserDetailsService.java new file mode 100644 index 0000000..4a5aed0 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/service/EnhanceUserDetailsService.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.service; + +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + *

Description: 自定义UserDetailsService接口,方便以后扩展

+ * + * @author : liuh + * @date : 2021/1/17 12:49 + */ +public interface EnhanceUserDetailsService extends UserDetailsService { + + /** + * 通过社交集成的唯一id,获取用户信息 + *

+ * 如果是短信验证码,openId就是手机号码 + * + * @param accessPrincipal 社交登录提供的相关信息 + * @param source 社交集成提供商类型 + * @return {@link UserDetails} + * @throws UsernameNotFoundException 用户不存在 + */ + UserDetails loadUserBySocial(String source, AccessPrincipal accessPrincipal) throws AuthenticationException; + + /** + * 系统用户名 + * + * @param username 用户账号 + * @return {@link QuaferUser} + * @throws UsernameNotFoundException 用户不存在 + */ + QuaferUser loadQuaferUserByUsername(String username) throws UsernameNotFoundException; +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/strategy/StrategyAuthorityDetailsService.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/strategy/StrategyAuthorityDetailsService.java new file mode 100644 index 0000000..37c9b4d --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/strategy/StrategyAuthorityDetailsService.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.strategy; + +import com.liuhung.engine.oauth2.core.definition.domain.Authority; + +import java.util.List; + +/** + *

Description: 系统范围服务策略定义

+ * + * @author : liuh + * @date : 2022/3/31 22:34 + */ +public interface StrategyAuthorityDetailsService { + + /** + * 获取全部权限 + * + * @return 权限集合 + */ + List findAll(); +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/strategy/StrategyUserDetailsService.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/strategy/StrategyUserDetailsService.java new file mode 100644 index 0000000..8f2d68d --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/definition/strategy/StrategyUserDetailsService.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.definition.strategy; + +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import org.springframework.security.core.AuthenticationException; + +/** + *

Description: 系统用户服务策略定义

+ * + * @author : liuh + * @date : 2022/2/17 10:52 + */ +public interface StrategyUserDetailsService { + + QuaferUser findUserDetailsByUsername(String userName) throws AuthenticationException; + + QuaferUser findUserDetailsBySocial(String source, AccessPrincipal accessPrincipal) throws AuthenticationException; +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/ApplicationType.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/ApplicationType.java new file mode 100644 index 0000000..581ec57 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/ApplicationType.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 应用类别

+ * + * @author : liuh + * @date : 2020/5/4 12:01 + */ +@Schema(title = "应用类型") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ApplicationType implements BaseUiEnum { + + /** + * 应用类型 + */ + WEB(0, "PC网页应用"), + SERVICE(1, "服务应用"), + APP(2, "手机应用"), + WAP(3, "手机网页应用"), + MINI(4, "小程序应用"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(title = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCT = new ArrayList<>(); + + static { + for (ApplicationType applicationType : ApplicationType.values()) { + INDEX_MAP.put(applicationType.getValue(), applicationType); + JSON_STRUCT.add(applicationType.getValue(), + ImmutableMap.builder() + .put("value", applicationType.getValue()) + .put("key", applicationType.name()) + .put("text", applicationType.getDescription()) + .put("index", applicationType.getValue()) + .build()); + } + } + + ApplicationType(Integer value, String description) { + this.value = value; + this.description = description; + } + + @Override + public String getDescription() { + return description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + public static ApplicationType get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCT; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/AuthenticationMethod.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/AuthenticationMethod.java new file mode 100644 index 0000000..497f812 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/AuthenticationMethod.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 客户端身份验证模式

+ * + * @author : liuh + * @date : 2022/3/17 14:49 + */ +@Schema(title = "OAuth2 Client 认证方式") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum AuthenticationMethod implements BaseUiEnum { + + /** + * enum + */ + CLIENT_SECRET_BASIC(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue(), "基于Client Secret的Basic验证模式"), + CLIENT_SECRET_POST(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue(), "基于Client Secret的Post验证模式"), + CLIENT_SECRET_JWT(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue(), "基于Client Secret的JWT验证模式"), + PRIVATE_KEY_JWT(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue(), "基于私钥的JWT验证模式"), + NONE(ClientAuthenticationMethod.NONE.getValue(), "不设置任何模式"); + + @Schema(title = "认证方法") + private final String value; + @Schema(title = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (AuthenticationMethod authenticationMethod : AuthenticationMethod.values()) { + INDEX_MAP.put(authenticationMethod.ordinal(), authenticationMethod); + JSON_STRUCTURE.add(authenticationMethod.ordinal(), + ImmutableMap.builder() + .put("value", authenticationMethod.getValue()) + .put("key", authenticationMethod.name()) + .put("text", authenticationMethod.getDescription()) + .put("index", authenticationMethod.ordinal()) + .build()); + } + } + + AuthenticationMethod(String value, String description) { + this.value = value; + this.description = description; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getValue() { + return value; + } + + public static AuthenticationMethod get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/Certificate.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/Certificate.java new file mode 100644 index 0000000..340593e --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/Certificate.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +/** + *

Description: 证书使用策略

+ * + * @author : liuh + * @date : 2022/3/6 18:32 + */ +public enum Certificate { + + /** + * Spring Authorization Server 默认的 JWK 生成方式 + */ + STANDARD, + /** + * 自定义证书 JWK 生成方式 + */ + CUSTOM; +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/GrantType.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/GrantType.java new file mode 100644 index 0000000..8478426 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/GrantType.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: OAuth2 认证模式枚举

+ * + * @author : liuh + * @date : 2021/10/16 14:39 + */ +@Schema(title = "OAuth2 认证模式") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum GrantType implements BaseUiEnum { + + /** + * enum + */ + AUTHORIZATION_CODE(BaseConstants.AUTHORIZATION_CODE, "Authorization Code 模式"), + PASSWORD(BaseConstants.PASSWORD, "Password 模式"), + CLIENT_CREDENTIALS(BaseConstants.CLIENT_CREDENTIALS, "Client Credentials 模式"), + REFRESH_TOKEN(BaseConstants.REFRESH_TOKEN, "Refresh Token 模式"), + SOCIAL_CREDENTIALS(BaseConstants.SOCIAL_CREDENTIALS, "Social Credentials 模式"); + + @Schema(title = "认证模式") + private final String value; + @Schema(title = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (GrantType grantType : GrantType.values()) { + INDEX_MAP.put(grantType.ordinal(), grantType); + JSON_STRUCTURE.add(grantType.ordinal(), + ImmutableMap.builder() + .put("value", grantType.getValue()) + .put("key", grantType.name()) + .put("text", grantType.getDescription()) + .put("index", grantType.ordinal()) + .build()); + } + } + + GrantType(String value, String description) { + this.value = value; + this.description = description; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDescription() { + return description; + } + + public static GrantType get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/PermissionExpression.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/PermissionExpression.java new file mode 100644 index 0000000..b86612e --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/PermissionExpression.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 安全表达式

+ * + * @author : liuh + * @date : 2021/8/14 3:49 + */ +@Schema(title = "Security 权限表达式") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum PermissionExpression implements BaseUiEnum { + /** + * 权限表达式 + */ + PERMIT_ALL("permitAll", "permitAll"), + ANONYMOUS("anonymous", "anonymous"), + REMEMBER_ME("rememberMe", "rememberMe"), + DENY_ALL("denyAll", "denyAll"), + AUTHENTICATED("authenticated", "authenticated"), + FULLY_AUTHENTICATED("fullyAuthenticated", "fullyAuthenticated"), + NOT_PERMIT_ALL("!permitAll", "!permitAll"), + NOT_ANONYMOUS("!anonymous", "!anonymous"), + NOT_REMEMBER_ME("!rememberMe", "!rememberMe"), + NOT_DENY_ALL("!denyAll", "!denyAll"), + NOT_AUTHENTICATED("!authenticated", "!authenticated"), + NOT_FULLY_AUTHENTICATED("!fullyAuthenticated", "!fullyAuthenticated"), + HAS_ROLE("hasRole", "hasRole"), + HAS_ANY_ROLE("hasAnyRole", "hasAnyRole"), + HAS_AUTHORITY("hasAuthority", "hasAuthority"), + HAS_ANY_AUTHORITY("hasAnyAuthority", "hasAnyAuthority"), + HAS_IP_ADDRESS("hasIpAddress", "hasIpAddress"); + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + @Schema(title = "索引") + private final String value; + @Schema(title = "说明") + private final String description; + + static { + for (PermissionExpression permissionExpression : PermissionExpression.values()) { + INDEX_MAP.put(permissionExpression.getValue(), permissionExpression); + JSON_STRUCTURE.add(permissionExpression.ordinal(), + ImmutableMap.builder() + .put("value", permissionExpression.getValue()) + .put("key", permissionExpression.name()) + .put("text", permissionExpression.getDescription()) + .put("index", permissionExpression.ordinal()) + .build()); + } + } + + PermissionExpression(String value, String description) { + this.value = value; + this.description = description; + } + + @JsonValue + @Override + public String getValue() { + return value; + } + + @Override + public String getDescription() { + return description; + } + + public static PermissionExpression get(String value) { + return INDEX_MAP.get(value); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/Signature.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/Signature.java new file mode 100644 index 0000000..16981c5 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/Signature.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: OAuth2 Signature

+ * + * @author : liuh + * @date : 2022/3/2 16:15 + */ +@Schema(name = "签名算法") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum Signature implements BaseUiEnum { + + /** + * enum + */ + RS256(0, "RS256"), + RS384(1, "RS384"), + RS512(2, "RS512"), + ES256(3, "ES256"), + ES384(4, "ES384"), + ES512(5, "ES512"), + PS256(6, "PS256"), + PS384(7, "PS384"), + PS512(8, "PS512"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(name = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (Signature signature : Signature.values()) { + INDEX_MAP.put(signature.getValue(), signature); + JSON_STRUCTURE.add(signature.getValue(), + ImmutableMap.builder() + .put("value", signature.getValue()) + .put("key", signature.name()) + .put("text", signature.getDescription()) + .put("index", signature.getValue()) + .build()); + } + } + + Signature(Integer value, String description) { + this.value = value; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDescription() { + return description; + } + + public static Signature get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/TokenFormat.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/TokenFormat.java new file mode 100644 index 0000000..40d64b7 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/enums/TokenFormat.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.enums; + +import com.liuhung.engine.assistant.core.definition.enums.BaseUiEnum; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: 令牌格式

+ * + * @author : liuh + * @date : 2022/3/25 0:02 + */ +@Schema(name = "令牌格式") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum TokenFormat implements BaseUiEnum { + + /** + * enum + */ + SELF_CONTAINED(0, "self-contained", "自包含格式令牌"), + REFERENCE(1, "reference", "引用(不透明)令牌"); + + @Schema(title = "枚举值") + private final Integer value; + @Schema(title = "格式") + private final String format; + @Schema(title = "文字") + private final String description; + + private static final Map INDEX_MAP = new HashMap<>(); + private static final List> JSON_STRUCTURE = new ArrayList<>(); + + static { + for (TokenFormat tokenFormat : TokenFormat.values()) { + INDEX_MAP.put(tokenFormat.getValue(), tokenFormat); + JSON_STRUCTURE.add(tokenFormat.getValue(), + ImmutableMap.builder() + // 使用数字作为 value, 适用于单选,同时数据库只存 value值即可 + // 使用具体的字符串值作为value, 适用于多选,同时数据库存储以逗号分隔拼接的字符串 + .put("value", tokenFormat.getValue()) + .put("key", tokenFormat.name()) + .put("text", tokenFormat.getDescription()) + .put("format", tokenFormat.getFormat()) + .put("index", tokenFormat.ordinal()) + .build()); + } + } + + TokenFormat(Integer value, String method, String description) { + this.value = value; + this.format = method; + this.description = description; + } + + /** + * 不加@JsonValue,转换的时候转换出完整的对象。 + * 加了@JsonValue,只会显示相应的属性的值 + *

+ * 不使用@JsonValue @JsonDeserializer类里面要做相应的处理 + * + * @return Enum枚举值 + */ + @JsonValue + @Override + public Integer getValue() { + return value; + } + + public String getFormat() { + return format; + } + + @Override + public String getDescription() { + return description; + } + + public static TokenFormat get(Integer index) { + return INDEX_MAP.get(index); + } + + public static List> getPreprocessedJsonStructure() { + return JSON_STRUCTURE; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/AccountEndpointLimitedException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/AccountEndpointLimitedException.java new file mode 100644 index 0000000..4d5d7c3 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/AccountEndpointLimitedException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import org.springframework.security.authentication.AccountStatusException; + +/** + *

Description: 登录端点限制

+ * + * @author : liuh + * @date : 2022/7/7 14:06 + */ +public class AccountEndpointLimitedException extends AccountStatusException { + + public AccountEndpointLimitedException(String msg) { + super(msg); + } + + public AccountEndpointLimitedException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/IllegalSymmetricKeyException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/IllegalSymmetricKeyException.java new file mode 100644 index 0000000..273482a --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/IllegalSymmetricKeyException.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + + +import com.liuhung.engine.assistant.core.domain.Feedback; +import org.apache.http.HttpStatus; + +/** + *

Description : 非法加密Key QuaferException

+ * + * @author : liuh + * @date : 2020/1/28 17:32 + */ +public class IllegalSymmetricKeyException extends PlatformAuthenticationException { + + public IllegalSymmetricKeyException(String msg, Throwable cause) { + super(msg, cause); + } + + public IllegalSymmetricKeyException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return new Feedback(50103, "静态AES加密算法KEY非法", HttpStatus.SC_NOT_IMPLEMENTED); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaArgumentIllegalException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaArgumentIllegalException.java new file mode 100644 index 0000000..b88c9f4 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaArgumentIllegalException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import org.apache.http.HttpStatus; + +/** + *

Description: Oauth2 使用的验证码参数错误

+ * + * @author : liuh + * @date : 2021/12/24 12:02 + */ +public class OAuth2CaptchaArgumentIllegalException extends OAuth2CaptchaException { + + public OAuth2CaptchaArgumentIllegalException(String msg, Throwable cause) { + super(msg, cause); + } + + public OAuth2CaptchaArgumentIllegalException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40613, "验证码参数格式错误", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaException.java new file mode 100644 index 0000000..cca13af --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaException.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.definition.exception.QuaferException; +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.domain.Result; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.core.Authentication; + +/** + *

Description: OAuth2 验证码基础 Exception

+ *

+ * 这里没有用基础定义的 PlatformAuthorizationException。主要问题是在自定义表单登录时,如果使用基础的 {@link org.springframework.security.core.AuthenticationException}, + * 在 Spring Security 标准代码中该Exception将不会抛出,而是进行二次的用户验证,这将导致在验证过程中直接跳过验证码的校验。 + * + * @author : liuh + * @date : 2022/4/12 22:33 + * @see org.springframework.security.authentication.ProviderManager#authenticate(Authentication) + */ +public class OAuth2CaptchaException extends AccountStatusException implements QuaferException { + + public OAuth2CaptchaException(String msg) { + super(msg); + } + + public OAuth2CaptchaException(String msg, Throwable cause) { + super(msg, cause); + } + + @Override + public Feedback getFeedback() { + return Feedback.ERROR; + } + + @Override + public Result getResult() { + Result result = Result.failure(); + result.code(getFeedback().getCode()); + result.message(getFeedback().getMessage()); + result.status(getFeedback().getStatus()); + result.stackTrace(super.getStackTrace()); + result.detail(super.getMessage()); + return result; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaHasExpiredException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaHasExpiredException.java new file mode 100644 index 0000000..2c22f6c --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaHasExpiredException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import org.apache.http.HttpStatus; + +/** + *

Description: Oauth2 使用的验证码不匹配错误

+ * + * @author : liuh + * @date : 2021/12/24 12:04 + */ +public class OAuth2CaptchaHasExpiredException extends OAuth2CaptchaException { + + public OAuth2CaptchaHasExpiredException(String msg, Throwable cause) { + super(msg, cause); + } + + public OAuth2CaptchaHasExpiredException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40610, "验证码已过期", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaIsEmptyException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaIsEmptyException.java new file mode 100644 index 0000000..2714a77 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaIsEmptyException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import org.apache.http.HttpStatus; + +/** + *

Description: 验证码为空

+ * + * @author : liuh + * @date : 2021/12/24 18:08 + */ +public class OAuth2CaptchaIsEmptyException extends OAuth2CaptchaException { + + public OAuth2CaptchaIsEmptyException(String msg, Throwable cause) { + super(msg, cause); + } + + public OAuth2CaptchaIsEmptyException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40611, "验证码不能为空", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaMismatchException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaMismatchException.java new file mode 100644 index 0000000..2b2c535 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/OAuth2CaptchaMismatchException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import org.apache.http.HttpStatus; + +/** + *

Description: Oauth2 使用的验证码不匹配错误

+ * + * @author : liuh + * @date : 2021/12/24 12:08 + */ +public class OAuth2CaptchaMismatchException extends OAuth2CaptchaException { + + public OAuth2CaptchaMismatchException(String msg, Throwable cause) { + super(msg, cause); + } + + public OAuth2CaptchaMismatchException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40612, "验证码不匹配", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/PlatformAuthenticationException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/PlatformAuthenticationException.java new file mode 100644 index 0000000..2b3c155 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/PlatformAuthenticationException.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.definition.exception.QuaferException; +import com.liuhung.engine.assistant.core.domain.Feedback; +import com.liuhung.engine.assistant.core.domain.Result; +import org.springframework.security.core.AuthenticationException; + +/** + *

Description: 平台认证基础Exception

+ * + * @author : liuh + * @date : 2021/10/16 14:41 + */ +public class PlatformAuthenticationException extends AuthenticationException implements QuaferException { + + public PlatformAuthenticationException(String msg, Throwable cause) { + super(msg, cause); + } + + public PlatformAuthenticationException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return Feedback.ERROR; + } + + @Override + public Result getResult() { + Result result = Result.failure(); + result.code(getFeedback().getCode()); + result.message(getFeedback().getMessage()); + result.status(getFeedback().getStatus()); + result.stackTrace(super.getStackTrace()); + result.detail(super.getMessage()); + return result; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SecurityGlobalExceptionHandler.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SecurityGlobalExceptionHandler.java new file mode 100644 index 0000000..7b1399d --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SecurityGlobalExceptionHandler.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.enums.ResultErrorCodes; +import com.liuhung.engine.assistant.core.exception.GlobalExceptionHandler; +import com.liuhung.engine.assistant.core.exception.PlatformException; +import com.liuhung.engine.oauth2.core.constants.OAuth2ErrorCodes; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; + +/** + *

Description: 统一异常处理器

+ * + * @author : liuh + * @date : 2019/11/18 8:12 + */ +@RestControllerAdvice +public class SecurityGlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(SecurityGlobalExceptionHandler.class); + + private static final Map> EXCEPTION_DICTIONARY = new HashMap<>(); + + static { + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.ACCESS_DENIED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.ACCESS_DENIED)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INSUFFICIENT_SCOPE, GlobalExceptionHandler.getForbiddenResult(ResultErrorCodes.INSUFFICIENT_SCOPE)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INVALID_CLIENT, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.INVALID_CLIENT)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INVALID_GRANT, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.INVALID_GRANT)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INVALID_REDIRECT_URI, GlobalExceptionHandler.getPreconditionFailedResult(ResultErrorCodes.INVALID_REDIRECT_URI)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INVALID_REQUEST, GlobalExceptionHandler.getPreconditionFailedResult(ResultErrorCodes.INVALID_REQUEST)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INVALID_SCOPE, GlobalExceptionHandler.getPreconditionFailedResult(ResultErrorCodes.INVALID_SCOPE)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.INVALID_TOKEN, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.INVALID_TOKEN)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.SERVER_ERROR, GlobalExceptionHandler.getInternalServerErrorResult(ResultErrorCodes.SERVER_ERROR)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.TEMPORARILY_UNAVAILABLE, GlobalExceptionHandler.getServiceUnavailableResult(ResultErrorCodes.TEMPORARILY_UNAVAILABLE)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.UNAUTHORIZED_CLIENT)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, GlobalExceptionHandler.getNotAcceptableResult(ResultErrorCodes.UNSUPPORTED_GRANT_TYPE)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, GlobalExceptionHandler.getNotAcceptableResult(ResultErrorCodes.UNSUPPORTED_RESPONSE_TYPE)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE, GlobalExceptionHandler.getNotAcceptableResult(ResultErrorCodes.UNSUPPORTED_TOKEN_TYPE)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.ACCOUNT_EXPIRED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.ACCOUNT_EXPIRED)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.BAD_CREDENTIALS, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.BAD_CREDENTIALS)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.CREDENTIALS_EXPIRED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.CREDENTIALS_EXPIRED)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.ACCOUNT_DISABLED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.ACCOUNT_DISABLED)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.ACCOUNT_LOCKED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.ACCOUNT_LOCKED)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.ACCOUNT_ENDPOINT_LIMITED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.ACCOUNT_ENDPOINT_LIMITED)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.USERNAME_NOT_FOUND, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.USERNAME_NOT_FOUND)); + EXCEPTION_DICTIONARY.put(OAuth2ErrorCodes.SESSION_EXPIRED, GlobalExceptionHandler.getUnauthorizedResult(ResultErrorCodes.SESSION_EXPIRED)); + } + + /** + * Rest Template 错误处理 + * + * @param ex 错误 + * @param request 请求 + * @param response 响应 + * @return Result 对象 + * @see baeldung + */ + @ExceptionHandler({HttpClientErrorException.class, HttpServerErrorException.class}) + public static Result restTemplateException(Exception ex, HttpServletRequest request, HttpServletResponse response) { + Result result = resolveException(ex, request.getRequestURI()); + response.setStatus(result.getStatus()); + return result; + } + + @ExceptionHandler({MethodArgumentNotValidException.class}) + public static Result validationMethodArgumentException(MethodArgumentNotValidException ex, HttpServletRequest request, HttpServletResponse response) { + return validationBindException(ex, request, response); + } + + @ExceptionHandler({BindException.class}) + public static Result validationBindException(BindException ex, HttpServletRequest request, HttpServletResponse response) { + Result result = resolveException(ex, request.getRequestURI()); + + BindingResult bindingResult = ex.getBindingResult(); + FieldError fieldError = bindingResult.getFieldError(); + //返回第一个错误的信息 + if (ObjectUtils.isNotEmpty(fieldError)) { + result.validation(fieldError.getDefaultMessage(), fieldError.getCode(), fieldError.getField()); + } + + response.setStatus(result.getStatus()); + return result; + } + + /** + * 统一异常处理 + * AuthenticationException + * + * @param ex 错误 + * @param request 请求 + * @param response 响应 + * @return Result 对象 + */ + @ExceptionHandler({AuthenticationException.class, PlatformAuthenticationException.class}) + public static Result authenticationException(Exception ex, HttpServletRequest request, HttpServletResponse response) { + Result result = resolveSecurityException(ex, request.getRequestURI()); + response.setStatus(result.getStatus()); + return result; + } + + @ExceptionHandler({OAuth2AuthenticationException.class}) + public static Result oAuth2AuthenticationException(Exception ex, HttpServletRequest request, HttpServletResponse response) { + Result result = resolveSecurityException(ex, request.getRequestURI()); + response.setStatus(result.getStatus()); + return result; + } + + @ExceptionHandler({Exception.class, PlatformException.class}) + public static Result exception(Exception ex, HttpServletRequest request, HttpServletResponse response) { + Result result = resolveException(ex, request.getRequestURI()); + response.setStatus(result.getStatus()); + return result; + } + + + public static Result resolveException(Exception ex, String path) { + return GlobalExceptionHandler.resolveException(ex, path); + } + + /** + * 静态解析认证异常 + * + * @param exception 错误信息 + * @return Result 对象 + */ + public static Result resolveSecurityException(Exception exception, String path) { + + Exception reason = new Exception(); + if (exception instanceof OAuth2AuthenticationException) { + OAuth2AuthenticationException oAuth2AuthenticationException = (OAuth2AuthenticationException) exception; + OAuth2Error oAuth2Error = oAuth2AuthenticationException.getError(); + if (EXCEPTION_DICTIONARY.containsKey(oAuth2Error.getErrorCode())) { + Result result = EXCEPTION_DICTIONARY.get(oAuth2Error.getErrorCode()); + result.path(oAuth2Error.getUri()); + result.stackTrace(exception.getStackTrace()); + result.detail(exception.getMessage()); + return result; + } + } else if (exception instanceof InsufficientAuthenticationException) { + Throwable throwable = exception.getCause(); + if (ObjectUtils.isNotEmpty(throwable)) { + reason = new Exception(throwable); + } else { + reason = exception; + } + log.debug("[Quafer] |- InsufficientAuthenticationException cause content is [{}]", reason.getClass().getSimpleName()); + } else { + reason = exception; + } + + return resolveException(reason, path); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SessionExpiredException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SessionExpiredException.java new file mode 100644 index 0000000..e72d5b4 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SessionExpiredException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import org.springframework.security.authentication.AccountStatusException; + +/** + *

Description: 自定义 Session 已过期

+ * + * @author : liuh + * @date : 2022/7/28 13:37 + */ +public class SessionExpiredException extends AccountStatusException { + + public SessionExpiredException(String msg) { + super(msg); + } + + public SessionExpiredException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SocialCredentialsParameterBindingFailedException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SocialCredentialsParameterBindingFailedException.java new file mode 100644 index 0000000..ab025fc --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SocialCredentialsParameterBindingFailedException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import org.springframework.security.core.AuthenticationException; + +/** + *

Description: 无法解析SocialType错误

+ * + * @author : liuh + * @date : 2021/5/16 9:37 + */ +public class SocialCredentialsParameterBindingFailedException extends AuthenticationException { + + public SocialCredentialsParameterBindingFailedException(String msg, Throwable cause) { + super(msg, cause); + } + + public SocialCredentialsParameterBindingFailedException(String msg) { + super(msg); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SocialCredentialsUserBindingFailedException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SocialCredentialsUserBindingFailedException.java new file mode 100644 index 0000000..ab86a88 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/SocialCredentialsUserBindingFailedException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +/** + *

Description: 社交登录绑定用户出错

+ * + * @author : liuh + * @date : 2021/5/18 9:31 + */ +public class SocialCredentialsUserBindingFailedException extends PlatformAuthenticationException { + + public SocialCredentialsUserBindingFailedException(String msg, Throwable cause) { + super(msg, cause); + } + + public SocialCredentialsUserBindingFailedException(String msg) { + super(msg); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/UsernameAlreadyExistsException.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/UsernameAlreadyExistsException.java new file mode 100644 index 0000000..82da37f --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/exception/UsernameAlreadyExistsException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.exception; + +import com.liuhung.engine.assistant.core.domain.Feedback; +import org.apache.http.HttpStatus; + +/** + *

Description: UsernameAlreadyExistsException

+ * + * @author : liuh + * @date : 2021/5/17 19:04 + */ +public class UsernameAlreadyExistsException extends PlatformAuthenticationException { + + public UsernameAlreadyExistsException(String msg, Throwable cause) { + super(msg, cause); + } + + public UsernameAlreadyExistsException(String msg) { + super(msg); + } + + @Override + public Feedback getFeedback() { + return new Feedback(40614, "静态AES加密算法KEY非法", HttpStatus.SC_NOT_ACCEPTABLE); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/FormLoginWebAuthenticationDetailsDeserializer.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/FormLoginWebAuthenticationDetailsDeserializer.java new file mode 100644 index 0000000..7878818 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/FormLoginWebAuthenticationDetailsDeserializer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.liuhung.engine.oauth2.core.definition.domain.FormLoginWebAuthenticationDetails; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.MissingNode; + +import java.io.IOException; + +/** + *

Description: FormLoginWebAuthenticationDetailsDeserializer

+ * + * @author : liuh + * @date : 2022/4/14 11:48 + */ +public class FormLoginWebAuthenticationDetailsDeserializer extends JsonDeserializer { + @Override + public FormLoginWebAuthenticationDetails deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JacksonException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode jsonNode = mapper.readTree(jp); + + String remoteAddress = readJsonNode(jsonNode, "remoteAddress").asText(); + String sessionId = readJsonNode(jsonNode, "sessionId").asText(); + String parameterName = readJsonNode(jsonNode, "parameterName").asText(); + String category = readJsonNode(jsonNode, "category").asText(); + String code = readJsonNode(jsonNode, "code").asText(); + String identity = readJsonNode(jsonNode, "identity").asText(); + boolean closed = readJsonNode(jsonNode, "closed").asBoolean(); + + return new FormLoginWebAuthenticationDetails(remoteAddress, sessionId, closed, parameterName, category, code, identity); + } + + private JsonNode readJsonNode(JsonNode jsonNode, String field) { + return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/FormLoginWebAuthenticationDetailsMixin.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/FormLoginWebAuthenticationDetailsMixin.java new file mode 100644 index 0000000..e0f548e --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/FormLoginWebAuthenticationDetailsMixin.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + *

Description: FormLoginWebAuthenticationDetailsMixin

+ * + * @author : liuh + * @date : 2022/4/14 11:03 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonDeserialize(using = FormLoginWebAuthenticationDetailsDeserializer.class) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.ANY) +public class FormLoginWebAuthenticationDetailsMixin { + +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferGrantedAuthorityDeserializer.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferGrantedAuthorityDeserializer.java new file mode 100644 index 0000000..eed39a2 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferGrantedAuthorityDeserializer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.liuhung.engine.oauth2.core.definition.domain.QuaferGrantedAuthority; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.MissingNode; + +import java.io.IOException; + +/** + *

Description: QuaferGrantedAuthority 反序列化

+ * + * @author : liuh + * @date : 2022/3/17 20:28 + */ +public class QuaferGrantedAuthorityDeserializer extends JsonDeserializer { + @Override + public QuaferGrantedAuthority deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode jsonNode = mapper.readTree(jp); + String authority = readJsonNode(jsonNode, "authority").asText(); + return new QuaferGrantedAuthority(authority); + } + + private JsonNode readJsonNode(JsonNode jsonNode, String field) { + return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferGrantedAuthorityMixin.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferGrantedAuthorityMixin.java new file mode 100644 index 0000000..ebc383f --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferGrantedAuthorityMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + *

Description: QuaferGrantedAuthority Jackson2 Mixin

+ * + * @author : liuh + * @date : 2022/3/17 20:28 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonDeserialize(using = QuaferGrantedAuthorityDeserializer.class) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class QuaferGrantedAuthorityMixin { +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferJackson2Module.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferJackson2Module.java new file mode 100644 index 0000000..894b93f --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferJackson2Module.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.liuhung.engine.oauth2.core.definition.domain.FormLoginWebAuthenticationDetails; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferGrantedAuthority; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.security.jackson2.SecurityJackson2Modules; + +/** + *

Description: 自定义 User Details Module

+ * + * @author : liuh + * @date : 2022/2/17 23:39 + */ +public class QuaferJackson2Module extends SimpleModule { + + public QuaferJackson2Module() { + super(QuaferJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null)); + } + + @Override + public void setupModule(SetupContext context) { + SecurityJackson2Modules.enableDefaultTyping(context.getOwner()); + context.setMixInAnnotations(QuaferUser.class, QuaferUserMixin.class); + context.setMixInAnnotations(QuaferGrantedAuthority.class, QuaferGrantedAuthorityMixin.class); + context.setMixInAnnotations(FormLoginWebAuthenticationDetails.class, FormLoginWebAuthenticationDetailsMixin.class); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferUserDeserializer.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferUserDeserializer.java new file mode 100644 index 0000000..a75cd69 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferUserDeserializer.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.liuhung.engine.oauth2.core.definition.domain.QuaferGrantedAuthority; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.MissingNode; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.io.IOException; +import java.util.Set; + +/** + *

Description: 自定义 UserDetails 序列化

+ * + * @author : liuh + * @date : 2022/2/17 21:01 + */ +public class QuaferUserDeserializer extends JsonDeserializer { + + private static final TypeReference> HERODOTUS_GRANTED_AUTHORITY_SET = new TypeReference>() { + }; + private static final TypeReference> HERODOTUS_ROLE_SET = new TypeReference>() { + }; + + /** + * This method will create {@link User} object. It will ensure successful object + * creation even if password key is null in serialized json, because credentials may + * be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In + * that case there won't be any password key in serialized json. + * + * @param jp the JsonParser + * @param ctxt the DeserializationContext + * @return the user + * @throws IOException if a exception during IO occurs + * @throws JsonProcessingException if an error during JSON processing occurs + */ + @Override + public QuaferUser deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode jsonNode = mapper.readTree(jp); + Set authorities = mapper.convertValue(jsonNode.get("authorities"), HERODOTUS_GRANTED_AUTHORITY_SET); + Set roles = mapper.convertValue(jsonNode.get("roles"), HERODOTUS_ROLE_SET); + JsonNode passwordNode = readJsonNode(jsonNode, "password"); + String userId = readJsonNode(jsonNode, "userId").asText(); + String username = readJsonNode(jsonNode, "username").asText(); + String password = passwordNode.asText(""); + boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean(); + boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean(); + boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean(); + boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean(); + String employeeId = readJsonNode(jsonNode, "employeeId").asText(); + String avatar = readJsonNode(jsonNode, "avatar").asText(); + QuaferUser result = new QuaferUser(userId, username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, roles, employeeId, avatar); + if (passwordNode.asText(null) == null) { + result.eraseCredentials(); + } + return result; + } + + private JsonNode readJsonNode(JsonNode jsonNode, String field) { + return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferUserMixin.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferUserMixin.java new file mode 100644 index 0000000..6497274 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/jackson2/QuaferUserMixin.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.jackson2; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + *

Description: 自定义 UserDetails Mixin

+ *

+ * This mixin class helps in serialize/deserialize {@link org.springframework.security.core.userdetails.User}. This class also register a custom deserializer UserDeserializer to deserialize User object successfully. In order to use this mixin you need to register two more mixin classes in your ObjectMapper configuration. + * SimpleGrantedAuthorityMixin + * UnmodifiableSetMixin + * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *

+ * See Also: UserDeserializer, CoreJackson2Module, SecurityJackson2Modules + * + * @author : liuh + * @date : 2022/2/17 21:57 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonDeserialize(using = QuaferUserDeserializer.class) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class QuaferUserMixin { +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/OAuth2ComplianceProperties.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/OAuth2ComplianceProperties.java new file mode 100644 index 0000000..c0fca3d --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/OAuth2ComplianceProperties.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.properties; + +import com.liuhung.engine.oauth2.core.constants.OAuth2Constants; +import com.google.common.base.MoreObjects; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.time.Duration; + +/** + *

Description: OAuth2 合规性配置参数

+ * + * @author : liuh + * @date : 2022/7/7 0:16 + */ +@ConfigurationProperties(prefix = OAuth2Constants.PROPERTY_OAUTH2_COMPLIANCE) +public class OAuth2ComplianceProperties { + + private SignInEndpointLimited signInEndpointLimited = new SignInEndpointLimited(); + + private SignInFailureLimited signInFailureLimited = new SignInFailureLimited(); + + private SignInKickOutLimited signInKickOutLimited = new SignInKickOutLimited(); + + public SignInEndpointLimited getSignInEndpointLimited() { + return signInEndpointLimited; + } + + public void setSignInEndpointLimited(SignInEndpointLimited signInEndpointLimited) { + this.signInEndpointLimited = signInEndpointLimited; + } + + public SignInFailureLimited getSignInFailureLimited() { + return signInFailureLimited; + } + + public void setSignInFailureLimited(SignInFailureLimited signInFailureLimited) { + this.signInFailureLimited = signInFailureLimited; + } + + public SignInKickOutLimited getSignInKickOutLimited() { + return signInKickOutLimited; + } + + public void setSignInKickOutLimited(SignInKickOutLimited signInKickOutLimited) { + this.signInKickOutLimited = signInKickOutLimited; + } + + public static class SignInFailureLimited { + /** + * 是否开启登录失败检测,默认开启 + */ + private Boolean enabled = true; + + /** + * 允许允许最大失败次数 + */ + private Integer maxTimes = 5; + + /** + * 是否自动解锁被锁定用户,默认开启 + */ + private Boolean autoUnlock = true; + + /** + * 记录失败次数的缓存过期时间,默认:2小时。 + */ + private Duration expire = Duration.ofHours(2); + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Integer getMaxTimes() { + return maxTimes; + } + + public void setMaxTimes(Integer maxTimes) { + this.maxTimes = maxTimes; + } + + public Duration getExpire() { + return expire; + } + + public void setExpire(Duration expire) { + this.expire = expire; + } + + public Boolean getAutoUnlock() { + return autoUnlock; + } + + public void setAutoUnlock(Boolean autoUnlock) { + this.autoUnlock = autoUnlock; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("enabled", enabled) + .add("maxTimes", maxTimes) + .add("autoUnlock", autoUnlock) + .add("expire", expire) + .toString(); + } + } + + public static class SignInEndpointLimited { + /** + * 同一终端登录限制是否开启,默认开启。 + */ + private Boolean enabled = false; + + /** + * 统一终端,允许同时登录的最大数量 + */ + private Integer maximum = 1; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Integer getMaximum() { + return maximum; + } + + public void setMaximum(Integer maximum) { + this.maximum = maximum; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("enabled", enabled) + .add("maximum", maximum) + .toString(); + } + } + + public static class SignInKickOutLimited { + /** + * 是否开启 Session 踢出功能,默认开启 + */ + private Boolean enabled = true; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("enabled", enabled) + .toString(); + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("signInEndpointLimited", signInEndpointLimited) + .add("signInFailureLimited", signInFailureLimited) + .add("signInKickOutLimited", signInKickOutLimited) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/OAuth2Properties.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/OAuth2Properties.java new file mode 100644 index 0000000..54efc0d --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/OAuth2Properties.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.properties; + +import com.liuhung.engine.oauth2.core.constants.OAuth2Constants; +import com.liuhung.engine.oauth2.core.enums.Certificate; +import com.google.common.base.MoreObjects; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + *

Description: OAuth2 配置属性

+ *

+ * 仅认证服务会使用到的安全相关配置,这是与 SecurityProperties 的主要区别。 + * + * @author : liuh + * @date : 2022/3/6 16:36 + */ +@ConfigurationProperties(prefix = OAuth2Constants.PROPERTY_PREFIX_OAUTH2) +public class OAuth2Properties { + + private Jwk jwk = new Jwk(); + + public Jwk getJwk() { + return jwk; + } + + public void setJwk(Jwk jwk) { + this.jwk = jwk; + } + + public static class Jwk { + + private enum Strategy { + STANDARD, CUSTOM + } + + /** + * 证书策略:standard OAuth2 标准证书模式;custom 自定义证书模式 + */ + private Certificate certificate = Certificate.CUSTOM; + + /** + * jks证书文件路径 + */ + private String jksKeyStore = "classpath*:certificate/quafer-cloud.jks"; + /** + * jks证书密码 + */ + private String jksKeyPassword = "Quafer-Cloud"; + /** + * jks证书密钥库密码 + */ + private String jksStorePassword = "Quafer-Cloud"; + /** + * jks证书别名 + */ + private String jksKeyAlias = "quafer-cloud"; + + public Certificate getCertificate() { + return certificate; + } + + public void setCertificate(Certificate certificate) { + this.certificate = certificate; + } + + public String getJksKeyStore() { + return jksKeyStore; + } + + public void setJksKeyStore(String jksKeyStore) { + this.jksKeyStore = jksKeyStore; + } + + public String getJksKeyPassword() { + return jksKeyPassword; + } + + public void setJksKeyPassword(String jksKeyPassword) { + this.jksKeyPassword = jksKeyPassword; + } + + public String getJksStorePassword() { + return jksStorePassword; + } + + public void setJksStorePassword(String jksStorePassword) { + this.jksStorePassword = jksStorePassword; + } + + public String getJksKeyAlias() { + return jksKeyAlias; + } + + public void setJksKeyAlias(String jksKeyAlias) { + this.jksKeyAlias = jksKeyAlias; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("certificate", certificate) + .add("jksKeyStore", jksKeyStore) + .add("jksKeyPassword", jksKeyPassword) + .add("jksStorePassword", jksStorePassword) + .add("jksKeyAlias", jksKeyAlias) + .toString(); + } + } + + /** + * 用于手动的指定 Request Matcher 安全规则。 + *

+ * permitAll 比较常用,因此先只增加该项。后续可根据需要添加 + */ + public static class Matcher { + /** + * 静态资源过滤 + */ + private List staticResources; + /** + * Security "permitAll" 权限列表。 + */ + private List permitAll; + + public List getStaticResources() { + return staticResources; + } + + public void setStaticResources(List staticResources) { + this.staticResources = staticResources; + } + + public List getPermitAll() { + return permitAll; + } + + public void setPermitAll(List permitAll) { + this.permitAll = permitAll; + } + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/SecurityProperties.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/SecurityProperties.java new file mode 100644 index 0000000..92573de --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/properties/SecurityProperties.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.properties; + +import com.liuhung.engine.assistant.core.enums.Target; +import com.liuhung.engine.oauth2.core.constants.OAuth2Constants; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.Serializable; +import java.util.List; + +/** + *

Description : 服务安全配置

+ *

+ * 所有服务会使用到的安全相关配置,包括认证服务和资源服务。 + * + * @author : liuh + * @date : 2019/11/28 13:08 + */ +@ConfigurationProperties(prefix = OAuth2Constants.PROPERTY_PREFIX_SECURITY) +public class SecurityProperties implements Serializable { + + /** + * Token 校验是采用远程方式还是本地方式。 + */ + private Target validate = Target.REMOTE; + private Matcher matcher = new Matcher(); + + public Target getValidate() { + return validate; + } + + public void setValidate(Target validate) { + this.validate = validate; + } + + public Matcher getMatcher() { + return matcher; + } + + public void setMatcher(Matcher matcher) { + this.matcher = matcher; + } + + /** + * 用于手动的指定 Request Matcher 安全规则。 + *

+ * permitAll 比较常用,因此先只增加该项。后续可根据需要添加 + */ + public static class Matcher { + /** + * 静态资源过滤 + */ + private List staticResources; + /** + * Security "permitAll" 权限列表。 + */ + private List permitAll; + /** + * 只校验是否请求中包含Token,不校验Token中是否包含该权限的资源 + */ + private List hasAuthenticated; + + public List getStaticResources() { + return staticResources; + } + + public void setStaticResources(List staticResources) { + this.staticResources = staticResources; + } + + public List getPermitAll() { + return permitAll; + } + + public void setPermitAll(List permitAll) { + this.permitAll = permitAll; + } + + public List getHasAuthenticated() { + return hasAuthenticated; + } + + public void setHasAuthenticated(List hasAuthenticated) { + this.hasAuthenticated = hasAuthenticated; + } + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/response/QuaferAccessDeniedHandler.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/response/QuaferAccessDeniedHandler.java new file mode 100644 index 0000000..a141bb2 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/response/QuaferAccessDeniedHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.response; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.oauth2.core.exception.SecurityGlobalExceptionHandler; +import com.liuhung.engine.web.core.utils.WebUtils; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + *

Description: 访问拒绝处理器

+ * + * @author : liuh + * @date : 2022/3/8 8:52 + */ +public class QuaferAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + Result result = SecurityGlobalExceptionHandler.resolveException(accessDeniedException, request.getRequestURI()); + response.setStatus(result.getStatus()); + WebUtils.renderJson(response, result); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/response/QuaferAuthenticationEntryPoint.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/response/QuaferAuthenticationEntryPoint.java new file mode 100644 index 0000000..ccd7808 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/response/QuaferAuthenticationEntryPoint.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.response; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.oauth2.core.exception.SecurityGlobalExceptionHandler; +import com.liuhung.engine.web.core.utils.WebUtils; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + *

Description: 自定义未认证处理

+ * + * @author : liuh + * @date : 2022/3/8 8:55 + */ +public class QuaferAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + Result result = SecurityGlobalExceptionHandler.resolveSecurityException(authException, request.getRequestURI()); + response.setStatus(result.getStatus()); + WebUtils.renderJson(response, result); + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/PrincipalUtils.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/PrincipalUtils.java new file mode 100644 index 0000000..d573d32 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/PrincipalUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.utils; + +import com.liuhung.engine.assistant.core.definition.constants.BaseConstants; +import com.liuhung.engine.assistant.core.domain.PrincipalDetails; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; + +import java.util.HashSet; +import java.util.List; + +/** + *

Description: 身份信息工具类

+ * + * @author : liuh + * @date : 2022/12/31 12:07 + */ +public class PrincipalUtils { + + public static PrincipalDetails toPrincipalDetails(QuaferUser quaferUser) { + PrincipalDetails details = new PrincipalDetails(); + details.setOpenId(quaferUser.getUserId()); + details.setUserName(quaferUser.getUsername()); + details.setRoles(quaferUser.getRoles()); + details.setAvatar(quaferUser.getAvatar()); + details.setEmployeeId(quaferUser.getEmployeeId()); + return details; + } + + public static PrincipalDetails toPrincipalDetails(OAuth2AuthenticatedPrincipal authenticatedPrincipal) { + PrincipalDetails details = new PrincipalDetails(); + details.setOpenId(authenticatedPrincipal.getAttribute(BaseConstants.OPEN_ID)); + details.setUserName(authenticatedPrincipal.getName()); + List roles = authenticatedPrincipal.getAttribute(BaseConstants.ROLES); + if (CollectionUtils.isNotEmpty(roles)) { + details.setRoles(new HashSet<>(roles)); + } + details.setAvatar(authenticatedPrincipal.getAttribute(BaseConstants.AVATAR)); + details.setEmployeeId(authenticatedPrincipal.getAttribute(BaseConstants.EMPLOYEE_ID)); + return details; + } + + public static PrincipalDetails toPrincipalDetails(Jwt jwt) { + PrincipalDetails details = new PrincipalDetails(); + details.setOpenId(jwt.getClaimAsString(BaseConstants.OPEN_ID)); + details.setUserName(jwt.getClaimAsString(JwtClaimNames.SUB)); + details.setRoles(jwt.getClaim(BaseConstants.ROLES)); + details.setAvatar(jwt.getClaimAsString(BaseConstants.AVATAR)); + details.setEmployeeId(jwt.getClaimAsString(BaseConstants.EMPLOYEE_ID)); + return details; + } +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/SecurityUtils.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/SecurityUtils.java new file mode 100644 index 0000000..2457b3c --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/SecurityUtils.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.utils; + +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + + +/** + * @author liuh + * @date 2018-3-8 + **/ +public class SecurityUtils { + + private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class); + + private static final PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); + + public static final String PREFIX_ROLE = "ROLE_"; + public static final String PREFIX_SCOPE = "SCOPE_"; + + /** + * 密码加密 + * + * @param password 明文密码 + * @return 已加密密码 + */ + public static String encrypt(String password) { + return passwordEncoder.encode(password); + } + + /** + * 密码验证 + * + * @param rawPassword 原始密码 + * @param encodedPassword 加密后的密码 + * @return 密码是否匹配 + */ + public static boolean matches(String rawPassword, String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + public static SecurityContext getSecurityContext() { + return SecurityContextHolder.getContext(); + } + + public static Authentication getAuthentication() { + return getSecurityContext().getAuthentication(); + } + + public static boolean isAuthenticated() { + return ObjectUtils.isNotEmpty(getAuthentication()) && getAuthentication().isAuthenticated(); + } + + public static Object getDetails() { + return getAuthentication().getDetails(); + } + + /** + * 当用户角色发生变化,或者用户角色对应的权限发生变化,那么就从数据库中重新查询用户相关信息 + * + * @param newQuaferUser 从数据库中重新查询并生成的用户信息 + */ + public static void reloadAuthority(QuaferUser newQuaferUser) { + // 重新new一个token,因为Authentication中的权限是不可变的. + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( + newQuaferUser, newQuaferUser.getPassword(), + newQuaferUser.getAuthorities()); + token.setDetails(getDetails()); + getSecurityContext().setAuthentication(token); + } + + /** + * 获取认证用户信息 + * + * @return 自定义 UserDetails {@link QuaferUser} + */ + @SuppressWarnings("unchecked") + public static QuaferUser getPrincipal() { + if (isAuthenticated()) { + Authentication authentication = getAuthentication(); + if (authentication.getPrincipal() instanceof QuaferUser) { + return (QuaferUser) authentication.getPrincipal(); + } + if (authentication.getPrincipal() instanceof Map) { + Map principal = (Map) authentication.getPrincipal(); + return BeanUtil.mapToBean(principal, QuaferUser.class, true, new CopyOptions()); + } + } + + return null; + } + + public static String getUsername() { + QuaferUser user = getPrincipal(); + if (user != null) { + return user.getUsername(); + } + return null; + } + + public static QuaferUser getPrincipals() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (principal != null) { + if (principal instanceof QuaferUser) { + return (QuaferUser) principal; + } else if (principal instanceof LinkedHashMap) { + // TODO: zhangyu 2019/7/15 感觉还可以升级一把,不吐linkedhashmap 直接就是oauth2user + // 2019/7/20 试验过将OAuth2UserAuthenticationConverter map中的?强制转换成oauth2user,试验失败,问题不是很急,可以先放着 + /** + * https://blog.csdn.net/m0_37834471/article/details/81814233 + * cn/itcraftsman/luban/auth/oauth2/OAuth2UserAuthenticationConverter.java + */ +// QuaferUser user = new QuaferUser(); +// BeanUtil.fillBeanWithMap((LinkedHashMap) principal, user, true); + return null; + } else if (principal instanceof String && principal.equals("anonymousUser")) { + return null; + } else { + throw new IllegalStateException("获取用户数据失败"); + } + } + return null; + } + + public static String getUserId() { + QuaferUser quaferUser = getPrincipal(); + if (ObjectUtils.isNotEmpty(quaferUser)) { + return quaferUser.getUserId(); + } + + return null; + } + + + public static String[] whitelistToAntMatchers(List list) { + if (CollectionUtils.isNotEmpty(list)) { + String[] array = new String[list.size()]; + log.debug("[Quafer] |- Fetch The REST White List."); + return list.toArray(array); + } + + log.warn("[Quafer] |- Can not Fetch The REST White List Configurations."); + return new String[]{}; + } + + public static String wellFormRolePrefix(String content) { + return wellFormPrefix(content, PREFIX_ROLE); + } + + public static String wellFormPrefix(String content, String prefix) { + if (StringUtils.startsWith(content, prefix)) { + return content; + } else { + return prefix + content; + } + } + +} diff --git a/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/SymmetricUtils.java b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/SymmetricUtils.java new file mode 100644 index 0000000..98622c0 --- /dev/null +++ b/engine-oauth2/oauth2-core/src/main/java/com/liuhung/engine/oauth2/core/utils/SymmetricUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.core.utils; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.oauth2.core.exception.IllegalSymmetricKeyException; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Description : 基于Hutool的Aes加解密工具

+ * + * @author : liuh + * @date : 2020/1/28 17:25 + */ +public class SymmetricUtils { + + private static final Logger log = LoggerFactory.getLogger(SymmetricUtils.class); + + private static String encryptedRealSecretKey(String symmetricKey) { + String realSecretKey = RandomUtil.randomString(16); + log.trace("[Quafer] |- Generate Random Secret Key is : [{}]", realSecretKey); + + AES ase = SecureUtil.aes(symmetricKey.getBytes()); + String encryptedRealSecretKey = ase.encryptHex(realSecretKey); + log.trace("[Quafer] |- Generate Encrypt Hex Secret Key is : [{}]", encryptedRealSecretKey); + + return encryptedRealSecretKey; + } + + public static String getEncryptedSymmetricKey() { + String symmetricKey = RandomUtil.randomString(16); + String realSecretKey = encryptedRealSecretKey(symmetricKey); + log.trace("[Quafer] |- Generate Symmetric Key is : [{}]", realSecretKey); + + StringBuilder builder = new StringBuilder(); + builder.append(symmetricKey); + builder.append(SymbolConstants.FORWARD_SLASH); + builder.append(realSecretKey); + return builder.toString(); + } + + public static byte[] getDecryptedSymmetricKey(String key) { + if (!StringUtils.contains(key, SymbolConstants.FORWARD_SLASH)) { + throw new IllegalSymmetricKeyException("Parameter Illegal!"); + } + + String[] keys = StringUtils.split(key, SymbolConstants.FORWARD_SLASH); + String symmetricKey = keys[0]; + String realSecretKey = keys[1]; + + AES ase = SecureUtil.aes(symmetricKey.getBytes()); + return ase.decrypt(realSecretKey); + } + + public static String decrypt(String content, byte[] key) { + if (ArrayUtils.isNotEmpty(key)) { + AES ase = SecureUtil.aes(key); + return ase.decryptStr(content); + } + + return ""; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/README.md b/engine-oauth2/oauth2-sdk-authentication-server/README.md new file mode 100644 index 0000000..ab76b09 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/README.md @@ -0,0 +1,10 @@ +## OAuth 2.0 自定义模式模块 + +**包含以下内容:** +1. Spring QuaferAuthorization Server 工具类。 +2. 自定义 OAuth 2.0 密码模式。 + +### 额外说明 + +1. Spring QuaferAuthorization Server 工具类。新版 spring-security-oauth2-authorization-server 很多代码都是“包”级可访问的,外部无法使用。为了方便扩展将其提取出来,便于使用。代码内容与原包代码基本一致。 +2. 最新的 OAuth 2 标准,已经不再包含密码模式。但是由于早期密码模式使用特别广泛,特别是基于 Vue 的前端。短期内无法将该模式剔除,因此予以保留。 \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/pom.xml b/engine-oauth2/oauth2-sdk-authentication-server/pom.xml new file mode 100644 index 0000000..b9ad39c --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/pom.xml @@ -0,0 +1,94 @@ + + + + + + 4.0.0 + + + engine-oauth2 + com.liuhung.engine + 2.7.8.0 + + + oauth2-sdk-authentication-server + 2.7.8.0 + jar + + 基于 Spring Authorization Server 的 quafer Cloud 基础核心组件模块 + + + + com.liuhung.engine + rest-spring-boot-starter + + + + com.liuhung.engine + oauth2-sdk-authentication + + + + com.liuhung.engine + captcha-spring-boot-starter + + + + + + + ${project.basedir}/src/main/java + + **/*.xml + + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + + woff + woff2 + eot + otf + ttf + svg + + + + + + + \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/configuration/OAuth2AuthenticationServerConfiguration.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/configuration/OAuth2AuthenticationServerConfiguration.java new file mode 100644 index 0000000..be3408d --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/configuration/OAuth2AuthenticationServerConfiguration.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.configuration; + +import com.liuhung.engine.oauth2.authentication.properties.OAuth2UiProperties; +import com.liuhung.engine.oauth2.core.properties.OAuth2ComplianceProperties; +import com.liuhung.engine.oauth2.core.properties.OAuth2Properties; +import com.liuhung.engine.oauth2.data.jpa.configuration.OAuth2DataJpaConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import javax.annotation.PostConstruct; + +/** + *

Description: OAuth2 Manager 模块配置

+ *

+ * {@link org.springframework.security.oauth2.jwt.JwtTimestampValidator} + * + * @author : liuh + * @date : 2022/2/26 12:35 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties({OAuth2Properties.class, OAuth2UiProperties.class, OAuth2ComplianceProperties.class}) +@Import({OAuth2DataJpaConfiguration.class}) +@EntityScan(basePackages = { + "com.liuhung.engine.oauth2.authentication.server.entity" +}) +@EnableJpaRepositories(basePackages = { + "com.liuhung.engine.oauth2.authentication.server.repository", +}) +@ComponentScan(basePackages = { + "com.liuhung.engine.oauth2.authentication.server.service", + "com.liuhung.engine.oauth2.authentication.server.controller", +}) +public class OAuth2AuthenticationServerConfiguration { + + private static final Logger log = LoggerFactory.getLogger(OAuth2AuthenticationServerConfiguration.class); + + @PostConstruct + public void postConstruct() { + log.debug("[Quafer] |- SDK [OAuth2 Authorization Server] Auto Configure."); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/CaptchaController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/CaptchaController.java new file mode 100644 index 0000000..963e53e --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/CaptchaController.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.captcha.core.dto.Captcha; +import com.liuhung.engine.captcha.core.dto.Verification; +import com.liuhung.engine.captcha.core.processor.CaptchaRendererFactory; +import com.liuhung.engine.rest.core.annotation.AccessLimited; +import com.liuhung.engine.rest.core.annotation.Crypto; +import com.liuhung.engine.rest.core.annotation.Idempotent; +import com.liuhung.engine.rest.core.controller.Controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + *

Description: 验证码Controller

+ * + * @author : liuh + * @date : 2021/12/12 10:44 + */ +@RestController +@RequestMapping("/open/captcha") +@Validated +@Tags({ + @Tag(name = "OAuth2 认证服务器接口"), + @Tag(name = "OAuth2 认证服务器开放接口"), + @Tag(name = "验证码接口") +}) +public class CaptchaController implements Controller { + + @Autowired + private CaptchaRendererFactory captchaRendererFactory; + + @AccessLimited + @Operation(summary = "获取验证码", description = "通过传递身份信息(类似于Session标识)", + responses = {@ApiResponse(description = "验证码图形信息", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class)))}) + @Parameters({ + @Parameter(name = "identity", required = true, in = ParameterIn.PATH, description = "身份信息"), + @Parameter(name = "category", required = true, in = ParameterIn.PATH, description = "验证码类型") + }) + @GetMapping + public Result create(@NotBlank(message = "身份信息不能为空") String identity, @NotBlank(message = "验证码类型不能为空") String category) { + Captcha captcha = captchaRendererFactory.getCaptcha(identity, category); + if (ObjectUtils.isNotEmpty(captcha)) { + return Result.success("验证码创建成功", captcha); + } else { + return Result.failure("验证码创建失败"); + } + } + + @Idempotent + @Crypto(responseEncrypt = false) + @Operation(summary = "验证码验证", description = "验证验证码返回数据是否正确。使用加密信息", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = "application/json")), + responses = {@ApiResponse(description = "验证结果", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "jigsawVerification", required = true, description = "验证码验证参数", schema = @Schema(implementation = Verification.class)) + }) + @PostMapping + public Result check(@Valid @RequestBody Verification verification) { + boolean isSuccess = captchaRendererFactory.verify(verification); + if (isSuccess) { + return Result.success("验证码创建成功", true); + } + return Result.failure("验证失败", true); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/ConsentController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/ConsentController.java new file mode 100644 index 0000000..1945083 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/ConsentController.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.definition.constants.SymbolConstants; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Application; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; +import com.liuhung.engine.oauth2.authentication.server.service.OAuth2ApplicationService; +import com.liuhung.engine.oauth2.authentication.server.service.OAuth2ScopeService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.security.Principal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * OAuth2 授权确认页面 - controller + * + * @author liuh + * @date 2022-03-01 + * @see org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter + */ +@Controller +public class ConsentController { + + private final OAuth2ApplicationService applicationService; + private final OAuth2AuthorizationConsentService authorizationConsentService; + private final OAuth2ScopeService scopeService; + + private Map dictionaries; + + public ConsentController(OAuth2ApplicationService applicationService, OAuth2AuthorizationConsentService authorizationConsentService, OAuth2ScopeService scopeService) { + this.applicationService = applicationService; + this.authorizationConsentService = authorizationConsentService; + this.scopeService = scopeService; + initDictionaries(); + } + + /** + * Consent页面(确认请求scope的页面) + * + * @param principal 用户信息 + * @param model model + * @param clientId 客户端ID + * @param scope 请求范围 + * @param state state参数 + * @return Consent页面 + */ + @GetMapping(value = "/oauth2/consent") + public String consent(Principal principal, Model model, + @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId, + @RequestParam(OAuth2ParameterNames.SCOPE) String scope, + @RequestParam(OAuth2ParameterNames.STATE) String state) { + + /** 移除已经授权过的scope */ + //待授权的scope + Set scopesToApprove = new HashSet<>(); + //之前已经授权过的scope + Set previouslyApprovedScopes = new HashSet<>(); + //获取客户端注册信息 + OAuth2Application application = this.applicationService.findByClientId(clientId); + //获取当前Client下用户之前的consent信息 + OAuth2AuthorizationConsent currentAuthorizationConsent = + this.authorizationConsentService.findById(clientId, principal.getName()); + //当前Client下用户已经授权的scope + Set authorizedScopes = Optional.ofNullable(currentAuthorizationConsent) + .map(OAuth2AuthorizationConsent::getScopes) + .orElse(Collections.emptySet()); + //遍历请求的scope,提取之前已授权过 和 待授权的scope + for (String requestedScope : StringUtils.delimitedListToStringArray(scope, SymbolConstants.SPACE)) { + if (authorizedScopes.contains(requestedScope)) { + previouslyApprovedScopes.add(requestedScope); + } else if (!OidcScopes.OPENID.equals(requestedScope)) { + scopesToApprove.add(requestedScope); + } + } + + //输出信息指consent页面 + model.addAttribute("clientId", clientId); + model.addAttribute("state", state); + model.addAttribute("scopes", withDescription(scopesToApprove)); + model.addAttribute("previouslyApprovedScopes", withDescription(previouslyApprovedScopes)); + model.addAttribute("principalName", principal.getName()); + model.addAttribute("applicationName", application.getApplicationName()); + model.addAttribute("logo", application.getLogo()); + return "consent"; + } + + private void initDictionaries() { + List scopes = scopeService.findAll(); + if (CollectionUtils.isNotEmpty(scopes)) { + if (MapUtils.isEmpty(dictionaries) || scopes.size() != dictionaries.size()) { + dictionaries = scopes.stream().collect(Collectors.toMap(OAuth2Scope::getScopeCode, item -> item)); + } + } + } + + /** + * 根据scope生成相关权限描述 + * + * @param scopes scope集合 + * @return scope描述集合 + */ + private Set withDescription(Set scopes) { + if (CollectionUtils.isNotEmpty(scopes)) { + return scopes.stream().map(item -> dictionaries.get(item)).collect(Collectors.toSet()); + } else { + return new HashSet<>(); + } + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/IdentityController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/IdentityController.java new file mode 100644 index 0000000..8066cfa --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/IdentityController.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.assistant.core.domain.SecretKey; +import com.liuhung.engine.oauth2.authentication.server.dto.Session; +import com.liuhung.engine.oauth2.authentication.server.dto.SessionCreate; +import com.liuhung.engine.oauth2.authentication.server.dto.SessionExchange; +import com.liuhung.engine.oauth2.authentication.server.service.InterfaceSecurityService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author liuh + * @see 参考文档 + */ +@RestController +@Tags({ + @Tag(name = "OAuth2 认证服务器接口"), + @Tag(name = "OAuth2 认证服务器开放接口"), + @Tag(name = "OAuth2 身份认证辅助接口") +}) +public class IdentityController { + + private final Logger log = LoggerFactory.getLogger(IdentityController.class); + + @Autowired + private InterfaceSecurityService interfaceSecurityService; + + @Operation(summary = "获取后台加密公钥", description = "根据未登录时的身份标识,在后台创建RSA公钥和私钥。身份标识为前端的唯一标识,如果为空,则在后台创建一个", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = "application/json")), + responses = {@ApiResponse(description = "自定义Session", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "sessionCreate", required = true, description = "Session创建请求参数", schema = @Schema(implementation = SessionCreate.class)), + }) + @PostMapping("/open/identity/session") + public Result codeToSession(@Validated @RequestBody SessionCreate sessionCreate) { + + SecretKey secretKey = interfaceSecurityService.createSecretKey(sessionCreate.getClientId(), sessionCreate.getClientSecret(), sessionCreate.getSessionId()); + if (ObjectUtils.isNotEmpty(secretKey)) { + Session session = new Session(); + session.setSessionId(secretKey.getIdentity()); + session.setPublicKey(secretKey.getPublicKey()); + session.setState(secretKey.getState()); + + return Result.content(session); + } + + return Result.failure(); + } + + @Operation(summary = "获取AES秘钥", description = "用后台publicKey,加密前台publicKey,到后台换取AES秘钥", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = "application/json")), + responses = {@ApiResponse(description = "加密后的AES", content = @Content(mediaType = "application/json"))}) + @Parameters({ + @Parameter(name = "sessionExchange", required = true, description = "秘钥交换", schema = @Schema(implementation = SessionExchange.class)), + }) + @PostMapping("/open/identity/exchange") + public Result exchange(@Validated @RequestBody SessionExchange sessionExchange) { + + String encryptedAesKey = interfaceSecurityService.exchange(sessionExchange.getSessionId(), sessionExchange.getConfidential()); + if (StringUtils.isNotEmpty(encryptedAesKey)) { + return Result.content(encryptedAesKey); + } + + return Result.failure(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/LoginController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/LoginController.java new file mode 100644 index 0000000..f937b53 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/LoginController.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.oauth2.authentication.properties.OAuth2UiProperties; +import com.liuhung.engine.oauth2.core.utils.SymmetricUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpMethod; +import org.springframework.security.web.WebAttributes; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.util.HtmlUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.util.Collections; +import java.util.Map; + +/** + *

Description: Security 登录控制器

+ * + * @author : liuh + * @date : 2022/3/21 19:52 + * @see org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer + * @see org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter + */ +@Controller +public class LoginController { + + private static final String DEFAULT_LOGIN_PAGE_VIEW = "login"; + private static final String DEFAULT_ERROR_PAGE_VIEW = "error"; + + private final OAuth2UiProperties uiProperties; + + @Autowired + public LoginController(OAuth2UiProperties uiProperties) { + this.uiProperties = uiProperties; + } + + + @RequestMapping(value = "/login", method = RequestMethod.GET) + public ModelAndView login(Map model, HttpServletRequest request) { + + ModelAndView modelAndView = new ModelAndView(DEFAULT_LOGIN_PAGE_VIEW); + + boolean loginError = isErrorPage(request); + boolean logoutSuccess = isLogoutSuccess(request); + String errorMessage = getErrorMessage(request); + + Map hiddenInputs = hiddenInputs(request); + + // 登录可配置用户名参数 + modelAndView.addObject("vulgar_tycoon", uiProperties.getUsernameParameter()); + // 登录可配置密码参数 + modelAndView.addObject("beast", uiProperties.getPasswordParameter()); + modelAndView.addObject("anubis", uiProperties.getRememberMeParameter()); + modelAndView.addObject("graphic", uiProperties.getCaptchaParameter()); + modelAndView.addObject("hide_verification_code", uiProperties.getCloseCaptcha()); + // Security 隐藏域 + // AES加密key + modelAndView.addObject("soup_spoon", SymmetricUtils.getEncryptedSymmetricKey()); + // 验证码类别 + modelAndView.addObject("verification_category", uiProperties.getCategory()); + modelAndView.addObject("hidden_inputs", hiddenInputs); + modelAndView.addObject("login_error", loginError); + modelAndView.addObject("logout_success", logoutSuccess); + modelAndView.addObject("message", StringUtils.isNotBlank(errorMessage) ? HtmlUtils.htmlEscape(errorMessage) : null); + + return modelAndView; + } + + private boolean isErrorPage(HttpServletRequest request) { + String failureUrl = DEFAULT_LOGIN_PAGE_VIEW + "?" + DEFAULT_ERROR_PAGE_VIEW; + return matches(request, failureUrl); + } + + private boolean isLogoutSuccess(HttpServletRequest request) { + String logoutSuccessUrl = DEFAULT_LOGIN_PAGE_VIEW + "?logout"; + return matches(request, logoutSuccessUrl); + } + + private Map hiddenInputs(HttpServletRequest request) { + CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + return (token != null) ? Collections.singletonMap(token.getParameterName(), token.getToken()) + : Collections.emptyMap(); + } + + private String getErrorMessage(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (ObjectUtils.isNotEmpty(session)) { + String message = (String) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + if (ObjectUtils.isNotEmpty(message)) { + return message; + } + } + + return null; + } + + private boolean matches(HttpServletRequest request, String url) { + if (!HttpMethod.GET.name().equals(request.getMethod()) || url == null) { + return false; + } + String uri = request.getRequestURI(); + int pathParamIndex = uri.indexOf(';'); + if (pathParamIndex > 0) { + // strip everything after the first semi-colon + uri = uri.substring(0, pathParamIndex); + } + if (request.getQueryString() != null) { + uri += "?" + request.getQueryString(); + } + if ("".equals(request.getContextPath())) { + return uri.equals(url); + } + return uri.equals(request.getContextPath() + url); + } + + +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ApplicationController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ApplicationController.java new file mode 100644 index 0000000..4624288 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ApplicationController.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.data.core.service.WriteableService; +import com.liuhung.engine.oauth2.authentication.server.dto.OAuth2ApplicationDto; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Application; +import com.liuhung.engine.oauth2.authentication.server.service.OAuth2ApplicationService; +import com.liuhung.engine.rest.core.controller.BaseController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

Description: OAuth2应用管理接口

+ * + * @author : liuh + * @date : 2022/3/1 18:52 + */ +@RestController +@RequestMapping("/authorize/application") +@Tags({ + @Tag(name = "OAuth2 认证服务接口"), + @Tag(name = "OAuth2 应用管理接口") +}) +public class OAuth2ApplicationController extends BaseController { + + private final OAuth2ApplicationService applicationService; + + @Autowired + public OAuth2ApplicationController(OAuth2ApplicationService applicationService) { + this.applicationService = applicationService; + } + + @Override + public WriteableService getWriteableService() { + return this.applicationService; + } + + @Operation(summary = "获取OAuth2Application分页数据", description = "通过pageNumber和pageSize获取分页数据") + @Parameters({ + @Parameter(name = "pageNumber", required = true, description = "当前页数"), + @Parameter(name = "pageSize", required = true, description = "每页显示数据条目") + }) + @GetMapping + @Override + public Result> findByPage( + @RequestParam("pageNumber") Integer pageNumber, + @RequestParam("pageSize") Integer pageSize) { + + Page pages = applicationService.findByPage(pageNumber, pageSize); + if (ObjectUtils.isNotEmpty(pages) && CollectionUtils.isNotEmpty(pages.getContent())) { + List auth2Applications = pages.getContent().stream().map(this::toDto).collect(Collectors.toList()); + return result(getPageInfoMap(auth2Applications, pages.getTotalPages(), pages.getTotalElements())); + } + + return Result.failure("查询数据失败!"); + } + + @Operation(summary = "保存或更新OAuth2应用", description = "接收JSON数据,转换为OauthClientDetails实体,进行更新") + @Parameters({ + @Parameter(name = "oauthClientDetails", required = true, description = "可转换为OauthClientDetails实体的json数据") + }) + @PostMapping + public Result saveOrUpdate(@RequestBody OAuth2ApplicationDto domain) { + OAuth2Application oAuth2Application = applicationService.saveOrUpdate(toEntity(domain)); + return result(oAuth2Application); + } + + @Operation(summary = "删除OAuth2应用", description = "根据应用ID删除OAuth2应用,以及相关联的关系数据") + @Parameters({ + @Parameter(name = "applicationId", required = true, description = "applicationId") + }) + @DeleteMapping + @Override + public Result delete(@RequestBody String applicationId) { + applicationService.deleteById(applicationId); + return Result.success("删除成功"); + } + + @Operation(summary = "给应用分配Scope", description = "给应用分配Scope") + @Parameters({ + @Parameter(name = "appKey", required = true, description = "appKey"), + @Parameter(name = "scopes[]", required = true, description = "Scope对象组成的数组") + }) + @PutMapping + public Result authorize(@RequestParam(name = "applicationId") String scopeId, @RequestParam(name = "scopes[]") String[] scopes) { + OAuth2Application application = applicationService.authorize(scopeId, scopes); + return result(application); + } + + private OAuth2ApplicationDto toDto(OAuth2Application entity) { + OAuth2ApplicationDto dto = new OAuth2ApplicationDto(); + dto.setApplicationId(entity.getApplicationId()); + dto.setApplicationName(entity.getApplicationName()); + dto.setAbbreviation(entity.getAbbreviation()); + dto.setLogo(entity.getLogo()); + dto.setHomepage(entity.getHomepage()); + dto.setApplicationType(entity.getApplicationType()); + dto.setClientId(entity.getClientId()); + dto.setClientSecret(entity.getClientSecret()); + dto.setRedirectUris(entity.getRedirectUris()); + dto.setAuthorizationGrantTypes(StringUtils.commaDelimitedListToSet(entity.getAuthorizationGrantTypes())); + dto.setClientAuthenticationMethods(StringUtils.commaDelimitedListToSet(entity.getClientAuthenticationMethods())); + dto.setRequireProofKey(entity.getRequireProofKey()); + dto.setRequireAuthorizationConsent(entity.getRequireAuthorizationConsent()); + dto.setJwkSetUrl(entity.getJwkSetUrl()); + dto.setAccessTokenValidity(entity.getAccessTokenValidity()); + dto.setReuseRefreshTokens(entity.getReuseRefreshTokens()); + dto.setRefreshTokenValidity(entity.getRefreshTokenValidity()); + dto.setIdTokenSignatureAlgorithm(entity.getIdTokenSignatureAlgorithm()); + dto.setScopes(entity.getScopes()); + dto.setReserved(entity.getReserved()); + dto.setDescription(entity.getDescription()); + dto.setReversion(entity.getReversion()); + dto.setRanking(entity.getRanking()); + dto.setStatus(entity.getStatus()); + dto.setClientSecretExpiresAt(entity.getClientSecretExpiresAt()); + dto.setIdTokenSignatureAlgorithm(entity.getIdTokenSignatureAlgorithm()); + dto.setAccessTokenFormat(entity.getAccessTokenFormat()); + dto.setAuthenticationSigningAlgorithm(entity.getAuthenticationSigningAlgorithm()); + return dto; + } + + public OAuth2Application toEntity(OAuth2ApplicationDto dto) { + OAuth2Application entity = new OAuth2Application(); + entity.setApplicationId(dto.getApplicationId()); + entity.setApplicationName(dto.getApplicationName()); + entity.setAbbreviation(dto.getAbbreviation()); + entity.setLogo(dto.getLogo()); + entity.setHomepage(dto.getHomepage()); + entity.setApplicationType(dto.getApplicationType()); + entity.setClientId(dto.getClientId()); + entity.setClientSecret(dto.getClientSecret()); + entity.setRedirectUris(dto.getRedirectUris()); + entity.setAuthorizationGrantTypes(StringUtils.collectionToCommaDelimitedString(dto.getAuthorizationGrantTypes())); + entity.setClientAuthenticationMethods(StringUtils.collectionToCommaDelimitedString(dto.getClientAuthenticationMethods())); + entity.setRequireProofKey(dto.getRequireProofKey()); + entity.setRequireAuthorizationConsent(dto.getRequireAuthorizationConsent()); + entity.setJwkSetUrl(dto.getJwkSetUrl()); + entity.setAccessTokenValidity(dto.getAccessTokenValidity()); + entity.setReuseRefreshTokens(dto.getReuseRefreshTokens()); + entity.setRefreshTokenValidity(dto.getRefreshTokenValidity()); + entity.setIdTokenSignatureAlgorithm(dto.getIdTokenSignatureAlgorithm()); + entity.setClientSecretExpiresAt(dto.getClientSecretExpiresAt()); + entity.setScopes(dto.getScopes()); + entity.setReserved(dto.getReserved()); + entity.setDescription(dto.getDescription()); + entity.setReversion(dto.getReversion()); + entity.setRanking(dto.getRanking()); + entity.setStatus(dto.getStatus()); + entity.setIdTokenSignatureAlgorithm(dto.getIdTokenSignatureAlgorithm()); + entity.setAccessTokenFormat(dto.getAccessTokenFormat()); + entity.setAuthenticationSigningAlgorithm(dto.getAuthenticationSigningAlgorithm()); + + return entity; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2AuthorityController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2AuthorityController.java new file mode 100644 index 0000000..4480b8f --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2AuthorityController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.oauth2.core.definition.domain.Authority; +import com.liuhung.engine.oauth2.core.definition.strategy.StrategyAuthorityDetailsService; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; +import com.liuhung.engine.rest.core.controller.Controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

Description: OAuth2 权限接口

+ * + * @author : liuh + * @date : 2022/4/1 18:20 + */ +@RestController +@RequestMapping("/authorize/authority") +@Tags({ + @Tag(name = "OAuth2 认证服务接口"), + @Tag(name = "OAuth2 权限读取接口") +}) +public class OAuth2AuthorityController implements Controller { + + private final StrategyAuthorityDetailsService strategyAuthorityDetailsService; + + @Autowired + public OAuth2AuthorityController(StrategyAuthorityDetailsService strategyAuthorityDetailsService) { + this.strategyAuthorityDetailsService = strategyAuthorityDetailsService; + } + + @Operation(summary = "查询所有权限数据", description = "查询所有权限数据用于给Scope分配权限", + responses = {@ApiResponse(description = "权限列表", content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2Authority.class)))}) + @GetMapping("/condition") + public Result> findAll() { + List authorities = strategyAuthorityDetailsService.findAll(); + if (CollectionUtils.isNotEmpty(authorities)) { + List result = toEntities(authorities); + return result(result); + } else { + return Result.empty("未查询到数据"); + } + } + + private List toEntities(List authorities) { + return authorities.stream().map(this::toEntity).collect(Collectors.toList()); + } + + private OAuth2Authority toEntity(Authority object) { + OAuth2Authority authority = new OAuth2Authority(); + authority.setAuthorityId(object.getAuthorityId()); + authority.setAuthorityCode(object.getAuthorityCode()); + authority.setServiceId(object.getServiceId()); + authority.setRequestMethod(object.getRequestMethod()); + authority.setUrl(object.getUrl()); + return authority; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2AuthorizationController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2AuthorizationController.java new file mode 100644 index 0000000..b99f400 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2AuthorizationController.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.data.core.service.WriteableService; +import com.liuhung.engine.oauth2.data.jpa.entity.QuaferAuthorization; +import com.liuhung.engine.oauth2.data.jpa.service.QuaferAuthorizationService; +import com.liuhung.engine.rest.core.controller.BaseWriteableRestController; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

Description: OAuth2 认证管理接口

+ * + * @author : liuh + * @date : 2022/3/1 18:52 + */ +@RestController +@RequestMapping("/authorize/authorization") +@Tags({ + @Tag(name = "OAuth2 认证服务接口"), + @Tag(name = "OAuth2 认证管理接口") +}) +public class OAuth2AuthorizationController extends BaseWriteableRestController { + + private final QuaferAuthorizationService quaferAuthorizationService; + + @Autowired + public OAuth2AuthorizationController(QuaferAuthorizationService quaferAuthorizationService) { + this.quaferAuthorizationService = quaferAuthorizationService; + } + + @Override + public WriteableService getWriteableService() { + return this.quaferAuthorizationService; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ConstantController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ConstantController.java new file mode 100644 index 0000000..4ac59b8 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ConstantController.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.oauth2.authentication.server.service.OAuth2ConstantService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.collections4.MapUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + *

Description: OAuth2 常量 Controller

+ * + * @author : liuh + * @date : 2022/3/17 15:00 + */ +@RestController +@RequestMapping("/authorize/constant") +@Tags({ + @Tag(name = "OAuth2 认证服务接口"), + @Tag(name = "常量接口") +}) +public class OAuth2ConstantController { + + private final OAuth2ConstantService constantService; + + @Autowired + public OAuth2ConstantController(OAuth2ConstantService constantService) { + this.constantService = constantService; + } + + @Operation(summary = "获取服务常量", description = "获取服务涉及的常量以及信息") + @GetMapping(value = "/enums") + public Result> findAllEnums() { + Map allEnums = constantService.getAllEnums(); + if (MapUtils.isNotEmpty(allEnums)) { + return Result.success("获取服务常量成功", allEnums); + } else { + return Result.failure("获取服务常量失败"); + } + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ScopeController.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ScopeController.java new file mode 100644 index 0000000..db7b00d --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/controller/OAuth2ScopeController.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.controller; + +import com.liuhung.engine.assistant.core.domain.Result; +import com.liuhung.engine.data.core.service.WriteableService; +import com.liuhung.engine.oauth2.authentication.server.dto.OAuth2AuthorityDto; +import com.liuhung.engine.oauth2.authentication.server.dto.OAuth2ScopeDto; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; +import com.liuhung.engine.oauth2.authentication.server.service.OAuth2ScopeService; +import com.liuhung.engine.rest.core.annotation.AccessLimited; +import com.liuhung.engine.rest.core.controller.BaseWriteableRestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + *

Description : OauthScopesController

+ * + * @author : liuh + * @date : 2020/3/25 17:10 + */ +@RestController +@RequestMapping("/authorize/scope") +@Tags({ + @Tag(name = "OAuth2 认证服务接口"), + @Tag(name = "OAuth2 权限范围管理接口") +}) +public class OAuth2ScopeController extends BaseWriteableRestController { + + private final OAuth2ScopeService scopeService; + + @Autowired + public OAuth2ScopeController(OAuth2ScopeService scopeService) { + this.scopeService = scopeService; + } + + @Override + public WriteableService getWriteableService() { + return this.scopeService; + } + + @Operation(summary = "给Scope分配权限", description = "给Scope分配权限", + responses = { + @ApiResponse(description = "查询到的角色", content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2ScopeDto.class))), + }) + @Parameters({ + @Parameter(name = "scope", required = true, description = "范围请求参数"), + }) + @PostMapping("/assigned") + public Result assigned(@RequestBody OAuth2ScopeDto scope) { + + Set authorities = new HashSet<>(); + if (CollectionUtils.isNotEmpty(scope.getAuthorities())) { + authorities = scope.getAuthorities().stream().map(this::toEntity).collect(Collectors.toSet()); + } + + OAuth2Scope result = scopeService.authorize(scope.getScopeId(), authorities); + return result(result); + } + + @AccessLimited + @Operation(summary = "获取全部范围", description = "获取全部范围") + @GetMapping("/list") + public Result> findAll() { + List oAuth2Scopes = scopeService.findAll(); + return result(oAuth2Scopes); + } + + @AccessLimited + @Operation(summary = "根据范围代码查询应用范围", description = "根据范围代码查询应用范围", + responses = { + @ApiResponse(description = "查询到的应用范围", content = @Content(mediaType = "application/json", schema = @Schema(implementation = OAuth2Scope.class))), + @ApiResponse(responseCode = "204", description = "查询成功,未查到数据"), + @ApiResponse(responseCode = "500", description = "查询失败") + } + ) + @GetMapping("/{scopeCode}") + public Result findByUserName(@PathVariable("scopeCode") String scopeCode) { + OAuth2Scope scope = scopeService.findByScopeCode(scopeCode); + return result(scope); + } + + private OAuth2Authority toEntity(OAuth2AuthorityDto dto) { + OAuth2Authority entity = new OAuth2Authority(); + entity.setAuthorityId(dto.getAuthorityId()); + entity.setAuthorityCode(dto.getAuthorityCode()); + entity.setServiceId(dto.getServiceId()); + entity.setRequestMethod(dto.getRequestMethod()); + entity.setUrl(dto.getUrl()); + return entity; + } + +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2ApplicationDto.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2ApplicationDto.java new file mode 100644 index 0000000..84aa441 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2ApplicationDto.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.dto; + +import com.liuhung.engine.assistant.core.json.jackson2.deserializer.ArrayOrStringDeserializer; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; +import com.liuhung.engine.oauth2.core.enums.ApplicationType; +import com.liuhung.engine.oauth2.core.enums.Signature; +import com.liuhung.engine.oauth2.core.enums.TokenFormat; +import com.liuhung.engine.rest.core.definition.dto.BaseSysDto; +import cn.hutool.core.util.IdUtil; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Set; + +/** + *

Description: OAuth2 Application Dto

+ *

+ * 方便数据的转换 + * + * @author : liuh + * @date : 2022/3/18 14:56 + */ +public class OAuth2ApplicationDto extends BaseSysDto { + + @Schema(name = "应用ID") + private String applicationId; + + @Schema(name = "应用名称", required = true) + private String applicationName; + + @Schema(name = "应用简称", title = "应用的简称、别名、缩写等信息") + private String abbreviation; + + @Schema(name = "Logo", title = "Logo存储信息,可以是URL或者路径等") + private String logo; + + @Schema(name = "主页信息", title = "应用相关的主页信息方便查询") + private String homepage; + + @Schema(name = "应用类型", title = "用于区分不同类型的应用") + private ApplicationType applicationType = ApplicationType.WEB; + + @Schema(name = "客户端Id", title = "默认为系统自动生成") + private String clientId = IdUtil.fastSimpleUUID(); + + @Schema(name = "客户端秘钥", title = "这里存储的客户端秘钥是明文,方便使用。默认为系统自动生成") + private String clientSecret = IdUtil.fastSimpleUUID(); + + @Schema(name = "客户端秘钥过期时间", title = "客户端秘钥过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "GMT+8", shape = JsonFormat.Shape.STRING) + private LocalDateTime clientSecretExpiresAt; + + @Schema(name = "回调地址", title = "支持多个值,以逗号分隔", required = true) + private String redirectUris; + + @Schema(name = "认证模式", title = "支持多个值,以逗号分隔", required = true) + @JsonDeserialize(using = ArrayOrStringDeserializer.class) + private Set authorizationGrantTypes = Collections.emptySet(); + + @Schema(name = "客户端认证模式", title = "支持多个值,以逗号分隔", required = true) + @JsonDeserialize(using = ArrayOrStringDeserializer.class) + private Set clientAuthenticationMethods = Collections.emptySet(); + + @Schema(name = "需要证明Key", title = "如果客户端在执行授权码授予流时需要提供验证密钥质询和验证器, 默认False") + private Boolean requireProofKey = Boolean.FALSE; + + @Schema(name = "需要认证确认", title = "如果客户端在执行授权码授予流时需要提供验证密钥质询和验证器, 默认False") + private Boolean requireAuthorizationConsent = Boolean.TRUE; + + @Schema(name = "客户端JSON Web密钥集的URL", title = "客户端JSON Web密钥集的URL") + private String jwkSetUrl; + + @Schema(name = "JWT 签名算法", title = "仅在 clientAuthenticationMethods 为 private_key_jwt 和 client_secret_jwt 方法下使用") + private Signature authenticationSigningAlgorithm; + + @Schema(name = "Access Token", title = "OAuth 2.0令牌的标准数据格式") + private TokenFormat accessTokenFormat = TokenFormat.SELF_CONTAINED; + + @Schema(name = "accessToken 有效时间", title = "默认5分钟,使用 Duration 时间格式") + @JsonDeserialize(using = DurationDeserializer.class) + private Duration accessTokenValidity = Duration.ofSeconds(5); + + @Schema(name = "是否重用 Refresh Token", title = "默认值 True") + private Boolean reuseRefreshTokens = Boolean.TRUE; + + @Schema(name = "refreshToken 有效时间", title = "默认60分钟,使用 Duration 时间格式") + @JsonDeserialize(using = DurationDeserializer.class) + private Duration refreshTokenValidity = Duration.ofHours(1); + + @Schema(name = "IdToken 签名算法", title = "JWT 算法用于签名 ID Token, 默认值 RS256") + private Signature idTokenSignatureAlgorithm = Signature.RS256; + + @Schema(name = "应用对应Scope", title = "传递应用对应Scope ID") + private Set scopes = Collections.emptySet(); + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getAbbreviation() { + return abbreviation; + } + + public void setAbbreviation(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + public String getHomepage() { + return homepage; + } + + public void setHomepage(String homepage) { + this.homepage = homepage; + } + + public ApplicationType getApplicationType() { + return applicationType; + } + + public void setApplicationType(ApplicationType applicationType) { + this.applicationType = applicationType; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getRedirectUris() { + return redirectUris; + } + + public void setRedirectUris(String redirectUris) { + this.redirectUris = redirectUris; + } + + public Set getAuthorizationGrantTypes() { + return authorizationGrantTypes; + } + + public void setAuthorizationGrantTypes(Set authorizationGrantTypes) { + this.authorizationGrantTypes = authorizationGrantTypes; + } + + public Set getClientAuthenticationMethods() { + return clientAuthenticationMethods; + } + + public void setClientAuthenticationMethods(Set clientAuthenticationMethods) { + this.clientAuthenticationMethods = clientAuthenticationMethods; + } + + public Boolean getRequireProofKey() { + return requireProofKey; + } + + public void setRequireProofKey(Boolean requireProofKey) { + this.requireProofKey = requireProofKey; + } + + public Boolean getRequireAuthorizationConsent() { + return requireAuthorizationConsent; + } + + public void setRequireAuthorizationConsent(Boolean requireAuthorizationConsent) { + this.requireAuthorizationConsent = requireAuthorizationConsent; + } + + public String getJwkSetUrl() { + return jwkSetUrl; + } + + public void setJwkSetUrl(String jwkSetUrl) { + this.jwkSetUrl = jwkSetUrl; + } + + public Duration getAccessTokenValidity() { + return accessTokenValidity; + } + + public void setAccessTokenValidity(Duration accessTokenValidity) { + this.accessTokenValidity = accessTokenValidity; + } + + public Boolean getReuseRefreshTokens() { + return reuseRefreshTokens; + } + + public void setReuseRefreshTokens(Boolean reuseRefreshTokens) { + this.reuseRefreshTokens = reuseRefreshTokens; + } + + public Duration getRefreshTokenValidity() { + return refreshTokenValidity; + } + + public void setRefreshTokenValidity(Duration refreshTokenValidity) { + this.refreshTokenValidity = refreshTokenValidity; + } + + public Signature getIdTokenSignatureAlgorithm() { + return idTokenSignatureAlgorithm; + } + + public void setIdTokenSignatureAlgorithm(Signature idTokenSignatureAlgorithm) { + this.idTokenSignatureAlgorithm = idTokenSignatureAlgorithm; + } + + public Set getScopes() { + return scopes; + } + + public void setScopes(Set scopes) { + this.scopes = scopes; + } + + public LocalDateTime getClientSecretExpiresAt() { + return clientSecretExpiresAt; + } + + public void setClientSecretExpiresAt(LocalDateTime clientSecretExpiresAt) { + this.clientSecretExpiresAt = clientSecretExpiresAt; + } + + public Signature getAuthenticationSigningAlgorithm() { + return authenticationSigningAlgorithm; + } + + public void setAuthenticationSigningAlgorithm(Signature authenticationSigningAlgorithm) { + this.authenticationSigningAlgorithm = authenticationSigningAlgorithm; + } + + public TokenFormat getAccessTokenFormat() { + return accessTokenFormat; + } + + public void setAccessTokenFormat(TokenFormat accessTokenFormat) { + this.accessTokenFormat = accessTokenFormat; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("applicationId", applicationId) + .add("applicationName", applicationName) + .add("abbreviation", abbreviation) + .add("logo", logo) + .add("homepage", homepage) + .add("applicationType", applicationType) + .add("clientId", clientId) + .add("clientSecret", clientSecret) + .add("clientSecretExpiresAt", clientSecretExpiresAt) + .add("redirectUris", redirectUris) + .add("authorizationGrantTypes", authorizationGrantTypes) + .add("clientAuthenticationMethods", clientAuthenticationMethods) + .add("requireProofKey", requireProofKey) + .add("requireAuthorizationConsent", requireAuthorizationConsent) + .add("jwkSetUrl", jwkSetUrl) + .add("authenticationSigningAlgorithm", authenticationSigningAlgorithm) + .add("accessTokenFormat", accessTokenFormat) + .add("accessTokenValidity", accessTokenValidity) + .add("reuseRefreshTokens", reuseRefreshTokens) + .add("refreshTokenValidity", refreshTokenValidity) + .add("idTokenSignatureAlgorithm", idTokenSignatureAlgorithm) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2AuthorityDto.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2AuthorityDto.java new file mode 100644 index 0000000..ee8774a --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2AuthorityDto.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.dto; + +import com.liuhung.engine.rest.core.definition.dto.BaseDto; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotNull; + +/** + *

Description: OAuth2 Authority Dto

+ * + * @author : liuh + * @date : 2022/4/1 13:55 + */ +@Schema(name = "OAuth2 权限请求 Dto") +public class OAuth2AuthorityDto extends BaseDto { + + @Schema(name = "权限ID") + @NotNull(message = "权限ID不能为空") + private String authorityId; + + @Schema(name = "权限代码") + @NotNull(message = "权限代码不能为空") + private String authorityCode; + + @Schema(name = "服务ID") + @NotNull(message = "服务ID不能为空") + private String serviceId; + + @Schema(name = "请求方法") + @NotNull(message = "请求方法不能为空") + private String requestMethod; + + @Schema(name = "请求URL") + @NotNull(message = "请求URL不能为空") + private String url; + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + + public String getAuthorityCode() { + return authorityCode; + } + + public void setAuthorityCode(String authorityCode) { + this.authorityCode = authorityCode; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public String getRequestMethod() { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) { + this.requestMethod = requestMethod; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("authorityId", authorityId) + .add("authorityCode", authorityCode) + .add("serviceId", serviceId) + .add("requestMethod", requestMethod) + .add("url", url) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2ScopeDto.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2ScopeDto.java new file mode 100644 index 0000000..589ec06 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/OAuth2ScopeDto.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.dto; + +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotNull; +import java.util.HashSet; +import java.util.Set; + +/** + *

Description: OAuth2 Scope Dto

+ * + * @author : liuh + * @date : 2022/4/1 13:55 + */ +@Schema(name = "OAuth2 范围请求 Dto") +public class OAuth2ScopeDto { + + @Schema(name = "范围ID") + @NotNull(message = "范围ID不能为空") + private String scopeId; + + @Schema(name = "范围权限列表") + private Set authorities = new HashSet<>(); + + public String getScopeId() { + return scopeId; + } + + public void setScopeId(String scopeId) { + this.scopeId = scopeId; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("scopeId", scopeId) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/Session.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/Session.java new file mode 100644 index 0000000..b8a1e67 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/Session.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.dto; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractDto; +import com.google.common.base.MoreObjects; + +/** + *

Description: Session响应实体

+ * + * @author : liuh + * @date : 2021/10/2 11:42 + */ +public class Session extends AbstractDto { + + /** + * 前端未登录时,唯一身份标识。如果由前端生成,则直接返回;如果由后端生成,则返回后端生成值 + */ + private String sessionId; + + /** + * 后台RSA公钥 + */ + private String publicKey; + + /** + * 本系统授权码模式校验参数 + */ + private String state; + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("sessionId", sessionId) + .add("publicKey", publicKey) + .add("state", state) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/SessionCreate.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/SessionCreate.java new file mode 100644 index 0000000..bcbde08 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/SessionCreate.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.dto; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractDto; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + *

Description: 加密创建请求

+ * + * @author : liuh + * @date : 2021/10/1 15:19 + */ +@Schema(title = "加密创建请求") +public class SessionCreate extends AbstractDto { + + @NotBlank(message = "客户端ID不能为空") + @Schema(title = "客户端ID") + private String clientId; + + @NotBlank(message = "客户端秘钥不能为空") + @Schema(title = "客户端秘钥") + private String clientSecret; + + @Schema(title = "未登录前端身份标识") + private String sessionId; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("clientId", clientId) + .add("clientSecret", clientSecret) + .add("sessionId", sessionId) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/SessionExchange.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/SessionExchange.java new file mode 100644 index 0000000..9965171 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/dto/SessionExchange.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.dto; + +import com.liuhung.engine.assistant.core.definition.domain.AbstractDto; +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + *

Description: 机要传递实体

+ * + * @author : liuh + * @date : 2021/10/2 16:29 + */ +@Schema(title = "机要传递实体") +public class SessionExchange extends AbstractDto { + + @NotBlank(message = "confidential参数不能为空") + @Schema(title = "用后端RSA PublicKey加密的前端RSA PublicKey") + private String confidential; + + @NotBlank(message = "Session Key不能为空") + @Schema(title = "未登录前端身份标识") + private String sessionId; + + public String getConfidential() { + return confidential; + } + + public void setConfidential(String confidential) { + this.confidential = confidential; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("confidential", confidential) + .add("sessionId", sessionId) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Application.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Application.java new file mode 100644 index 0000000..90de38e --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Application.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.entity; + +import com.liuhung.engine.data.core.entity.BaseSysEntity; +import com.liuhung.engine.oauth2.core.constants.OAuth2Constants; +import com.liuhung.engine.oauth2.core.enums.ApplicationType; +import com.liuhung.engine.oauth2.core.enums.Signature; +import com.liuhung.engine.oauth2.core.enums.TokenFormat; +import cn.hutool.core.util.IdUtil; +import com.google.common.base.MoreObjects; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +/** + *

Description: OAuth2 应用

+ *

+ * Spring Authorization Server 默认的 RegisteredClient 不便于扩展。增加该类用于存储标准 RegisteredClient 表结构以外的扩展信息。 + * + * @author : liuh + * @date : 2022/3/1 16:45 + */ +@Entity +@Table(name = "oauth2_application", indexes = { + @Index(name = "oauth2_application_id_idx", columnList = "application_id"), + @Index(name = "oauth2_application_cid_idx", columnList = "client_id")}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = OAuth2Constants.REGION_OAUTH2_APPLICATION) +public class OAuth2Application extends BaseSysEntity { + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "application_id", length = 64) + private String applicationId; + + @Column(name = "application_name", length = 128) + private String applicationName; + + @Column(name = "abbreviation", length = 64) + private String abbreviation; + + @Column(name = "logo", length = 1024) + private String logo; + + @Column(name = "homepage", length = 1024) + private String homepage; + + @Column(name = "application_type") + @Enumerated(EnumType.ORDINAL) + private ApplicationType applicationType = ApplicationType.WEB; + + @Column(name = "client_id", length = 100) + private String clientId = IdUtil.fastSimpleUUID(); + + @Column(name = "client_secret", length = 100) + private String clientSecret = IdUtil.fastSimpleUUID(); + + @Column(name = "client_secret_expires_at") + private LocalDateTime clientSecretExpiresAt; + + @Column(name = "redirect_uris", length = 1000) + private String redirectUris; + + @Column(name = "authorization_grant_types", length = 1000) + private String authorizationGrantTypes; + + @Column(name = "client_authentication_methods", length = 1000) + private String clientAuthenticationMethods; + + @Column(name = "require_proof_key") + private Boolean requireProofKey = Boolean.FALSE; + + @Column(name = "require_authorization_consent") + private Boolean requireAuthorizationConsent = Boolean.TRUE; + + @Column(name = "jwk_set_url", length = 1000) + private String jwkSetUrl; + + @Column(name = "signing_algorithm") + @Enumerated(EnumType.ORDINAL) + private Signature authenticationSigningAlgorithm; + + @Column(name = "access_token_format") + @Enumerated(EnumType.ORDINAL) + private TokenFormat accessTokenFormat = TokenFormat.REFERENCE; + + @Column(name = "access_token_validity") + private Duration accessTokenValidity = Duration.ofMinutes(5); + + @Column(name = "reuse_refresh_tokens") + private Boolean reuseRefreshTokens = Boolean.TRUE; + + @Column(name = "refresh_token_validity") + private Duration refreshTokenValidity = Duration.ofMinutes(60); + + @Column(name = "authorization_code_ttl") + private Duration authorizationCodeTtl = Duration.ofMinutes(5); + + @Column(name = "signature_algorithm") + @Enumerated(EnumType.ORDINAL) + private Signature idTokenSignatureAlgorithm = Signature.RS256; + + @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = OAuth2Constants.REGION_OAUTH2_APPLICATION_SCOPE) + @ManyToMany(fetch = FetchType.EAGER) + @Fetch(FetchMode.SUBSELECT) + @JoinTable(name = "oauth2_application_scope", + joinColumns = {@JoinColumn(name = "application_id")}, + inverseJoinColumns = {@JoinColumn(name = "scope_id")}, + uniqueConstraints = {@UniqueConstraint(columnNames = {"application_id", "scope_id"})}, + indexes = {@Index(name = "oauth2_application_scope_aid_idx", columnList = "application_id"), @Index(name = "oauth2_application_scope_sid_idx", columnList = "scope_id")}) + private Set scopes = new HashSet<>(); + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getAbbreviation() { + return abbreviation; + } + + public void setAbbreviation(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + public String getHomepage() { + return homepage; + } + + public void setHomepage(String homepage) { + this.homepage = homepage; + } + + public ApplicationType getApplicationType() { + return applicationType; + } + + public void setApplicationType(ApplicationType applicationType) { + this.applicationType = applicationType; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getRedirectUris() { + return redirectUris; + } + + public void setRedirectUris(String redirectUris) { + this.redirectUris = redirectUris; + } + + public String getAuthorizationGrantTypes() { + return authorizationGrantTypes; + } + + public void setAuthorizationGrantTypes(String authorizationGrantTypes) { + this.authorizationGrantTypes = authorizationGrantTypes; + } + + public String getClientAuthenticationMethods() { + return clientAuthenticationMethods; + } + + public void setClientAuthenticationMethods(String clientAuthenticationMethods) { + this.clientAuthenticationMethods = clientAuthenticationMethods; + } + + public Boolean getRequireProofKey() { + return requireProofKey; + } + + public void setRequireProofKey(Boolean requireProofKey) { + this.requireProofKey = requireProofKey; + } + + public Boolean getRequireAuthorizationConsent() { + return requireAuthorizationConsent; + } + + public void setRequireAuthorizationConsent(Boolean requireAuthorizationConsent) { + this.requireAuthorizationConsent = requireAuthorizationConsent; + } + + public String getJwkSetUrl() { + return jwkSetUrl; + } + + public void setJwkSetUrl(String jwkSetUrl) { + this.jwkSetUrl = jwkSetUrl; + } + + public Duration getAccessTokenValidity() { + return accessTokenValidity; + } + + public void setAccessTokenValidity(Duration accessTokenValidity) { + this.accessTokenValidity = accessTokenValidity; + } + + public Boolean getReuseRefreshTokens() { + return reuseRefreshTokens; + } + + public void setReuseRefreshTokens(Boolean reuseRefreshTokens) { + this.reuseRefreshTokens = reuseRefreshTokens; + } + + public Duration getRefreshTokenValidity() { + return refreshTokenValidity; + } + + public void setRefreshTokenValidity(Duration refreshTokenValidity) { + this.refreshTokenValidity = refreshTokenValidity; + } + + public Signature getIdTokenSignatureAlgorithm() { + return idTokenSignatureAlgorithm; + } + + public void setIdTokenSignatureAlgorithm(Signature signature) { + this.idTokenSignatureAlgorithm = signature; + } + + public Set getScopes() { + return scopes; + } + + public void setScopes(Set scopes) { + this.scopes = scopes; + } + + public LocalDateTime getClientSecretExpiresAt() { + return clientSecretExpiresAt; + } + + public void setClientSecretExpiresAt(LocalDateTime clientSecretExpiresAt) { + this.clientSecretExpiresAt = clientSecretExpiresAt; + } + + public Signature getAuthenticationSigningAlgorithm() { + return authenticationSigningAlgorithm; + } + + public void setAuthenticationSigningAlgorithm(Signature authenticationSigningAlgorithm) { + this.authenticationSigningAlgorithm = authenticationSigningAlgorithm; + } + + public TokenFormat getAccessTokenFormat() { + return accessTokenFormat; + } + + public void setAccessTokenFormat(TokenFormat accessTokenFormat) { + this.accessTokenFormat = accessTokenFormat; + } + + public Duration getAuthorizationCodeTtl() { + return authorizationCodeTtl; + } + + public void setAuthorizationCodeTtl(Duration authorizationCodeTtl) { + this.authorizationCodeTtl = authorizationCodeTtl; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("applicationId", applicationId) + .add("applicationName", applicationName) + .add("abbreviation", abbreviation) + .add("logo", logo) + .add("homepage", homepage) + .add("applicationType", applicationType) + .add("clientId", clientId) + .add("clientSecret", clientSecret) + .add("clientSecretExpiresAt", clientSecretExpiresAt) + .add("redirectUris", redirectUris) + .add("authorizationGrantTypes", authorizationGrantTypes) + .add("clientAuthenticationMethods", clientAuthenticationMethods) + .add("requireProofKey", requireProofKey) + .add("requireAuthorizationConsent", requireAuthorizationConsent) + .add("jwkSetUrl", jwkSetUrl) + .add("authenticationSigningAlgorithm", authenticationSigningAlgorithm) + .add("accessTokenFormat", accessTokenFormat) + .add("accessTokenValidity", accessTokenValidity) + .add("reuseRefreshTokens", reuseRefreshTokens) + .add("refreshTokenValidity", refreshTokenValidity) + .add("idTokenSignatureAlgorithm", idTokenSignatureAlgorithm) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Authority.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Authority.java new file mode 100644 index 0000000..65fc949 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Authority.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.entity; + +import com.liuhung.engine.data.core.entity.BaseSysEntity; +import com.liuhung.engine.oauth2.core.constants.OAuth2Constants; +import com.google.common.base.MoreObjects; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + +/** + *

Description: 客户端权限

+ * + * @author : liuh + * @date : 2022/4/1 13:39 + */ +@Entity +@Table(name = "oauth2_authority", indexes = {@Index(name = "oauth2_authority_id_idx", columnList = "authority_id")}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = OAuth2Constants.REGION_OAUTH2_AUTHORITY) +public class OAuth2Authority extends BaseSysEntity { + + @Id + @GeneratedValue(generator = "oauth2-authority-uuid") + @GenericGenerator(name = "oauth2-authority-uuid", strategy = "generator.server.authentication.oauth2.com.liuhung.engine.OAuth2AuthorityUUIDGenerator") + @Column(name = "authority_id", length = 64) + private String authorityId; + + @Column(name = "authority_code", length = 128) + private String authorityCode; + + @Column(name = "service_id", length = 128) + private String serviceId; + + @Column(name = "request_method", length = 20) + private String requestMethod; + + @Column(name = "url", length = 2048) + private String url; + + public String getAuthorityId() { + return authorityId; + } + + public void setAuthorityId(String authorityId) { + this.authorityId = authorityId; + } + + public String getAuthorityCode() { + return authorityCode; + } + + public void setAuthorityCode(String authorityCode) { + this.authorityCode = authorityCode; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public String getRequestMethod() { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) { + this.requestMethod = requestMethod; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("authorityId", authorityId) + .add("authorityCode", authorityCode) + .add("serviceId", serviceId) + .add("requestMethod", requestMethod) + .add("url", url) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Scope.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Scope.java new file mode 100644 index 0000000..6820eab --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/entity/OAuth2Scope.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.entity; + +import com.liuhung.engine.data.core.entity.BaseSysEntity; +import com.liuhung.engine.oauth2.core.constants.OAuth2Constants; +import com.google.common.base.MoreObjects; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; + +/** + *

Description : Oauth Scope

+ * + * @author : liuh + * @date : 2020/3/19 14:15 + */ +@Entity +@Table(name = "oauth2_scope", uniqueConstraints = {@UniqueConstraint(columnNames = {"scope_code"})}, indexes = { + @Index(name = "oauth2_scope_id_idx", columnList = "scope_id"), + @Index(name = "oauth2_scope_code_idx", columnList = "scope_code")}) +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = OAuth2Constants.REGION_OAUTH2_SCOPE) +public class OAuth2Scope extends BaseSysEntity { + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @Column(name = "scope_id", length = 64) + private String scopeId; + + @Column(name = "scope_code", length = 128, unique = true) + private String scopeCode; + + @Column(name = "scope_name", length = 128) + private String scopeName; + + @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = OAuth2Constants.REGION_OAUTH2_AUTHORITY) + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REMOVE, CascadeType.MERGE}, fetch = FetchType.EAGER) + @Fetch(FetchMode.SUBSELECT) + @JoinTable(name = "oauth2_scope_authority", + joinColumns = {@JoinColumn(name = "scope_id")}, + inverseJoinColumns = {@JoinColumn(name = "authority_id")}, + uniqueConstraints = {@UniqueConstraint(columnNames = {"scope_id", "authority_id"})}, + indexes = {@Index(name = "oauth2_scope_authority_sid_idx", columnList = "scope_id"), @Index(name = "oauth2_scope_authority_aid_idx", columnList = "authority_id")}) + private Set authorities = new HashSet<>(); + + public String getScopeId() { + return scopeId; + } + + public void setScopeId(String scopeId) { + this.scopeId = scopeId; + } + + public String getScopeCode() { + return scopeCode; + } + + public void setScopeCode(String scopeCode) { + this.scopeCode = scopeCode; + } + + public String getScopeName() { + return scopeName; + } + + public void setScopeName(String scopeName) { + this.scopeName = scopeName; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + OAuth2Scope that = (OAuth2Scope) o; + + return new EqualsBuilder() + .append(getScopeId(), that.getScopeId()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getScopeId()) + .toHashCode(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("scopeId", scopeId) + .add("scopeCode", scopeCode) + .add("scopeName", scopeName) + .toString(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/generator/OAuth2AuthorityUUIDGenerator.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/generator/OAuth2AuthorityUUIDGenerator.java new file mode 100644 index 0000000..a8e67ce --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/generator/OAuth2AuthorityUUIDGenerator.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.generator; + +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.UUIDGenerator; + +import java.io.Serializable; + +/** + *

Description: 使得保存实体类时可以在保留主键生成策略的情况下自定义表的主键

+ * + * @author : liuh + * @date : 2022/3/31 21:11 + */ +public class OAuth2AuthorityUUIDGenerator extends UUIDGenerator { + + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { + if (ObjectUtils.isEmpty(object)) { + throw new HibernateException(new NullPointerException()); + } + + OAuth2Authority authority = (OAuth2Authority) object; + + if (StringUtils.isEmpty(authority.getAuthorityId())) { + return super.generate(session, object); + } else { + return authority.getAuthorityId(); + } + } +} + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/processor/QuaferClientDetailsService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/processor/QuaferClientDetailsService.java new file mode 100644 index 0000000..2e8772e --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/processor/QuaferClientDetailsService.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.processor; + +import com.liuhung.engine.oauth2.core.definition.domain.QuaferGrantedAuthority; +import com.liuhung.engine.oauth2.core.definition.service.EnhanceClientDetailsService; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Application; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; +import com.liuhung.engine.oauth2.authentication.server.service.OAuth2ApplicationService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + *

Description: 客户端交互处理器

+ * + * @author : liuh + * @date : 2022/4/1 15:21 + */ +public class QuaferClientDetailsService implements EnhanceClientDetailsService { + + private final OAuth2ApplicationService applicationService; + + public QuaferClientDetailsService(OAuth2ApplicationService applicationService) { + this.applicationService = applicationService; + } + + @Override + public Set findAuthoritiesById(String clientId) { + + OAuth2Application application = applicationService.findByClientId(clientId); + if (ObjectUtils.isNotEmpty(application)) { + Set scopes = application.getScopes(); + Set result = new HashSet<>(); + if (CollectionUtils.isNotEmpty(scopes)) { + for (OAuth2Scope scope : scopes) { + Set authorities = scope.getAuthorities(); + if (CollectionUtils.isNotEmpty(authorities)) { + Set grantedAuthorities = authorities.stream().map(item -> new QuaferGrantedAuthority(item.getAuthorityCode())).collect(Collectors.toSet()); + result.addAll(grantedAuthorities); + } + } + } + return result; + } + + return new HashSet<>(); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/processor/QuaferUserDetailsService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/processor/QuaferUserDetailsService.java new file mode 100644 index 0000000..9458564 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/processor/QuaferUserDetailsService.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.processor; + +import com.liuhung.engine.assistant.core.domain.AccessPrincipal; +import com.liuhung.engine.oauth2.core.definition.domain.QuaferUser; +import com.liuhung.engine.oauth2.core.definition.service.EnhanceUserDetailsService; +import com.liuhung.engine.oauth2.core.definition.strategy.StrategyUserDetailsService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + *

Description: UserDetailsService核心类

+ *

+ * 之前一直使用Fegin进行UserDetailsService的远程调用。现在直接改为数据库访问。主要原因是: + * 1. 根据目前的设计,Oauth的表与系统权限相关的表是在一个库中的。因此UAA和UPMS分开是为了以后提高性能考虑,逻辑上没有必要分成两个服务。 + * 2. UserDetailsService 和 ClientDetailsService 是Oauth核心内容,调用频繁增加一道远程调用增加消耗而已。 + * 3. UserDetailsService 和 ClientDetailsService 是Oauth核心内容,只是UAA在使用。 + * 4. UserDetailsService 和 ClientDetailsService 是Oauth核心内容,是各种验证权限之前必须调用的内容。 + * 一方面:使用feign的方式调用,只能采取作为白名单的方式,安全性无法保证。 + * 另一方面:会产生调用的循环。 + * 因此,最终考虑把这两个服务相关的代码,抽取至UPMS API,采用UAA直接访问数据库的方式。 + * + * @author : liuh + * @date : 2019/11/25 11:02 + */ +public class QuaferUserDetailsService implements EnhanceUserDetailsService { + + private static final Logger log = LoggerFactory.getLogger(QuaferUserDetailsService.class); + + private final StrategyUserDetailsService strategyUserDetailsService; + + public QuaferUserDetailsService(StrategyUserDetailsService strategyUserDetailsService) { + this.strategyUserDetailsService = strategyUserDetailsService; + } + + +// @Override +// public QuaferUser loadUserByUsername(String username) throws UsernameNotFoundException { +// QuaferUser QuaferUser = strategyUserDetailsService.findUserDetailsByUsername(username); +// log.debug("[Quafer] |- UserDetailsService loaded user : [{}]", username); +// return QuaferUser; +// } + + @Override + public UserDetails loadUserBySocial(String source, AccessPrincipal accessPrincipal) throws UsernameNotFoundException { + QuaferUser QuaferUser = strategyUserDetailsService.findUserDetailsBySocial(StringUtils.toRootUpperCase(source), accessPrincipal); + log.debug("[Quafer] |- UserDetailsService loaded social user : [{}]", QuaferUser.getUsername()); + return QuaferUser; + } + + @Override + public QuaferUser loadQuaferUserByUsername(String username) throws UsernameNotFoundException { + QuaferUser QuaferUser = strategyUserDetailsService.findUserDetailsByUsername(username); + log.debug("[Quafer] |- UserDetailsService loaded user : [{}]", username); + return QuaferUser; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return loadQuaferUserByUsername(username); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2ApplicationRepository.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2ApplicationRepository.java new file mode 100644 index 0000000..c63d246 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2ApplicationRepository.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Application; + +/** + *

Description: OAuth2ApplicationRepository

+ * + * @author : liuh + * @date : 2022/3/1 18:05 + */ +public interface OAuth2ApplicationRepository extends BaseRepository { + + /** + * 根据 Client ID 查询 OAuth2Application + * + * @param clientId OAuth2Application 中的 clientId + * @return {@link OAuth2Application} + */ + OAuth2Application findByClientId(String clientId); +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2AuthorityRepository.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2AuthorityRepository.java new file mode 100644 index 0000000..cd1ae96 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2AuthorityRepository.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; + +/** + *

Description: OAuth2AuthorityRepository

+ * + * @author : liuh + * @date : 2022/4/1 13:52 + */ +public interface OAuth2AuthorityRepository extends BaseRepository { +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2ScopeRepository.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2ScopeRepository.java new file mode 100644 index 0000000..1777ee1 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/repository/OAuth2ScopeRepository.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.repository; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; + +/** + *

Description : OauthScopeRepository

+ * + * @author : liuh + * @date : 2020/3/19 16:57 + */ +public interface OAuth2ScopeRepository extends BaseRepository { + + /** + * 根据范围代码查询应用范围 + * + * @param scopeCode 范围代码 + * @return 应用范围 {@link OAuth2Scope} + */ + OAuth2Scope findByScopeCode(String scopeCode); + +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/InterfaceSecurityService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/InterfaceSecurityService.java new file mode 100644 index 0000000..2aa4e8c --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/InterfaceSecurityService.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.service; + +import com.liuhung.engine.assistant.core.domain.SecretKey; +import com.liuhung.engine.oauth2.core.utils.SecurityUtils; +import com.liuhung.engine.rest.protect.crypto.processor.HttpCryptoProcessor; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.stereotype.Service; + +/** + *

Description: 请求加密服务

+ * + * @author : liuh + * @date : 2021/9/30 18:08 + */ +@Service +public class InterfaceSecurityService { + + private static final Logger log = LoggerFactory.getLogger(InterfaceSecurityService.class); + + private static final String PKCS1_BEGIN = "-----BEGIN RSA PUBLIC KEY-----"; + private static final String PKCS1_END = "-----END RSA PUBLIC KEY-----"; + private static final String PKCS8_BEGIN = "-----BEGIN PUBLIC KEY-----"; + private static final String PKCS8_END = "-----END PUBLIC KEY-----"; + + private final HttpCryptoProcessor httpCryptoProcessor; + private final RegisteredClientRepository registeredClientRepository; + + @Autowired + public InterfaceSecurityService(HttpCryptoProcessor httpCryptoProcessor, RegisteredClientRepository registeredClientRepository) { + this.httpCryptoProcessor = httpCryptoProcessor; + this.registeredClientRepository = registeredClientRepository; + } + + /** + * 检查终端是否是合法终端 + * + * @param clientId OAuth2 终端ID + * @param clientSecret OAuth2 终端密码 + */ + private RegisteredClient validateClient(String clientId, String clientSecret) { + RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId); + + boolean isMatch = false; + if (ObjectUtils.isNotEmpty(registeredClient)) { + isMatch = SecurityUtils.matches(clientSecret, registeredClient.getClientSecret()); + } + + if (!isMatch) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); + } + + return registeredClient; + } + + public SecretKey createSecretKey(String clientId, String clientSecret, String sessionId) { + // 检测终端是否是有效终端 + RegisteredClient registeredClient = this.validateClient(clientId, clientSecret); + return httpCryptoProcessor.createSecretKey(sessionId, registeredClient.getTokenSettings().getAccessTokenTimeToLive()); + } + + /** + * 前端用后端PublicKey加密前端PublicKey后,将该值传递给后端,用于加密 AES KEY + * + * @param sessionId Session 标识 + * @param confidentialBase64 前端用后端PublicKey加密前端PublicKey。前端使用node-rsa加密后的数据是base64编码 + * @return 前端RSA PublicKey 加密后的 AES Key + */ + public String exchange(String sessionId, String confidentialBase64) { + return httpCryptoProcessor.exchange(sessionId, confidentialBase64); + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ApplicationService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ApplicationService.java new file mode 100644 index 0000000..1d08029 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ApplicationService.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.service; + +import com.liuhung.engine.assistant.core.enums.Target; +import com.liuhung.engine.assistant.core.exception.transaction.TransactionalRollbackException; +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.oauth2.authentication.server.repository.OAuth2ApplicationRepository; +import com.liuhung.engine.oauth2.core.properties.SecurityProperties; +import com.liuhung.engine.oauth2.data.jpa.repository.QuaferRegisteredClientRepository; +import com.liuhung.engine.oauth2.data.jpa.utils.OAuth2AuthorizationUtils; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Application; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; +import cn.hutool.core.date.DateUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.HashSet; +import java.util.Set; + +/** + *

Description: OAuth2ApplicationService

+ * + * @author : liuh + * @date : 2022/3/1 18:06 + */ +@Service +public class OAuth2ApplicationService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(OAuth2ApplicationService.class); + + private final RegisteredClientRepository registeredClientRepository; + private final QuaferRegisteredClientRepository quaferRegisteredClientRepository; + private final OAuth2ApplicationRepository applicationRepository; + private final SecurityProperties securityProperties; + + @Autowired + public OAuth2ApplicationService(RegisteredClientRepository registeredClientRepository, QuaferRegisteredClientRepository quaferRegisteredClientRepository, OAuth2ApplicationRepository applicationRepository, SecurityProperties securityProperties) { + this.registeredClientRepository = registeredClientRepository; + this.quaferRegisteredClientRepository = quaferRegisteredClientRepository; + this.applicationRepository = applicationRepository; + this.securityProperties = securityProperties; + } + + @Override + public BaseRepository getRepository() { + return this.applicationRepository; + } + + @Transactional(rollbackFor = TransactionalRollbackException.class) + @Override + public OAuth2Application saveOrUpdate(OAuth2Application entity) { + OAuth2Application application = super.saveOrUpdate(entity); + if (ObjectUtils.isNotEmpty(application)) { + registeredClientRepository.save(toRegisteredClient(application)); + log.debug("[Quafer] |- OAuth2ApplicationService saveOrUpdate."); + return application; + } else { + log.error("[Quafer] |- OAuth2ApplicationService saveOrUpdate error, rollback data!"); + throw new NullPointerException("save or update OAuth2Application failed"); + } + } + + @Transactional(rollbackFor = TransactionalRollbackException.class) + @Override + public void deleteById(String id) { + super.deleteById(id); + quaferRegisteredClientRepository.deleteById(id); + log.debug("[Quafer] |- OAuth2ApplicationService deleteById."); + } + + public OAuth2Application authorize(String applicationId, String[] scopeIds) { + + Set scopes = new HashSet<>(); + for (String scopeId : scopeIds) { + OAuth2Scope scope = new OAuth2Scope(); + scope.setScopeId(scopeId); + scopes.add(scope); + } + + OAuth2Application oldApplication = findById(applicationId); + oldApplication.setScopes(scopes); + + OAuth2Application newApplication = saveOrUpdate(oldApplication); + log.debug("[Quafer] |- OAuth2ApplicationService assign."); + return newApplication; + } + + public OAuth2Application findByClientId(String clientId) { + OAuth2Application application = applicationRepository.findByClientId(clientId); + log.debug("[Quafer] |- OAuth2ApplicationService findByClientId."); + return application; + } + + private RegisteredClient toRegisteredClient(OAuth2Application application) { + + Set clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(application.getClientAuthenticationMethods()); + Set authorizationGrantTypes = StringUtils.commaDelimitedListToSet(application.getAuthorizationGrantTypes()); + Set redirectUris = StringUtils.commaDelimitedListToSet(application.getRedirectUris()); + Set clientScopes = application.getScopes(); + + return RegisteredClient.withId(application.getApplicationId()) + // 客户端id 需要唯一 + .clientId(application.getClientId()) + // 客户端密码 + .clientSecret(application.getClientSecret()) + .clientSecretExpiresAt(DateUtil.toInstant(application.getClientSecretExpiresAt())) + .clientAuthenticationMethods(authenticationMethods -> + clientAuthenticationMethods.forEach(authenticationMethod -> + authenticationMethods.add(OAuth2AuthorizationUtils.resolveClientAuthenticationMethod(authenticationMethod)))) + .authorizationGrantTypes((grantTypes) -> + authorizationGrantTypes.forEach(grantType -> + grantTypes.add(OAuth2AuthorizationUtils.resolveAuthorizationGrantType(grantType)))) + .redirectUris((uris) -> uris.addAll(redirectUris)) + .scopes((scopes) -> clientScopes.forEach(clientScope -> scopes.add(clientScope.getScopeCode()))) + .clientSettings(createClientSettings(application)) + .tokenSettings(createTokenSettings(application)) + .build(); + } + + private ClientSettings createClientSettings(OAuth2Application application) { + ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder(); + clientSettingsBuilder.requireAuthorizationConsent(application.getRequireAuthorizationConsent()); + clientSettingsBuilder.requireProofKey(application.getRequireProofKey()); + if (StringUtils.hasText(application.getJwkSetUrl())) { + clientSettingsBuilder.jwkSetUrl(application.getJwkSetUrl()); + } + if (ObjectUtils.isNotEmpty(application.getAuthenticationSigningAlgorithm())) { + JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.from(application.getAuthenticationSigningAlgorithm().name()); + if (ObjectUtils.isNotEmpty(jwsAlgorithm)) { + clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(jwsAlgorithm); + } + } + return clientSettingsBuilder.build(); + } + + private TokenSettings createTokenSettings(OAuth2Application application) { + TokenSettings.Builder tokenSettingsBuilder = TokenSettings.builder(); + // accessToken 的有效期 + tokenSettingsBuilder.accessTokenTimeToLive(application.getAccessTokenValidity()); + // refreshToken 的有效期 + tokenSettingsBuilder.refreshTokenTimeToLive(application.getRefreshTokenValidity()); + // 是否可重用刷新令牌 + tokenSettingsBuilder.reuseRefreshTokens(application.getReuseRefreshTokens()); + tokenSettingsBuilder.authorizationCodeTimeToLive(application.getAuthorizationCodeTtl()); + tokenSettingsBuilder.accessTokenFormat(getTokenFormat()); + if (ObjectUtils.isNotEmpty(application.getIdTokenSignatureAlgorithm())) { + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(application.getIdTokenSignatureAlgorithm().name()); + if (ObjectUtils.isNotEmpty(signatureAlgorithm)) { + tokenSettingsBuilder.idTokenSignatureAlgorithm(signatureAlgorithm); + } + } + return tokenSettingsBuilder.build(); + } + + private OAuth2TokenFormat getTokenFormat() { + if (securityProperties.getValidate() == Target.REMOTE) { + return new OAuth2TokenFormat("reference"); + } else { + return new OAuth2TokenFormat("self-contained"); + } + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2AuthorityService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2AuthorityService.java new file mode 100644 index 0000000..3738ae6 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2AuthorityService.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.oauth2.authentication.server.repository.OAuth2AuthorityRepository; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

Description: OAuth2AuthorityService

+ * + * @author : liuh + * @date : 2022/4/1 13:53 + */ +@Service +public class OAuth2AuthorityService extends BaseLayeredService { + + private final OAuth2AuthorityRepository authorityRepository; + + @Autowired + public OAuth2AuthorityService(OAuth2AuthorityRepository authorityRepository) { + this.authorityRepository = authorityRepository; + } + + @Override + public BaseRepository getRepository() { + return authorityRepository; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ConstantService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ConstantService.java new file mode 100644 index 0000000..3607194 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ConstantService.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.service; + +import com.liuhung.engine.assistant.core.enums.Database; +import com.liuhung.engine.assistant.core.enums.ServerDevice; +import com.liuhung.engine.oauth2.core.enums.*; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Description: OAuth2 常量服务

+ * + * @author : liuh + * @date : 2022/3/17 14:36 + */ +@Service +public class OAuth2ConstantService { + + private static final List> APPLICATION_TYPE_ENUM = ApplicationType.getPreprocessedJsonStructure(); + private static final List> GRANT_TYPE_ENUM = GrantType.getPreprocessedJsonStructure(); + private static final List> SIGNATURE_ENUM = Signature.getPreprocessedJsonStructure(); + private static final List> AUTHENTICATION_METHOD_ENUM = AuthenticationMethod.getPreprocessedJsonStructure(); + private static final List> PERMISSION_EXPRESSION_ENUM = PermissionExpression.getPreprocessedJsonStructure(); + private static final List> DATABASE_ENUM = Database.getPreprocessedJsonStructure(); + private static final List> SERVER_DEVICE_ENUM = ServerDevice.getPreprocessedJsonStructure(); + + public Map getAllEnums() { + Map map = new HashMap<>(8); + map.put("applicationType", APPLICATION_TYPE_ENUM); + map.put("grantType", GRANT_TYPE_ENUM); + map.put("signature", SIGNATURE_ENUM); + map.put("permissionExpression", PERMISSION_EXPRESSION_ENUM); + map.put("authenticationMethod", AUTHENTICATION_METHOD_ENUM); + map.put("database", DATABASE_ENUM); + map.put("serverDevice", SERVER_DEVICE_ENUM); + return map; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ScopeService.java b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ScopeService.java new file mode 100644 index 0000000..af63700 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/java/com/liuhung/engine/oauth2/authentication/server/service/OAuth2ScopeService.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2030 liuhung + * + * quafer Engine licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * quafer Engine 采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改 quafer Cloud 源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://git.liuhung.com/gz/quafer-engine + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ + +package com.liuhung.engine.oauth2.authentication.server.service; + +import com.liuhung.engine.data.core.repository.BaseRepository; +import com.liuhung.engine.data.core.service.BaseLayeredService; +import com.liuhung.engine.oauth2.authentication.server.repository.OAuth2ScopeRepository; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Authority; +import com.liuhung.engine.oauth2.authentication.server.entity.OAuth2Scope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + *

Description : OauthScopeService

+ * + * @author : liuh + * @date : 2020/3/19 17:00 + */ +@Service +public class OAuth2ScopeService extends BaseLayeredService { + + private static final Logger log = LoggerFactory.getLogger(OAuth2ScopeService.class); + + private final OAuth2ScopeRepository oauthScopesRepository; + + @Autowired + public OAuth2ScopeService(OAuth2ScopeRepository oauthScopesRepository) { + this.oauthScopesRepository = oauthScopesRepository; + } + + @Override + public BaseRepository getRepository() { + return oauthScopesRepository; + } + + public OAuth2Scope authorize(String scopeId, Set authorities) { + + OAuth2Scope oldScope = findById(scopeId); + oldScope.setAuthorities(authorities); + + OAuth2Scope newScope = saveOrUpdate(oldScope); + log.debug("[Quafer] |- OAuth2ScopeService assign."); + return newScope; + } + + public OAuth2Scope findByScopeCode(String scopeCode) { + OAuth2Scope scope = oauthScopesRepository.findByScopeCode(scopeCode); + log.debug("[Quafer] |- OAuth2ScopeService findByScopeCode."); + return scope; + } +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/authorization/common.js b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/authorization/common.js new file mode 100644 index 0000000..023512a --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/authorization/common.js @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2019-2021 liuh(ov_001@163.com) + * + * Project Name: quafer-cloud + * Module Name: quafer-engine-oauth + * File Name: common.js + * Author: liuh + * Date: 2021/05/08 07:24:08 + */ + +$.ajaxSetup({ + complete: function (xhr, status) { + + switch (xhr.status) { + case 204: + console.log('Flowable operation was successful'); + break; + case 400: + let title_400 = '请求参数错误,请检查前后台参数!'; + $.confirm.error(title_400); + break; + case 404: + let title_404 = '请求地址不存在,请检查具体代码!'; + $.confirm.error(title_404); + break; + case 409: + let title_409 = '流程已被挂起,如需使用请联系管理员激活!'; + $.confirm.autoCloseWarning('注意', title_409); + break; + case 500: + let code = xhr.status; + let message = ''; + if (xhr.responseJSON && xhr.responseJSON.code) { + code = xhr.responseJSON.code; + message = xhr.responseJSON.message; + } + let title_500 = "系统出现" + message + "问题,代码【" + code + "】,请稍后再试或者联系管理员!" + $.confirm.errorWithLogout(title_500); + break; + case 200: + break; + default: + console.log('-----please check the status ------'); + console.log(xhr); + console.log(status); + } + } +}); + +$.confirm = { + autoCloseSuccess: function (title, text) { + let that = this; + that.autoClose(title, text, 'success'); + }, + autoCloseWarning: function (title, text) { + let that = this; + that.autoClose(title, text, 'warning'); + }, + autoClose: function (title, text, type) { + swal({ + title: title, + text: text, + type: type, + timer: 2000, + showConfirmButton: false + }); + }, + error: function (text) { + let that = this; + that.errorWithConfirmAction(text); + }, + warning: function (text) { + let that = this; + that.withConfirmButton('注意!', text, 'warning', '好的') + }, + errorWithLogout: function (text) { + let that = this; + let confirmAction = function () { + location.href = '/logout'; + }; + that.errorWithConfirmAction(text, confirmAction); + }, + errorWithConfirmAction: function (text, confirmAction) { + let that = this; + that.withConfirmButton('错误!', text, 'error', '好的', confirmAction) + }, + defaultDeleteWarning: function (confirmAction) { + let that = this; + + let cancelAction = function () { + that.autoCloseSuccess("您已取消!", "这条信息又安全了 :),此窗口将会在 2 秒钟后自动关闭。"); + }; + + that.withConfirmAndCancelButton("你确定要删除这条信息么?", "此条信息删除之后将无法恢复,请慎重操作!", "warning", "是的, 删除!", "再想想, 取消!", confirmAction, cancelAction); + }, + withConfirmAndCancelButton(title, text, type, confirmButtonText, cancelButtonText, confirmAction, cancelAction) { + swal({ + title: title, + text: text, + type: type, + showCancelButton: true, + confirmButtonColor: "#DD6B55", + confirmButtonText: confirmButtonText, + cancelButtonText: cancelButtonText, + closeOnConfirm: false, + closeOnCancel: false + }, function (isConfirm) { + if (isConfirm) { + if (confirmAction) { + confirmAction(); + } + } else { + if (cancelAction) { + cancelAction(); + } + } + }); + }, + withConfirmButton: function (title, text, type, confirmButtonText, confirmAction) { + swal({ + title: title, + text: text, + type: type, + showCancelButton: false, + confirmButtonColor: "#DD6B55", + confirmButtonText: confirmButtonText, + closeOnConfirm: true + }, function (isConfirm) { + if (isConfirm) { + if (confirmAction) { + confirmAction(); + } + } + }); + } + +}; + +$.http = { + + request: function (url, body, method, type, dataType) { + let data = body; + let contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + if (type && type.toLowerCase() === 'json') { + contentType = 'application/json;charset=utf-8'; + if (method !== 'GET') { + data = JSON.stringify(body); + } + } + + return new Promise((resolve, reject) => { + $.ajax({ + url: url, + data: data, + type: method, + contentType: contentType, + dataType: dataType || "json", + async: true, + success: function (result) { + resolve(result); + }, + error: function (error) { + reject(error); + } + }); + }); + }, + get: function (url, params = {}) { + let that = this; + return that.request(url, params, 'GET'); + }, + put: function (url, data, type) { + let that = this; + return that.request(url, data, 'PUT', type); + }, + post: function (url, data = {}, type) { + return this.request(url, data, 'POST', type); + }, + delete: function (url, data = {}, type) { + let that = this; + return that.request(url, data, 'DELETE', type); + }, + postWithNoResposeBody: function(url, data = {}) { + let that = this; + return that.request(url, data, 'POST', 'json', 'text'); + } +}; + +//所以页面的消息通知方法 + +$.information = { + configuration: function (colorName, text, placementFrom, placementAlign, animateEnter, animateExit) { + if (colorName === null || colorName === '') { + colorName = 'bg-black'; + } + if (text === null || text === '') { + text = 'Turning standard Bootstrap alerts'; + } + if (animateEnter === null || animateEnter === '') { + animateEnter = 'animated fadeInDown'; + } + if (animateExit === null || animateExit === '') { + animateExit = 'animated fadeOutUp'; + } + let allowDismiss = true; + + $.notify( + { + message: text + }, + { + type: colorName, + allow_dismiss: allowDismiss, + newest_on_top: true, + timer: 1000, + placement: { + from: placementFrom, + align: placementAlign + }, + animate: { + enter: animateEnter, + exit: animateExit + }, + template: '' + }); + }, + success: function (message) { + this.configuration('alert-success', message, 'top', 'center', 'animated fadeInDown', 'animated fadeOutUp') + }, + warning: function (message) { + this.configuration('alert-warning', message, 'top', 'center', 'animated fadeInDown', 'animated fadeOutUp') + }, + error: function (message) { + this.configuration('alert-danger', message, 'top', 'center', 'animated fadeInDown', 'animated fadeOutUp') + }, + info: function (message) { + this.configuration('alert-info', message, 'top', 'center', 'animated fadeInDown', 'animated fadeOutUp') + }, + notify: function (type, message) { + switch (type) { + case 'success': + this.success(message); + break; + case 'warning': + this.warning(message); + break; + case 'error': + this.error(message); + break; + case 'info': + this.info(message); + break; + default: + this.info(message); + } + } +}; diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/authorization/security/system-security.js b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/authorization/security/system-security.js new file mode 100644 index 0000000..20935c9 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/authorization/security/system-security.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2021 liuh(ov_001@163.com) + * + * Project Name: quafer-cloud + * Module Name: quafer-engine-oauth + * File Name: system-security.js + * Author: liuh + * Date: 2021/05/08 07:24:08 + */ + +$.SECURITY = { + informations: function () { + let href = location.href; + if (href.indexOf("kickout") > 0) { + $.information.warning('您的账号在另一台设备上登录,如非本人操作,请立即修改密码!'); + } + if (href.indexOf("invalid") > 0) { + $.information.warning('登录超时,请重新登录'); + } + }, + encrypt: function (content, key) { + if (content) { + let byteContent = CryptoJS.enc.Utf8.parse(content); + let byteKey = CryptoJS.enc.Utf8.parse(key); + let encryptContent = CryptoJS.AES.encrypt(byteContent, byteKey, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + return encryptContent.ciphertext.toString(); + } + + return content; + }, + + decrypt: function (content, key) { + let hexContent = CryptoJS.enc.Hex.parse(content); + let base64Content = CryptoJS.enc.Base64.stringify(hexContent); + + let byteKey = CryptoJS.enc.Utf8.parse(key); + + let decryptContent = CryptoJS.AES.decrypt(base64Content, byteKey, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + + return decryptContent.toString(CryptoJS.enc.Utf8); + }, + + decryptSymmetric: function(symmetric) { + let that = this; + let keys = symmetric.split('/'); + let ninjutsu = keys[0]; + let ninja = keys[1]; + + return that.decrypt(ninja, ninjutsu); + }, + + encryptFormData: function (tank, fighter, missile, symmetric) { + let that = this; + let darts = that.decryptSymmetric(symmetric); + + return { + encryptTank: this.encrypt(tank, darts), + encryptFighter: this.encrypt(fighter, darts), + encryptMissile: this.encrypt(missile, darts) + }; + } +}; diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/materialize.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/materialize.css new file mode 100644 index 0000000..9515953 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/materialize.css @@ -0,0 +1,416 @@ +/*! + * Materialize v0.97.7 (http://materializecss.com) + * Copyright 2014-2015 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ + +/* Radio Buttons + ========================================================================== */ +[type="radio"]:not(:checked), +[type="radio"]:checked { + position: absolute; + left: -9999px; + opacity: 0; +} + +[type="radio"]:not(:checked) + label, +[type="radio"]:checked + label { + position: relative; + padding-left: 35px; + cursor: pointer; + display: inline-block; + height: 25px; + line-height: 25px; + font-size: 1rem; + transition: .28s ease; + /* webkit (konqueror) browsers */ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +[type="radio"] + label:before, +[type="radio"] + label:after { + content: ''; + position: absolute; + left: 0; + top: 0; + margin: 4px; + width: 16px; + height: 16px; + z-index: 0; + transition: .28s ease; +} + +/* Unchecked styles */ +[type="radio"]:not(:checked) + label:before, +[type="radio"]:not(:checked) + label:after, +[type="radio"]:checked + label:before, +[type="radio"]:checked + label:after, +[type="radio"].with-gap:checked + label:before, +[type="radio"].with-gap:checked + label:after { + border-radius: 50%; +} + +[type="radio"]:not(:checked) + label:before, +[type="radio"]:not(:checked) + label:after { + border: 2px solid #5a5a5a; +} + +[type="radio"]:not(:checked) + label:after { + z-index: -1; + -webkit-transform: scale(0); + transform: scale(0); +} + +/* Checked styles */ +[type="radio"]:checked + label:before { + border: 2px solid transparent; +} + +[type="radio"]:checked + label:after, +[type="radio"].with-gap:checked + label:before, +[type="radio"].with-gap:checked + label:after { + border: 2px solid #26a69a; +} + +[type="radio"]:checked + label:after, +[type="radio"].with-gap:checked + label:after { + background-color: #26a69a; + z-index: 0; +} + +[type="radio"]:checked + label:after { + -webkit-transform: scale(1.02); + transform: scale(1.02); +} + +/* Radio With gap */ +[type="radio"].with-gap:checked + label:after { + -webkit-transform: scale(0.5); + transform: scale(0.5); +} + +/* Focused styles */ +[type="radio"].tabbed:focus + label:before { + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1); +} + +/* Disabled Radio With gap */ +[type="radio"].with-gap:disabled:checked + label:before { + border: 2px solid rgba(0, 0, 0, 0.26); +} + +[type="radio"].with-gap:disabled:checked + label:after { + border: none; + background-color: rgba(0, 0, 0, 0.26); +} + +/* Disabled style */ +[type="radio"]:disabled:not(:checked) + label:before, +[type="radio"]:disabled:checked + label:before { + background-color: transparent; + border-color: rgba(0, 0, 0, 0.26); +} + +[type="radio"]:disabled + label { + color: rgba(0, 0, 0, 0.26); +} + +[type="radio"]:disabled:not(:checked) + label:before { + border-color: rgba(0, 0, 0, 0.26); +} + +[type="radio"]:disabled:checked + label:after { + background-color: rgba(0, 0, 0, 0.26); + border-color: #BDBDBD; +} + +/* Checkboxes + ========================================================================== */ +/* CUSTOM CSS CHECKBOXES */ +form p { + margin-bottom: 10px; + text-align: left; +} + +form p:last-child { + margin-bottom: 0; +} + +/* Remove default checkbox */ +[type="checkbox"]:not(:checked), +[type="checkbox"]:checked { + position: absolute; + left: -9999px; + opacity: 0; +} + +[type="checkbox"] { + /* checkbox aspect */ +} + +[type="checkbox"] + label { + position: relative; + padding-left: 35px; + cursor: pointer; + display: inline-block; + height: 25px; + line-height: 25px; + font-size: 1rem; + -webkit-user-select: none; + /* webkit (safari, chrome) browsers */ + -moz-user-select: none; + /* mozilla browsers */ + -khtml-user-select: none; + /* webkit (konqueror) browsers */ + -ms-user-select: none; + /* IE10+ */ +} + +[type="checkbox"] + label:before, +[type="checkbox"]:not(.filled-in) + label:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 18px; + z-index: 0; + border: 2px solid #5a5a5a; + border-radius: 1px; + margin-top: 2px; + transition: .2s; +} + +[type="checkbox"]:not(.filled-in) + label:after { + border: 0; + -webkit-transform: scale(0); + transform: scale(0); +} + +[type="checkbox"]:not(:checked):disabled + label:before { + border: none; + background-color: rgba(0, 0, 0, 0.26); +} + +[type="checkbox"].tabbed:focus + label:after { + -webkit-transform: scale(1); + transform: scale(1); + border: 0; + border-radius: 50%; + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1); + background-color: rgba(0, 0, 0, 0.1); +} + +[type="checkbox"]:checked + label:before { + top: -4px; + left: -5px; + width: 12px; + height: 22px; + border-top: 2px solid transparent; + border-left: 2px solid transparent; + border-right: 2px solid #26a69a; + border-bottom: 2px solid #26a69a; + -webkit-transform: rotate(40deg); + transform: rotate(40deg); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-transform-origin: 100% 100%; + transform-origin: 100% 100%; +} + +[type="checkbox"]:checked:disabled + label:before { + border-right: 2px solid rgba(0, 0, 0, 0.26); + border-bottom: 2px solid rgba(0, 0, 0, 0.26); +} + +/* Indeterminate checkbox */ +[type="checkbox"]:indeterminate + label:before { + top: -11px; + left: -12px; + width: 10px; + height: 22px; + border-top: none; + border-left: none; + border-right: 2px solid #26a69a; + border-bottom: none; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-transform-origin: 100% 100%; + transform-origin: 100% 100%; +} + +[type="checkbox"]:indeterminate:disabled + label:before { + border-right: 2px solid rgba(0, 0, 0, 0.26); + background-color: transparent; +} + +[type="checkbox"].filled-in + label:after { + border-radius: 2px; +} + +[type="checkbox"].filled-in + label:before, +[type="checkbox"].filled-in + label:after { + content: ''; + left: 0; + position: absolute; + /* .1s delay is for check animation */ + transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s; + z-index: 1; +} + +[type="checkbox"].filled-in:not(:checked) + label:before { + width: 0; + height: 0; + border: 3px solid transparent; + left: 6px; + top: 10px; + -webkit-transform: rotateZ(37deg); + transform: rotateZ(37deg); + -webkit-transform-origin: 20% 40%; + transform-origin: 100% 100%; +} + +[type="checkbox"].filled-in:not(:checked) + label:after { + height: 20px; + width: 20px; + background-color: transparent; + border: 2px solid #5a5a5a; + top: 0px; + z-index: 0; +} + +[type="checkbox"].filled-in:checked + label:before { + top: 0; + left: 1px; + width: 8px; + height: 13px; + border-top: 2px solid transparent; + border-left: 2px solid transparent; + border-right: 2px solid #fff; + border-bottom: 2px solid #fff; + -webkit-transform: rotateZ(37deg); + transform: rotateZ(37deg); + -webkit-transform-origin: 100% 100%; + transform-origin: 100% 100%; +} + +[type="checkbox"].filled-in:checked + label:after { + top: 0; + width: 20px; + height: 20px; + border: 2px solid #26a69a; + background-color: #26a69a; + z-index: 0; +} + +[type="checkbox"].filled-in.tabbed:focus + label:after { + border-radius: 2px; + border-color: #5a5a5a; + background-color: rgba(0, 0, 0, 0.1); +} + +[type="checkbox"].filled-in.tabbed:checked:focus + label:after { + border-radius: 2px; + background-color: #26a69a; + border-color: #26a69a; +} + +[type="checkbox"].filled-in:disabled:not(:checked) + label:before { + background-color: transparent; + border: 2px solid transparent; +} + +[type="checkbox"].filled-in:disabled:not(:checked) + label:after { + border-color: transparent; + background-color: #BDBDBD; +} + +[type="checkbox"].filled-in:disabled:checked + label:before { + background-color: transparent; +} + +[type="checkbox"].filled-in:disabled:checked + label:after { + background-color: #BDBDBD; + border-color: #BDBDBD; +} + +/* Switch + ========================================================================== */ +.switch, +.switch * { + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; +} + +.switch label { + cursor: pointer; +} + +.switch label input[type=checkbox] { + opacity: 0; + width: 0; + height: 0; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #84c7c1; +} + +.switch label input[type=checkbox]:checked + .lever:after { + background-color: #26a69a; + left: 24px; +} + +.switch label .lever { + content: ""; + display: inline-block; + position: relative; + width: 40px; + height: 15px; + background-color: #818181; + border-radius: 15px; + margin-right: 10px; + transition: background 0.3s ease; + vertical-align: middle; + margin: 0 16px; +} + +.switch label .lever:after { + content: ""; + position: absolute; + display: inline-block; + width: 21px; + height: 21px; + background-color: #F1F1F1; + border-radius: 21px; + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4); + left: -5px; + top: -3px; + transition: left 0.3s ease, background .3s ease, box-shadow 0.1s ease; +} + +input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after, +input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(38, 166, 154, 0.1); +} + +input[type=checkbox]:not(:disabled) ~ .lever:active:after, +input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 0, 0, 0.08); +} + +.switch input[type=checkbox][disabled] + .lever { + cursor: default; +} + +.switch label input[type=checkbox][disabled] + .lever:after, +.switch label input[type=checkbox][disabled]:checked + .lever:after { + background-color: #BDBDBD; +} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/style.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/style.css new file mode 100644 index 0000000..5cfbc24 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/style.css @@ -0,0 +1,8218 @@ +/* Navbar ====================================== */ +@import url(materialize.css); +.navbar { + font-family: "Roboto", sans-serif; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + border: none; + position: fixed; + top: 0; + left: 0; + z-index: 12; + width: 100%; } +.navbar .navbar-brand { + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; } +.navbar .navbar-custom-right-menu { + float: right; } +.navbar .navbar-toggle { + text-decoration: none; + color: #fff; + width: 20px; + height: 20px; + margin-top: -4px; + margin-right: 17px; } +.navbar .navbar-toggle:before { + content: '\E8D5'; + font-family: 'Material Icons'; + font-size: 26px; } +.navbar .navbar-collapse.in { + overflow: visible; } + +.ls-closed .sidebar { + margin-left: -300px; } + +.ls-closed section.content { + margin-left: 15px; } + +.ls-closed .bars:after, .ls-closed .bars:before { + font-family: 'Material Icons'; + font-size: 24px; + position: absolute; + top: 18px; + left: 20px; + margin-right: 10px; + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -webkit-transition: all 0.3s; + transition: all 0.3s; } + +.ls-closed .bars:before { + content: '\E5D2'; + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); } + +.ls-closed .bars:after { + content: '\E5C4'; + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); } + +.ls-closed .navbar-brand { + margin-left: 30px; } + +.overlay-open .bars:before { + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); } + +.overlay-open .bars:after { + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); } + +.navbar-header { + padding: 10px 7px; } +.navbar-header .bars { + float: left; + text-decoration: none; } + +.navbar-nav > li > a { + padding: 7px 7px 2px 7px; + margin-top: 17px; + margin-left: 5px; } + +.navbar-nav .dropdown-menu { + margin-top: -40px !important; } + +.label-count { + position: absolute; + top: 2px; + right: 6px; + font-size: 10px; + line-height: 15px; + background-color: #000; + padding: 0 4px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; } + +.col-red .navbar .navbar-brand, +.col-red .navbar .navbar-brand:hover, +.col-red .navbar .navbar-brand:active, +.col-red .navbar .navbar-brand:focus { + color: #fff; } + +.col-red .navbar .nav > li > a:hover, +.col-red .navbar .nav > li > a:focus, +.col-red .navbar .nav .open > a, +.col-red .navbar .nav .open > a:hover, +.col-red .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-red .navbar .nav > li > a { + color: #fff; } + +.col-red .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-red .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-pink .navbar .navbar-brand, +.col-pink .navbar .navbar-brand:hover, +.col-pink .navbar .navbar-brand:active, +.col-pink .navbar .navbar-brand:focus { + color: #fff; } + +.col-pink .navbar .nav > li > a:hover, +.col-pink .navbar .nav > li > a:focus, +.col-pink .navbar .nav .open > a, +.col-pink .navbar .nav .open > a:hover, +.col-pink .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-pink .navbar .nav > li > a { + color: #fff; } + +.col-pink .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-pink .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-purple .navbar .navbar-brand, +.col-purple .navbar .navbar-brand:hover, +.col-purple .navbar .navbar-brand:active, +.col-purple .navbar .navbar-brand:focus { + color: #fff; } + +.col-purple .navbar .nav > li > a:hover, +.col-purple .navbar .nav > li > a:focus, +.col-purple .navbar .nav .open > a, +.col-purple .navbar .nav .open > a:hover, +.col-purple .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-purple .navbar .nav > li > a { + color: #fff; } + +.col-purple .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-purple .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-deep-purple .navbar .navbar-brand, +.col-deep-purple .navbar .navbar-brand:hover, +.col-deep-purple .navbar .navbar-brand:active, +.col-deep-purple .navbar .navbar-brand:focus { + color: #fff; } + +.col-deep-purple .navbar .nav > li > a:hover, +.col-deep-purple .navbar .nav > li > a:focus, +.col-deep-purple .navbar .nav .open > a, +.col-deep-purple .navbar .nav .open > a:hover, +.col-deep-purple .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-deep-purple .navbar .nav > li > a { + color: #fff; } + +.col-deep-purple .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-deep-purple .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-indigo .navbar .navbar-brand, +.col-indigo .navbar .navbar-brand:hover, +.col-indigo .navbar .navbar-brand:active, +.col-indigo .navbar .navbar-brand:focus { + color: #fff; } + +.col-indigo .navbar .nav > li > a:hover, +.col-indigo .navbar .nav > li > a:focus, +.col-indigo .navbar .nav .open > a, +.col-indigo .navbar .nav .open > a:hover, +.col-indigo .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-indigo .navbar .nav > li > a { + color: #fff; } + +.col-indigo .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-indigo .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-blue .navbar .navbar-brand, +.col-blue .navbar .navbar-brand:hover, +.col-blue .navbar .navbar-brand:active, +.col-blue .navbar .navbar-brand:focus { + color: #fff; } + +.col-blue .navbar .nav > li > a:hover, +.col-blue .navbar .nav > li > a:focus, +.col-blue .navbar .nav .open > a, +.col-blue .navbar .nav .open > a:hover, +.col-blue .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-blue .navbar .nav > li > a { + color: #fff; } + +.col-blue .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-blue .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-light-blue .navbar .navbar-brand, +.col-light-blue .navbar .navbar-brand:hover, +.col-light-blue .navbar .navbar-brand:active, +.col-light-blue .navbar .navbar-brand:focus { + color: #fff; } + +.col-light-blue .navbar .nav > li > a:hover, +.col-light-blue .navbar .nav > li > a:focus, +.col-light-blue .navbar .nav .open > a, +.col-light-blue .navbar .nav .open > a:hover, +.col-light-blue .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-light-blue .navbar .nav > li > a { + color: #fff; } + +.col-light-blue .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-light-blue .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-cyan .navbar .navbar-brand, +.col-cyan .navbar .navbar-brand:hover, +.col-cyan .navbar .navbar-brand:active, +.col-cyan .navbar .navbar-brand:focus { + color: #fff; } + +.col-cyan .navbar .nav > li > a:hover, +.col-cyan .navbar .nav > li > a:focus, +.col-cyan .navbar .nav .open > a, +.col-cyan .navbar .nav .open > a:hover, +.col-cyan .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-cyan .navbar .nav > li > a { + color: #fff; } + +.col-cyan .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-cyan .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-teal .navbar .navbar-brand, +.col-teal .navbar .navbar-brand:hover, +.col-teal .navbar .navbar-brand:active, +.col-teal .navbar .navbar-brand:focus { + color: #fff; } + +.col-teal .navbar .nav > li > a:hover, +.col-teal .navbar .nav > li > a:focus, +.col-teal .navbar .nav .open > a, +.col-teal .navbar .nav .open > a:hover, +.col-teal .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-teal .navbar .nav > li > a { + color: #fff; } + +.col-teal .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-teal .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-green .navbar .navbar-brand, +.col-green .navbar .navbar-brand:hover, +.col-green .navbar .navbar-brand:active, +.col-green .navbar .navbar-brand:focus { + color: #fff; } + +.col-green .navbar .nav > li > a:hover, +.col-green .navbar .nav > li > a:focus, +.col-green .navbar .nav .open > a, +.col-green .navbar .nav .open > a:hover, +.col-green .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-green .navbar .nav > li > a { + color: #fff; } + +.col-green .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-green .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-light-green .navbar .navbar-brand, +.col-light-green .navbar .navbar-brand:hover, +.col-light-green .navbar .navbar-brand:active, +.col-light-green .navbar .navbar-brand:focus { + color: #fff; } + +.col-light-green .navbar .nav > li > a:hover, +.col-light-green .navbar .nav > li > a:focus, +.col-light-green .navbar .nav .open > a, +.col-light-green .navbar .nav .open > a:hover, +.col-light-green .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-light-green .navbar .nav > li > a { + color: #fff; } + +.col-light-green .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-light-green .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-lime .navbar .navbar-brand, +.col-lime .navbar .navbar-brand:hover, +.col-lime .navbar .navbar-brand:active, +.col-lime .navbar .navbar-brand:focus { + color: #fff; } + +.col-lime .navbar .nav > li > a:hover, +.col-lime .navbar .nav > li > a:focus, +.col-lime .navbar .nav .open > a, +.col-lime .navbar .nav .open > a:hover, +.col-lime .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-lime .navbar .nav > li > a { + color: #fff; } + +.col-lime .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-lime .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-yellow .navbar .navbar-brand, +.col-yellow .navbar .navbar-brand:hover, +.col-yellow .navbar .navbar-brand:active, +.col-yellow .navbar .navbar-brand:focus { + color: #fff; } + +.col-yellow .navbar .nav > li > a:hover, +.col-yellow .navbar .nav > li > a:focus, +.col-yellow .navbar .nav .open > a, +.col-yellow .navbar .nav .open > a:hover, +.col-yellow .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-yellow .navbar .nav > li > a { + color: #fff; } + +.col-yellow .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-yellow .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-amber .navbar .navbar-brand, +.col-amber .navbar .navbar-brand:hover, +.col-amber .navbar .navbar-brand:active, +.col-amber .navbar .navbar-brand:focus { + color: #fff; } + +.col-amber .navbar .nav > li > a:hover, +.col-amber .navbar .nav > li > a:focus, +.col-amber .navbar .nav .open > a, +.col-amber .navbar .nav .open > a:hover, +.col-amber .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-amber .navbar .nav > li > a { + color: #fff; } + +.col-amber .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-amber .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-orange .navbar .navbar-brand, +.col-orange .navbar .navbar-brand:hover, +.col-orange .navbar .navbar-brand:active, +.col-orange .navbar .navbar-brand:focus { + color: #fff; } + +.col-orange .navbar .nav > li > a:hover, +.col-orange .navbar .nav > li > a:focus, +.col-orange .navbar .nav .open > a, +.col-orange .navbar .nav .open > a:hover, +.col-orange .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-orange .navbar .nav > li > a { + color: #fff; } + +.col-orange .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-orange .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-deep-orange .navbar .navbar-brand, +.col-deep-orange .navbar .navbar-brand:hover, +.col-deep-orange .navbar .navbar-brand:active, +.col-deep-orange .navbar .navbar-brand:focus { + color: #fff; } + +.col-deep-orange .navbar .nav > li > a:hover, +.col-deep-orange .navbar .nav > li > a:focus, +.col-deep-orange .navbar .nav .open > a, +.col-deep-orange .navbar .nav .open > a:hover, +.col-deep-orange .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-deep-orange .navbar .nav > li > a { + color: #fff; } + +.col-deep-orange .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-deep-orange .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-brown .navbar .navbar-brand, +.col-brown .navbar .navbar-brand:hover, +.col-brown .navbar .navbar-brand:active, +.col-brown .navbar .navbar-brand:focus { + color: #fff; } + +.col-brown .navbar .nav > li > a:hover, +.col-brown .navbar .nav > li > a:focus, +.col-brown .navbar .nav .open > a, +.col-brown .navbar .nav .open > a:hover, +.col-brown .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-brown .navbar .nav > li > a { + color: #fff; } + +.col-brown .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-brown .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-grey .navbar .navbar-brand, +.col-grey .navbar .navbar-brand:hover, +.col-grey .navbar .navbar-brand:active, +.col-grey .navbar .navbar-brand:focus { + color: #fff; } + +.col-grey .navbar .nav > li > a:hover, +.col-grey .navbar .nav > li > a:focus, +.col-grey .navbar .nav .open > a, +.col-grey .navbar .nav .open > a:hover, +.col-grey .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-grey .navbar .nav > li > a { + color: #fff; } + +.col-grey .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-grey .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-blue-grey .navbar .navbar-brand, +.col-blue-grey .navbar .navbar-brand:hover, +.col-blue-grey .navbar .navbar-brand:active, +.col-blue-grey .navbar .navbar-brand:focus { + color: #fff; } + +.col-blue-grey .navbar .nav > li > a:hover, +.col-blue-grey .navbar .nav > li > a:focus, +.col-blue-grey .navbar .nav .open > a, +.col-blue-grey .navbar .nav .open > a:hover, +.col-blue-grey .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-blue-grey .navbar .nav > li > a { + color: #fff; } + +.col-blue-grey .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-blue-grey .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-black .navbar .navbar-brand, +.col-black .navbar .navbar-brand:hover, +.col-black .navbar .navbar-brand:active, +.col-black .navbar .navbar-brand:focus { + color: #fff; } + +.col-black .navbar .nav > li > a:hover, +.col-black .navbar .nav > li > a:focus, +.col-black .navbar .nav .open > a, +.col-black .navbar .nav .open > a:hover, +.col-black .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-black .navbar .nav > li > a { + color: #fff; } + +.col-black .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-black .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-white .navbar .navbar-brand, +.col-white .navbar .navbar-brand:hover, +.col-white .navbar .navbar-brand:active, +.col-white .navbar .navbar-brand:focus { + color: #fff; } + +.col-white .navbar .nav > li > a:hover, +.col-white .navbar .nav > li > a:focus, +.col-white .navbar .nav .open > a, +.col-white .navbar .nav .open > a:hover, +.col-white .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-white .navbar .nav > li > a { + color: #fff; } + +.col-white .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-white .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +/* Material Icons ============================== */ +.material-icons.md-18 { + font-size: 18px; } + +.material-icons.md-24 { + font-size: 24px; } + +.material-icons.md-26 { + font-size: 26px; } + +.material-icons.md-28 { + font-size: 28px; } + +.material-icons.md-30 { + font-size: 30px; } + +.material-icons.md-32 { + font-size: 32px; } + +.material-icons.md-36 { + font-size: 36px; } + +.material-icons.md-48 { + font-size: 48px; } + +/* Helpers ===================================== */ +.m-l--125 { + margin-left: -125px; } + +.m-t--125 { + margin-top: -125px; } + +.m-r--125 { + margin-right: -125px; } + +.m-b--125 { + margin-bottom: -125px; } + +.m-l--120 { + margin-left: -120px; } + +.m-t--120 { + margin-top: -120px; } + +.m-r--120 { + margin-right: -120px; } + +.m-b--120 { + margin-bottom: -120px; } + +.m-l--115 { + margin-left: -115px; } + +.m-t--115 { + margin-top: -115px; } + +.m-r--115 { + margin-right: -115px; } + +.m-b--115 { + margin-bottom: -115px; } + +.m-l--110 { + margin-left: -110px; } + +.m-t--110 { + margin-top: -110px; } + +.m-r--110 { + margin-right: -110px; } + +.m-b--110 { + margin-bottom: -110px; } + +.m-l--105 { + margin-left: -105px; } + +.m-t--105 { + margin-top: -105px; } + +.m-r--105 { + margin-right: -105px; } + +.m-b--105 { + margin-bottom: -105px; } + +.m-l--100 { + margin-left: -100px; } + +.m-t--100 { + margin-top: -100px; } + +.m-r--100 { + margin-right: -100px; } + +.m-b--100 { + margin-bottom: -100px; } + +.m-l--95 { + margin-left: -95px; } + +.m-t--95 { + margin-top: -95px; } + +.m-r--95 { + margin-right: -95px; } + +.m-b--95 { + margin-bottom: -95px; } + +.m-l--90 { + margin-left: -90px; } + +.m-t--90 { + margin-top: -90px; } + +.m-r--90 { + margin-right: -90px; } + +.m-b--90 { + margin-bottom: -90px; } + +.m-l--85 { + margin-left: -85px; } + +.m-t--85 { + margin-top: -85px; } + +.m-r--85 { + margin-right: -85px; } + +.m-b--85 { + margin-bottom: -85px; } + +.m-l--80 { + margin-left: -80px; } + +.m-t--80 { + margin-top: -80px; } + +.m-r--80 { + margin-right: -80px; } + +.m-b--80 { + margin-bottom: -80px; } + +.m-l--75 { + margin-left: -75px; } + +.m-t--75 { + margin-top: -75px; } + +.m-r--75 { + margin-right: -75px; } + +.m-b--75 { + margin-bottom: -75px; } + +.m-l--70 { + margin-left: -70px; } + +.m-t--70 { + margin-top: -70px; } + +.m-r--70 { + margin-right: -70px; } + +.m-b--70 { + margin-bottom: -70px; } + +.m-l--65 { + margin-left: -65px; } + +.m-t--65 { + margin-top: -65px; } + +.m-r--65 { + margin-right: -65px; } + +.m-b--65 { + margin-bottom: -65px; } + +.m-l--60 { + margin-left: -60px; } + +.m-t--60 { + margin-top: -60px; } + +.m-r--60 { + margin-right: -60px; } + +.m-b--60 { + margin-bottom: -60px; } + +.m-l--55 { + margin-left: -55px; } + +.m-t--55 { + margin-top: -55px; } + +.m-r--55 { + margin-right: -55px; } + +.m-b--55 { + margin-bottom: -55px; } + +.m-l--50 { + margin-left: -50px; } + +.m-t--50 { + margin-top: -50px; } + +.m-r--50 { + margin-right: -50px; } + +.m-b--50 { + margin-bottom: -50px; } + +.m-l--45 { + margin-left: -45px; } + +.m-t--45 { + margin-top: -45px; } + +.m-r--45 { + margin-right: -45px; } + +.m-b--45 { + margin-bottom: -45px; } + +.m-l--40 { + margin-left: -40px; } + +.m-t--40 { + margin-top: -40px; } + +.m-r--40 { + margin-right: -40px; } + +.m-b--40 { + margin-bottom: -40px; } + +.m-l--35 { + margin-left: -35px; } + +.m-t--35 { + margin-top: -35px; } + +.m-r--35 { + margin-right: -35px; } + +.m-b--35 { + margin-bottom: -35px; } + +.m-l--30 { + margin-left: -30px; } + +.m-t--30 { + margin-top: -30px; } + +.m-r--30 { + margin-right: -30px; } + +.m-b--30 { + margin-bottom: -30px; } + +.m-l--25 { + margin-left: -25px; } + +.m-t--25 { + margin-top: -25px; } + +.m-r--25 { + margin-right: -25px; } + +.m-b--25 { + margin-bottom: -25px; } + +.m-l--20 { + margin-left: -20px; } + +.m-t--20 { + margin-top: -20px; } + +.m-r--20 { + margin-right: -20px; } + +.m-b--20 { + margin-bottom: -20px; } + +.m-l--15 { + margin-left: -15px; } + +.m-t--15 { + margin-top: -15px; } + +.m-r--15 { + margin-right: -15px; } + +.m-b--15 { + margin-bottom: -15px; } + +.m-l--10 { + margin-left: -10px; } + +.m-t--10 { + margin-top: -10px; } + +.m-r--10 { + margin-right: -10px; } + +.m-b--10 { + margin-bottom: -10px; } + +.m-l--5 { + margin-left: -5px; } + +.m-t--5 { + margin-top: -5px; } + +.m-r--5 { + margin-right: -5px; } + +.m-b--5 { + margin-bottom: -5px; } + +.m-l-0 { + margin-left: 0px; } + +.m-t-0 { + margin-top: 0px; } + +.m-r-0 { + margin-right: 0px; } + +.m-b-0 { + margin-bottom: 0px; } + +.m-l-5 { + margin-left: 5px; } + +.m-t-5 { + margin-top: 5px; } + +.m-r-5 { + margin-right: 5px; } + +.m-b-5 { + margin-bottom: 5px; } + +.m-l-10 { + margin-left: 10px; } + +.m-t-10 { + margin-top: 10px; } + +.m-r-10 { + margin-right: 10px; } + +.m-b-10 { + margin-bottom: 10px; } + +.m-l-15 { + margin-left: 15px; } + +.m-t-15 { + margin-top: 15px; } + +.m-r-15 { + margin-right: 15px; } + +.m-b-15 { + margin-bottom: 15px; } + +.m-l-20 { + margin-left: 20px; } + +.m-t-20 { + margin-top: 20px; } + +.m-r-20 { + margin-right: 20px; } + +.m-b-20 { + margin-bottom: 20px; } + +.m-l-25 { + margin-left: 25px; } + +.m-t-25 { + margin-top: 25px; } + +.m-r-25 { + margin-right: 25px; } + +.m-b-25 { + margin-bottom: 25px; } + +.m-l-30 { + margin-left: 30px; } + +.m-t-30 { + margin-top: 30px; } + +.m-r-30 { + margin-right: 30px; } + +.m-b-30 { + margin-bottom: 30px; } + +.m-l-35 { + margin-left: 35px; } + +.m-t-35 { + margin-top: 35px; } + +.m-r-35 { + margin-right: 35px; } + +.m-b-35 { + margin-bottom: 35px; } + +.m-l-40 { + margin-left: 40px; } + +.m-t-40 { + margin-top: 40px; } + +.m-r-40 { + margin-right: 40px; } + +.m-b-40 { + margin-bottom: 40px; } + +.m-l-45 { + margin-left: 45px; } + +.m-t-45 { + margin-top: 45px; } + +.m-r-45 { + margin-right: 45px; } + +.m-b-45 { + margin-bottom: 45px; } + +.m-l-50 { + margin-left: 50px; } + +.m-t-50 { + margin-top: 50px; } + +.m-r-50 { + margin-right: 50px; } + +.m-b-50 { + margin-bottom: 50px; } + +.m-l-55 { + margin-left: 55px; } + +.m-t-55 { + margin-top: 55px; } + +.m-r-55 { + margin-right: 55px; } + +.m-b-55 { + margin-bottom: 55px; } + +.m-l-60 { + margin-left: 60px; } + +.m-t-60 { + margin-top: 60px; } + +.m-r-60 { + margin-right: 60px; } + +.m-b-60 { + margin-bottom: 60px; } + +.m-l-65 { + margin-left: 65px; } + +.m-t-65 { + margin-top: 65px; } + +.m-r-65 { + margin-right: 65px; } + +.m-b-65 { + margin-bottom: 65px; } + +.m-l-70 { + margin-left: 70px; } + +.m-t-70 { + margin-top: 70px; } + +.m-r-70 { + margin-right: 70px; } + +.m-b-70 { + margin-bottom: 70px; } + +.m-l-75 { + margin-left: 75px; } + +.m-t-75 { + margin-top: 75px; } + +.m-r-75 { + margin-right: 75px; } + +.m-b-75 { + margin-bottom: 75px; } + +.m-l-80 { + margin-left: 80px; } + +.m-t-80 { + margin-top: 80px; } + +.m-r-80 { + margin-right: 80px; } + +.m-b-80 { + margin-bottom: 80px; } + +.m-l-85 { + margin-left: 85px; } + +.m-t-85 { + margin-top: 85px; } + +.m-r-85 { + margin-right: 85px; } + +.m-b-85 { + margin-bottom: 85px; } + +.m-l-90 { + margin-left: 90px; } + +.m-t-90 { + margin-top: 90px; } + +.m-r-90 { + margin-right: 90px; } + +.m-b-90 { + margin-bottom: 90px; } + +.m-l-95 { + margin-left: 95px; } + +.m-t-95 { + margin-top: 95px; } + +.m-r-95 { + margin-right: 95px; } + +.m-b-95 { + margin-bottom: 95px; } + +.m-l-100 { + margin-left: 100px; } + +.m-t-100 { + margin-top: 100px; } + +.m-r-100 { + margin-right: 100px; } + +.m-b-100 { + margin-bottom: 100px; } + +.m-l-105 { + margin-left: 105px; } + +.m-t-105 { + margin-top: 105px; } + +.m-r-105 { + margin-right: 105px; } + +.m-b-105 { + margin-bottom: 105px; } + +.m-l-110 { + margin-left: 110px; } + +.m-t-110 { + margin-top: 110px; } + +.m-r-110 { + margin-right: 110px; } + +.m-b-110 { + margin-bottom: 110px; } + +.m-l-115 { + margin-left: 115px; } + +.m-t-115 { + margin-top: 115px; } + +.m-r-115 { + margin-right: 115px; } + +.m-b-115 { + margin-bottom: 115px; } + +.m-l-120 { + margin-left: 120px; } + +.m-t-120 { + margin-top: 120px; } + +.m-r-120 { + margin-right: 120px; } + +.m-b-120 { + margin-bottom: 120px; } + +.m-l-125 { + margin-left: 125px; } + +.m-t-125 { + margin-top: 125px; } + +.m-r-125 { + margin-right: 125px; } + +.m-b-125 { + margin-bottom: 125px; } + +.margin-0 { + margin: 0; } + +.p-l-0 { + padding-left: 0px; } + +.p-t-0 { + padding-top: 0px; } + +.p-r-0 { + padding-right: 0px; } + +.p-b-0 { + padding-bottom: 0px; } + +.p-l-5 { + padding-left: 5px; } + +.p-t-5 { + padding-top: 5px; } + +.p-r-5 { + padding-right: 5px; } + +.p-b-5 { + padding-bottom: 5px; } + +.p-l-10 { + padding-left: 10px; } + +.p-t-10 { + padding-top: 10px; } + +.p-r-10 { + padding-right: 10px; } + +.p-b-10 { + padding-bottom: 10px; } + +.p-l-15 { + padding-left: 15px; } + +.p-t-15 { + padding-top: 15px; } + +.p-r-15 { + padding-right: 15px; } + +.p-b-15 { + padding-bottom: 15px; } + +.p-l-20 { + padding-left: 20px; } + +.p-t-20 { + padding-top: 20px; } + +.p-r-20 { + padding-right: 20px; } + +.p-b-20 { + padding-bottom: 20px; } + +.p-l-25 { + padding-left: 25px; } + +.p-t-25 { + padding-top: 25px; } + +.p-r-25 { + padding-right: 25px; } + +.p-b-25 { + padding-bottom: 25px; } + +.p-l-30 { + padding-left: 30px; } + +.p-t-30 { + padding-top: 30px; } + +.p-r-30 { + padding-right: 30px; } + +.p-b-30 { + padding-bottom: 30px; } + +.p-l-35 { + padding-left: 35px; } + +.p-t-35 { + padding-top: 35px; } + +.p-r-35 { + padding-right: 35px; } + +.p-b-35 { + padding-bottom: 35px; } + +.p-l-40 { + padding-left: 40px; } + +.p-t-40 { + padding-top: 40px; } + +.p-r-40 { + padding-right: 40px; } + +.p-b-40 { + padding-bottom: 40px; } + +.p-l-45 { + padding-left: 45px; } + +.p-t-45 { + padding-top: 45px; } + +.p-r-45 { + padding-right: 45px; } + +.p-b-45 { + padding-bottom: 45px; } + +.p-l-50 { + padding-left: 50px; } + +.p-t-50 { + padding-top: 50px; } + +.p-r-50 { + padding-right: 50px; } + +.p-b-50 { + padding-bottom: 50px; } + +.p-l-55 { + padding-left: 55px; } + +.p-t-55 { + padding-top: 55px; } + +.p-r-55 { + padding-right: 55px; } + +.p-b-55 { + padding-bottom: 55px; } + +.p-l-60 { + padding-left: 60px; } + +.p-t-60 { + padding-top: 60px; } + +.p-r-60 { + padding-right: 60px; } + +.p-b-60 { + padding-bottom: 60px; } + +.p-l-65 { + padding-left: 65px; } + +.p-t-65 { + padding-top: 65px; } + +.p-r-65 { + padding-right: 65px; } + +.p-b-65 { + padding-bottom: 65px; } + +.p-l-70 { + padding-left: 70px; } + +.p-t-70 { + padding-top: 70px; } + +.p-r-70 { + padding-right: 70px; } + +.p-b-70 { + padding-bottom: 70px; } + +.p-l-75 { + padding-left: 75px; } + +.p-t-75 { + padding-top: 75px; } + +.p-r-75 { + padding-right: 75px; } + +.p-b-75 { + padding-bottom: 75px; } + +.p-l-80 { + padding-left: 80px; } + +.p-t-80 { + padding-top: 80px; } + +.p-r-80 { + padding-right: 80px; } + +.p-b-80 { + padding-bottom: 80px; } + +.p-l-85 { + padding-left: 85px; } + +.p-t-85 { + padding-top: 85px; } + +.p-r-85 { + padding-right: 85px; } + +.p-b-85 { + padding-bottom: 85px; } + +.p-l-90 { + padding-left: 90px; } + +.p-t-90 { + padding-top: 90px; } + +.p-r-90 { + padding-right: 90px; } + +.p-b-90 { + padding-bottom: 90px; } + +.p-l-95 { + padding-left: 95px; } + +.p-t-95 { + padding-top: 95px; } + +.p-r-95 { + padding-right: 95px; } + +.p-b-95 { + padding-bottom: 95px; } + +.p-l-100 { + padding-left: 100px; } + +.p-t-100 { + padding-top: 100px; } + +.p-r-100 { + padding-right: 100px; } + +.p-b-100 { + padding-bottom: 100px; } + +.p-l-105 { + padding-left: 105px; } + +.p-t-105 { + padding-top: 105px; } + +.p-r-105 { + padding-right: 105px; } + +.p-b-105 { + padding-bottom: 105px; } + +.p-l-110 { + padding-left: 110px; } + +.p-t-110 { + padding-top: 110px; } + +.p-r-110 { + padding-right: 110px; } + +.p-b-110 { + padding-bottom: 110px; } + +.p-l-115 { + padding-left: 115px; } + +.p-t-115 { + padding-top: 115px; } + +.p-r-115 { + padding-right: 115px; } + +.p-b-115 { + padding-bottom: 115px; } + +.p-l-120 { + padding-left: 120px; } + +.p-t-120 { + padding-top: 120px; } + +.p-r-120 { + padding-right: 120px; } + +.p-b-120 { + padding-bottom: 120px; } + +.p-l-125 { + padding-left: 125px; } + +.p-t-125 { + padding-top: 125px; } + +.p-r-125 { + padding-right: 125px; } + +.p-b-125 { + padding-bottom: 125px; } + +.padding-0 { + padding: 0; } + +.font-6 { + font-size: 6px; } + +.font-7 { + font-size: 7px; } + +.font-8 { + font-size: 8px; } + +.font-9 { + font-size: 9px; } + +.font-10 { + font-size: 10px; } + +.font-11 { + font-size: 11px; } + +.font-12 { + font-size: 12px; } + +.font-13 { + font-size: 13px; } + +.font-14 { + font-size: 14px; } + +.font-15 { + font-size: 15px; } + +.font-16 { + font-size: 16px; } + +.font-17 { + font-size: 17px; } + +.font-18 { + font-size: 18px; } + +.font-19 { + font-size: 19px; } + +.font-20 { + font-size: 20px; } + +.font-21 { + font-size: 21px; } + +.font-22 { + font-size: 22px; } + +.font-23 { + font-size: 23px; } + +.font-24 { + font-size: 24px; } + +.font-25 { + font-size: 25px; } + +.font-26 { + font-size: 26px; } + +.font-27 { + font-size: 27px; } + +.font-28 { + font-size: 28px; } + +.font-29 { + font-size: 29px; } + +.font-30 { + font-size: 30px; } + +.font-31 { + font-size: 31px; } + +.font-32 { + font-size: 32px; } + +.font-33 { + font-size: 33px; } + +.font-34 { + font-size: 34px; } + +.font-35 { + font-size: 35px; } + +.font-36 { + font-size: 36px; } + +.font-37 { + font-size: 37px; } + +.font-38 { + font-size: 38px; } + +.font-39 { + font-size: 39px; } + +.font-40 { + font-size: 40px; } + +.font-41 { + font-size: 41px; } + +.font-42 { + font-size: 42px; } + +.font-43 { + font-size: 43px; } + +.font-44 { + font-size: 44px; } + +.font-45 { + font-size: 45px; } + +.font-46 { + font-size: 46px; } + +.font-47 { + font-size: 47px; } + +.font-48 { + font-size: 48px; } + +.font-49 { + font-size: 49px; } + +.font-50 { + font-size: 50px; } + +.align-left { + text-align: left; } + +.align-center { + text-align: center; } + +.align-right { + text-align: right; } + +.align-justify { + text-align: justify; } + +.no-resize { + resize: none; } + +.font-bold { + font-weight: bold; } + +.font-italic { + font-style: italic; } + +.font-underline { + text-decoration: underline; } + +.font-line-through { + text-decoration: line-through; } + +.font-overline { + text-decoration: overline; } + +.block-header { + margin-bottom: 15px; } +.block-header h2 { + margin: 0 !important; + color: #666 !important; + font-weight: normal; + font-size: 16px; } +.block-header h2 small { + display: block; + font-size: 12px; + margin-top: 8px; + color: #888; } +.block-header h2 small a { + font-weight: bold; + color: #777; } + +.bg-red { + background-color: #F44336 !important; + color: #fff; } +.bg-red .content .text, +.bg-red .content .number { + color: #fff !important; } + +.bg-pink { + background-color: #E91E63 !important; + color: #fff; } +.bg-pink .content .text, +.bg-pink .content .number { + color: #fff !important; } + +.bg-purple { + background-color: #9C27B0 !important; + color: #fff; } +.bg-purple .content .text, +.bg-purple .content .number { + color: #fff !important; } + +.bg-deep-purple { + background-color: #673AB7 !important; + color: #fff; } +.bg-deep-purple .content .text, +.bg-deep-purple .content .number { + color: #fff !important; } + +.bg-indigo { + background-color: #3F51B5 !important; + color: #fff; } +.bg-indigo .content .text, +.bg-indigo .content .number { + color: #fff !important; } + +.bg-blue { + background-color: #2196F3 !important; + color: #fff; } +.bg-blue .content .text, +.bg-blue .content .number { + color: #fff !important; } + +.bg-light-blue { + background-color: #03A9F4 !important; + color: #fff; } +.bg-light-blue .content .text, +.bg-light-blue .content .number { + color: #fff !important; } + +.bg-cyan { + background-color: #00BCD4 !important; + color: #fff; } +.bg-cyan .content .text, +.bg-cyan .content .number { + color: #fff !important; } + +.bg-teal { + background-color: #009688 !important; + color: #fff; } +.bg-teal .content .text, +.bg-teal .content .number { + color: #fff !important; } + +.bg-green { + background-color: #4CAF50 !important; + color: #fff; } +.bg-green .content .text, +.bg-green .content .number { + color: #fff !important; } + +.bg-light-green { + background-color: #8BC34A !important; + color: #fff; } +.bg-light-green .content .text, +.bg-light-green .content .number { + color: #fff !important; } + +.bg-lime { + background-color: #CDDC39 !important; + color: #fff; } +.bg-lime .content .text, +.bg-lime .content .number { + color: #fff !important; } + +.bg-yellow { + background-color: #ffe821 !important; + color: #fff; } +.bg-yellow .content .text, +.bg-yellow .content .number { + color: #fff !important; } + +.bg-amber { + background-color: #FFC107 !important; + color: #fff; } +.bg-amber .content .text, +.bg-amber .content .number { + color: #fff !important; } + +.bg-orange { + background-color: #FF9800 !important; + color: #fff; } +.bg-orange .content .text, +.bg-orange .content .number { + color: #fff !important; } + +.bg-deep-orange { + background-color: #FF5722 !important; + color: #fff; } +.bg-deep-orange .content .text, +.bg-deep-orange .content .number { + color: #fff !important; } + +.bg-brown { + background-color: #795548 !important; + color: #fff; } +.bg-brown .content .text, +.bg-brown .content .number { + color: #fff !important; } + +.bg-grey { + background-color: #9E9E9E !important; + color: #fff; } +.bg-grey .content .text, +.bg-grey .content .number { + color: #fff !important; } + +.bg-blue-grey { + background-color: #607D8B !important; + color: #fff; } +.bg-blue-grey .content .text, +.bg-blue-grey .content .number { + color: #fff !important; } + +.bg-black { + background-color: #000000 !important; + color: #fff; } +.bg-black .content .text, +.bg-black .content .number { + color: #fff !important; } + +.bg-white { + background-color: #ffffff !important; + color: #fff; } +.bg-white .content .text, +.bg-white .content .number { + color: #fff !important; } + +.col-red { + color: #F44336 !important; } + +.col-pink { + color: #E91E63 !important; } + +.col-purple { + color: #9C27B0 !important; } + +.col-deep-purple { + color: #673AB7 !important; } + +.col-indigo { + color: #3F51B5 !important; } + +.col-blue { + color: #2196F3 !important; } + +.col-light-blue { + color: #03A9F4 !important; } + +.col-cyan { + color: #00BCD4 !important; } + +.col-teal { + color: #009688 !important; } + +.col-green { + color: #4CAF50 !important; } + +.col-light-green { + color: #8BC34A !important; } + +.col-lime { + color: #CDDC39 !important; } + +.col-yellow { + color: #ffe821 !important; } + +.col-amber { + color: #FFC107 !important; } + +.col-orange { + color: #FF9800 !important; } + +.col-deep-orange { + color: #FF5722 !important; } + +.col-brown { + color: #795548 !important; } + +.col-grey { + color: #9E9E9E !important; } + +.col-blue-grey { + color: #607D8B !important; } + +.col-black { + color: #000000 !important; } + +.col-white { + color: #ffffff !important; } + +/* Custom Animate ============================== */ +@-ms-keyframes spin { + from { + -ms-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -o-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + to { + -ms-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@-moz-keyframes spin { + from { + -moz-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -o-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + to { + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@-webkit-keyframes spin { + from { + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -o-transform: rotate(0deg); + transform: rotate(0deg); } + to { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes spin { + from { + -moz-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -o-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + to { + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +/* Demo ======================================== */ +.demo-button-sizes .btn { + margin-bottom: 5px; } + +.icon-button-demo button { + margin-right: 5px; + margin-bottom: 12px; } + +.icon-and-text-button-demo button { + margin-right: 5px; + margin-bottom: 12px; + width: 16.66666666666667%; } + +.button-demo ul { + padding-left: 0; } +.button-demo ul li { + list-style: none; + padding-left: 0; + display: inline-block; + margin-right: 7px; } +.button-demo ul li .btn { + display: block; + min-width: 175px; } + +.button-demo .btn { + margin-right: 8px; + margin-bottom: 13px; + min-width: 120px; } + +.demo-button-groups .btn-group { + margin-right: 10px; } + +.demo-button-toolbar .btn-toolbar { + float: left; + margin-right: 25px; } + +.demo-button-nesting > .btn-group { + margin-right: 15px; } + +.demo-single-button-dropdowns > .btn-group { + margin-right: 10px; } + +.demo-splite-button-dropdowns > .btn-group { + margin-right: 10px; } + +.demo-dropup .dropup { + margin-right: 10px; } + +.demo-checkbox label, +.demo-radio-button label { + min-width: 150px; } + +.demo-knob-chart div { + margin-right: 15px; } + +.demo-switch .switch { + display: inline-block; + min-width: 170px; } + +.demo-switch .demo-switch-title { + min-width: 95px; + display: inline-block; } + +.demo-color-box { + padding: 15px 0; + text-align: center; + margin-bottom: 20px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; } +.demo-color-box .color-name { + font-size: 16px; + margin-bottom: 5px; } +.demo-color-box .color-code, +.demo-color-box .color-class-name { + font-size: 13px; } + +.demo-image-copyright { + text-align: right; + font-style: italic; + font-size: 12px; + color: #777; + margin: 5px 0 10px 0; } +.demo-image-copyright a { + font-weight: bold; + color: #555 !important; } + +.demo-tagsinput-area { + margin-bottom: 50px !important; } + +.demo-icon-container .demo-google-material-icon { + margin-bottom: 5px; + text-align: left; } +.demo-icon-container .demo-google-material-icon .icon-name { + position: relative; + top: -8px; + left: 7px; } +.demo-icon-container .demo-google-material-icon .material-icons { + width: 24px; } + +.demo-preloader .preloader { + margin-right: 10px; } + +.irs-demo { + margin-bottom: 40px; } +.irs-demo .irs { + margin-top: 15px; } + +.right-sidebar .nav-tabs + .tab-content { + padding: 0; } + +.right-sidebar p { + margin: 20px 15px 15px 15px; + font-weight: bold; + text-align: center; } + +.right-sidebar #settings .setting-list { + list-style: none; + padding-left: 0; + margin-bottom: 20px; } +.right-sidebar #settings .setting-list li { + padding: 15px; + position: relative; + border-top: 1px solid #eee; } +.right-sidebar #settings .setting-list li .switch { + position: absolute; + top: 15px; + right: 5px; } + +.demo-choose-skin { + list-style: none; + padding-left: 0; + overflow-y: hidden; } +.demo-choose-skin li { + border-bottom: 1px solid #eee; + padding: 10px 10px 4px 10px; + position: relative; + cursor: pointer; } +.demo-choose-skin li.active { + background-color: #eee; } +.demo-choose-skin li.active:after { + font-family: 'Material Icons'; + position: absolute; + top: 10px; + right: 10px; + content: '\E876'; + font-size: 18px; + font-weight: bold; } +.demo-choose-skin li:hover { + background-color: #eee; } +.demo-choose-skin li div { + width: 24px; + height: 24px; + display: inline-block; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; } +.demo-choose-skin li span { + position: relative; + bottom: 7px; + left: 5px; } +.demo-choose-skin .red { + background-color: #F44336; } +.demo-choose-skin .pink { + background-color: #E91E63; } +.demo-choose-skin .purple { + background-color: #9C27B0; } +.demo-choose-skin .deep-purple { + background-color: #673AB7; } +.demo-choose-skin .indigo { + background-color: #3F51B5; } +.demo-choose-skin .blue { + background-color: #2196F3; } +.demo-choose-skin .light-blue { + background-color: #03A9F4; } +.demo-choose-skin .cyan { + background-color: #00BCD4; } +.demo-choose-skin .teal { + background-color: #009688; } +.demo-choose-skin .green { + background-color: #4CAF50; } +.demo-choose-skin .light-green { + background-color: #8BC34A; } +.demo-choose-skin .lime { + background-color: #CDDC39; } +.demo-choose-skin .yellow { + background-color: #ffe821; } +.demo-choose-skin .amber { + background-color: #FFC107; } +.demo-choose-skin .orange { + background-color: #FF9800; } +.demo-choose-skin .deep-orange { + background-color: #FF5722; } +.demo-choose-skin .brown { + background-color: #795548; } +.demo-choose-skin .grey { + background-color: #9E9E9E; } +.demo-choose-skin .blue-grey { + background-color: #607D8B; } +.demo-choose-skin .black { + background-color: #000000; } +.demo-choose-skin .white { + background-color: #ffffff; } + +/* Materialize Css | Taken from www.materializecss.com */ +/* Media ======================================= */ +@media (max-width: 767px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: 35px; + width: 73%; } + .navbar .navbar-header { + display: none; + margin-bottom: -6px; + width: calc(100% + 30px); } + .navbar .nav > li { + display: inline-block; } + .navbar .navbar-nav { + margin-top: -10px; + margin-bottom: 1px; + margin-left: -7px; } + .navbar .navbar-nav .open .dropdown-menu { + display: none; + background-color: #fff; + position: absolute; } + .navbar .dropdown-menu { + margin-left: -50px; } + .navbar .js-right-sidebar { + margin-top: 15px; } + .navbar { + min-height:0; + } + .dt-buttons { + float: none !important; + text-align: center; + margin-bottom: 15px; } + .panel-switch-btn { + top: 12px; + right: 0 !important; } } + +@media (min-width: 768px) and (max-width: 991px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: 20px; } } + +@media (min-width: 992px) and (max-width: 1169px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: 20px; } } + +/* General ===================================== */ +body { + background-color: #e9e9e9; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -webkit-transition: all 0.5s; + transition: all 0.5s; + font-family: 'Roboto', Arial, Tahoma, sans-serif; } + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: bold; } + +button, +input, +select, +a { + outline: none !important; } + +.no-animate { + -o-transition-property: none !important; + -moz-transition-property: none !important; + -ms-transition-property: none !important; + -webkit-transition-property: none !important; + transition-property: none !important; + -o-transform: none !important; + -moz-transform: none !important; + -ms-transform: none !important; + -webkit-transform: none !important; + transform: none !important; + -webkit-animation: none !important; + -moz-animation: none !important; + -o-animation: none !important; + -ms-animation: none !important; + animation: none !important; } + +section.content { + margin: 15px 15px 0 15px; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } + +/* Dashboard =================================== */ +.dashboard-flot-chart { + height: 275px; } + +.dashboard-donut-chart { + height: 265px; } + +.dashboard-line-chart { + height: 250px; } + +.dashboard-stat-list { + list-style: none; + padding-left: 0; + margin-top: 40px; } +.dashboard-stat-list li { + padding: 16px 0 0 0; } +.dashboard-stat-list li small { + font-size: 8px; } + +.dashboard-task-infos .progress { + height: 10px; + margin-bottom: 0; + position: relative; + top: 6px; } + +/* Buttons ===================================== */ +.btn:focus { + outline: none !important; } + +.btn-circle { + border: none; + outline: none !important; + overflow: hidden; + width: 40px; + height: 40px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + -ms-border-radius: 50%; + border-radius: 50%; } +.btn-circle i { + font-size: 18px; + position: relative; + left: -1px; } + +.btn-link { + font-weight: bold; + color: #333; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } +.btn-link:active, .btn-link:focus { + text-decoration: none; + color: #333; } +.btn-link:hover { + text-decoration: none; + color: #333; + background-color: #ddd; } + +.btn-circle-lg { + border: none; + outline: none !important; + overflow: hidden; + width: 50px; + height: 50px; + -webkit-border-radius: 50% !important; + -moz-border-radius: 50% !important; + -ms-border-radius: 50% !important; + border-radius: 50% !important; } +.btn-circle-lg i { + font-size: 26px !important; + position: relative !important; + left: 0px !important; + top: 6px !important; } + +.btn:not(.btn-link):not(.btn-circle) { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.16), 0 2px 10px rgba(0, 0, 0, 0.12); + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + border-radius: 2px; + border: none; + font-size: 13px; + outline: none; } +.btn:not(.btn-link):not(.btn-circle):hover { + outline: none; } +.btn:not(.btn-link):not(.btn-circle) i { + font-size: 20px; + position: relative; + top: 3px; } +.btn:not(.btn-link):not(.btn-circle) span { + position: relative; + top: -2px; + margin-left: 3px; } + +.btn-warning, +.btn-warning:hover, +.btn-warning:active, +.btn-warning:focus { + background-color: #ff9600 !important; } + +.btn-danger, +.btn-danger:hover, +.btn-danger:active, +.btn-danger:focus { + background-color: #fb483a !important; } + +.btn-info, +.btn-info:hover, +.btn-info:active, +.btn-info:focus { + background-color: #00b0e4 !important; } + +.btn-success, +.btn-success:hover, +.btn-success:active, +.btn-success:focus { + background-color: #2b982b !important; } + +.btn-primary, +.btn-primary:hover, +.btn-primary:active, +.btn-primary:focus { + background-color: #1f91f3 !important; } + +.btn-default, +.btn-default:hover, +.btn-default:active, +.btn-default:focus { + background-color: #fff !important; } + +.btn-group, +.btn-group-vertical { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.16), 0 2px 10px rgba(0, 0, 0, 0.12); } +.btn-group .btn, +.btn-group-vertical .btn { + box-shadow: none !important; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.btn-group .btn .caret, +.btn-group-vertical .btn .caret { + position: relative; + bottom: 1px; } +.btn-group .btn-group, +.btn-group-vertical .btn-group { + box-shadow: none !important; } +.btn-group .btn + .dropdown-toggle, +.btn-group-vertical .btn + .dropdown-toggle { + border-left: 1px solid rgba(0, 0, 0, 0.08) !important; } + +/* Bootstrap Tags Input ======================== */ +.bootstrap-tagsinput { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + -ms-box-shadow: none !important; + box-shadow: none !important; + border: none !important; } + +/* noUISlider ================================== */ +.noUi-target { + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-touch-action: none; + touch-action: none; + -ms-user-select: none; + -moz-user-select: none; + user-select: none; + -moz-box-sizing: border-box; + box-sizing: border-box; + position: relative; + direction: ltr; } +.noUi-target * { + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-touch-action: none; + touch-action: none; + -ms-user-select: none; + -moz-user-select: none; + user-select: none; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.noUi-base { + width: 100%; + height: 100%; + position: relative; + z-index: 1; } + +.noUi-origin { + position: absolute; + right: 0; + top: 6px; + left: 0; + bottom: 0; } + +.noUi-handle { + position: relative; + z-index: 1; } + +.noUi-stacking .noUi-handle { + z-index: 10; } + +.noUi-state-tap .noUi-origin { + -webkit-transition: left 0.25s, top 0.25s; + transition: left 0.25s, top 0.25s; } + +.noUi-state-drag * { + cursor: inherit !important; } + +.noUi-base { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + +.noUi-horizontal { + height: 18px; } +.noUi-horizontal .noUi-handle { + width: 34px; + height: 28px; + left: -17px; + top: -6px; } + +.noUi-vertical { + width: 18px; } +.noUi-vertical .noUi-handle { + width: 28px; + height: 34px; + left: -6px; + top: -17px; } + +.noUi-background { + background: #FAFAFA; + box-shadow: inset 0 1px 1px #f0f0f0; } + +.noUi-connect { + background: #3FB8AF; + box-shadow: inset 0 0 3px rgba(51, 51, 51, 0.45); + -webkit-transition: background 450ms; + transition: background 450ms; } + +.noUi-origin { + border-radius: 2px; } + +.noUi-target { + border-radius: 4px; + border: 1px solid #D3D3D3; + box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; } +.noUi-target.noUi-connect { + box-shadow: inset 0 0 3px rgba(51, 51, 51, 0.45), 0 3px 6px -5px #BBB; } + +.noUi-dragable { + cursor: w-resize; } + +.noUi-vertical .noUi-dragable { + cursor: n-resize; } + +.noUi-handle { + border: 1px solid #D9D9D9; + border-radius: 3px; + background: #FFF; + cursor: default; + box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #EBEBEB, 0 3px 6px -3px #BBB; } + +.noUi-active { + box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #DDD, 0 3px 6px -3px #BBB; } + +.noUi-handle:before { + content: ""; + display: block; + position: absolute; + height: 14px; + width: 1px; + background: #E8E7E6; + left: 14px; + top: 6px; } + +.noUi-handle:after { + content: ""; + display: block; + position: absolute; + height: 14px; + width: 1px; + background: #E8E7E6; + left: 14px; + top: 6px; + left: 17px; } + +.noUi-vertical .noUi-handle:before { + width: 14px; + height: 1px; + left: 6px; + top: 14px; } + +.noUi-vertical .noUi-handle:after { + width: 14px; + height: 1px; + left: 6px; + top: 14px; + top: 17px; } + +[disabled].noUi-connect, [disabled] .noUi-connect { + background: #B8B8B8; } + +[disabled].noUi-origin, [disabled] .noUi-handle { + cursor: not-allowed; } + +.noUi-target { + box-shadow: none; + border: none; } + +.noUi-base { + height: 15px; + top: -6px; } + +.noUi-background { + height: 3px; + top: 6px; + background-color: #bfbfbf; + box-shadow: none; } + +.noUi-horizontal { + height: 3px; } + +.noUi-connect { + height: 3px; + top: 6px; + background-color: #26A69A; + box-shadow: none; } + +.noUi-horizontal .noUi-handle { + width: 15px; + height: 15px; + border-radius: 50%; + box-shadow: none; + background-color: #26A69A; + border: none; + left: -5px; + top: -6px; + transition: width 0.2s cubic-bezier(0.215, 0.61, 0.355, 1), height 0.2s cubic-bezier(0.215, 0.61, 0.355, 1), left 0.2s cubic-bezier(0.215, 0.61, 0.355, 1), top 0.2s cubic-bezier(0.215, 0.61, 0.355, 1); } + +.noUi-handle:before, .noUi-handle:after { + content: none; } + +.noUi-target .noUi-active.noUi-handle { + -webkit-box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + -ms-box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); } + +.noUi-target .range-label { + position: absolute; + height: 30px; + width: 30px; + top: -17px; + left: -2px; + background-color: #26A69A; + border-radius: 50%; + transition: border-radius 0.25s cubic-bezier(0.215, 0.61, 0.355, 1), transform 0.25s cubic-bezier(0.215, 0.61, 0.355, 1); + transform: scale(0.5) rotate(-45deg); + transform-origin: 50% 100%; } + +.noUi-target .noUi-active .range-label { + border-radius: 15px 15px 15px 0; + transform: rotate(-45deg) translate(23px, -25px); } + +.range-label span { + width: 100%; + text-align: center; + color: #fff; + font-size: 12px; + transform: rotate(45deg); + opacity: 0; + position: absolute; + top: 7px; + left: -1px; + transition: opacity 0.25s cubic-bezier(0.215, 0.61, 0.355, 1); } + +.noUi-active .range-label span { + opacity: 1; } + +/* Multi Select ================================ */ +.ms-container { + width: auto !important; } +.ms-container .ms-list { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + -ms-box-shadow: none !important; + box-shadow: none !important; + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + -ms-border-radius: 0 !important; + border-radius: 0 !important; } +.ms-container .ms-list.ms-focus { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + -ms-box-shadow: none !important; + box-shadow: none !important; } +.ms-container .ms-selectable, +.ms-container .ms-selection { + min-width: 250px !important; } +.ms-container .ms-selectable li.ms-hover, +.ms-container .ms-selection li.ms-hover { + color: #000000 !important; + background-color: #e6e6e6 !important; } +.ms-container .ms-selectable li.ms-elem-selectable, +.ms-container .ms-selectable li.ms-elem-selection, +.ms-container .ms-selection li.ms-elem-selectable, +.ms-container .ms-selection li.ms-elem-selection { + padding: 9px 15px 6px 15px !important; } +.ms-container .ms-optgroup-label { + padding: 5px 0 0 8px !important; } + +/* Card ======================================== */ +.card { + background: #fff; + min-height: 50px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + position: relative; + margin-bottom: 30px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + border-radius: 2px; } +.card .card-inside-title { + margin-top: 25px; + margin-bottom: 15px; + display: block; + font-size: 15px; + color: #000; } +.card .card-inside-title small { + color: #999; + display: block; + font-size: 11px; + margin-top: 5px; } +.card .card-inside-title small a { + color: #777; + font-weight: bold; } +.card .card-inside-title:first-child { + margin-top: 0; } +.card .bg-red, +.card .bg-pink, +.card .bg-purple, +.card .bg-deep-purple, +.card .bg-indigo, +.card .bg-blue, +.card .bg-light-blue, +.card .bg-cyan, +.card .bg-teal, +.card .bg-green, +.card .bg-light-green, +.card .bg-lime, +.card .bg-yellow, +.card .bg-amber, +.card .bg-orange, +.card .bg-deep-orange, +.card .bg-brown, +.card .bg-grey, +.card .bg-blue-grey, +.card .bg-black { + border-bottom: none !important; + color: #fff !important; } +.card .bg-red h2, .card .bg-red small, .card .bg-red .material-icons, +.card .bg-pink h2, +.card .bg-pink small, +.card .bg-pink .material-icons, +.card .bg-purple h2, +.card .bg-purple small, +.card .bg-purple .material-icons, +.card .bg-deep-purple h2, +.card .bg-deep-purple small, +.card .bg-deep-purple .material-icons, +.card .bg-indigo h2, +.card .bg-indigo small, +.card .bg-indigo .material-icons, +.card .bg-blue h2, +.card .bg-blue small, +.card .bg-blue .material-icons, +.card .bg-light-blue h2, +.card .bg-light-blue small, +.card .bg-light-blue .material-icons, +.card .bg-cyan h2, +.card .bg-cyan small, +.card .bg-cyan .material-icons, +.card .bg-teal h2, +.card .bg-teal small, +.card .bg-teal .material-icons, +.card .bg-green h2, +.card .bg-green small, +.card .bg-green .material-icons, +.card .bg-light-green h2, +.card .bg-light-green small, +.card .bg-light-green .material-icons, +.card .bg-lime h2, +.card .bg-lime small, +.card .bg-lime .material-icons, +.card .bg-yellow h2, +.card .bg-yellow small, +.card .bg-yellow .material-icons, +.card .bg-amber h2, +.card .bg-amber small, +.card .bg-amber .material-icons, +.card .bg-orange h2, +.card .bg-orange small, +.card .bg-orange .material-icons, +.card .bg-deep-orange h2, +.card .bg-deep-orange small, +.card .bg-deep-orange .material-icons, +.card .bg-brown h2, +.card .bg-brown small, +.card .bg-brown .material-icons, +.card .bg-grey h2, +.card .bg-grey small, +.card .bg-grey .material-icons, +.card .bg-blue-grey h2, +.card .bg-blue-grey small, +.card .bg-blue-grey .material-icons, +.card .bg-black h2, +.card .bg-black small, +.card .bg-black .material-icons { + color: #fff !important; } +.card .bg-red .badge, +.card .bg-pink .badge, +.card .bg-purple .badge, +.card .bg-deep-purple .badge, +.card .bg-indigo .badge, +.card .bg-blue .badge, +.card .bg-light-blue .badge, +.card .bg-cyan .badge, +.card .bg-teal .badge, +.card .bg-green .badge, +.card .bg-light-green .badge, +.card .bg-lime .badge, +.card .bg-yellow .badge, +.card .bg-amber .badge, +.card .bg-orange .badge, +.card .bg-deep-orange .badge, +.card .bg-brown .badge, +.card .bg-grey .badge, +.card .bg-blue-grey .badge, +.card .bg-black .badge { + background-color: #fff; + color: #555; } +.card .header { + color: #555; + padding: 10px; + margin-left: 8px; + position: relative; + border-bottom: 1px solid rgba(204, 204, 204, 0.35); } +.card .header .header-dropdown { + position: absolute; + top: 20px; + right: 15px; + list-style: none; } +.card .header .header-dropdown .dropdown-menu li { + display: block !important; } +.card .header .header-dropdown li { + display: inline-block; } +.card .header .header-dropdown i { + font-size: 20px; + color: #999; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -webkit-transition: all 0.5s; + transition: all 0.5s; } +.card .header .header-dropdown i:hover { + color: #000; } +.card .header h2 { + margin: 0; + font-size: 18px; + font-weight: normal; + color: #111; } +.card .header h2 small { + display: block; + font-size: 12px; + margin-top: 5px; + color: #999; + line-height: 15px; } +.card .header h2 small a { + font-weight: bold; + color: #777; } +.card .header .col-xs-12 h2 { + margin-top: 5px; } +.card .body { + font-size: 14px; + color: #555; + padding: 20px; } +.card .body .col-xs-1, +.card .body .col-sm-1, +.card .body .col-md-1, +.card .body .col-lg-1 { + margin-bottom: 20px; } +.card .body .col-xs-2, +.card .body .col-sm-2, +.card .body .col-md-2, +.card .body .col-lg-2 { + margin-bottom: 20px; } +.card .body .col-xs-3, +.card .body .col-sm-3, +.card .body .col-md-3, +.card .body .col-lg-3 { + margin-bottom: 20px; } +.card .body .col-xs-4, +.card .body .col-sm-4, +.card .body .col-md-4, +.card .body .col-lg-4 { + margin-bottom: 20px; } +.card .body .col-xs-5, +.card .body .col-sm-5, +.card .body .col-md-5, +.card .body .col-lg-5 { + margin-bottom: 20px; } +.card .body .col-xs-6, +.card .body .col-sm-6, +.card .body .col-md-6, +.card .body .col-lg-6 { + margin-bottom: 20px; } +.card .body .col-xs-7, +.card .body .col-sm-7, +.card .body .col-md-7, +.card .body .col-lg-7 { + margin-bottom: 20px; } +.card .body .col-xs-8, +.card .body .col-sm-8, +.card .body .col-md-8, +.card .body .col-lg-8 { + margin-bottom: 20px; } +.card .body .col-xs-9, +.card .body .col-sm-9, +.card .body .col-md-9, +.card .body .col-lg-9 { + margin-bottom: 20px; } +.card .body .col-xs-10, +.card .body .col-sm-10, +.card .body .col-md-10, +.card .body .col-lg-10 { + margin-bottom: 20px; } +.card .body .col-xs-11, +.card .body .col-sm-11, +.card .body .col-md-11, +.card .body .col-lg-11 { + margin-bottom: 20px; } +.card .body .col-xs-12, +.card .body .col-sm-12, +.card .body .col-md-12, +.card .body .col-lg-12 { + margin-bottom: 20px; } + +/* Infobox ===================================== */ +.info-box { + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + height: 80px; + display: flex; + cursor: default; + background-color: #fff; + position: relative; + overflow: hidden; + margin-bottom: 30px; } +.info-box .icon { + display: inline-block; + text-align: center; + background-color: rgba(0, 0, 0, 0.12); + width: 80px; } +.info-box .icon i { + color: #fff; + font-size: 50px; + line-height: 80px; } +.info-box .icon .chart.chart-bar { + height: 100%; + line-height: 100px; } +.info-box .icon .chart.chart-bar canvas { + vertical-align: baseline !important; } +.info-box .icon .chart.chart-pie { + height: 100%; + line-height: 123px; } +.info-box .icon .chart.chart-pie canvas { + vertical-align: baseline !important; } +.info-box .icon .chart.chart-line { + height: 100%; + line-height: 115px; } +.info-box .icon .chart.chart-line canvas { + vertical-align: baseline !important; } +.info-box .content { + display: inline-block; + padding: 7px 10px; } +.info-box .content .text { + font-size: 13px; + margin-top: 11px; + color: #555; } +.info-box .content .number { + font-weight: normal; + font-size: 26px; + margin-top: -4px; + color: #555; } + +.info-box.hover-zoom-effect .icon { + overflow: hidden; } +.info-box.hover-zoom-effect .icon i { + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; } + +.info-box.hover-zoom-effect:hover .icon i { + opacity: 0.4; + -moz-transform: rotate(-32deg) scale(1.4); + -ms-transform: rotate(-32deg) scale(1.4); + -o-transform: rotate(-32deg) scale(1.4); + -webkit-transform: rotate(-32deg) scale(1.4); + transform: rotate(-32deg) scale(1.4); } + +.info-box.hover-expand-effect:after { + background-color: rgba(0, 0, 0, 0.05); + content: "."; + position: absolute; + left: 80px; + top: 0; + width: 0; + height: 100%; + color: transparent; + -moz-transition: all 0.95s; + -o-transition: all 0.95s; + -webkit-transition: all 0.95s; + transition: all 0.95s; } + +.info-box.hover-expand-effect:hover:after { + width: 100%; } + +.info-box-2 { + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + height: 80px; + display: flex; + cursor: default; + background-color: #fff; + position: relative; + overflow: hidden; + margin-bottom: 30px; } +.info-box-2 .icon { + display: inline-block; + text-align: center; + width: 80px; } +.info-box-2 .icon i { + color: #fff; + font-size: 50px; + line-height: 80px; } +.info-box-2 .chart.chart-bar { + height: 100%; + line-height: 105px; } +.info-box-2 .chart.chart-bar canvas { + vertical-align: baseline !important; } +.info-box-2 .chart.chart-pie { + height: 100%; + line-height: 123px; } +.info-box-2 .chart.chart-pie canvas { + vertical-align: baseline !important; } +.info-box-2 .chart.chart-line { + height: 100%; + line-height: 115px; } +.info-box-2 .chart.chart-line canvas { + vertical-align: baseline !important; } +.info-box-2 .content { + display: inline-block; + padding: 7px 10px; } +.info-box-2 .content .text { + font-size: 13px; + margin-top: 11px; + color: #555; } +.info-box-2 .content .number { + font-weight: normal; + font-size: 26px; + margin-top: -4px; + color: #555; } + +.info-box-2.hover-zoom-effect .icon { + overflow: hidden; } +.info-box-2.hover-zoom-effect .icon i { + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; } + +.info-box-2.hover-zoom-effect:hover .icon i { + opacity: 0.4; + -moz-transform: rotate(-32deg) scale(1.4); + -ms-transform: rotate(-32deg) scale(1.4); + -o-transform: rotate(-32deg) scale(1.4); + -webkit-transform: rotate(-32deg) scale(1.4); + transform: rotate(-32deg) scale(1.4); } + +.info-box-2.hover-expand-effect:after { + background-color: rgba(0, 0, 0, 0.05); + content: "."; + position: absolute; + left: 0; + top: 0; + width: 0; + height: 100%; + color: transparent; + -moz-transition: all 0.95s; + -o-transition: all 0.95s; + -webkit-transition: all 0.95s; + transition: all 0.95s; } + +.info-box-2.hover-expand-effect:hover:after { + width: 100%; } + +.info-box-3 { + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + height: 80px; + display: flex; + cursor: default; + background-color: #fff; + position: relative; + overflow: hidden; + margin-bottom: 30px; } +.info-box-3 .icon { + position: absolute; + right: 10px; + bottom: 2px; + text-align: center; } +.info-box-3 .icon i { + color: rgba(0, 0, 0, 0.15); + font-size: 60px; } +.info-box-3 .chart { + margin-right: 5px; } +.info-box-3 .chart.chart-bar { + height: 100%; + line-height: 50px; } +.info-box-3 .chart.chart-bar canvas { + vertical-align: baseline !important; } +.info-box-3 .chart.chart-pie { + height: 100%; + line-height: 34px; } +.info-box-3 .chart.chart-pie canvas { + vertical-align: baseline !important; } +.info-box-3 .chart.chart-line { + height: 100%; + line-height: 40px; } +.info-box-3 .chart.chart-line canvas { + vertical-align: baseline !important; } +.info-box-3 .content { + display: inline-block; + padding: 7px 16px; } +.info-box-3 .content .text { + font-size: 13px; + margin-top: 11px; + color: #555; } +.info-box-3 .content .number { + font-weight: normal; + font-size: 26px; + margin-top: -4px; + color: #555; } + +.info-box-3.hover-zoom-effect .icon i { + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; } + +.info-box-3.hover-zoom-effect:hover .icon i { + opacity: 0.4; + -moz-transform: rotate(-32deg) scale(1.4); + -ms-transform: rotate(-32deg) scale(1.4); + -o-transform: rotate(-32deg) scale(1.4); + -webkit-transform: rotate(-32deg) scale(1.4); + transform: rotate(-32deg) scale(1.4); } + +.info-box-3.hover-expand-effect:after { + background-color: rgba(0, 0, 0, 0.05); + content: "."; + position: absolute; + left: 0; + top: 0; + width: 0; + height: 100%; + color: transparent; + -moz-transition: all 0.95s; + -o-transition: all 0.95s; + -webkit-transition: all 0.95s; + transition: all 0.95s; } + +.info-box-3.hover-expand-effect:hover:after { + width: 100%; } + +.info-box-4 { + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + height: 80px; + display: flex; + cursor: default; + background-color: #fff; + position: relative; + overflow: hidden; + margin-bottom: 30px; } +.info-box-4 .icon { + position: absolute; + right: 10px; + bottom: 2px; + text-align: center; } +.info-box-4 .icon i { + color: rgba(0, 0, 0, 0.15); + font-size: 60px; } +.info-box-4 .chart { + margin-right: 5px; } +.info-box-4 .chart.chart-bar { + height: 100%; + line-height: 50px; } +.info-box-4 .chart.chart-bar canvas { + vertical-align: baseline !important; } +.info-box-4 .chart.chart-pie { + height: 100%; + line-height: 34px; } +.info-box-4 .chart.chart-pie canvas { + vertical-align: baseline !important; } +.info-box-4 .chart.chart-line { + height: 100%; + line-height: 40px; } +.info-box-4 .chart.chart-line canvas { + vertical-align: baseline !important; } +.info-box-4 .content { + display: inline-block; + padding: 7px 16px; } +.info-box-4 .content .text { + font-size: 13px; + margin-top: 11px; + color: #555; } +.info-box-4 .content .number { + font-weight: normal; + font-size: 26px; + margin-top: -4px; + color: #555; } + +.info-box-4.hover-zoom-effect .icon i { + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; } + +.info-box-4.hover-zoom-effect:hover .icon i { + opacity: 0.4; + -moz-transform: rotate(-32deg) scale(1.4); + -ms-transform: rotate(-32deg) scale(1.4); + -o-transform: rotate(-32deg) scale(1.4); + -webkit-transform: rotate(-32deg) scale(1.4); + transform: rotate(-32deg) scale(1.4); } + +.info-box-4.hover-expand-effect:after { + background-color: rgba(0, 0, 0, 0.05); + content: "."; + position: absolute; + left: 0; + top: 0; + width: 0; + height: 100%; + color: transparent; + -moz-transition: all 0.95s; + -o-transition: all 0.95s; + -webkit-transition: all 0.95s; + transition: all 0.95s; } + +.info-box-4.hover-expand-effect:hover:after { + width: 100%; } + +/* Alerts ====================================== */ +.alert { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; + border: none; + color: #fff !important; } +.alert .alert-link { + color: #fff; + text-decoration: underline; + font-weight: bold; } + +.alert-success { + background-color: #2b982b; } + +.alert-info { + background-color: #00b0e4; } + +.alert-warning { + background-color: #ff9600 !important; } + +.alert-danger { + background-color: #fb483a !important; } + +.alert-dismissible .close { + color: #fff; + opacity: 1; + border: none; + text-shadow: none; } + +/* Dialogs (SweetAlert) ======================== */ +.sweet-alert { + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + -ms-border-radius: 0 !important; + border-radius: 0 !important; } +.sweet-alert p { + font-size: 14px !important; } +.sweet-alert .sa-input-error { + top: 23px !important; + right: 13px !important; } +.sweet-alert h2 { + font-size: 18px !important; + margin: 0 0 5px 0 !important; } +.sweet-alert button { + font-size: 15px !important; + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + -ms-border-radius: 0 !important; + border-radius: 0 !important; + padding: 5px 20px !important; } + +/* Checkbox & Radio ============================ */ +[type="checkbox"] + label { + padding-left: 26px; + height: 25px; + line-height: 21px; + font-size: 13px; + font-weight: normal; } + +[type="checkbox"]:checked + label:before { + top: -4px; + left: -2px; + width: 11px; + height: 19px; } + +[type="checkbox"]:checked.chk-col-red + label:before { + border-right: 2px solid #F44336; + border-bottom: 2px solid #F44336; } + +[type="checkbox"]:checked.chk-col-pink + label:before { + border-right: 2px solid #E91E63; + border-bottom: 2px solid #E91E63; } + +[type="checkbox"]:checked.chk-col-purple + label:before { + border-right: 2px solid #9C27B0; + border-bottom: 2px solid #9C27B0; } + +[type="checkbox"]:checked.chk-col-deep-purple + label:before { + border-right: 2px solid #673AB7; + border-bottom: 2px solid #673AB7; } + +[type="checkbox"]:checked.chk-col-indigo + label:before { + border-right: 2px solid #3F51B5; + border-bottom: 2px solid #3F51B5; } + +[type="checkbox"]:checked.chk-col-blue + label:before { + border-right: 2px solid #2196F3; + border-bottom: 2px solid #2196F3; } + +[type="checkbox"]:checked.chk-col-light-blue + label:before { + border-right: 2px solid #03A9F4; + border-bottom: 2px solid #03A9F4; } + +[type="checkbox"]:checked.chk-col-cyan + label:before { + border-right: 2px solid #00BCD4; + border-bottom: 2px solid #00BCD4; } + +[type="checkbox"]:checked.chk-col-teal + label:before { + border-right: 2px solid #009688; + border-bottom: 2px solid #009688; } + +[type="checkbox"]:checked.chk-col-green + label:before { + border-right: 2px solid #4CAF50; + border-bottom: 2px solid #4CAF50; } + +[type="checkbox"]:checked.chk-col-light-green + label:before { + border-right: 2px solid #8BC34A; + border-bottom: 2px solid #8BC34A; } + +[type="checkbox"]:checked.chk-col-lime + label:before { + border-right: 2px solid #CDDC39; + border-bottom: 2px solid #CDDC39; } + +[type="checkbox"]:checked.chk-col-yellow + label:before { + border-right: 2px solid #ffe821; + border-bottom: 2px solid #ffe821; } + +[type="checkbox"]:checked.chk-col-amber + label:before { + border-right: 2px solid #FFC107; + border-bottom: 2px solid #FFC107; } + +[type="checkbox"]:checked.chk-col-orange + label:before { + border-right: 2px solid #FF9800; + border-bottom: 2px solid #FF9800; } + +[type="checkbox"]:checked.chk-col-deep-orange + label:before { + border-right: 2px solid #FF5722; + border-bottom: 2px solid #FF5722; } + +[type="checkbox"]:checked.chk-col-brown + label:before { + border-right: 2px solid #795548; + border-bottom: 2px solid #795548; } + +[type="checkbox"]:checked.chk-col-grey + label:before { + border-right: 2px solid #9E9E9E; + border-bottom: 2px solid #9E9E9E; } + +[type="checkbox"]:checked.chk-col-blue-grey + label:before { + border-right: 2px solid #607D8B; + border-bottom: 2px solid #607D8B; } + +[type="checkbox"]:checked.chk-col-black + label:before { + border-right: 2px solid #000000; + border-bottom: 2px solid #000000; } + +[type="checkbox"]:checked.chk-col-white + label:before { + border-right: 2px solid #ffffff; + border-bottom: 2px solid #ffffff; } + +[type="checkbox"].filled-in:checked + label:after { + top: 0; + width: 20px; + height: 20px; + border: 2px solid #26a69a; + background-color: #26a69a; + z-index: 0; } + +[type="checkbox"].filled-in:checked + label:before { + border-right: 2px solid #fff !important; + border-bottom: 2px solid #fff !important; } + +[type="checkbox"].filled-in:checked.chk-col-red + label:after { + border: 2px solid #F44336; + background-color: #F44336; } + +[type="checkbox"].filled-in:checked.chk-col-pink + label:after { + border: 2px solid #E91E63; + background-color: #E91E63; } + +[type="checkbox"].filled-in:checked.chk-col-purple + label:after { + border: 2px solid #9C27B0; + background-color: #9C27B0; } + +[type="checkbox"].filled-in:checked.chk-col-deep-purple + label:after { + border: 2px solid #673AB7; + background-color: #673AB7; } + +[type="checkbox"].filled-in:checked.chk-col-indigo + label:after { + border: 2px solid #3F51B5; + background-color: #3F51B5; } + +[type="checkbox"].filled-in:checked.chk-col-blue + label:after { + border: 2px solid #2196F3; + background-color: #2196F3; } + +[type="checkbox"].filled-in:checked.chk-col-light-blue + label:after { + border: 2px solid #03A9F4; + background-color: #03A9F4; } + +[type="checkbox"].filled-in:checked.chk-col-cyan + label:after { + border: 2px solid #00BCD4; + background-color: #00BCD4; } + +[type="checkbox"].filled-in:checked.chk-col-teal + label:after { + border: 2px solid #009688; + background-color: #009688; } + +[type="checkbox"].filled-in:checked.chk-col-green + label:after { + border: 2px solid #4CAF50; + background-color: #4CAF50; } + +[type="checkbox"].filled-in:checked.chk-col-light-green + label:after { + border: 2px solid #8BC34A; + background-color: #8BC34A; } + +[type="checkbox"].filled-in:checked.chk-col-lime + label:after { + border: 2px solid #CDDC39; + background-color: #CDDC39; } + +[type="checkbox"].filled-in:checked.chk-col-yellow + label:after { + border: 2px solid #ffe821; + background-color: #ffe821; } + +[type="checkbox"].filled-in:checked.chk-col-amber + label:after { + border: 2px solid #FFC107; + background-color: #FFC107; } + +[type="checkbox"].filled-in:checked.chk-col-orange + label:after { + border: 2px solid #FF9800; + background-color: #FF9800; } + +[type="checkbox"].filled-in:checked.chk-col-deep-orange + label:after { + border: 2px solid #FF5722; + background-color: #FF5722; } + +[type="checkbox"].filled-in:checked.chk-col-brown + label:after { + border: 2px solid #795548; + background-color: #795548; } + +[type="checkbox"].filled-in:checked.chk-col-grey + label:after { + border: 2px solid #9E9E9E; + background-color: #9E9E9E; } + +[type="checkbox"].filled-in:checked.chk-col-blue-grey + label:after { + border: 2px solid #607D8B; + background-color: #607D8B; } + +[type="checkbox"].filled-in:checked.chk-col-black + label:after { + border: 2px solid #000000; + background-color: #000000; } + +[type="checkbox"].filled-in:checked.chk-col-white + label:after { + border: 2px solid #ffffff; + background-color: #ffffff; } + +[type="radio"]:not(:checked) + label { + padding-left: 26px; + height: 25px; + line-height: 25px; + font-size: 13px; + font-weight: normal; } + +[type="radio"]:checked + label { + padding-left: 26px; + height: 25px; + line-height: 25px; + font-size: 13px; + font-weight: normal; } + +[type="radio"].radio-col-red:checked + label:after { + background-color: #F44336; + border-color: #F44336; } + +[type="radio"].radio-col-pink:checked + label:after { + background-color: #E91E63; + border-color: #E91E63; } + +[type="radio"].radio-col-purple:checked + label:after { + background-color: #9C27B0; + border-color: #9C27B0; } + +[type="radio"].radio-col-deep-purple:checked + label:after { + background-color: #673AB7; + border-color: #673AB7; } + +[type="radio"].radio-col-indigo:checked + label:after { + background-color: #3F51B5; + border-color: #3F51B5; } + +[type="radio"].radio-col-blue:checked + label:after { + background-color: #2196F3; + border-color: #2196F3; } + +[type="radio"].radio-col-light-blue:checked + label:after { + background-color: #03A9F4; + border-color: #03A9F4; } + +[type="radio"].radio-col-cyan:checked + label:after { + background-color: #00BCD4; + border-color: #00BCD4; } + +[type="radio"].radio-col-teal:checked + label:after { + background-color: #009688; + border-color: #009688; } + +[type="radio"].radio-col-green:checked + label:after { + background-color: #4CAF50; + border-color: #4CAF50; } + +[type="radio"].radio-col-light-green:checked + label:after { + background-color: #8BC34A; + border-color: #8BC34A; } + +[type="radio"].radio-col-lime:checked + label:after { + background-color: #CDDC39; + border-color: #CDDC39; } + +[type="radio"].radio-col-yellow:checked + label:after { + background-color: #ffe821; + border-color: #ffe821; } + +[type="radio"].radio-col-amber:checked + label:after { + background-color: #FFC107; + border-color: #FFC107; } + +[type="radio"].radio-col-orange:checked + label:after { + background-color: #FF9800; + border-color: #FF9800; } + +[type="radio"].radio-col-deep-orange:checked + label:after { + background-color: #FF5722; + border-color: #FF5722; } + +[type="radio"].radio-col-brown:checked + label:after { + background-color: #795548; + border-color: #795548; } + +[type="radio"].radio-col-grey:checked + label:after { + background-color: #9E9E9E; + border-color: #9E9E9E; } + +[type="radio"].radio-col-blue-grey:checked + label:after { + background-color: #607D8B; + border-color: #607D8B; } + +[type="radio"].radio-col-black:checked + label:after { + background-color: #000000; + border-color: #000000; } + +[type="radio"].radio-col-white:checked + label:after { + background-color: #ffffff; + border-color: #ffffff; } + +[type="radio"].with-gap.radio-col-red:checked + label:before { + border: 2px solid #F44336; } + +[type="radio"].with-gap.radio-col-red:checked + label:after { + background-color: #F44336; + border: 2px solid #F44336; } + +[type="radio"].with-gap.radio-col-pink:checked + label:before { + border: 2px solid #E91E63; } + +[type="radio"].with-gap.radio-col-pink:checked + label:after { + background-color: #E91E63; + border: 2px solid #E91E63; } + +[type="radio"].with-gap.radio-col-purple:checked + label:before { + border: 2px solid #9C27B0; } + +[type="radio"].with-gap.radio-col-purple:checked + label:after { + background-color: #9C27B0; + border: 2px solid #9C27B0; } + +[type="radio"].with-gap.radio-col-deep-purple:checked + label:before { + border: 2px solid #673AB7; } + +[type="radio"].with-gap.radio-col-deep-purple:checked + label:after { + background-color: #673AB7; + border: 2px solid #673AB7; } + +[type="radio"].with-gap.radio-col-indigo:checked + label:before { + border: 2px solid #3F51B5; } + +[type="radio"].with-gap.radio-col-indigo:checked + label:after { + background-color: #3F51B5; + border: 2px solid #3F51B5; } + +[type="radio"].with-gap.radio-col-blue:checked + label:before { + border: 2px solid #2196F3; } + +[type="radio"].with-gap.radio-col-blue:checked + label:after { + background-color: #2196F3; + border: 2px solid #2196F3; } + +[type="radio"].with-gap.radio-col-light-blue:checked + label:before { + border: 2px solid #03A9F4; } + +[type="radio"].with-gap.radio-col-light-blue:checked + label:after { + background-color: #03A9F4; + border: 2px solid #03A9F4; } + +[type="radio"].with-gap.radio-col-cyan:checked + label:before { + border: 2px solid #00BCD4; } + +[type="radio"].with-gap.radio-col-cyan:checked + label:after { + background-color: #00BCD4; + border: 2px solid #00BCD4; } + +[type="radio"].with-gap.radio-col-teal:checked + label:before { + border: 2px solid #009688; } + +[type="radio"].with-gap.radio-col-teal:checked + label:after { + background-color: #009688; + border: 2px solid #009688; } + +[type="radio"].with-gap.radio-col-green:checked + label:before { + border: 2px solid #4CAF50; } + +[type="radio"].with-gap.radio-col-green:checked + label:after { + background-color: #4CAF50; + border: 2px solid #4CAF50; } + +[type="radio"].with-gap.radio-col-light-green:checked + label:before { + border: 2px solid #8BC34A; } + +[type="radio"].with-gap.radio-col-light-green:checked + label:after { + background-color: #8BC34A; + border: 2px solid #8BC34A; } + +[type="radio"].with-gap.radio-col-lime:checked + label:before { + border: 2px solid #CDDC39; } + +[type="radio"].with-gap.radio-col-lime:checked + label:after { + background-color: #CDDC39; + border: 2px solid #CDDC39; } + +[type="radio"].with-gap.radio-col-yellow:checked + label:before { + border: 2px solid #ffe821; } + +[type="radio"].with-gap.radio-col-yellow:checked + label:after { + background-color: #ffe821; + border: 2px solid #ffe821; } + +[type="radio"].with-gap.radio-col-amber:checked + label:before { + border: 2px solid #FFC107; } + +[type="radio"].with-gap.radio-col-amber:checked + label:after { + background-color: #FFC107; + border: 2px solid #FFC107; } + +[type="radio"].with-gap.radio-col-orange:checked + label:before { + border: 2px solid #FF9800; } + +[type="radio"].with-gap.radio-col-orange:checked + label:after { + background-color: #FF9800; + border: 2px solid #FF9800; } + +[type="radio"].with-gap.radio-col-deep-orange:checked + label:before { + border: 2px solid #FF5722; } + +[type="radio"].with-gap.radio-col-deep-orange:checked + label:after { + background-color: #FF5722; + border: 2px solid #FF5722; } + +[type="radio"].with-gap.radio-col-brown:checked + label:before { + border: 2px solid #795548; } + +[type="radio"].with-gap.radio-col-brown:checked + label:after { + background-color: #795548; + border: 2px solid #795548; } + +[type="radio"].with-gap.radio-col-grey:checked + label:before { + border: 2px solid #9E9E9E; } + +[type="radio"].with-gap.radio-col-grey:checked + label:after { + background-color: #9E9E9E; + border: 2px solid #9E9E9E; } + +[type="radio"].with-gap.radio-col-blue-grey:checked + label:before { + border: 2px solid #607D8B; } + +[type="radio"].with-gap.radio-col-blue-grey:checked + label:after { + background-color: #607D8B; + border: 2px solid #607D8B; } + +[type="radio"].with-gap.radio-col-black:checked + label:before { + border: 2px solid #000000; } + +[type="radio"].with-gap.radio-col-black:checked + label:after { + background-color: #000000; + border: 2px solid #000000; } + +[type="radio"].with-gap.radio-col-white:checked + label:before { + border: 2px solid #ffffff; } + +[type="radio"].with-gap.radio-col-white:checked + label:after { + background-color: #ffffff; + border: 2px solid #ffffff; } + +/* Switch ====================================== */ +.switch label { + font-weight: normal; + font-size: 13px; } +.switch label .lever { + margin: 0 14px; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-red:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(244, 67, 54, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-red { + background-color: rgba(244, 67, 54, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-red:after { + background-color: #F44336; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-pink:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(233, 30, 99, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-pink { + background-color: rgba(233, 30, 99, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-pink:after { + background-color: #E91E63; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-purple:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(156, 39, 176, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-purple { + background-color: rgba(156, 39, 176, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-purple:after { + background-color: #9C27B0; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-deep-purple:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(103, 58, 183, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-deep-purple { + background-color: rgba(103, 58, 183, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-deep-purple:after { + background-color: #673AB7; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-indigo:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(63, 81, 181, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-indigo { + background-color: rgba(63, 81, 181, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-indigo:after { + background-color: #3F51B5; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-blue:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(33, 150, 243, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-blue { + background-color: rgba(33, 150, 243, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-blue:after { + background-color: #2196F3; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-light-blue:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(3, 169, 244, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-light-blue { + background-color: rgba(3, 169, 244, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-light-blue:after { + background-color: #03A9F4; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-cyan:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 188, 212, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-cyan { + background-color: rgba(0, 188, 212, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-cyan:after { + background-color: #00BCD4; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-teal:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 150, 136, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-teal { + background-color: rgba(0, 150, 136, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-teal:after { + background-color: #009688; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-green:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(76, 175, 80, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-green { + background-color: rgba(76, 175, 80, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-green:after { + background-color: #4CAF50; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-light-green:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(139, 195, 74, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-light-green { + background-color: rgba(139, 195, 74, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-light-green:after { + background-color: #8BC34A; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-lime:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(205, 220, 57, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-lime { + background-color: rgba(205, 220, 57, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-lime:after { + background-color: #CDDC39; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-yellow:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(255, 232, 33, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-yellow { + background-color: rgba(255, 232, 33, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-yellow:after { + background-color: #ffe821; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-amber:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(255, 193, 7, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-amber { + background-color: rgba(255, 193, 7, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-amber:after { + background-color: #FFC107; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-orange:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(255, 152, 0, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-orange { + background-color: rgba(255, 152, 0, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-orange:after { + background-color: #FF9800; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-deep-orange:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(255, 87, 34, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-deep-orange { + background-color: rgba(255, 87, 34, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-deep-orange:after { + background-color: #FF5722; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-brown:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(121, 85, 72, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-brown { + background-color: rgba(121, 85, 72, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-brown:after { + background-color: #795548; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-grey:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(158, 158, 158, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-grey { + background-color: rgba(158, 158, 158, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-grey:after { + background-color: #9E9E9E; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-blue-grey:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(96, 125, 139, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-blue-grey { + background-color: rgba(96, 125, 139, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-blue-grey:after { + background-color: #607D8B; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-black:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(0, 0, 0, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-black { + background-color: rgba(0, 0, 0, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-black:after { + background-color: #000000; } +.switch label input[type=checkbox]:checked:not(:disabled) ~ .lever.switch-col-white:active:after { + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.4), 0 0 0 15px rgba(255, 255, 255, 0.1); } +.switch label input[type=checkbox]:checked + .lever.switch-col-white { + background-color: rgba(255, 255, 255, 0.5); } +.switch label input[type=checkbox]:checked + .lever.switch-col-white:after { + background-color: #ffffff; } + +/* DateTime Picker ============================= */ +.dtp div.dtp-date, +.dtp div.dtp-time { + background: #007d72; } + +.dtp > .dtp-content > .dtp-date-view > header.dtp-header { + background: #009688; } + +.dtp .dtp-buttons .dtp-btn-ok { + margin-left: 10px; } + +.dtp .dtp-buttons .dtp-btn-clear { + margin-right: 10px !important; } + +.dtp .p10 > a { + color: #fff; } + +.dtp div.dtp-actual-year { + font-size: 1.5em; + color: #ffffff; } + +.dtp table.dtp-picker-days tr td a.selected { + background: #007d72; + color: #fff; } + +.datepicker.datepicker-dropdown.dropdown-menu { + margin-top: 0 !important; } + +.datepicker table tr td.active { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#009688), to(#009688)); + background-image: -webkit-linear-gradient(to bottom, #009688, #009688); + background-image: -o-linear-gradient(to bottom, #009688, #009688); + background-image: linear-gradient(to bottom, #009688, #009688); + border: none; } +.datepicker table tr td.active:hover.active { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#009688), to(#009688)); + background-image: -webkit-linear-gradient(to bottom, #009688, #009688); + background-image: -o-linear-gradient(to bottom, #009688, #009688); + background-image: linear-gradient(to bottom, #009688, #009688); + border: none; } + +.datepicker table tr td.selected { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#009688), to(#009688)); + background-image: -webkit-linear-gradient(to bottom, #009688, #009688); + background-image: -o-linear-gradient(to bottom, #009688, #009688); + background-image: linear-gradient(to bottom, #009688, #009688); + border: none; } + +.datepicker table tr td span.active { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#009688), to(#009688)); + background-image: -webkit-linear-gradient(to bottom, #009688, #009688); + background-image: -o-linear-gradient(to bottom, #009688, #009688); + background-image: linear-gradient(to bottom, #009688, #009688); + border: none; } +.datepicker table tr td span.active:hover.active { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#009688), to(#009688)); + background-image: -webkit-linear-gradient(to bottom, #009688, #009688); + background-image: -o-linear-gradient(to bottom, #009688, #009688); + background-image: linear-gradient(to bottom, #009688, #009688); + border: none; } + +.datepicker table.table-condensed > tbody > tr > td { + padding: 6px 9px; } + +.input-daterange .form-control { + text-align: left; } + +.input-daterange .input-group-addon { + padding-right: 10px !important; } + +/* Bootstrap Select ============================ */ +.bootstrap-select { + box-shadow: none !important; + border-bottom: 1px solid #ddd !important; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.bootstrap-select .dropdown-toggle:focus, .bootstrap-select .dropdown-toggle:active { + outline: none !important; } +.bootstrap-select .bs-searchbox, +.bootstrap-select .bs-actionsbox, +.bootstrap-select .bs-donebutton { + padding: 0 0 5px 0; + border-bottom: 1px solid #e9e9e9; } +.bootstrap-select .bs-searchbox .form-control, +.bootstrap-select .bs-actionsbox .form-control, +.bootstrap-select .bs-donebutton .form-control { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + -ms-box-shadow: none !important; + box-shadow: none !important; + border: none; + margin-left: 30px; } +.bootstrap-select .bs-searchbox { + position: relative; } +.bootstrap-select .bs-searchbox:after { + content: '\E8B6'; + font-family: 'Material Icons'; + position: absolute; + top: 0; + left: 10px; + font-size: 25px; } +.bootstrap-select ul.dropdown-menu { + margin-top: 0 !important; } +.bootstrap-select .dropdown-menu li.selected a { + background-color: #eee !important; + color: #555 !important; } +.bootstrap-select .dropdown-menu .active a { + background-color: transparent; + color: #333 !important; } +.bootstrap-select .dropdown-menu .notify { + background-color: #F44336 !important; + color: #fff !important; + border: none !important; } + +.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark { + margin-top: 9px; } + +/* Tooltip & Popovers ========================== */ +.tooltip { + font-size: 13px; } +.tooltip .tooltip-inner { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +.popover { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + border: 1px solid rgba(0, 0, 0, 0.08); } +.popover .popover-title { + font-weight: bold; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + background-color: #e9e9e9; + border-bottom: 1px solid #ddd; } +.popover .popover-content { + font-size: 13px; + color: #777; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +/* Nav Tabs ==================================== */ +.nav-tabs { + border-bottom: 2px solid #eee; } +.nav-tabs > li { + position: relative; + top: 3px; + left: -2px; } +.nav-tabs > li > a { + border: none !important; + color: #999 !important; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.nav-tabs > li > a:hover, .nav-tabs > li > a:active, .nav-tabs > li > a:focus { + background-color: transparent !important; } +.nav-tabs > li > a:before { + content: ''; + position: absolute; + left: 0; + width: 100%; + height: 0; + border-bottom: 2px solid #2196F3; + bottom: 2px; + -moz-transform: scaleX(0); + -ms-transform: scaleX(0); + -o-transform: scaleX(0); + -webkit-transform: scaleX(0); + transform: scaleX(0); + -moz-transition: 0.1s ease-in; + -o-transition: 0.1s ease-in; + -webkit-transition: 0.1s ease-in; + transition: 0.1s ease-in; } +.nav-tabs > li > a .material-icons { + position: relative; + top: 7px; + margin-bottom: 8px; } +.nav-tabs li.active a { + color: #222 !important; } +.nav-tabs li.active a:hover, .nav-tabs li.active a:active, .nav-tabs li.active a:focus { + background-color: transparent !important; } +.nav-tabs li.active a:before { + -moz-transform: scaleX(1); + -ms-transform: scaleX(1); + -o-transform: scaleX(1); + -webkit-transform: scaleX(1); + transform: scaleX(1); } +.nav-tabs + .tab-content { + padding: 15px 0; } + +.nav-tabs.tab-col-red > li > a:before { + border-bottom: 2px solid #F44336; } + +.nav-tabs.tab-col-pink > li > a:before { + border-bottom: 2px solid #E91E63; } + +.nav-tabs.tab-col-purple > li > a:before { + border-bottom: 2px solid #9C27B0; } + +.nav-tabs.tab-col-deep-purple > li > a:before { + border-bottom: 2px solid #673AB7; } + +.nav-tabs.tab-col-indigo > li > a:before { + border-bottom: 2px solid #3F51B5; } + +.nav-tabs.tab-col-blue > li > a:before { + border-bottom: 2px solid #2196F3; } + +.nav-tabs.tab-col-light-blue > li > a:before { + border-bottom: 2px solid #03A9F4; } + +.nav-tabs.tab-col-cyan > li > a:before { + border-bottom: 2px solid #00BCD4; } + +.nav-tabs.tab-col-teal > li > a:before { + border-bottom: 2px solid #009688; } + +.nav-tabs.tab-col-green > li > a:before { + border-bottom: 2px solid #4CAF50; } + +.nav-tabs.tab-col-light-green > li > a:before { + border-bottom: 2px solid #8BC34A; } + +.nav-tabs.tab-col-lime > li > a:before { + border-bottom: 2px solid #CDDC39; } + +.nav-tabs.tab-col-yellow > li > a:before { + border-bottom: 2px solid #ffe821; } + +.nav-tabs.tab-col-amber > li > a:before { + border-bottom: 2px solid #FFC107; } + +.nav-tabs.tab-col-orange > li > a:before { + border-bottom: 2px solid #FF9800; } + +.nav-tabs.tab-col-deep-orange > li > a:before { + border-bottom: 2px solid #FF5722; } + +.nav-tabs.tab-col-brown > li > a:before { + border-bottom: 2px solid #795548; } + +.nav-tabs.tab-col-grey > li > a:before { + border-bottom: 2px solid #9E9E9E; } + +.nav-tabs.tab-col-blue-grey > li > a:before { + border-bottom: 2px solid #607D8B; } + +.nav-tabs.tab-col-black > li > a:before { + border-bottom: 2px solid #000000; } + +.nav-tabs.tab-col-white > li > a:before { + border-bottom: 2px solid #ffffff; } + +/* Thumbnails ================================== */ +.thumbnail { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.thumbnail p:not(button) { + color: #999999; + font-size: 14px; } +.thumbnail h3 { + font-weight: bold; + font-size: 17px; } + +/* Modals ====================================== */ +.modal .modal-header { + border: none; + padding: 25px 25px 5px 25px; } +.modal .modal-header .modal-title { + font-weight: bold; + font-size: 16px; } + +.modal .modal-content { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.31) !important; + border: none; } +.modal .modal-content .modal-body { + color: #777; + padding: 15px 25px; } + +.modal .modal-footer { + border: none; } + +.modal-col-red { + background-color: #F44336; } +.modal-col-red .modal-body, +.modal-col-red .modal-title { + color: #fff !important; } +.modal-col-red .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-red .modal-footer .btn-link { + color: #fff !important; } +.modal-col-red .modal-footer .btn-link:hover, .modal-col-red .modal-footer .btn-link:active, .modal-col-red .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-pink { + background-color: #E91E63; } +.modal-col-pink .modal-body, +.modal-col-pink .modal-title { + color: #fff !important; } +.modal-col-pink .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-pink .modal-footer .btn-link { + color: #fff !important; } +.modal-col-pink .modal-footer .btn-link:hover, .modal-col-pink .modal-footer .btn-link:active, .modal-col-pink .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-purple { + background-color: #9C27B0; } +.modal-col-purple .modal-body, +.modal-col-purple .modal-title { + color: #fff !important; } +.modal-col-purple .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-purple .modal-footer .btn-link { + color: #fff !important; } +.modal-col-purple .modal-footer .btn-link:hover, .modal-col-purple .modal-footer .btn-link:active, .modal-col-purple .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-deep-purple { + background-color: #673AB7; } +.modal-col-deep-purple .modal-body, +.modal-col-deep-purple .modal-title { + color: #fff !important; } +.modal-col-deep-purple .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-deep-purple .modal-footer .btn-link { + color: #fff !important; } +.modal-col-deep-purple .modal-footer .btn-link:hover, .modal-col-deep-purple .modal-footer .btn-link:active, .modal-col-deep-purple .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-indigo { + background-color: #3F51B5; } +.modal-col-indigo .modal-body, +.modal-col-indigo .modal-title { + color: #fff !important; } +.modal-col-indigo .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-indigo .modal-footer .btn-link { + color: #fff !important; } +.modal-col-indigo .modal-footer .btn-link:hover, .modal-col-indigo .modal-footer .btn-link:active, .modal-col-indigo .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-blue { + background-color: #2196F3; } +.modal-col-blue .modal-body, +.modal-col-blue .modal-title { + color: #fff !important; } +.modal-col-blue .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-blue .modal-footer .btn-link { + color: #fff !important; } +.modal-col-blue .modal-footer .btn-link:hover, .modal-col-blue .modal-footer .btn-link:active, .modal-col-blue .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-light-blue { + background-color: #03A9F4; } +.modal-col-light-blue .modal-body, +.modal-col-light-blue .modal-title { + color: #fff !important; } +.modal-col-light-blue .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-light-blue .modal-footer .btn-link { + color: #fff !important; } +.modal-col-light-blue .modal-footer .btn-link:hover, .modal-col-light-blue .modal-footer .btn-link:active, .modal-col-light-blue .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-cyan { + background-color: #00BCD4; } +.modal-col-cyan .modal-body, +.modal-col-cyan .modal-title { + color: #fff !important; } +.modal-col-cyan .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-cyan .modal-footer .btn-link { + color: #fff !important; } +.modal-col-cyan .modal-footer .btn-link:hover, .modal-col-cyan .modal-footer .btn-link:active, .modal-col-cyan .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-teal { + background-color: #009688; } +.modal-col-teal .modal-body, +.modal-col-teal .modal-title { + color: #fff !important; } +.modal-col-teal .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-teal .modal-footer .btn-link { + color: #fff !important; } +.modal-col-teal .modal-footer .btn-link:hover, .modal-col-teal .modal-footer .btn-link:active, .modal-col-teal .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-green { + background-color: #4CAF50; } +.modal-col-green .modal-body, +.modal-col-green .modal-title { + color: #fff !important; } +.modal-col-green .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-green .modal-footer .btn-link { + color: #fff !important; } +.modal-col-green .modal-footer .btn-link:hover, .modal-col-green .modal-footer .btn-link:active, .modal-col-green .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-light-green { + background-color: #8BC34A; } +.modal-col-light-green .modal-body, +.modal-col-light-green .modal-title { + color: #fff !important; } +.modal-col-light-green .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-light-green .modal-footer .btn-link { + color: #fff !important; } +.modal-col-light-green .modal-footer .btn-link:hover, .modal-col-light-green .modal-footer .btn-link:active, .modal-col-light-green .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-lime { + background-color: #CDDC39; } +.modal-col-lime .modal-body, +.modal-col-lime .modal-title { + color: #fff !important; } +.modal-col-lime .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-lime .modal-footer .btn-link { + color: #fff !important; } +.modal-col-lime .modal-footer .btn-link:hover, .modal-col-lime .modal-footer .btn-link:active, .modal-col-lime .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-yellow { + background-color: #ffe821; } +.modal-col-yellow .modal-body, +.modal-col-yellow .modal-title { + color: #fff !important; } +.modal-col-yellow .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-yellow .modal-footer .btn-link { + color: #fff !important; } +.modal-col-yellow .modal-footer .btn-link:hover, .modal-col-yellow .modal-footer .btn-link:active, .modal-col-yellow .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-amber { + background-color: #FFC107; } +.modal-col-amber .modal-body, +.modal-col-amber .modal-title { + color: #fff !important; } +.modal-col-amber .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-amber .modal-footer .btn-link { + color: #fff !important; } +.modal-col-amber .modal-footer .btn-link:hover, .modal-col-amber .modal-footer .btn-link:active, .modal-col-amber .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-orange { + background-color: #FF9800; } +.modal-col-orange .modal-body, +.modal-col-orange .modal-title { + color: #fff !important; } +.modal-col-orange .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-orange .modal-footer .btn-link { + color: #fff !important; } +.modal-col-orange .modal-footer .btn-link:hover, .modal-col-orange .modal-footer .btn-link:active, .modal-col-orange .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-deep-orange { + background-color: #FF5722; } +.modal-col-deep-orange .modal-body, +.modal-col-deep-orange .modal-title { + color: #fff !important; } +.modal-col-deep-orange .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-deep-orange .modal-footer .btn-link { + color: #fff !important; } +.modal-col-deep-orange .modal-footer .btn-link:hover, .modal-col-deep-orange .modal-footer .btn-link:active, .modal-col-deep-orange .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-brown { + background-color: #795548; } +.modal-col-brown .modal-body, +.modal-col-brown .modal-title { + color: #fff !important; } +.modal-col-brown .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-brown .modal-footer .btn-link { + color: #fff !important; } +.modal-col-brown .modal-footer .btn-link:hover, .modal-col-brown .modal-footer .btn-link:active, .modal-col-brown .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-grey { + background-color: #9E9E9E; } +.modal-col-grey .modal-body, +.modal-col-grey .modal-title { + color: #fff !important; } +.modal-col-grey .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-grey .modal-footer .btn-link { + color: #fff !important; } +.modal-col-grey .modal-footer .btn-link:hover, .modal-col-grey .modal-footer .btn-link:active, .modal-col-grey .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-blue-grey { + background-color: #607D8B; } +.modal-col-blue-grey .modal-body, +.modal-col-blue-grey .modal-title { + color: #fff !important; } +.modal-col-blue-grey .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-blue-grey .modal-footer .btn-link { + color: #fff !important; } +.modal-col-blue-grey .modal-footer .btn-link:hover, .modal-col-blue-grey .modal-footer .btn-link:active, .modal-col-blue-grey .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-black { + background-color: #000000; } +.modal-col-black .modal-body, +.modal-col-black .modal-title { + color: #fff !important; } +.modal-col-black .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-black .modal-footer .btn-link { + color: #fff !important; } +.modal-col-black .modal-footer .btn-link:hover, .modal-col-black .modal-footer .btn-link:active, .modal-col-black .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +.modal-col-white { + background-color: #ffffff; } +.modal-col-white .modal-body, +.modal-col-white .modal-title { + color: #fff !important; } +.modal-col-white .modal-footer { + background-color: rgba(0, 0, 0, 0.12); } +.modal-col-white .modal-footer .btn-link { + color: #fff !important; } +.modal-col-white .modal-footer .btn-link:hover, .modal-col-white .modal-footer .btn-link:active, .modal-col-white .modal-footer .btn-link:focus { + background-color: rgba(0, 0, 0, 0.12); } + +/* Labels ====================================== */ +.label { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +.label-primary { + background-color: #1f91f3; } + +.label-success { + background-color: #2b982b; } + +.label-info { + background-color: #00b0e4; } + +.label-warning { + background-color: #ff9600; } + +.label-danger { + background-color: #fb483a; } + +/* Collapse ==================================== */ +.collapse .well, +.collapse.in .well, +.collapsing .well { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + margin-bottom: 0; } + +/* Tables ====================================== */ +.table tbody tr td, .table tbody tr th { + padding: 10px; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; } + +.table tbody tr.primary td, .table tbody tr.primary th { + background-color: #1f91f3; + color: #fff; } + +.table tbody tr.success td, .table tbody tr.success th { + background-color: #2b982b; + color: #fff; } + +.table tbody tr.info td, .table tbody tr.info th { + background-color: #00b0e4; + color: #fff; } + +.table tbody tr.warning td, .table tbody tr.warning th { + background-color: #ff9600; + color: #fff; } + +.table tbody tr.danger td, .table tbody tr.danger th { + background-color: #fb483a; + color: #fff; } + +.table thead tr th { + padding: 10px; + border-bottom: 1px solid #eee; } + +.table-bordered { + border-top: 1px solid #eee; } +.table-bordered tbody tr td, .table-bordered tbody tr th { + padding: 10px; + border: 1px solid #eee; } +.table-bordered thead tr th { + padding: 10px; + border: 1px solid #eee; } + +/* Panel ======================================= */ +.panel-group .panel-col-red { + border: 1px solid #F44336; } +.panel-group .panel-col-red .panel-title { + background-color: #F44336 !important; + color: #fff; } +.panel-group .panel-col-red .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-pink { + border: 1px solid #E91E63; } +.panel-group .panel-col-pink .panel-title { + background-color: #E91E63 !important; + color: #fff; } +.panel-group .panel-col-pink .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-purple { + border: 1px solid #9C27B0; } +.panel-group .panel-col-purple .panel-title { + background-color: #9C27B0 !important; + color: #fff; } +.panel-group .panel-col-purple .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-deep-purple { + border: 1px solid #673AB7; } +.panel-group .panel-col-deep-purple .panel-title { + background-color: #673AB7 !important; + color: #fff; } +.panel-group .panel-col-deep-purple .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-indigo { + border: 1px solid #3F51B5; } +.panel-group .panel-col-indigo .panel-title { + background-color: #3F51B5 !important; + color: #fff; } +.panel-group .panel-col-indigo .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-blue { + border: 1px solid #2196F3; } +.panel-group .panel-col-blue .panel-title { + background-color: #2196F3 !important; + color: #fff; } +.panel-group .panel-col-blue .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-light-blue { + border: 1px solid #03A9F4; } +.panel-group .panel-col-light-blue .panel-title { + background-color: #03A9F4 !important; + color: #fff; } +.panel-group .panel-col-light-blue .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-cyan { + border: 1px solid #00BCD4; } +.panel-group .panel-col-cyan .panel-title { + background-color: #00BCD4 !important; + color: #fff; } +.panel-group .panel-col-cyan .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-teal { + border: 1px solid #009688; } +.panel-group .panel-col-teal .panel-title { + background-color: #009688 !important; + color: #fff; } +.panel-group .panel-col-teal .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-green { + border: 1px solid #4CAF50; } +.panel-group .panel-col-green .panel-title { + background-color: #4CAF50 !important; + color: #fff; } +.panel-group .panel-col-green .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-light-green { + border: 1px solid #8BC34A; } +.panel-group .panel-col-light-green .panel-title { + background-color: #8BC34A !important; + color: #fff; } +.panel-group .panel-col-light-green .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-lime { + border: 1px solid #CDDC39; } +.panel-group .panel-col-lime .panel-title { + background-color: #CDDC39 !important; + color: #fff; } +.panel-group .panel-col-lime .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-yellow { + border: 1px solid #ffe821; } +.panel-group .panel-col-yellow .panel-title { + background-color: #ffe821 !important; + color: #fff; } +.panel-group .panel-col-yellow .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-amber { + border: 1px solid #FFC107; } +.panel-group .panel-col-amber .panel-title { + background-color: #FFC107 !important; + color: #fff; } +.panel-group .panel-col-amber .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-orange { + border: 1px solid #FF9800; } +.panel-group .panel-col-orange .panel-title { + background-color: #FF9800 !important; + color: #fff; } +.panel-group .panel-col-orange .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-deep-orange { + border: 1px solid #FF5722; } +.panel-group .panel-col-deep-orange .panel-title { + background-color: #FF5722 !important; + color: #fff; } +.panel-group .panel-col-deep-orange .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-brown { + border: 1px solid #795548; } +.panel-group .panel-col-brown .panel-title { + background-color: #795548 !important; + color: #fff; } +.panel-group .panel-col-brown .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-grey { + border: 1px solid #9E9E9E; } +.panel-group .panel-col-grey .panel-title { + background-color: #9E9E9E !important; + color: #fff; } +.panel-group .panel-col-grey .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-blue-grey { + border: 1px solid #607D8B; } +.panel-group .panel-col-blue-grey .panel-title { + background-color: #607D8B !important; + color: #fff; } +.panel-group .panel-col-blue-grey .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-black { + border: 1px solid #000000; } +.panel-group .panel-col-black .panel-title { + background-color: #000000 !important; + color: #fff; } +.panel-group .panel-col-black .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel-col-white { + border: 1px solid #ffffff; } +.panel-group .panel-col-white .panel-title { + background-color: #ffffff !important; + color: #fff; } +.panel-group .panel-col-white .panel-body { + border-top-color: transparent !important; } + +.panel-group .panel { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.panel-group .panel .panel-title .material-icons { + float: left; + line-height: 16px; + margin-right: 8px; } +.panel-group .panel .panel-heading { + padding: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.panel-group .panel .panel-heading a { + display: block; + padding: 10px 15px; } +.panel-group .panel .panel-heading a:hover, .panel-group .panel .panel-heading a:focus, .panel-group .panel .panel-heading a:active { + text-decoration: none; } +.panel-group .panel .panel-body { + color: #555; } + +.panel-group .panel-primary { + border: 1px solid #1f91f3; } +.panel-group .panel-primary .panel-title { + background-color: #1f91f3; } + +.panel-group .panel-success { + border: 1px solid #2b982b; } +.panel-group .panel-success .panel-title { + background-color: #2b982b; + color: #fff; } + +.panel-group .panel-warning { + border: 1px solid #ff9600; } +.panel-group .panel-warning .panel-title { + background-color: #ff9600; + color: #fff; } + +.panel-group .panel-danger { + border: 1px solid #fb483a; } +.panel-group .panel-danger .panel-title { + background-color: #fb483a; + color: #fff; } + +.full-body .panel-col-red .panel-body { + border-top-color: #fff !important; + background-color: #F44336; + color: #fff; } + +.full-body .panel-col-pink .panel-body { + border-top-color: #fff !important; + background-color: #E91E63; + color: #fff; } + +.full-body .panel-col-purple .panel-body { + border-top-color: #fff !important; + background-color: #9C27B0; + color: #fff; } + +.full-body .panel-col-deep-purple .panel-body { + border-top-color: #fff !important; + background-color: #673AB7; + color: #fff; } + +.full-body .panel-col-indigo .panel-body { + border-top-color: #fff !important; + background-color: #3F51B5; + color: #fff; } + +.full-body .panel-col-blue .panel-body { + border-top-color: #fff !important; + background-color: #2196F3; + color: #fff; } + +.full-body .panel-col-light-blue .panel-body { + border-top-color: #fff !important; + background-color: #03A9F4; + color: #fff; } + +.full-body .panel-col-cyan .panel-body { + border-top-color: #fff !important; + background-color: #00BCD4; + color: #fff; } + +.full-body .panel-col-teal .panel-body { + border-top-color: #fff !important; + background-color: #009688; + color: #fff; } + +.full-body .panel-col-green .panel-body { + border-top-color: #fff !important; + background-color: #4CAF50; + color: #fff; } + +.full-body .panel-col-light-green .panel-body { + border-top-color: #fff !important; + background-color: #8BC34A; + color: #fff; } + +.full-body .panel-col-lime .panel-body { + border-top-color: #fff !important; + background-color: #CDDC39; + color: #fff; } + +.full-body .panel-col-yellow .panel-body { + border-top-color: #fff !important; + background-color: #ffe821; + color: #fff; } + +.full-body .panel-col-amber .panel-body { + border-top-color: #fff !important; + background-color: #FFC107; + color: #fff; } + +.full-body .panel-col-orange .panel-body { + border-top-color: #fff !important; + background-color: #FF9800; + color: #fff; } + +.full-body .panel-col-deep-orange .panel-body { + border-top-color: #fff !important; + background-color: #FF5722; + color: #fff; } + +.full-body .panel-col-brown .panel-body { + border-top-color: #fff !important; + background-color: #795548; + color: #fff; } + +.full-body .panel-col-grey .panel-body { + border-top-color: #fff !important; + background-color: #9E9E9E; + color: #fff; } + +.full-body .panel-col-blue-grey .panel-body { + border-top-color: #fff !important; + background-color: #607D8B; + color: #fff; } + +.full-body .panel-col-black .panel-body { + border-top-color: #fff !important; + background-color: #000000; + color: #fff; } + +.full-body .panel-col-white .panel-body { + border-top-color: #fff !important; + background-color: #ffffff; + color: #fff; } + +.full-body .panel-primary .panel-body { + border-top-color: #fff !important; + background-color: #1f91f3; + color: #fff; } + +.full-body .panel-success .panel-body { + border-top-color: #fff !important; + background-color: #2b982b; + color: #fff; } + +.full-body .panel-warning .panel-body { + border-top-color: #fff !important; + background-color: #ff9600; + color: #fff; } + +.full-body .panel-danger .panel-body { + border-top-color: #fff !important; + background-color: #fb483a; + color: #fff; } + +/* Progress Bars =============================== */ +.progress { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + height: 22px; } +.progress .progress-bar { + line-height: 23px; + background-color: #1f91f3; } +.progress .progress-bar-success { + background-color: #2b982b; } +.progress .progress-bar-info { + background-color: #00b0e4; } +.progress .progress-bar-warning { + background-color: #ff9600; } +.progress .progress-bar-danger { + background-color: #fb483a; } + +/* Ion Range Slider ============================ */ +.irs .irs-min, +.irs .irs-max, +.irs .irs-from, +.irs .irs-to, +.irs .irs-single { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +/* Input Group ================================= */ +.input-group { + width: 100%; + margin-bottom: 20px; } +.input-group .form-line { + display: inline-block; + width: 100%; + border-bottom: 1px solid #ddd; + position: relative; } +.input-group .form-line:after { + content: ''; + position: absolute; + left: 0; + width: 100%; + bottom: -2px; + -moz-transform: scaleX(0); + -ms-transform: scaleX(0); + -o-transform: scaleX(0); + -webkit-transform: scaleX(0); + transform: scaleX(0); + -moz-transition: 0.25s ease-in; + -o-transition: 0.25s ease-in; + -webkit-transition: 0.25s ease-in; + transition: 0.25s ease-in; + border-bottom: 2px solid #1f91f3; } +.input-group .form-line + .input-group-addon { + padding-right: 0; + padding-left: 10px; } +.input-group .help-info { + float: right; + font-size: 12px; + margin-top: 5px; + color: #999; } +.input-group label.error { + font-size: 12px; + display: block; + margin-top: 5px; + font-weight: normal; + color: #F44336; } +.input-group .form-line.error:after { + border-bottom: 2px solid #F44336; } +.input-group .form-line.success:after { + border-bottom: 2px solid #4CAF50; } +.input-group .form-line.warning:after { + border-bottom: 2px solid #FFC107; } +.input-group .form-line.focused:after { + -moz-transform: scaleX(1); + -ms-transform: scaleX(1); + -o-transform: scaleX(1); + -webkit-transform: scaleX(1); + transform: scaleX(1); } +.input-group .form-line.focused .form-label { + bottom: 25px; + left: 0; + font-size: 12px; } +.input-group .input-group-addon { + border: none; + background-color: transparent; + padding-left: 0; + font-weight: bold; } +.input-group .input-group-addon .material-icons { + font-size: 18px; + color: #555; } +.input-group input[type="text"], +.input-group .form-control { + border: none; + box-shadow: none; + padding-left: 0; } +.input-group .form-control:focus { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + -ms-box-shadow: none !important; + box-shadow: none !important; } + +.input-group.input-group-sm .input-group-addon i { + font-size: 14px; } + +.input-group.input-group-sm .form-control { + font-size: 12px; } + +.input-group.input-group-lg .input-group-addon i { + font-size: 26px; } + +.input-group.input-group-lg .form-control { + font-size: 18px; } + +.form-control-label { + text-align: right; } +.form-control-label label { + margin-top: 8px; } + +.form-horizontal .form-group { + margin-bottom: 0; } + +.form-group { + width: 100%; + margin-bottom: 25px; } +.form-group .form-control { + width: 100%; + border: none; + box-shadow: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + padding-left: 0; } +.form-group .help-info { + float: right; + font-size: 12px; + margin-top: 5px; + color: #999; } +.form-group label.error { + font-size: 12px; + display: block; + margin-top: 5px; + font-weight: normal; + color: #F44336; } +.form-group .form-line { + width: 100%; + position: relative; + border-bottom: 1px solid #ddd; } +.form-group .form-line:after { + content: ''; + position: absolute; + left: 0; + width: 100%; + height: 0; + bottom: -1px; + -moz-transform: scaleX(0); + -ms-transform: scaleX(0); + -o-transform: scaleX(0); + -webkit-transform: scaleX(0); + transform: scaleX(0); + -moz-transition: 0.25s ease-in; + -o-transition: 0.25s ease-in; + -webkit-transition: 0.25s ease-in; + transition: 0.25s ease-in; + border-bottom: 2px solid #1f91f3; } +.form-group .form-line .form-label { + font-weight: normal; + color: #aaa; + position: absolute; + top: 10px; + left: 0; + cursor: text; + -moz-transition: 0.2s; + -o-transition: 0.2s; + -webkit-transition: 0.2s; + transition: 0.2s; } +.form-group .form-line.error:after { + border-bottom: 2px solid #F44336; } +.form-group .form-line.success:after { + border-bottom: 2px solid #4CAF50; } +.form-group .form-line.warning:after { + border-bottom: 2px solid #FFC107; } +.form-group .form-line.focused:after { + -moz-transform: scaleX(1); + -ms-transform: scaleX(1); + -o-transform: scaleX(1); + -webkit-transform: scaleX(1); + transform: scaleX(1); } +.form-group .form-line.focused .form-label { + top: -10px; + left: 0; + font-size: 12px; } + +.form-group-sm .form-label { + font-size: 12px; } + +.form-group-sm .form-line.focused .form-label { + bottom: 20px; + font-size: 10px; } + +.form-group-lg .form-label { + font-size: 18px; } + +.form-group-lg .form-line.focused .form-label { + bottom: 35px; + font-size: 12px; } + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: transparent; } + +/* Color Picker ================================ */ +.colorpicker { + z-index: 1; } +.colorpicker:before, .colorpicker:after { + display: none !important; } + +/* Dropzone ==================================== */ +.dropzone { + border: 2px solid transparent !important; + background-color: #eee !important; } +.dropzone .dz-message .drag-icon-cph .material-icons { + font-size: 80px; + color: #777; } + +.dz-drag-hover { + border: 2px dashed #888 !important; } + +/* Breadcrumbs ================================= */ +.breadcrumb { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + background-color: transparent; + font-size: 13px; + margin-bottom: 10px; } +.breadcrumb li a { + color: #444; + text-decoration: none; } +.breadcrumb li a .material-icons { + font-size: 18px; + position: relative; + top: 4px; } +.breadcrumb li .material-icons { + font-size: 18px; + position: relative; + top: 4px; } +.breadcrumb > li + li:before { + content: '>\00a0'; } + +.breadcrumb-col-red li a { + color: #F44336 !important; + font-weight: bold; } + +.breadcrumb-bg-red { + background-color: #F44336 !important; } +.breadcrumb-bg-red li { + color: #fff !important; } +.breadcrumb-bg-red li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-red li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-red li + li:before { + color: #fff; } + +.breadcrumb-col-pink li a { + color: #E91E63 !important; + font-weight: bold; } + +.breadcrumb-bg-pink { + background-color: #E91E63 !important; } +.breadcrumb-bg-pink li { + color: #fff !important; } +.breadcrumb-bg-pink li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-pink li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-pink li + li:before { + color: #fff; } + +.breadcrumb-col-purple li a { + color: #9C27B0 !important; + font-weight: bold; } + +.breadcrumb-bg-purple { + background-color: #9C27B0 !important; } +.breadcrumb-bg-purple li { + color: #fff !important; } +.breadcrumb-bg-purple li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-purple li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-purple li + li:before { + color: #fff; } + +.breadcrumb-col-deep-purple li a { + color: #673AB7 !important; + font-weight: bold; } + +.breadcrumb-bg-deep-purple { + background-color: #673AB7 !important; } +.breadcrumb-bg-deep-purple li { + color: #fff !important; } +.breadcrumb-bg-deep-purple li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-deep-purple li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-deep-purple li + li:before { + color: #fff; } + +.breadcrumb-col-indigo li a { + color: #3F51B5 !important; + font-weight: bold; } + +.breadcrumb-bg-indigo { + background-color: #3F51B5 !important; } +.breadcrumb-bg-indigo li { + color: #fff !important; } +.breadcrumb-bg-indigo li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-indigo li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-indigo li + li:before { + color: #fff; } + +.breadcrumb-col-blue li a { + color: #2196F3 !important; + font-weight: bold; } + +.breadcrumb-bg-blue { + background-color: #2196F3 !important; } +.breadcrumb-bg-blue li { + color: #fff !important; } +.breadcrumb-bg-blue li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-blue li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-blue li + li:before { + color: #fff; } + +.breadcrumb-col-light-blue li a { + color: #03A9F4 !important; + font-weight: bold; } + +.breadcrumb-bg-light-blue { + background-color: #03A9F4 !important; } +.breadcrumb-bg-light-blue li { + color: #fff !important; } +.breadcrumb-bg-light-blue li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-light-blue li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-light-blue li + li:before { + color: #fff; } + +.breadcrumb-col-cyan li a { + color: #00BCD4 !important; + font-weight: bold; } + +.breadcrumb-bg-cyan { + background-color: #00BCD4 !important; } +.breadcrumb-bg-cyan li { + color: #fff !important; } +.breadcrumb-bg-cyan li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-cyan li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-cyan li + li:before { + color: #fff; } + +.breadcrumb-col-teal li a { + color: #009688 !important; + font-weight: bold; } + +.breadcrumb-bg-teal { + background-color: #009688 !important; } +.breadcrumb-bg-teal li { + color: #fff !important; } +.breadcrumb-bg-teal li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-teal li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-teal li + li:before { + color: #fff; } + +.breadcrumb-col-green li a { + color: #4CAF50 !important; + font-weight: bold; } + +.breadcrumb-bg-green { + background-color: #4CAF50 !important; } +.breadcrumb-bg-green li { + color: #fff !important; } +.breadcrumb-bg-green li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-green li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-green li + li:before { + color: #fff; } + +.breadcrumb-col-light-green li a { + color: #8BC34A !important; + font-weight: bold; } + +.breadcrumb-bg-light-green { + background-color: #8BC34A !important; } +.breadcrumb-bg-light-green li { + color: #fff !important; } +.breadcrumb-bg-light-green li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-light-green li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-light-green li + li:before { + color: #fff; } + +.breadcrumb-col-lime li a { + color: #CDDC39 !important; + font-weight: bold; } + +.breadcrumb-bg-lime { + background-color: #CDDC39 !important; } +.breadcrumb-bg-lime li { + color: #fff !important; } +.breadcrumb-bg-lime li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-lime li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-lime li + li:before { + color: #fff; } + +.breadcrumb-col-yellow li a { + color: #ffe821 !important; + font-weight: bold; } + +.breadcrumb-bg-yellow { + background-color: #ffe821 !important; } +.breadcrumb-bg-yellow li { + color: #fff !important; } +.breadcrumb-bg-yellow li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-yellow li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-yellow li + li:before { + color: #fff; } + +.breadcrumb-col-amber li a { + color: #FFC107 !important; + font-weight: bold; } + +.breadcrumb-bg-amber { + background-color: #FFC107 !important; } +.breadcrumb-bg-amber li { + color: #fff !important; } +.breadcrumb-bg-amber li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-amber li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-amber li + li:before { + color: #fff; } + +.breadcrumb-col-orange li a { + color: #FF9800 !important; + font-weight: bold; } + +.breadcrumb-bg-orange { + background-color: #FF9800 !important; } +.breadcrumb-bg-orange li { + color: #fff !important; } +.breadcrumb-bg-orange li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-orange li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-orange li + li:before { + color: #fff; } + +.breadcrumb-col-deep-orange li a { + color: #FF5722 !important; + font-weight: bold; } + +.breadcrumb-bg-deep-orange { + background-color: #FF5722 !important; } +.breadcrumb-bg-deep-orange li { + color: #fff !important; } +.breadcrumb-bg-deep-orange li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-deep-orange li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-deep-orange li + li:before { + color: #fff; } + +.breadcrumb-col-brown li a { + color: #795548 !important; + font-weight: bold; } + +.breadcrumb-bg-brown { + background-color: #795548 !important; } +.breadcrumb-bg-brown li { + color: #fff !important; } +.breadcrumb-bg-brown li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-brown li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-brown li + li:before { + color: #fff; } + +.breadcrumb-col-grey li a { + color: #9E9E9E !important; + font-weight: bold; } + +.breadcrumb-bg-grey { + background-color: #9E9E9E !important; } +.breadcrumb-bg-grey li { + color: #fff !important; } +.breadcrumb-bg-grey li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-grey li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-grey li + li:before { + color: #fff; } + +.breadcrumb-col-blue-grey li a { + color: #607D8B !important; + font-weight: bold; } + +.breadcrumb-bg-blue-grey { + background-color: #607D8B !important; } +.breadcrumb-bg-blue-grey li { + color: #fff !important; } +.breadcrumb-bg-blue-grey li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-blue-grey li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-blue-grey li + li:before { + color: #fff; } + +.breadcrumb-col-black li a { + color: #000000 !important; + font-weight: bold; } + +.breadcrumb-bg-black { + background-color: #000000 !important; } +.breadcrumb-bg-black li { + color: #fff !important; } +.breadcrumb-bg-black li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-black li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-black li + li:before { + color: #fff; } + +.breadcrumb-col-white li a { + color: #ffffff !important; + font-weight: bold; } + +.breadcrumb-bg-white { + background-color: #ffffff !important; } +.breadcrumb-bg-white li { + color: #fff !important; } +.breadcrumb-bg-white li a { + color: #fff; + font-weight: bold; } +.breadcrumb-bg-white li a .material-icons { + padding-bottom: 8px; } +.breadcrumb-bg-white li + li:before { + color: #fff; } + +/* Badge | List Group Item ===================== */ +.badge { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + border-radius: 2px; } + +.list-group-item { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } + +.list-group .active { + background-color: #2196F3; + border-color: #2196F3; } +.list-group .active:hover, .list-group .active:focus, .list-group .active:active { + background-color: #2196F3; + border-color: #2196F3; } +.list-group .active .list-group-item-text { + color: #dfe9f1; + font-size: 13px; } +.list-group .active .list-group-item-text:hover, .list-group .active .list-group-item-text:active, .list-group .active .list-group-item-text:focus { + color: #dfe9f1; } + +.list-group .list-group-item.active:hover .list-group-item-text, .list-group .list-group-item.active:focus .list-group-item-text, .list-group .list-group-item.active:active .list-group-item-text { + color: #dfe9f1; } + +.list-group .list-group-item:first-child, .list-group .list-group-item:last-child { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +.list-group .list-group-item .list-group-item-heading { + font-weight: bold; + font-size: 17px; } + +.list-group .list-group-item-success { + background-color: #2b982b; + border: none; + color: #fff; } +.list-group .list-group-item-success:hover, .list-group .list-group-item-success:focus { + background-color: #2b982b; + color: #fff; + opacity: 0.8; } + +.list-group .list-group-item-info { + background-color: #00b0e4; + border: none; + color: #fff; } +.list-group .list-group-item-info:hover, .list-group .list-group-item-info:focus { + background-color: #00b0e4; + color: #fff; + opacity: 0.8; } + +.list-group .list-group-item-warning { + background-color: #ff9600; + border: none; + color: #fff; } +.list-group .list-group-item-warning:hover, .list-group .list-group-item-warning:focus { + background-color: #ff9600; + color: #fff; + opacity: 0.8; } + +.list-group .list-group-item-danger { + background-color: #fb483a; + border: none; + color: #fff; } +.list-group .list-group-item-danger:hover, .list-group .list-group-item-danger:focus { + background-color: #fb483a; + color: #fff; + opacity: 0.8; } + +.list-group .pl-red { + stroke: #F44336; } + +.list-group .list-group-bg-red { + background-color: #F44336; + border: none; + color: #fff; } +.list-group .list-group-bg-red:hover, .list-group .list-group-bg-red:focus { + background-color: #F44336; + color: #fff; + opacity: 0.8; } + +.list-group .pl-pink { + stroke: #E91E63; } + +.list-group .list-group-bg-pink { + background-color: #E91E63; + border: none; + color: #fff; } +.list-group .list-group-bg-pink:hover, .list-group .list-group-bg-pink:focus { + background-color: #E91E63; + color: #fff; + opacity: 0.8; } + +.list-group .pl-purple { + stroke: #9C27B0; } + +.list-group .list-group-bg-purple { + background-color: #9C27B0; + border: none; + color: #fff; } +.list-group .list-group-bg-purple:hover, .list-group .list-group-bg-purple:focus { + background-color: #9C27B0; + color: #fff; + opacity: 0.8; } + +.list-group .pl-deep-purple { + stroke: #673AB7; } + +.list-group .list-group-bg-deep-purple { + background-color: #673AB7; + border: none; + color: #fff; } +.list-group .list-group-bg-deep-purple:hover, .list-group .list-group-bg-deep-purple:focus { + background-color: #673AB7; + color: #fff; + opacity: 0.8; } + +.list-group .pl-indigo { + stroke: #3F51B5; } + +.list-group .list-group-bg-indigo { + background-color: #3F51B5; + border: none; + color: #fff; } +.list-group .list-group-bg-indigo:hover, .list-group .list-group-bg-indigo:focus { + background-color: #3F51B5; + color: #fff; + opacity: 0.8; } + +.list-group .pl-blue { + stroke: #2196F3; } + +.list-group .list-group-bg-blue { + background-color: #2196F3; + border: none; + color: #fff; } +.list-group .list-group-bg-blue:hover, .list-group .list-group-bg-blue:focus { + background-color: #2196F3; + color: #fff; + opacity: 0.8; } + +.list-group .pl-light-blue { + stroke: #03A9F4; } + +.list-group .list-group-bg-light-blue { + background-color: #03A9F4; + border: none; + color: #fff; } +.list-group .list-group-bg-light-blue:hover, .list-group .list-group-bg-light-blue:focus { + background-color: #03A9F4; + color: #fff; + opacity: 0.8; } + +.list-group .pl-cyan { + stroke: #00BCD4; } + +.list-group .list-group-bg-cyan { + background-color: #00BCD4; + border: none; + color: #fff; } +.list-group .list-group-bg-cyan:hover, .list-group .list-group-bg-cyan:focus { + background-color: #00BCD4; + color: #fff; + opacity: 0.8; } + +.list-group .pl-teal { + stroke: #009688; } + +.list-group .list-group-bg-teal { + background-color: #009688; + border: none; + color: #fff; } +.list-group .list-group-bg-teal:hover, .list-group .list-group-bg-teal:focus { + background-color: #009688; + color: #fff; + opacity: 0.8; } + +.list-group .pl-green { + stroke: #4CAF50; } + +.list-group .list-group-bg-green { + background-color: #4CAF50; + border: none; + color: #fff; } +.list-group .list-group-bg-green:hover, .list-group .list-group-bg-green:focus { + background-color: #4CAF50; + color: #fff; + opacity: 0.8; } + +.list-group .pl-light-green { + stroke: #8BC34A; } + +.list-group .list-group-bg-light-green { + background-color: #8BC34A; + border: none; + color: #fff; } +.list-group .list-group-bg-light-green:hover, .list-group .list-group-bg-light-green:focus { + background-color: #8BC34A; + color: #fff; + opacity: 0.8; } + +.list-group .pl-lime { + stroke: #CDDC39; } + +.list-group .list-group-bg-lime { + background-color: #CDDC39; + border: none; + color: #fff; } +.list-group .list-group-bg-lime:hover, .list-group .list-group-bg-lime:focus { + background-color: #CDDC39; + color: #fff; + opacity: 0.8; } + +.list-group .pl-yellow { + stroke: #ffe821; } + +.list-group .list-group-bg-yellow { + background-color: #ffe821; + border: none; + color: #fff; } +.list-group .list-group-bg-yellow:hover, .list-group .list-group-bg-yellow:focus { + background-color: #ffe821; + color: #fff; + opacity: 0.8; } + +.list-group .pl-amber { + stroke: #FFC107; } + +.list-group .list-group-bg-amber { + background-color: #FFC107; + border: none; + color: #fff; } +.list-group .list-group-bg-amber:hover, .list-group .list-group-bg-amber:focus { + background-color: #FFC107; + color: #fff; + opacity: 0.8; } + +.list-group .pl-orange { + stroke: #FF9800; } + +.list-group .list-group-bg-orange { + background-color: #FF9800; + border: none; + color: #fff; } +.list-group .list-group-bg-orange:hover, .list-group .list-group-bg-orange:focus { + background-color: #FF9800; + color: #fff; + opacity: 0.8; } + +.list-group .pl-deep-orange { + stroke: #FF5722; } + +.list-group .list-group-bg-deep-orange { + background-color: #FF5722; + border: none; + color: #fff; } +.list-group .list-group-bg-deep-orange:hover, .list-group .list-group-bg-deep-orange:focus { + background-color: #FF5722; + color: #fff; + opacity: 0.8; } + +.list-group .pl-brown { + stroke: #795548; } + +.list-group .list-group-bg-brown { + background-color: #795548; + border: none; + color: #fff; } +.list-group .list-group-bg-brown:hover, .list-group .list-group-bg-brown:focus { + background-color: #795548; + color: #fff; + opacity: 0.8; } + +.list-group .pl-grey { + stroke: #9E9E9E; } + +.list-group .list-group-bg-grey { + background-color: #9E9E9E; + border: none; + color: #fff; } +.list-group .list-group-bg-grey:hover, .list-group .list-group-bg-grey:focus { + background-color: #9E9E9E; + color: #fff; + opacity: 0.8; } + +.list-group .pl-blue-grey { + stroke: #607D8B; } + +.list-group .list-group-bg-blue-grey { + background-color: #607D8B; + border: none; + color: #fff; } +.list-group .list-group-bg-blue-grey:hover, .list-group .list-group-bg-blue-grey:focus { + background-color: #607D8B; + color: #fff; + opacity: 0.8; } + +.list-group .pl-black { + stroke: #000000; } + +.list-group .list-group-bg-black { + background-color: #000000; + border: none; + color: #fff; } +.list-group .list-group-bg-black:hover, .list-group .list-group-bg-black:focus { + background-color: #000000; + color: #fff; + opacity: 0.8; } + +.list-group .pl-white { + stroke: #ffffff; } + +.list-group .list-group-bg-white { + background-color: #ffffff; + border: none; + color: #fff; } +.list-group .list-group-bg-white:hover, .list-group .list-group-bg-white:focus { + background-color: #ffffff; + color: #fff; + opacity: 0.8; } + +/* Pagination & Pager ========================== */ +.pager li > a { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + border: none; + background-color: transparent; + color: #222; + font-weight: bold; } + +.pager li a:focus, +.pager li a:active { + background-color: transparent; } + +.pagination .disabled a, +.pagination .disabled a:hover, +.pagination .disabled a:focus, +.pagination .disabled a:active { + color: #bbb; } + +.pagination li.active a { + background-color: #2196F3; } + +.pagination li { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.pagination li a:focus, +.pagination li a:active { + background-color: transparent; + color: #555; } + +.pagination > li > a { + border: none; + font-weight: bold; + color: #555; } + +.pagination > li:first-child > a, +.pagination > li:last-child > a { + width: auto; + height: 32px; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.pagination > li:first-child > a .material-icons, +.pagination > li:last-child > a .material-icons { + position: relative; + bottom: 2px; } + +.pagination-sm > li:first-child > a, +.pagination-sm > li:last-child > a { + width: 28px; + height: 28px; } +.pagination-sm > li:first-child > a .material-icons, +.pagination-sm > li:last-child > a .material-icons { + position: relative; + top: -1px; + left: -6px; + font-size: 20px; } + +.pagination-lg > li:first-child > a, +.pagination-lg > li:last-child > a { + width: 44px; + height: 44px; } +.pagination-lg > li:first-child > a .material-icons, +.pagination-lg > li:last-child > a .material-icons { + font-size: 30px; + position: relative; + top: -3px; + left: -10px; } + +/* Media Object ================================ */ +.media { + margin-bottom: 25px; } +.media .media-body { + color: #777; + font-size: 13px; } +.media .media-body .media-heading { + font-size: 16px; + font-weight: bold; + color: #333; } + +/* Form Wizard ================================= */ +.wizard, +.tabcontrol { + display: block; + width: 100%; + overflow: hidden; } + +.wizard a, +.tabcontrol a { + outline: 0; } + +.wizard ul, +.tabcontrol ul { + list-style: none !important; + padding: 0; + margin: 0; } + +.wizard ul > li, .tabcontrol ul > li { + display: block; + padding: 0; } + +/* Accessibility */ +.wizard > .steps .current-info, +.tabcontrol > .steps .current-info, +.wizard > .content > .title, +.tabcontrol > .content > .title { + position: absolute; + left: -999em; } + +.wizard > .steps { + position: relative; + display: block; + width: 100%; } + +.wizard.vertical > .steps { + float: left; + width: 30%; } + +.wizard.vertical > .steps > ul > li { + float: none; + width: 100%; } + +.wizard.vertical > .content { + float: left; + margin: 0 0 0.5em 0; + width: 70%; } + +.wizard.vertical > .actions { + float: right; + width: 100%; } + +.wizard.vertical > .actions > ul > li { + margin: 0 0 0 1em; } + +.wizard > .steps .number { + font-size: 1.429em; } + +.wizard > .steps > ul > li { + width: 25%; + float: left; } + +.wizard > .actions > ul > li { + float: left; } + +.wizard > .steps a { + display: block; + width: auto; + margin: 0 0.5em 0.5em; + padding: 1em 1em; + text-decoration: none; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; } +.wizard > .steps a:hover, .wizard > .steps a:active { + display: block; + width: auto; + margin: 0 0.5em 0.5em; + padding: 1em 1em; + text-decoration: none; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; } + +.wizard > .steps .disabled a { + background: #eee; + color: #aaa; + cursor: default; } +.wizard > .steps .disabled a:hover, .wizard > .steps .disabled a:active { + background: #eee; + color: #aaa; + cursor: default; } + +.wizard > .steps .current a { + background: #2184be; + color: #fff; + cursor: default; } +.wizard > .steps .current a:hover, .wizard > .steps .current a:active { + background: #2184be; + color: #fff; + cursor: default; } + +.wizard > .steps .done a { + background: #9dc8e2; + color: #fff; } +.wizard > .steps .done a:hover, .wizard > .steps .done a:active { + background: #9dc8e2; + color: #fff; } + +.wizard > .steps .error a { + background: #ff3111; + color: #fff; } +.wizard > .steps .error a:hover, .wizard > .steps .error a:active { + background: #ff3111; + color: #fff; } + +.wizard > .content { + border: 1px solid #ddd; + display: block; + margin: 0.5em; + min-height: 35em; + overflow: hidden; + position: relative; + width: auto; } + +.wizard > .actions { + position: relative; + display: block; + text-align: right; + width: 100%; } + +.wizard > .actions > ul { + display: inline-block; + text-align: right; } +.wizard > .actions > ul > li { + margin: 0 0.5em; } + +.wizard > .actions a { + background: #009688; + color: #fff; + display: block; + padding: 0.5em 1em; + text-decoration: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.wizard > .actions a:hover, .wizard > .actions a:active { + background: #009688; + color: #fff; + display: block; + padding: 0.5em 1em; + text-decoration: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +.wizard > .actions .disabled a { + background: #eee; + color: #aaa; } +.wizard > .actions .disabled a:hover, .wizard > .actions .disabled a:active { + background: #eee; + color: #aaa; } + +.tabcontrol > .steps { + position: relative; + display: block; + width: 100%; } +.tabcontrol > .steps > ul { + position: relative; + margin: 6px 0 0 0; + top: 1px; + z-index: 1; } +.tabcontrol > .steps > ul > li { + float: left; + margin: 5px 2px 0 0; + padding: 1px; + -webkit-border-top-left-radius: 5px; + -webkit-border-top-right-radius: 5px; + -moz-border-radius-topleft: 5px; + -moz-border-radius-topright: 5px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; } +.tabcontrol > .steps > ul > li:hover { + background: #edecec; + border: 1px solid #bbb; + padding: 0; } +.tabcontrol > .steps > ul > li.current { + background: #fff; + border: 1px solid #bbb; + border-bottom: 0 none; + padding: 0 0 1px 0; + margin-top: 0; } +.tabcontrol > .steps > ul > li.current > a { + padding: 15px 30px 10px 30px; } +.tabcontrol > .steps > ul > li > a { + color: #5f5f5f; + display: inline-block; + border: 0 none; + margin: 0; + padding: 10px 30px; + text-decoration: none; } +.tabcontrol > .steps > ul > li > a:hover { + text-decoration: none; } + +.tabcontrol > .content { + position: relative; + display: inline-block; + width: 100%; + height: 35em; + overflow: hidden; + border-top: 1px solid #bbb; + padding-top: 20px; } +.tabcontrol > .content > .body { + float: left; + position: absolute; + width: 95%; + height: 95%; + padding: 2.5%; } +.tabcontrol > .content > .body ul { + list-style: disc !important; } +.tabcontrol > .content > .body ul > li { + display: list-item; } + +.wizard .content { + min-height: 245px; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + overflow-y: auto; } +.wizard .content .body { + padding: 15px; } + +.wizard .steps a { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } +.wizard .steps a:active, .wizard .steps a:focus, .wizard .steps a:hover { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +.wizard .steps .done a { + background-color: rgba(0, 150, 136, 0.6); } +.wizard .steps .done a:hover, .wizard .steps .done a:active, .wizard .steps .done a:focus { + background-color: rgba(0, 150, 136, 0.5); } + +.wizard .steps .error a { + background-color: #F44336 !important; } + +.wizard .steps .current a { + background-color: #009688; } +.wizard .steps .current a:active, .wizard .steps .current a:focus, .wizard .steps .current a:hover { + background-color: #009688; } + +/* Waves ======================================= */ +.waves-effect.waves-red .waves-ripple { + background: rgba(244, 67, 54, 0.5); } + +.waves-effect.waves-pink .waves-ripple { + background: rgba(233, 30, 99, 0.5); } + +.waves-effect.waves-purple .waves-ripple { + background: rgba(156, 39, 176, 0.5); } + +.waves-effect.waves-deep-purple .waves-ripple { + background: rgba(103, 58, 183, 0.5); } + +.waves-effect.waves-indigo .waves-ripple { + background: rgba(63, 81, 181, 0.5); } + +.waves-effect.waves-blue .waves-ripple { + background: rgba(33, 150, 243, 0.5); } + +.waves-effect.waves-light-blue .waves-ripple { + background: rgba(3, 169, 244, 0.5); } + +.waves-effect.waves-cyan .waves-ripple { + background: rgba(0, 188, 212, 0.5); } + +.waves-effect.waves-teal .waves-ripple { + background: rgba(0, 150, 136, 0.5); } + +.waves-effect.waves-green .waves-ripple { + background: rgba(76, 175, 80, 0.5); } + +.waves-effect.waves-light-green .waves-ripple { + background: rgba(139, 195, 74, 0.5); } + +.waves-effect.waves-lime .waves-ripple { + background: rgba(205, 220, 57, 0.5); } + +.waves-effect.waves-yellow .waves-ripple { + background: rgba(255, 232, 33, 0.5); } + +.waves-effect.waves-amber .waves-ripple { + background: rgba(255, 193, 7, 0.5); } + +.waves-effect.waves-orange .waves-ripple { + background: rgba(255, 152, 0, 0.5); } + +.waves-effect.waves-deep-orange .waves-ripple { + background: rgba(255, 87, 34, 0.5); } + +.waves-effect.waves-brown .waves-ripple { + background: rgba(121, 85, 72, 0.5); } + +.waves-effect.waves-grey .waves-ripple { + background: rgba(158, 158, 158, 0.5); } + +.waves-effect.waves-blue-grey .waves-ripple { + background: rgba(96, 125, 139, 0.5); } + +.waves-effect.waves-black .waves-ripple { + background: rgba(0, 0, 0, 0.5); } + +.waves-effect.waves-white .waves-ripple { + background: rgba(255, 255, 255, 0.5); } + +/* Page Loader ================================= */ +.page-loader-wrapper { + z-index: 99999999; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + background: #eee; + overflow: hidden; + text-align: center; } +.page-loader-wrapper p { + font-size: 13px; + margin-top: 10px; + font-weight: bold; + color: #444; } +.page-loader-wrapper .loader { + position: relative; + top: calc(50% - 30px); } + +/* Preloaders ================================== */ +.md-preloader .pl-red { + stroke: #F44336; } + +.md-preloader .pl-pink { + stroke: #E91E63; } + +.md-preloader .pl-purple { + stroke: #9C27B0; } + +.md-preloader .pl-deep-purple { + stroke: #673AB7; } + +.md-preloader .pl-indigo { + stroke: #3F51B5; } + +.md-preloader .pl-blue { + stroke: #2196F3; } + +.md-preloader .pl-light-blue { + stroke: #03A9F4; } + +.md-preloader .pl-cyan { + stroke: #00BCD4; } + +.md-preloader .pl-teal { + stroke: #009688; } + +.md-preloader .pl-green { + stroke: #4CAF50; } + +.md-preloader .pl-light-green { + stroke: #8BC34A; } + +.md-preloader .pl-lime { + stroke: #CDDC39; } + +.md-preloader .pl-yellow { + stroke: #ffe821; } + +.md-preloader .pl-amber { + stroke: #FFC107; } + +.md-preloader .pl-orange { + stroke: #FF9800; } + +.md-preloader .pl-deep-orange { + stroke: #FF5722; } + +.md-preloader .pl-brown { + stroke: #795548; } + +.md-preloader .pl-grey { + stroke: #9E9E9E; } + +.md-preloader .pl-blue-grey { + stroke: #607D8B; } + +.md-preloader .pl-black { + stroke: #000000; } + +.md-preloader .pl-white { + stroke: #ffffff; } + +.preloader { + display: inline-block; + position: relative; + width: 50px; + height: 50px; + -webkit-animation: container-rotate 1568ms linear infinite; + -moz-animation: container-rotate 1568ms linear infinite; + -o-animation: container-rotate 1568ms linear infinite; + animation: container-rotate 1568ms linear infinite; } +.preloader.pl-size-xl { + width: 75px; + height: 75px; } +.preloader.pl-size-l { + width: 60px; + height: 60px; } +.preloader.pl-size-md { + width: 50px; + height: 50px; } +.preloader.pl-size-sm { + width: 40px; + height: 40px; } +.preloader.pl-size-xs { + width: 25px; + height: 25px; } + +.spinner-layer { + position: absolute; + width: 100%; + height: 100%; + border-color: #F44336; + -ms-opacity: 1; + opacity: 1; + -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + -moz-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + -o-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; } +.spinner-layer.pl-red { + border-color: #F44336; } +.spinner-layer.pl-pink { + border-color: #E91E63; } +.spinner-layer.pl-purple { + border-color: #9C27B0; } +.spinner-layer.pl-deep-purple { + border-color: #673AB7; } +.spinner-layer.pl-indigo { + border-color: #3F51B5; } +.spinner-layer.pl-blue { + border-color: #2196F3; } +.spinner-layer.pl-light-blue { + border-color: #03A9F4; } +.spinner-layer.pl-cyan { + border-color: #00BCD4; } +.spinner-layer.pl-teal { + border-color: #009688; } +.spinner-layer.pl-green { + border-color: #4CAF50; } +.spinner-layer.pl-light-green { + border-color: #8BC34A; } +.spinner-layer.pl-lime { + border-color: #CDDC39; } +.spinner-layer.pl-yellow { + border-color: #ffe821; } +.spinner-layer.pl-amber { + border-color: #FFC107; } +.spinner-layer.pl-orange { + border-color: #FF9800; } +.spinner-layer.pl-deep-orange { + border-color: #FF5722; } +.spinner-layer.pl-brown { + border-color: #795548; } +.spinner-layer.pl-grey { + border-color: #9E9E9E; } +.spinner-layer.pl-blue-grey { + border-color: #607D8B; } +.spinner-layer.pl-black { + border-color: #000000; } +.spinner-layer.pl-white { + border-color: #ffffff; } + +.right { + float: right !important; } + +.gap-patch { + position: absolute; + top: 0; + left: 45%; + width: 10%; + height: 100%; + overflow: hidden; + border-color: inherit; } +.gap-patch.circle { + width: 1000%; + left: -450%; } + +.circle-clipper { + display: inline-block; + position: relative; + width: 50%; + height: 100%; + overflow: hidden; + border-color: inherit; } +.circle-clipper .circle { + width: 200%; + height: 100%; + border-width: 3px; + border-style: solid; + border-color: inherit; + border-bottom-color: transparent !important; + -ms-border-radius: 50%; + border-radius: 50%; + -webkit-animation: none; + animation: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; } +.circle-clipper.left .circle { + left: 0; + border-right-color: transparent !important; + -webkit-transform: rotate(129deg); + -moz-transform: rotate(129deg); + -ms-transform: rotate(129deg); + -o-transform: rotate(129deg); + transform: rotate(129deg); + -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + -moz-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + -o-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; } +.circle-clipper.right .circle { + left: -100%; + border-left-color: transparent !important; + -webkit-transform: rotate(-129deg); + -moz-transform: rotate(-129deg); + -ms-transform: rotate(-129deg); + -o-transform: rotate(-129deg); + transform: rotate(-129deg); + -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + -moz-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + -o-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; + animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; } + +@-webkit-keyframes container-rotate { + to { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes container-rotate { + to { + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@-webkit-keyframes fill-unfill-rotate { + 12.5% { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); } + 25% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + 37.5% { + -webkit-transform: rotate(405deg); + transform: rotate(405deg); } + 50% { + -webkit-transform: rotate(540deg); + transform: rotate(540deg); } + 62.5% { + -webkit-transform: rotate(675deg); + transform: rotate(675deg); } + 75% { + -webkit-transform: rotate(810deg); + transform: rotate(810deg); } + 87.5% { + -webkit-transform: rotate(945deg); + transform: rotate(945deg); } + to { + -webkit-transform: rotate(1080deg); + transform: rotate(1080deg); } } + +@keyframes fill-unfill-rotate { + 12.5% { + transform: rotate(135deg); } + 25% { + transform: rotate(270deg); } + 37.5% { + transform: rotate(405deg); } + 50% { + transform: rotate(540deg); } + 62.5% { + transform: rotate(675deg); } + 75% { + transform: rotate(810deg); } + 87.5% { + transform: rotate(945deg); } + to { + transform: rotate(1080deg); } } + +@-webkit-keyframes left-spin { + from { + -webkit-transform: rotate(130deg); + -moz-transform: rotate(130deg); + -ms-transform: rotate(130deg); + -o-transform: rotate(130deg); + transform: rotate(130deg); } + 50% { + -webkit-transform: rotate(-5deg); + -moz-transform: rotate(-5deg); + -ms-transform: rotate(-5deg); + -o-transform: rotate(-5deg); + transform: rotate(-5deg); } + to { + -webkit-transform: rotate(130deg); + -moz-transform: rotate(130deg); + -ms-transform: rotate(130deg); + -o-transform: rotate(130deg); + transform: rotate(130deg); } } + +@keyframes left-spin { + from { + -moz-transform: rotate(130deg); + -ms-transform: rotate(130deg); + -o-transform: rotate(130deg); + -webkit-transform: rotate(130deg); + transform: rotate(130deg); } + 50% { + -moz-transform: rotate(-5deg); + -ms-transform: rotate(-5deg); + -o-transform: rotate(-5deg); + -webkit-transform: rotate(-5deg); + transform: rotate(-5deg); } + to { + -moz-transform: rotate(130deg); + -ms-transform: rotate(130deg); + -o-transform: rotate(130deg); + -webkit-transform: rotate(130deg); + transform: rotate(130deg); } } + +@-webkit-keyframes right-spin { + from { + -webkit-transform: rotate(-130deg); + -moz-transform: rotate(-130deg); + -ms-transform: rotate(-130deg); + -o-transform: rotate(-130deg); + transform: rotate(-130deg); } + 50% { + -webkit-transform: rotate(5deg); + -moz-transform: rotate(5deg); + -ms-transform: rotate(5deg); + -o-transform: rotate(5deg); + transform: rotate(5deg); } + to { + -webkit-transform: rotate(-130deg); + -moz-transform: rotate(-130deg); + -ms-transform: rotate(-130deg); + -o-transform: rotate(-130deg); + transform: rotate(-130deg); } } + +@-moz-keyframes right-spin { + from { + -moz-transform: rotate(-130deg); + -ms-transform: rotate(-130deg); + -o-transform: rotate(-130deg); + -webkit-transform: rotate(-130deg); + transform: rotate(-130deg); } + 50% { + -moz-transform: rotate(5deg); + -ms-transform: rotate(5deg); + -o-transform: rotate(5deg); + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } + to { + -moz-transform: rotate(-130deg); + -ms-transform: rotate(-130deg); + -o-transform: rotate(-130deg); + -webkit-transform: rotate(-130deg); + transform: rotate(-130deg); } } + +@keyframes right-spin { + from { + -moz-transform: rotate(-130deg); + -ms-transform: rotate(-130deg); + -o-transform: rotate(-130deg); + -webkit-transform: rotate(-130deg); + transform: rotate(-130deg); } + 50% { + -moz-transform: rotate(5deg); + -ms-transform: rotate(5deg); + -o-transform: rotate(5deg); + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } + to { + -moz-transform: rotate(-130deg); + -ms-transform: rotate(-130deg); + -o-transform: rotate(-130deg); + -webkit-transform: rotate(-130deg); + transform: rotate(-130deg); } } + +/* Navbars ===================================== */ +.navbar { + font-family: "Roboto", sans-serif; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); + border: none; + position: inherit; + top: 0; + left: 0; + z-index: 12; + width: 100%; } +.navbar .navbar-brand { + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; } +.navbar .navbar-custom-right-menu { + float: right; } +.navbar .navbar-toggle { + text-decoration: none; + color: #fff; + width: 20px; + height: 20px; + margin-top: -4px; + margin-right: 17px; } +.navbar .navbar-toggle:before { + content: '\E8D5'; + font-family: 'Material Icons'; + font-size: 26px; } +.navbar .navbar-collapse.in { + overflow: visible; } + +.ls-closed .sidebar { + margin-left: -300px; } + +.ls-closed section.content { + margin-left: 15px; } + +.ls-closed .bars:after, .ls-closed .bars:before { + font-family: 'Material Icons'; + font-size: 24px; + position: absolute; + top: 18px; + left: 20px; + margin-right: 10px; + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -webkit-transition: all 0.3s; + transition: all 0.3s; } + +.ls-closed .bars:before { + content: '\E5D2'; + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); } + +.ls-closed .bars:after { + content: '\E5C4'; + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); } + +.ls-closed .navbar-brand { + margin-left: 30px; } + +.overlay-open .bars:before { + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); } + +.overlay-open .bars:after { + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); } + +.navbar-header { + padding: 10px 7px; } +.navbar-header .bars { + float: left; + text-decoration: none; } + +.navbar-nav > li > a { + padding: 7px 7px 2px 7px; + margin-top: 17px; + margin-left: 5px; } + +.navbar-nav .dropdown-menu { + margin-top: -40px !important; } + +.label-count { + position: absolute; + top: 2px; + right: 6px; + font-size: 10px; + line-height: 15px; + background-color: #000; + padding: 0 4px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; } + +.col-red .navbar .navbar-brand, +.col-red .navbar .navbar-brand:hover, +.col-red .navbar .navbar-brand:active, +.col-red .navbar .navbar-brand:focus { + color: #fff; } + +.col-red .navbar .nav > li > a:hover, +.col-red .navbar .nav > li > a:focus, +.col-red .navbar .nav .open > a, +.col-red .navbar .nav .open > a:hover, +.col-red .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-red .navbar .nav > li > a { + color: #fff; } + +.col-red .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-red .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-pink .navbar .navbar-brand, +.col-pink .navbar .navbar-brand:hover, +.col-pink .navbar .navbar-brand:active, +.col-pink .navbar .navbar-brand:focus { + color: #fff; } + +.col-pink .navbar .nav > li > a:hover, +.col-pink .navbar .nav > li > a:focus, +.col-pink .navbar .nav .open > a, +.col-pink .navbar .nav .open > a:hover, +.col-pink .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-pink .navbar .nav > li > a { + color: #fff; } + +.col-pink .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-pink .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-purple .navbar .navbar-brand, +.col-purple .navbar .navbar-brand:hover, +.col-purple .navbar .navbar-brand:active, +.col-purple .navbar .navbar-brand:focus { + color: #fff; } + +.col-purple .navbar .nav > li > a:hover, +.col-purple .navbar .nav > li > a:focus, +.col-purple .navbar .nav .open > a, +.col-purple .navbar .nav .open > a:hover, +.col-purple .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-purple .navbar .nav > li > a { + color: #fff; } + +.col-purple .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-purple .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-deep-purple .navbar .navbar-brand, +.col-deep-purple .navbar .navbar-brand:hover, +.col-deep-purple .navbar .navbar-brand:active, +.col-deep-purple .navbar .navbar-brand:focus { + color: #fff; } + +.col-deep-purple .navbar .nav > li > a:hover, +.col-deep-purple .navbar .nav > li > a:focus, +.col-deep-purple .navbar .nav .open > a, +.col-deep-purple .navbar .nav .open > a:hover, +.col-deep-purple .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-deep-purple .navbar .nav > li > a { + color: #fff; } + +.col-deep-purple .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-deep-purple .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-indigo .navbar .navbar-brand, +.col-indigo .navbar .navbar-brand:hover, +.col-indigo .navbar .navbar-brand:active, +.col-indigo .navbar .navbar-brand:focus { + color: #fff; } + +.col-indigo .navbar .nav > li > a:hover, +.col-indigo .navbar .nav > li > a:focus, +.col-indigo .navbar .nav .open > a, +.col-indigo .navbar .nav .open > a:hover, +.col-indigo .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-indigo .navbar .nav > li > a { + color: #fff; } + +.col-indigo .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-indigo .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-blue .navbar .navbar-brand, +.col-blue .navbar .navbar-brand:hover, +.col-blue .navbar .navbar-brand:active, +.col-blue .navbar .navbar-brand:focus { + color: #fff; } + +.col-blue .navbar .nav > li > a:hover, +.col-blue .navbar .nav > li > a:focus, +.col-blue .navbar .nav .open > a, +.col-blue .navbar .nav .open > a:hover, +.col-blue .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-blue .navbar .nav > li > a { + color: #fff; } + +.col-blue .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-blue .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-light-blue .navbar .navbar-brand, +.col-light-blue .navbar .navbar-brand:hover, +.col-light-blue .navbar .navbar-brand:active, +.col-light-blue .navbar .navbar-brand:focus { + color: #fff; } + +.col-light-blue .navbar .nav > li > a:hover, +.col-light-blue .navbar .nav > li > a:focus, +.col-light-blue .navbar .nav .open > a, +.col-light-blue .navbar .nav .open > a:hover, +.col-light-blue .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-light-blue .navbar .nav > li > a { + color: #fff; } + +.col-light-blue .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-light-blue .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-cyan .navbar .navbar-brand, +.col-cyan .navbar .navbar-brand:hover, +.col-cyan .navbar .navbar-brand:active, +.col-cyan .navbar .navbar-brand:focus { + color: #fff; } + +.col-cyan .navbar .nav > li > a:hover, +.col-cyan .navbar .nav > li > a:focus, +.col-cyan .navbar .nav .open > a, +.col-cyan .navbar .nav .open > a:hover, +.col-cyan .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-cyan .navbar .nav > li > a { + color: #fff; } + +.col-cyan .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-cyan .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-teal .navbar .navbar-brand, +.col-teal .navbar .navbar-brand:hover, +.col-teal .navbar .navbar-brand:active, +.col-teal .navbar .navbar-brand:focus { + color: #fff; } + +.col-teal .navbar .nav > li > a:hover, +.col-teal .navbar .nav > li > a:focus, +.col-teal .navbar .nav .open > a, +.col-teal .navbar .nav .open > a:hover, +.col-teal .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-teal .navbar .nav > li > a { + color: #fff; } + +.col-teal .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-teal .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-green .navbar .navbar-brand, +.col-green .navbar .navbar-brand:hover, +.col-green .navbar .navbar-brand:active, +.col-green .navbar .navbar-brand:focus { + color: #fff; } + +.col-green .navbar .nav > li > a:hover, +.col-green .navbar .nav > li > a:focus, +.col-green .navbar .nav .open > a, +.col-green .navbar .nav .open > a:hover, +.col-green .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-green .navbar .nav > li > a { + color: #fff; } + +.col-green .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-green .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-light-green .navbar .navbar-brand, +.col-light-green .navbar .navbar-brand:hover, +.col-light-green .navbar .navbar-brand:active, +.col-light-green .navbar .navbar-brand:focus { + color: #fff; } + +.col-light-green .navbar .nav > li > a:hover, +.col-light-green .navbar .nav > li > a:focus, +.col-light-green .navbar .nav .open > a, +.col-light-green .navbar .nav .open > a:hover, +.col-light-green .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-light-green .navbar .nav > li > a { + color: #fff; } + +.col-light-green .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-light-green .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-lime .navbar .navbar-brand, +.col-lime .navbar .navbar-brand:hover, +.col-lime .navbar .navbar-brand:active, +.col-lime .navbar .navbar-brand:focus { + color: #fff; } + +.col-lime .navbar .nav > li > a:hover, +.col-lime .navbar .nav > li > a:focus, +.col-lime .navbar .nav .open > a, +.col-lime .navbar .nav .open > a:hover, +.col-lime .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-lime .navbar .nav > li > a { + color: #fff; } + +.col-lime .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-lime .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-yellow .navbar .navbar-brand, +.col-yellow .navbar .navbar-brand:hover, +.col-yellow .navbar .navbar-brand:active, +.col-yellow .navbar .navbar-brand:focus { + color: #fff; } + +.col-yellow .navbar .nav > li > a:hover, +.col-yellow .navbar .nav > li > a:focus, +.col-yellow .navbar .nav .open > a, +.col-yellow .navbar .nav .open > a:hover, +.col-yellow .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-yellow .navbar .nav > li > a { + color: #fff; } + +.col-yellow .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-yellow .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-amber .navbar .navbar-brand, +.col-amber .navbar .navbar-brand:hover, +.col-amber .navbar .navbar-brand:active, +.col-amber .navbar .navbar-brand:focus { + color: #fff; } + +.col-amber .navbar .nav > li > a:hover, +.col-amber .navbar .nav > li > a:focus, +.col-amber .navbar .nav .open > a, +.col-amber .navbar .nav .open > a:hover, +.col-amber .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-amber .navbar .nav > li > a { + color: #fff; } + +.col-amber .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-amber .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-orange .navbar .navbar-brand, +.col-orange .navbar .navbar-brand:hover, +.col-orange .navbar .navbar-brand:active, +.col-orange .navbar .navbar-brand:focus { + color: #fff; } + +.col-orange .navbar .nav > li > a:hover, +.col-orange .navbar .nav > li > a:focus, +.col-orange .navbar .nav .open > a, +.col-orange .navbar .nav .open > a:hover, +.col-orange .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-orange .navbar .nav > li > a { + color: #fff; } + +.col-orange .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-orange .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-deep-orange .navbar .navbar-brand, +.col-deep-orange .navbar .navbar-brand:hover, +.col-deep-orange .navbar .navbar-brand:active, +.col-deep-orange .navbar .navbar-brand:focus { + color: #fff; } + +.col-deep-orange .navbar .nav > li > a:hover, +.col-deep-orange .navbar .nav > li > a:focus, +.col-deep-orange .navbar .nav .open > a, +.col-deep-orange .navbar .nav .open > a:hover, +.col-deep-orange .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-deep-orange .navbar .nav > li > a { + color: #fff; } + +.col-deep-orange .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-deep-orange .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-brown .navbar .navbar-brand, +.col-brown .navbar .navbar-brand:hover, +.col-brown .navbar .navbar-brand:active, +.col-brown .navbar .navbar-brand:focus { + color: #fff; } + +.col-brown .navbar .nav > li > a:hover, +.col-brown .navbar .nav > li > a:focus, +.col-brown .navbar .nav .open > a, +.col-brown .navbar .nav .open > a:hover, +.col-brown .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-brown .navbar .nav > li > a { + color: #fff; } + +.col-brown .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-brown .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-grey .navbar .navbar-brand, +.col-grey .navbar .navbar-brand:hover, +.col-grey .navbar .navbar-brand:active, +.col-grey .navbar .navbar-brand:focus { + color: #fff; } + +.col-grey .navbar .nav > li > a:hover, +.col-grey .navbar .nav > li > a:focus, +.col-grey .navbar .nav .open > a, +.col-grey .navbar .nav .open > a:hover, +.col-grey .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-grey .navbar .nav > li > a { + color: #fff; } + +.col-grey .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-grey .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-blue-grey .navbar .navbar-brand, +.col-blue-grey .navbar .navbar-brand:hover, +.col-blue-grey .navbar .navbar-brand:active, +.col-blue-grey .navbar .navbar-brand:focus { + color: #fff; } + +.col-blue-grey .navbar .nav > li > a:hover, +.col-blue-grey .navbar .nav > li > a:focus, +.col-blue-grey .navbar .nav .open > a, +.col-blue-grey .navbar .nav .open > a:hover, +.col-blue-grey .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-blue-grey .navbar .nav > li > a { + color: #fff; } + +.col-blue-grey .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-blue-grey .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-black .navbar .navbar-brand, +.col-black .navbar .navbar-brand:hover, +.col-black .navbar .navbar-brand:active, +.col-black .navbar .navbar-brand:focus { + color: #fff; } + +.col-black .navbar .nav > li > a:hover, +.col-black .navbar .nav > li > a:focus, +.col-black .navbar .nav .open > a, +.col-black .navbar .nav .open > a:hover, +.col-black .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-black .navbar .nav > li > a { + color: #fff; } + +.col-black .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-black .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +.col-white .navbar .navbar-brand, +.col-white .navbar .navbar-brand:hover, +.col-white .navbar .navbar-brand:active, +.col-white .navbar .navbar-brand:focus { + color: #fff; } + +.col-white .navbar .nav > li > a:hover, +.col-white .navbar .nav > li > a:focus, +.col-white .navbar .nav .open > a, +.col-white .navbar .nav .open > a:hover, +.col-white .navbar .nav .open > a:focus { + background-color: rgba(0, 0, 0, 0.05); } + +.col-white .navbar .nav > li > a { + color: #fff; } + +.col-white .navbar .bars { + float: left; + padding: 10px 20px; + font-size: 22px; + color: #fff; + margin-right: 10px; + margin-left: -10px; + margin-top: 4px; } + +.col-white .navbar .bars:hover { + background-color: rgba(0, 0, 0, 0.08); } + +/* Dropdown Menu =============================== */ +.dropdown-menu { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + margin-top: -35px !important; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + border: none; } +.dropdown-menu .divider { + margin: 5px 0; } +.dropdown-menu .header { + font-size: 13px; + font-weight: bold; + min-width: 270px; + border-bottom: 1px solid #eee; + text-align: center; + padding: 4px 0 6px 0; } +.dropdown-menu ul.menu { + padding-left: 0; } +.dropdown-menu ul.menu.tasks h4 { + color: #333; + font-size: 13px; + margin: 0 0 8px 0; } +.dropdown-menu ul.menu.tasks h4 small { + float: right; + margin-top: 6px; } +.dropdown-menu ul.menu.tasks .progress { + height: 7px; + margin-bottom: 7px; } +.dropdown-menu ul.menu .icon-circle { + width: 36px; + height: 36px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + -ms-border-radius: 50%; + border-radius: 50%; + color: #fff; + text-align: center; + display: inline-block; } +.dropdown-menu ul.menu .icon-circle i { + font-size: 18px; + line-height: 36px; } +.dropdown-menu ul.menu li { + border-bottom: 1px solid #eee; } +.dropdown-menu ul.menu li:last-child { + border-bottom: none; } +.dropdown-menu ul.menu li a { + padding: 7px 11px; + text-decoration: none; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } +.dropdown-menu ul.menu li a:hover { + background-color: #e9e9e9; } +.dropdown-menu ul.menu .menu-info { + display: inline-block; + position: relative; + top: 3px; + left: 5px; } +.dropdown-menu ul.menu .menu-info h4 { + margin: 0; + font-size: 13px; + color: #333; } +.dropdown-menu ul.menu .menu-info p { + margin: 0; + font-size: 11px; + color: #aaa; } +.dropdown-menu ul.menu .menu-info p .material-icons { + font-size: 13px; + color: #aaa; + position: relative; + top: 2px; } +.dropdown-menu .footer a { + text-align: center; + border-top: 1px solid #eee; + padding: 5px 0 5px 0; + font-size: 12px; + margin-bottom: -5px; } +.dropdown-menu .footer a:hover { + background-color: transparent; } +.dropdown-menu > li > a { + padding: 7px 18px; + color: #666; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -webkit-transition: all 0.5s; + transition: all 0.5s; + font-size: 14px; + line-height: 25px; } +.dropdown-menu > li > a:hover { + background-color: rgba(0, 0, 0, 0.075); } +.dropdown-menu > li > a i.material-icons { + float: left; + margin-right: 7px; + margin-top: 2px; + font-size: 20px; } + +.dropdown-animated { + -webkit-animation-duration: .3s !important; + -moz-animation-duration: .3s !important; + -o-animation-duration: .3s !important; + animation-duration: .3s !important; } + +/* Left Sidebar & Overlay ====================== */ +.overlay { + position: fixed; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: none; + z-index: 10; } + +.overlay-open .sidebar { + margin-left: 0; + z-index: 99999999; } + +.sidebar { + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -webkit-transition: all 0.5s; + transition: all 0.5s; + font-family: "Roboto", sans-serif; + background: #fdfdfd; + width: 300px; + overflow: hidden; + display: inline-block; + height: calc(100vh - 70px); + position: fixed; + top: 70px; + left: 0; + -webkit-box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + -ms-box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + z-index: 11 !important; } +.sidebar .legal { + position: absolute; + bottom: 0; + width: 100%; + border-top: 1px solid #eee; + padding: 15px; + overflow: hidden; } +.sidebar .legal .copyright { + font-size: 13px; + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; } +.sidebar .legal .copyright a { + font-weight: bold; + text-decoration: none; } +.sidebar .legal .version { + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; + margin-top: 5px; + font-size: 13px; } +.sidebar .user-info { + padding: 13px 15px 12px 15px; + white-space: nowrap; + position: relative; + border-bottom: 1px solid #e9e9e9; + background: url("../images/user-img-background.jpg") no-repeat no-repeat; + height: 135px; } +.sidebar .user-info .image { + margin-right: 12px; + display: inline-block; } +.sidebar .user-info .image img { + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + -ms-border-radius: 50%; + border-radius: 50%; + vertical-align: bottom !important; } +.sidebar .user-info .info-container { + cursor: default; + display: block; + position: relative; + top: 25px; } +.sidebar .user-info .info-container .name { + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; + font-size: 14px; + max-width: 200px; + color: #fff; } +.sidebar .user-info .info-container .email { + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; + font-size: 12px; + max-width: 200px; + color: #fff; } +.sidebar .user-info .info-container .user-helper-dropdown { + position: absolute; + right: -3px; + bottom: -12px; + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; + cursor: pointer; + color: #fff; } +.sidebar .menu { + position: relative; + overflow-y: auto; + height: 90vh; } +.sidebar .menu .list { + list-style: none; + padding-left: 0; } +.sidebar .menu .list li.active > :first-child span { + font-weight: bold; } +.sidebar .menu .list .header { + background: #eee; + font-size: 12px; + font-weight: 600; + padding: 8px 16px; } +.sidebar .menu .list i.material-icons { + margin-top: 4px; } +.sidebar .menu .list .menu-toggle:after, .sidebar .menu .list .menu-toggle:before { + position: absolute; + top: calc(50% - 14px); + right: 17px; + font-size: 19px; + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -webkit-transition: all 0.3s; + transition: all 0.3s; } +.sidebar .menu .list .menu-toggle:before { + content: '+'; + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); } +.sidebar .menu .list .menu-toggle:after { + content: '\2013'; + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); } +.sidebar .menu .list .menu-toggle.toggled:before { + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + -webkit-transform: scale(0); + transform: scale(0); } +.sidebar .menu .list .menu-toggle.toggled:after { + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); } +.sidebar .menu .list a { + color: #747474; + position: relative; + display: inline-flex; + vertical-align: middle; + width: 100%; + padding: 10px 13px; } +.sidebar .menu .list a:hover, .sidebar .menu .list a:active, .sidebar .menu .list a:focus { + text-decoration: none !important; } +.sidebar .menu .list a small { + position: absolute; + top: calc(50% - 7.5px); + right: 15px; } +.sidebar .menu .list a span { + margin: 7px 0 7px 12px; + color: #333; + font-weight: bold; + font-size: 14px; + overflow: hidden; } +.sidebar .menu .list .ml-menu { + list-style: none; + display: none; + padding-left: 0; } +.sidebar .menu .list .ml-menu span { + font-weight: normal; + font-size: 14px; + margin: 3px 0 1px 6px; } +.sidebar .menu .list .ml-menu li a { + padding-left: 55px; + padding-top: 7px; + padding-bottom: 7px; } +.sidebar .menu .list .ml-menu li.active a.toggled:not(.menu-toggle) { + font-weight: 600; + margin-left: 5px; } +.sidebar .menu .list .ml-menu li.active a.toggled:not(.menu-toggle):before { + content: '\E315'; + font-family: 'Material Icons'; + position: relative; + font-size: 21px; + height: 20px; + top: -5px; + right: 0px; } +.sidebar .menu .list .ml-menu li .ml-menu li a { + padding-left: 80px; } +.sidebar .menu .list .ml-menu li .ml-menu .ml-menu li a { + padding-left: 95px; } + +.right-sidebar { + width: 280px; + height: calc(100vh - 70px); + position: fixed; + right: -300px; + top: 70px; + background: #fdfdfd; + z-index: 11 !important; + -webkit-box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.1); + -moz-box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.1); + -ms-box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.1); + box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.1); + overflow: hidden; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } +.right-sidebar.open { + right: 0; } +.right-sidebar .nav-tabs { + font-weight: 600; + font-size: 13px; + width: 100%; + margin-left: 2px; } +.right-sidebar .nav-tabs li { + text-align: center; } +.right-sidebar .nav-tabs li > a { + margin-right: 0; } +.right-sidebar .nav-tabs li:first-child { + width: 45%; } +.right-sidebar .nav-tabs li:last-child { + width: 55%; } + +/* Bootstrap Notify ============================ */ +.bootstrap-notify-container { + max-width: 320px; + text-align: center; } + +/* Jquery Nestable ============================= */ +.dd-handle { + background-color: #f9f9f9 !important; } +.dd-handle:hover { + color: #2196F3; } + +.nestable-dark-theme .dd-handle { + background: #ccc !important; + border: 1px solid #999 !important; } + +.dd3-handle { + background: #999 !important; } + +.dd3-content:hover { + color: #2196F3; } + +/* Login Page ================================== */ +.login-page { + background-color: #00BCD4; + padding-left: 0; + max-width: 360px; + margin: 5% auto; + overflow-x: hidden; } +.login-page .login-box .msg { + color: #555; + margin-bottom: 30px; + text-align: center; } +.login-page .login-box a { + font-size: 14px; + text-decoration: none; + color: #00BCD4; } +.login-page .login-box .logo { + margin-bottom: 20px; } +.login-page .login-box .logo a { + font-size: 36px; + display: block; + width: 100%; + text-align: center; + color: #fff; } +.login-page .login-box .logo small { + display: block; + width: 100%; + text-align: center; + color: #fff; + margin-top: -5px; } + +/* Sign Up Page ================================ */ +.signup-page { + background-color: #00BCD4; + padding-left: 0; + max-width: 360px; + margin: 5% auto; + overflow-x: hidden; } +.signup-page .signup-box .msg { + color: #555; + margin-bottom: 30px; + text-align: center; } +.signup-page .signup-box a { + font-size: 14px; + text-decoration: none; + color: #00BCD4; } +.signup-page .signup-box .logo { + margin-bottom: 20px; } +.signup-page .signup-box .logo a { + font-size: 36px; + display: block; + width: 100%; + text-align: center; + color: #fff; } +.signup-page .signup-box .logo small { + display: block; + width: 100%; + text-align: center; + color: #fff; + margin-top: -5px; } + +/* Forgot Password Page ======================== */ +.fp-page { + background-color: #00BCD4; + padding-left: 0; + max-width: 360px; + margin: 5% auto; + overflow-x: hidden; } +.fp-page .fp-box .msg { + color: #555; + margin-bottom: 30px; + text-align: center; } +.fp-page .fp-box a { + font-size: 14px; + text-decoration: none; + color: #00BCD4; } +.fp-page .fp-box .logo { + margin-bottom: 20px; } +.fp-page .fp-box .logo a { + font-size: 36px; + display: block; + width: 100%; + text-align: center; + color: #fff; } +.fp-page .fp-box .logo small { + display: block; + width: 100%; + text-align: center; + color: #fff; + margin-top: -5px; } + +/* 404 Not Found Page ========================== */ +.four-zero-four { + width: 100%; + text-align: center; + margin: 5% auto; } +.four-zero-four .four-zero-four-container .error-code { + font-size: 160px; } +.four-zero-four .four-zero-four-container .error-message { + font-size: 26px; + color: #333; + font-weight: bold; + margin-top: -40px; } +.four-zero-four .four-zero-four-container .button-place { + margin-top: 32px; } + +/* 500 Server Error Page ======================= */ +.five-zero-zero { + width: 100%; + text-align: center; + margin: 5% auto; } +.five-zero-zero .five-zero-zero-container .error-code { + font-size: 160px; } +.five-zero-zero .five-zero-zero-container .error-message { + font-size: 27px; + color: #333; + font-weight: bold; + margin-top: -40px; } +.five-zero-zero .five-zero-zero-container .button-place { + margin-top: 32px; } + +/* Profile Page ================================ */ +.profile-card .profile-header { + background-color: #2196f3; + padding: 42px 0; } + +.profile-card .profile-body .image-area { + text-align: center; + margin-top: -64px; } +.profile-card .profile-body .image-area img { + border: 2px solid #2196f3; + padding: 2px; + margin: 2px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + -ms-border-radius: 50%; + border-radius: 50%; } + +.profile-card .profile-body .content-area { + text-align: center; + border-bottom: 1px solid #ddd; + padding-bottom: 15px; } +.profile-card .profile-body .content-area p { + margin-bottom: 0; } +.profile-card .profile-body .content-area p:last-child { + font-weight: 600; + color: #ad1455; + margin-top: 5px; } + +.profile-card .profile-footer { + padding: 0px 15px 15px 15px; } +.profile-card .profile-footer ul { + margin: 0; + padding: 0; + list-style: none; } +.profile-card .profile-footer ul li { + border-bottom: 1px solid #eee; + padding: 10px 0; } +.profile-card .profile-footer ul li:last-child { + border-bottom: none; + margin-bottom: 15px; } +.profile-card .profile-footer ul li span:first-child { + font-weight: bold; } +.profile-card .profile-footer ul li span:last-child { + float: right; } + +.card-about-me .body ul { + margin: 0; + padding: 0; + list-style: none; } +.card-about-me .body ul li { + border-bottom: 1px solid #eee; + margin-bottom: 10px; + padding-bottom: 15px; } +.card-about-me .body ul li:last-child { + border: none; + margin-bottom: 0; + padding-bottom: 0; } +.card-about-me .body ul li .title { + font-weight: bold; + color: #666; } +.card-about-me .body ul li .title i { + margin-right: 2px; + position: relative; + top: 7px; } +.card-about-me .body ul li .content { + margin-top: 10px; + color: #999; + font-size: 13px; } + +.panel-post { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.panel-post .panel-heading { + background-color: #fff; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } +.panel-post .panel-heading .media { + margin-bottom: 0; } +.panel-post .panel-heading .media a img { + width: 42px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + -ms-border-radius: 50%; + border-radius: 50%; } +.panel-post .panel-heading .media .media-body { + padding-top: 5px; } +.panel-post .panel-heading .media .media-body h4 { + font-size: 14px; } +.panel-post .panel-heading .media .media-body h4 a { + color: #666; } +.panel-post .panel-body { + padding: 0; } +.panel-post .panel-body .post .post-heading { + padding: 12px 15px; } +.panel-post .panel-body .post .post-heading p { + margin-bottom: 0; } +.panel-post .panel-footer { + background-color: #fff; + border: none; } +.panel-post .panel-footer ul { + margin: 0; + padding: 0; + list-style: none; } +.panel-post .panel-footer ul li { + display: inline-block; + margin-right: 12px; } +.panel-post .panel-footer ul li:last-child { + float: right; + margin-right: 0; } +.panel-post .panel-footer ul li a { + color: #777; + text-decoration: none; } +.panel-post .panel-footer ul li a i { + font-size: 16px; + position: relative; + top: 4px; + margin-right: 2px; } +.panel-post .panel-footer ul li a span { + font-size: 13px; } +.panel-post .panel-footer .form-group { + margin-bottom: 5px; + margin-top: 20px; } + +/* Maps ======================================== */ +/* Google Maps */ +.gmap { + width: 100%; + height: 400px; } + +/* jVector Map */ +.jvector-map { + width: 100%; + height: 600px; } + +/* Charts ====================================== */ +/* Morris */ +.morris-hover.morris-default-style { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; } + +/* Flot */ +.flot-chart { + width: 100%; + height: 320px; } + +.panel-switch-btn { + position: relative; + right: 20px; + z-index: 9; } +.panel-switch-btn label { + font-weight: bold !important; } + +.legendLabel { + width: 85px !important; + position: relative; + left: 3px; } + +#multiple_axis_chart .legendLabel { + width: 160px !important; } + +/* Sparkline */ +.sparkline { + text-align: center; } + +/* Searchbar =================================== */ +.search-bar { + position: fixed; + top: -100px; + left: 0; + z-index: 9999999; + width: 100%; + -moz-transition: 0.25s; + -o-transition: 0.25s; + -webkit-transition: 0.25s; + transition: 0.25s; } +.search-bar.open { + top: 0; } +.search-bar .search-icon { + position: absolute; + top: 20px; + left: 14px; } +.search-bar .search-icon .material-icons { + font-size: 32px; + color: #999; } +.search-bar .close-search { + position: absolute; + cursor: pointer; + font-size: 30px; + top: 16px; + right: 18px; } +.search-bar .close-search .material-icons { + color: #999; + opacity: 1; + -moz-transition: 0.5s; + -o-transition: 0.5s; + -webkit-transition: 0.5s; + transition: 0.5s; } +.search-bar .close-search .material-icons:hover { + opacity: .5; } +.search-bar input[type="text"] { + width: 100%; + font-size: 16px; + padding: 25px 60px 23px 56px; + border: none; } + +/* Jquery DataTable ============================ */ +.dataTables_wrapper { + position: relative; } +.dataTables_wrapper select { + border: none; + border-bottom: 1px solid #ddd; + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; } +.dataTables_wrapper select:active, .dataTables_wrapper select:focus { + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; } +.dataTables_wrapper input[type="search"] { + -webkit-border-radius: 0; + -moz-border-radius: 0; + -ms-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; + border: none; + font-size: 12px; + border-bottom: 1px solid #ddd; } +.dataTables_wrapper input[type="search"]:focus, .dataTables_wrapper input[type="search"]:active { + border-bottom: 2px solid #1f91f3; } +.dataTables_wrapper .dt-buttons { + float: left; } +.dataTables_wrapper .dt-buttons a.dt-button { + background-color: #607D8B; + color: #fff; + padding: 7px 12px; + margin-right: 5px; + text-decoration: none; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.16), 0 2px 10px rgba(0, 0, 0, 0.12); + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + border-radius: 2px; + border: none; + font-size: 13px; + outline: none; } +.dataTables_wrapper .dt-buttons a.dt-button:active { + opacity: 0.8; } + +.dt-button-info { + position: fixed; + top: 50%; + left: 50%; + min-width: 400px; + text-align: center; + background-color: #fff; + border: 2px solid #999; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; + margin-top: -100px; + margin-left: -200px; + z-index: 21; } +.dt-button-info h2 { + color: #777; } +.dt-button-info div { + color: #777; + margin-bottom: 20px; } + +/* Light Gallery ================================ */ +.lg-outer .lg-thumb-item, +.lg-outer .lg-toogle-thumb { + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + -ms-border-radius: 0 !important; + border-radius: 0 !important; } + +/* For Internet Explorer 10 ===================== */ +html.ie10 .sidebar .menu .list li { + line-height: 30px; } + +html.ie10 .sidebar .menu .list .ml-menu li.active a:not(.menu-toggle).toggled:before { + top: 6px !important; + line-height: 20px !important; } + +html.ie10 .sidebar .user-info .info-container { + top: 15px; } + +html.ie10 .search-bar input[type="text"] { + padding: 26px 60px 26px 56px; } + +html.ie10 .dropdown-menu ul.menu li a { + margin-top: -22px; } + +html.ie10 .bs-searchbox .form-control { + width: 90%; } + +/* For Internet Explorer 11 ===================== */ +html.ie11 .sidebar .menu .list .ml-menu li.active a:not(.menu-toggle).toggled:before { + top: 6px !important; + line-height: 20px !important; } + +html.ie11 .sidebar .user-info .info-container { + top: 15px; } + +html.ie11 .search-bar input[type="text"] { + padding: 26px 60px 26px 56px; } + +html.ie11 .dropdown-menu ul.menu li a { + margin-top: -22px; } + +html.ie11 .bs-searchbox .form-control { + width: 90%; } diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/all-themes.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/all-themes.css new file mode 100644 index 0000000..96fb068 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/all-themes.css @@ -0,0 +1,900 @@ +.theme-red .navbar { + background-color: #F44336; } + +.theme-red .navbar-brand { + color: #fff; } + .theme-red .navbar-brand:hover { + color: #fff; } + .theme-red .navbar-brand:active { + color: #fff; } + .theme-red .navbar-brand:focus { + color: #fff; } + +.theme-red .nav > li > a { + color: #fff; } + .theme-red .nav > li > a:hover { + background-color: transparent; } + .theme-red .nav > li > a:focus { + background-color: transparent; } + +.theme-red .nav .open > a { + background-color: transparent; } + .theme-red .nav .open > a:hover { + background-color: transparent; } + .theme-red .nav .open > a:focus { + background-color: transparent; } + +.theme-red .bars { + color: #fff; } + +.theme-red .sidebar .menu .list li.active { + background-color: transparent; } + .theme-red .sidebar .menu .list li.active > :first-child i, .theme-red .sidebar .menu .list li.active > :first-child span { + color: #F44336; } + +.theme-red .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-red .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-red .sidebar .legal { + background-color: #fff; } + .theme-red .sidebar .legal .copyright a { + color: #F44336 !important; } + +.theme-pink .navbar { + background-color: #E91E63; } + +.theme-pink .navbar-brand { + color: #fff; } + .theme-pink .navbar-brand:hover { + color: #fff; } + .theme-pink .navbar-brand:active { + color: #fff; } + .theme-pink .navbar-brand:focus { + color: #fff; } + +.theme-pink .nav > li > a { + color: #fff; } + .theme-pink .nav > li > a:hover { + background-color: transparent; } + .theme-pink .nav > li > a:focus { + background-color: transparent; } + +.theme-pink .nav .open > a { + background-color: transparent; } + .theme-pink .nav .open > a:hover { + background-color: transparent; } + .theme-pink .nav .open > a:focus { + background-color: transparent; } + +.theme-pink .bars { + color: #fff; } + +.theme-pink .sidebar .menu .list li.active { + background-color: transparent; } + .theme-pink .sidebar .menu .list li.active > :first-child i, .theme-pink .sidebar .menu .list li.active > :first-child span { + color: #E91E63; } + +.theme-pink .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-pink .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-pink .sidebar .legal { + background-color: #fff; } + .theme-pink .sidebar .legal .copyright a { + color: #E91E63 !important; } + +.theme-purple .navbar { + background-color: #9C27B0; } + +.theme-purple .navbar-brand { + color: #fff; } + .theme-purple .navbar-brand:hover { + color: #fff; } + .theme-purple .navbar-brand:active { + color: #fff; } + .theme-purple .navbar-brand:focus { + color: #fff; } + +.theme-purple .nav > li > a { + color: #fff; } + .theme-purple .nav > li > a:hover { + background-color: transparent; } + .theme-purple .nav > li > a:focus { + background-color: transparent; } + +.theme-purple .nav .open > a { + background-color: transparent; } + .theme-purple .nav .open > a:hover { + background-color: transparent; } + .theme-purple .nav .open > a:focus { + background-color: transparent; } + +.theme-purple .bars { + color: #fff; } + +.theme-purple .sidebar .menu .list li.active { + background-color: transparent; } + .theme-purple .sidebar .menu .list li.active > :first-child i, .theme-purple .sidebar .menu .list li.active > :first-child span { + color: #9C27B0; } + +.theme-purple .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-purple .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-purple .sidebar .legal { + background-color: #fff; } + .theme-purple .sidebar .legal .copyright a { + color: #9C27B0 !important; } + +.theme-deep-purple .navbar { + background-color: #673AB7; } + +.theme-deep-purple .navbar-brand { + color: #fff; } + .theme-deep-purple .navbar-brand:hover { + color: #fff; } + .theme-deep-purple .navbar-brand:active { + color: #fff; } + .theme-deep-purple .navbar-brand:focus { + color: #fff; } + +.theme-deep-purple .nav > li > a { + color: #fff; } + .theme-deep-purple .nav > li > a:hover { + background-color: transparent; } + .theme-deep-purple .nav > li > a:focus { + background-color: transparent; } + +.theme-deep-purple .nav .open > a { + background-color: transparent; } + .theme-deep-purple .nav .open > a:hover { + background-color: transparent; } + .theme-deep-purple .nav .open > a:focus { + background-color: transparent; } + +.theme-deep-purple .bars { + color: #fff; } + +.theme-deep-purple .sidebar .menu .list li.active { + background-color: transparent; } + .theme-deep-purple .sidebar .menu .list li.active > :first-child i, .theme-deep-purple .sidebar .menu .list li.active > :first-child span { + color: #673AB7; } + +.theme-deep-purple .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-deep-purple .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-deep-purple .sidebar .legal { + background-color: #fff; } + .theme-deep-purple .sidebar .legal .copyright a { + color: #673AB7 !important; } + +.theme-indigo .navbar { + background-color: #3F51B5; } + +.theme-indigo .navbar-brand { + color: #fff; } + .theme-indigo .navbar-brand:hover { + color: #fff; } + .theme-indigo .navbar-brand:active { + color: #fff; } + .theme-indigo .navbar-brand:focus { + color: #fff; } + +.theme-indigo .nav > li > a { + color: #fff; } + .theme-indigo .nav > li > a:hover { + background-color: transparent; } + .theme-indigo .nav > li > a:focus { + background-color: transparent; } + +.theme-indigo .nav .open > a { + background-color: transparent; } + .theme-indigo .nav .open > a:hover { + background-color: transparent; } + .theme-indigo .nav .open > a:focus { + background-color: transparent; } + +.theme-indigo .bars { + color: #fff; } + +.theme-indigo .sidebar .menu .list li.active { + background-color: transparent; } + .theme-indigo .sidebar .menu .list li.active > :first-child i, .theme-indigo .sidebar .menu .list li.active > :first-child span { + color: #3F51B5; } + +.theme-indigo .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-indigo .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-indigo .sidebar .legal { + background-color: #fff; } + .theme-indigo .sidebar .legal .copyright a { + color: #3F51B5 !important; } + +.theme-blue .navbar { + background-color: #2196F3; } + +.theme-blue .navbar-brand { + color: #fff; } + .theme-blue .navbar-brand:hover { + color: #fff; } + .theme-blue .navbar-brand:active { + color: #fff; } + .theme-blue .navbar-brand:focus { + color: #fff; } + +.theme-blue .nav > li > a { + color: #fff; } + .theme-blue .nav > li > a:hover { + background-color: transparent; } + .theme-blue .nav > li > a:focus { + background-color: transparent; } + +.theme-blue .nav .open > a { + background-color: transparent; } + .theme-blue .nav .open > a:hover { + background-color: transparent; } + .theme-blue .nav .open > a:focus { + background-color: transparent; } + +.theme-blue .bars { + color: #fff; } + +.theme-blue .sidebar .menu .list li.active { + background-color: transparent; } + .theme-blue .sidebar .menu .list li.active > :first-child i, .theme-blue .sidebar .menu .list li.active > :first-child span { + color: #2196F3; } + +.theme-blue .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-blue .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-blue .sidebar .legal { + background-color: #fff; } + .theme-blue .sidebar .legal .copyright a { + color: #2196F3 !important; } + +.theme-light-blue .navbar { + background-color: #03A9F4; } + +.theme-light-blue .navbar-brand { + color: #fff; } + .theme-light-blue .navbar-brand:hover { + color: #fff; } + .theme-light-blue .navbar-brand:active { + color: #fff; } + .theme-light-blue .navbar-brand:focus { + color: #fff; } + +.theme-light-blue .nav > li > a { + color: #fff; } + .theme-light-blue .nav > li > a:hover { + background-color: transparent; } + .theme-light-blue .nav > li > a:focus { + background-color: transparent; } + +.theme-light-blue .nav .open > a { + background-color: transparent; } + .theme-light-blue .nav .open > a:hover { + background-color: transparent; } + .theme-light-blue .nav .open > a:focus { + background-color: transparent; } + +.theme-light-blue .bars { + color: #fff; } + +.theme-light-blue .sidebar .menu .list li.active { + background-color: transparent; } + .theme-light-blue .sidebar .menu .list li.active > :first-child i, .theme-light-blue .sidebar .menu .list li.active > :first-child span { + color: #03A9F4; } + +.theme-light-blue .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-light-blue .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-light-blue .sidebar .legal { + background-color: #fff; } + .theme-light-blue .sidebar .legal .copyright a { + color: #03A9F4 !important; } + +.theme-cyan .navbar { + background-color: #00BCD4; } + +.theme-cyan .navbar-brand { + color: #fff; } + .theme-cyan .navbar-brand:hover { + color: #fff; } + .theme-cyan .navbar-brand:active { + color: #fff; } + .theme-cyan .navbar-brand:focus { + color: #fff; } + +.theme-cyan .nav > li > a { + color: #fff; } + .theme-cyan .nav > li > a:hover { + background-color: transparent; } + .theme-cyan .nav > li > a:focus { + background-color: transparent; } + +.theme-cyan .nav .open > a { + background-color: transparent; } + .theme-cyan .nav .open > a:hover { + background-color: transparent; } + .theme-cyan .nav .open > a:focus { + background-color: transparent; } + +.theme-cyan .bars { + color: #fff; } + +.theme-cyan .sidebar .menu .list li.active { + background-color: transparent; } + .theme-cyan .sidebar .menu .list li.active > :first-child i, .theme-cyan .sidebar .menu .list li.active > :first-child span { + color: #00BCD4; } + +.theme-cyan .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-cyan .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-cyan .sidebar .legal { + background-color: #fff; } + .theme-cyan .sidebar .legal .copyright a { + color: #00BCD4 !important; } + +.theme-teal .navbar { + background-color: #009688; } + +.theme-teal .navbar-brand { + color: #fff; } + .theme-teal .navbar-brand:hover { + color: #fff; } + .theme-teal .navbar-brand:active { + color: #fff; } + .theme-teal .navbar-brand:focus { + color: #fff; } + +.theme-teal .nav > li > a { + color: #fff; } + .theme-teal .nav > li > a:hover { + background-color: transparent; } + .theme-teal .nav > li > a:focus { + background-color: transparent; } + +.theme-teal .nav .open > a { + background-color: transparent; } + .theme-teal .nav .open > a:hover { + background-color: transparent; } + .theme-teal .nav .open > a:focus { + background-color: transparent; } + +.theme-teal .bars { + color: #fff; } + +.theme-teal .sidebar .menu .list li.active { + background-color: transparent; } + .theme-teal .sidebar .menu .list li.active > :first-child i, .theme-teal .sidebar .menu .list li.active > :first-child span { + color: #009688; } + +.theme-teal .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-teal .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-teal .sidebar .legal { + background-color: #fff; } + .theme-teal .sidebar .legal .copyright a { + color: #009688 !important; } + +.theme-green .navbar { + background-color: #4CAF50; } + +.theme-green .navbar-brand { + color: #fff; } + .theme-green .navbar-brand:hover { + color: #fff; } + .theme-green .navbar-brand:active { + color: #fff; } + .theme-green .navbar-brand:focus { + color: #fff; } + +.theme-green .nav > li > a { + color: #fff; } + .theme-green .nav > li > a:hover { + background-color: transparent; } + .theme-green .nav > li > a:focus { + background-color: transparent; } + +.theme-green .nav .open > a { + background-color: transparent; } + .theme-green .nav .open > a:hover { + background-color: transparent; } + .theme-green .nav .open > a:focus { + background-color: transparent; } + +.theme-green .bars { + color: #fff; } + +.theme-green .sidebar .menu .list li.active { + background-color: transparent; } + .theme-green .sidebar .menu .list li.active > :first-child i, .theme-green .sidebar .menu .list li.active > :first-child span { + color: #4CAF50; } + +.theme-green .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-green .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-green .sidebar .legal { + background-color: #fff; } + .theme-green .sidebar .legal .copyright a { + color: #4CAF50 !important; } + +.theme-light-green .navbar { + background-color: #8BC34A; } + +.theme-light-green .navbar-brand { + color: #fff; } + .theme-light-green .navbar-brand:hover { + color: #fff; } + .theme-light-green .navbar-brand:active { + color: #fff; } + .theme-light-green .navbar-brand:focus { + color: #fff; } + +.theme-light-green .nav > li > a { + color: #fff; } + .theme-light-green .nav > li > a:hover { + background-color: transparent; } + .theme-light-green .nav > li > a:focus { + background-color: transparent; } + +.theme-light-green .nav .open > a { + background-color: transparent; } + .theme-light-green .nav .open > a:hover { + background-color: transparent; } + .theme-light-green .nav .open > a:focus { + background-color: transparent; } + +.theme-light-green .bars { + color: #fff; } + +.theme-light-green .sidebar .menu .list li.active { + background-color: transparent; } + .theme-light-green .sidebar .menu .list li.active > :first-child i, .theme-light-green .sidebar .menu .list li.active > :first-child span { + color: #8BC34A; } + +.theme-light-green .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-light-green .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-light-green .sidebar .legal { + background-color: #fff; } + .theme-light-green .sidebar .legal .copyright a { + color: #8BC34A !important; } + +.theme-lime .navbar { + background-color: #CDDC39; } + +.theme-lime .navbar-brand { + color: #fff; } + .theme-lime .navbar-brand:hover { + color: #fff; } + .theme-lime .navbar-brand:active { + color: #fff; } + .theme-lime .navbar-brand:focus { + color: #fff; } + +.theme-lime .nav > li > a { + color: #fff; } + .theme-lime .nav > li > a:hover { + background-color: transparent; } + .theme-lime .nav > li > a:focus { + background-color: transparent; } + +.theme-lime .nav .open > a { + background-color: transparent; } + .theme-lime .nav .open > a:hover { + background-color: transparent; } + .theme-lime .nav .open > a:focus { + background-color: transparent; } + +.theme-lime .bars { + color: #fff; } + +.theme-lime .sidebar .menu .list li.active { + background-color: transparent; } + .theme-lime .sidebar .menu .list li.active > :first-child i, .theme-lime .sidebar .menu .list li.active > :first-child span { + color: #CDDC39; } + +.theme-lime .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-lime .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-lime .sidebar .legal { + background-color: #fff; } + .theme-lime .sidebar .legal .copyright a { + color: #CDDC39 !important; } + +.theme-yellow .navbar { + background-color: #FFEB3B; } + +.theme-yellow .navbar-brand { + color: #fff; } + .theme-yellow .navbar-brand:hover { + color: #fff; } + .theme-yellow .navbar-brand:active { + color: #fff; } + .theme-yellow .navbar-brand:focus { + color: #fff; } + +.theme-yellow .nav > li > a { + color: #fff; } + .theme-yellow .nav > li > a:hover { + background-color: transparent; } + .theme-yellow .nav > li > a:focus { + background-color: transparent; } + +.theme-yellow .nav .open > a { + background-color: transparent; } + .theme-yellow .nav .open > a:hover { + background-color: transparent; } + .theme-yellow .nav .open > a:focus { + background-color: transparent; } + +.theme-yellow .bars { + color: #fff; } + +.theme-yellow .sidebar .menu .list li.active { + background-color: transparent; } + .theme-yellow .sidebar .menu .list li.active > :first-child i, .theme-yellow .sidebar .menu .list li.active > :first-child span { + color: #FFEB3B; } + +.theme-yellow .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-yellow .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-yellow .sidebar .legal { + background-color: #fff; } + .theme-yellow .sidebar .legal .copyright a { + color: #FFEB3B !important; } + +.theme-amber .navbar { + background-color: #FFC107; } + +.theme-amber .navbar-brand { + color: #fff; } + .theme-amber .navbar-brand:hover { + color: #fff; } + .theme-amber .navbar-brand:active { + color: #fff; } + .theme-amber .navbar-brand:focus { + color: #fff; } + +.theme-amber .nav > li > a { + color: #fff; } + .theme-amber .nav > li > a:hover { + background-color: transparent; } + .theme-amber .nav > li > a:focus { + background-color: transparent; } + +.theme-amber .nav .open > a { + background-color: transparent; } + .theme-amber .nav .open > a:hover { + background-color: transparent; } + .theme-amber .nav .open > a:focus { + background-color: transparent; } + +.theme-amber .bars { + color: #fff; } + +.theme-amber .sidebar .menu .list li.active { + background-color: transparent; } + .theme-amber .sidebar .menu .list li.active > :first-child i, .theme-amber .sidebar .menu .list li.active > :first-child span { + color: #FFC107; } + +.theme-amber .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-amber .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-amber .sidebar .legal { + background-color: #fff; } + .theme-amber .sidebar .legal .copyright a { + color: #FFC107 !important; } + +.theme-orange .navbar { + background-color: #FF9800; } + +.theme-orange .navbar-brand { + color: #fff; } + .theme-orange .navbar-brand:hover { + color: #fff; } + .theme-orange .navbar-brand:active { + color: #fff; } + .theme-orange .navbar-brand:focus { + color: #fff; } + +.theme-orange .nav > li > a { + color: #fff; } + .theme-orange .nav > li > a:hover { + background-color: transparent; } + .theme-orange .nav > li > a:focus { + background-color: transparent; } + +.theme-orange .nav .open > a { + background-color: transparent; } + .theme-orange .nav .open > a:hover { + background-color: transparent; } + .theme-orange .nav .open > a:focus { + background-color: transparent; } + +.theme-orange .bars { + color: #fff; } + +.theme-orange .sidebar .menu .list li.active { + background-color: transparent; } + .theme-orange .sidebar .menu .list li.active > :first-child i, .theme-orange .sidebar .menu .list li.active > :first-child span { + color: #FF9800; } + +.theme-orange .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-orange .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-orange .sidebar .legal { + background-color: #fff; } + .theme-orange .sidebar .legal .copyright a { + color: #FF9800 !important; } + +.theme-deep-orange .navbar { + background-color: #FF5722; } + +.theme-deep-orange .navbar-brand { + color: #fff; } + .theme-deep-orange .navbar-brand:hover { + color: #fff; } + .theme-deep-orange .navbar-brand:active { + color: #fff; } + .theme-deep-orange .navbar-brand:focus { + color: #fff; } + +.theme-deep-orange .nav > li > a { + color: #fff; } + .theme-deep-orange .nav > li > a:hover { + background-color: transparent; } + .theme-deep-orange .nav > li > a:focus { + background-color: transparent; } + +.theme-deep-orange .nav .open > a { + background-color: transparent; } + .theme-deep-orange .nav .open > a:hover { + background-color: transparent; } + .theme-deep-orange .nav .open > a:focus { + background-color: transparent; } + +.theme-deep-orange .bars { + color: #fff; } + +.theme-deep-orange .sidebar .menu .list li.active { + background-color: transparent; } + .theme-deep-orange .sidebar .menu .list li.active > :first-child i, .theme-deep-orange .sidebar .menu .list li.active > :first-child span { + color: #FF5722; } + +.theme-deep-orange .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-deep-orange .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-deep-orange .sidebar .legal { + background-color: #fff; } + .theme-deep-orange .sidebar .legal .copyright a { + color: #FF5722 !important; } + +.theme-brown .navbar { + background-color: #795548; } + +.theme-brown .navbar-brand { + color: #fff; } + .theme-brown .navbar-brand:hover { + color: #fff; } + .theme-brown .navbar-brand:active { + color: #fff; } + .theme-brown .navbar-brand:focus { + color: #fff; } + +.theme-brown .nav > li > a { + color: #fff; } + .theme-brown .nav > li > a:hover { + background-color: transparent; } + .theme-brown .nav > li > a:focus { + background-color: transparent; } + +.theme-brown .nav .open > a { + background-color: transparent; } + .theme-brown .nav .open > a:hover { + background-color: transparent; } + .theme-brown .nav .open > a:focus { + background-color: transparent; } + +.theme-brown .bars { + color: #fff; } + +.theme-brown .sidebar .menu .list li.active { + background-color: transparent; } + .theme-brown .sidebar .menu .list li.active > :first-child i, .theme-brown .sidebar .menu .list li.active > :first-child span { + color: #795548; } + +.theme-brown .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-brown .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-brown .sidebar .legal { + background-color: #fff; } + .theme-brown .sidebar .legal .copyright a { + color: #795548 !important; } + +.theme-grey .navbar { + background-color: #9E9E9E; } + +.theme-grey .navbar-brand { + color: #fff; } + .theme-grey .navbar-brand:hover { + color: #fff; } + .theme-grey .navbar-brand:active { + color: #fff; } + .theme-grey .navbar-brand:focus { + color: #fff; } + +.theme-grey .nav > li > a { + color: #fff; } + .theme-grey .nav > li > a:hover { + background-color: transparent; } + .theme-grey .nav > li > a:focus { + background-color: transparent; } + +.theme-grey .nav .open > a { + background-color: transparent; } + .theme-grey .nav .open > a:hover { + background-color: transparent; } + .theme-grey .nav .open > a:focus { + background-color: transparent; } + +.theme-grey .bars { + color: #fff; } + +.theme-grey .sidebar .menu .list li.active { + background-color: transparent; } + .theme-grey .sidebar .menu .list li.active > :first-child i, .theme-grey .sidebar .menu .list li.active > :first-child span { + color: #9E9E9E; } + +.theme-grey .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-grey .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-grey .sidebar .legal { + background-color: #fff; } + .theme-grey .sidebar .legal .copyright a { + color: #9E9E9E !important; } + +.theme-blue-grey .navbar { + background-color: #607D8B; } + +.theme-blue-grey .navbar-brand { + color: #fff; } + .theme-blue-grey .navbar-brand:hover { + color: #fff; } + .theme-blue-grey .navbar-brand:active { + color: #fff; } + .theme-blue-grey .navbar-brand:focus { + color: #fff; } + +.theme-blue-grey .nav > li > a { + color: #fff; } + .theme-blue-grey .nav > li > a:hover { + background-color: transparent; } + .theme-blue-grey .nav > li > a:focus { + background-color: transparent; } + +.theme-blue-grey .nav .open > a { + background-color: transparent; } + .theme-blue-grey .nav .open > a:hover { + background-color: transparent; } + .theme-blue-grey .nav .open > a:focus { + background-color: transparent; } + +.theme-blue-grey .bars { + color: #fff; } + +.theme-blue-grey .sidebar .menu .list li.active { + background-color: transparent; } + .theme-blue-grey .sidebar .menu .list li.active > :first-child i, .theme-blue-grey .sidebar .menu .list li.active > :first-child span { + color: #607D8B; } + +.theme-blue-grey .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-blue-grey .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-blue-grey .sidebar .legal { + background-color: #fff; } + .theme-blue-grey .sidebar .legal .copyright a { + color: #607D8B !important; } + +.theme-black .navbar { + background-color: #000; } + +.theme-black .navbar-brand { + color: #fff; } + .theme-black .navbar-brand:hover { + color: #fff; } + .theme-black .navbar-brand:active { + color: #fff; } + .theme-black .navbar-brand:focus { + color: #fff; } + +.theme-black .nav > li > a { + color: #fff; } + .theme-black .nav > li > a:hover { + background-color: transparent; } + .theme-black .nav > li > a:focus { + background-color: transparent; } + +.theme-black .nav .open > a { + background-color: transparent; } + .theme-black .nav .open > a:hover { + background-color: transparent; } + .theme-black .nav .open > a:focus { + background-color: transparent; } + +.theme-black .bars { + color: #fff; } + +.theme-black .sidebar .menu .list li.active { + background-color: transparent; } + .theme-black .sidebar .menu .list li.active > :first-child i, .theme-black .sidebar .menu .list li.active > :first-child span { + color: #000; } + +.theme-black .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-black .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-black .sidebar .legal { + background-color: #fff; } + .theme-black .sidebar .legal .copyright a { + color: #000 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/all-themes.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/all-themes.min.css new file mode 100644 index 0000000..ecb4023 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/all-themes.min.css @@ -0,0 +1 @@ +.theme-red .navbar{background-color:#f44336;}.theme-red .navbar-brand{color:#fff;}.theme-red .navbar-brand:hover{color:#fff;}.theme-red .navbar-brand:active{color:#fff;}.theme-red .navbar-brand:focus{color:#fff;}.theme-red .nav>li>a{color:#fff;}.theme-red .nav>li>a:hover{background-color:transparent;}.theme-red .nav>li>a:focus{background-color:transparent;}.theme-red .nav .open>a{background-color:transparent;}.theme-red .nav .open>a:hover{background-color:transparent;}.theme-red .nav .open>a:focus{background-color:transparent;}.theme-red .bars{color:#fff;}.theme-red .sidebar .menu .list li.active{background-color:transparent;}.theme-red .sidebar .menu .list li.active>:first-child i,.theme-red .sidebar .menu .list li.active>:first-child span{color:#f44336;}.theme-red .sidebar .menu .list .toggled{background-color:transparent;}.theme-red .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-red .sidebar .legal{background-color:#fff;}.theme-red .sidebar .legal .copyright a{color:#f44336 !important;}.theme-pink .navbar{background-color:#e91e63;}.theme-pink .navbar-brand{color:#fff;}.theme-pink .navbar-brand:hover{color:#fff;}.theme-pink .navbar-brand:active{color:#fff;}.theme-pink .navbar-brand:focus{color:#fff;}.theme-pink .nav>li>a{color:#fff;}.theme-pink .nav>li>a:hover{background-color:transparent;}.theme-pink .nav>li>a:focus{background-color:transparent;}.theme-pink .nav .open>a{background-color:transparent;}.theme-pink .nav .open>a:hover{background-color:transparent;}.theme-pink .nav .open>a:focus{background-color:transparent;}.theme-pink .bars{color:#fff;}.theme-pink .sidebar .menu .list li.active{background-color:transparent;}.theme-pink .sidebar .menu .list li.active>:first-child i,.theme-pink .sidebar .menu .list li.active>:first-child span{color:#e91e63;}.theme-pink .sidebar .menu .list .toggled{background-color:transparent;}.theme-pink .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-pink .sidebar .legal{background-color:#fff;}.theme-pink .sidebar .legal .copyright a{color:#e91e63 !important;}.theme-purple .navbar{background-color:#9c27b0;}.theme-purple .navbar-brand{color:#fff;}.theme-purple .navbar-brand:hover{color:#fff;}.theme-purple .navbar-brand:active{color:#fff;}.theme-purple .navbar-brand:focus{color:#fff;}.theme-purple .nav>li>a{color:#fff;}.theme-purple .nav>li>a:hover{background-color:transparent;}.theme-purple .nav>li>a:focus{background-color:transparent;}.theme-purple .nav .open>a{background-color:transparent;}.theme-purple .nav .open>a:hover{background-color:transparent;}.theme-purple .nav .open>a:focus{background-color:transparent;}.theme-purple .bars{color:#fff;}.theme-purple .sidebar .menu .list li.active{background-color:transparent;}.theme-purple .sidebar .menu .list li.active>:first-child i,.theme-purple .sidebar .menu .list li.active>:first-child span{color:#9c27b0;}.theme-purple .sidebar .menu .list .toggled{background-color:transparent;}.theme-purple .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-purple .sidebar .legal{background-color:#fff;}.theme-purple .sidebar .legal .copyright a{color:#9c27b0 !important;}.theme-deep-purple .navbar{background-color:#673ab7;}.theme-deep-purple .navbar-brand{color:#fff;}.theme-deep-purple .navbar-brand:hover{color:#fff;}.theme-deep-purple .navbar-brand:active{color:#fff;}.theme-deep-purple .navbar-brand:focus{color:#fff;}.theme-deep-purple .nav>li>a{color:#fff;}.theme-deep-purple .nav>li>a:hover{background-color:transparent;}.theme-deep-purple .nav>li>a:focus{background-color:transparent;}.theme-deep-purple .nav .open>a{background-color:transparent;}.theme-deep-purple .nav .open>a:hover{background-color:transparent;}.theme-deep-purple .nav .open>a:focus{background-color:transparent;}.theme-deep-purple .bars{color:#fff;}.theme-deep-purple .sidebar .menu .list li.active{background-color:transparent;}.theme-deep-purple .sidebar .menu .list li.active>:first-child i,.theme-deep-purple .sidebar .menu .list li.active>:first-child span{color:#673ab7;}.theme-deep-purple .sidebar .menu .list .toggled{background-color:transparent;}.theme-deep-purple .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-deep-purple .sidebar .legal{background-color:#fff;}.theme-deep-purple .sidebar .legal .copyright a{color:#673ab7 !important;}.theme-indigo .navbar{background-color:#3f51b5;}.theme-indigo .navbar-brand{color:#fff;}.theme-indigo .navbar-brand:hover{color:#fff;}.theme-indigo .navbar-brand:active{color:#fff;}.theme-indigo .navbar-brand:focus{color:#fff;}.theme-indigo .nav>li>a{color:#fff;}.theme-indigo .nav>li>a:hover{background-color:transparent;}.theme-indigo .nav>li>a:focus{background-color:transparent;}.theme-indigo .nav .open>a{background-color:transparent;}.theme-indigo .nav .open>a:hover{background-color:transparent;}.theme-indigo .nav .open>a:focus{background-color:transparent;}.theme-indigo .bars{color:#fff;}.theme-indigo .sidebar .menu .list li.active{background-color:transparent;}.theme-indigo .sidebar .menu .list li.active>:first-child i,.theme-indigo .sidebar .menu .list li.active>:first-child span{color:#3f51b5;}.theme-indigo .sidebar .menu .list .toggled{background-color:transparent;}.theme-indigo .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-indigo .sidebar .legal{background-color:#fff;}.theme-indigo .sidebar .legal .copyright a{color:#3f51b5 !important;}.theme-blue .navbar{background-color:#2196f3;}.theme-blue .navbar-brand{color:#fff;}.theme-blue .navbar-brand:hover{color:#fff;}.theme-blue .navbar-brand:active{color:#fff;}.theme-blue .navbar-brand:focus{color:#fff;}.theme-blue .nav>li>a{color:#fff;}.theme-blue .nav>li>a:hover{background-color:transparent;}.theme-blue .nav>li>a:focus{background-color:transparent;}.theme-blue .nav .open>a{background-color:transparent;}.theme-blue .nav .open>a:hover{background-color:transparent;}.theme-blue .nav .open>a:focus{background-color:transparent;}.theme-blue .bars{color:#fff;}.theme-blue .sidebar .menu .list li.active{background-color:transparent;}.theme-blue .sidebar .menu .list li.active>:first-child i,.theme-blue .sidebar .menu .list li.active>:first-child span{color:#2196f3;}.theme-blue .sidebar .menu .list .toggled{background-color:transparent;}.theme-blue .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-blue .sidebar .legal{background-color:#fff;}.theme-blue .sidebar .legal .copyright a{color:#2196f3 !important;}.theme-light-blue .navbar{background-color:#03a9f4;}.theme-light-blue .navbar-brand{color:#fff;}.theme-light-blue .navbar-brand:hover{color:#fff;}.theme-light-blue .navbar-brand:active{color:#fff;}.theme-light-blue .navbar-brand:focus{color:#fff;}.theme-light-blue .nav>li>a{color:#fff;}.theme-light-blue .nav>li>a:hover{background-color:transparent;}.theme-light-blue .nav>li>a:focus{background-color:transparent;}.theme-light-blue .nav .open>a{background-color:transparent;}.theme-light-blue .nav .open>a:hover{background-color:transparent;}.theme-light-blue .nav .open>a:focus{background-color:transparent;}.theme-light-blue .bars{color:#fff;}.theme-light-blue .sidebar .menu .list li.active{background-color:transparent;}.theme-light-blue .sidebar .menu .list li.active>:first-child i,.theme-light-blue .sidebar .menu .list li.active>:first-child span{color:#03a9f4;}.theme-light-blue .sidebar .menu .list .toggled{background-color:transparent;}.theme-light-blue .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-light-blue .sidebar .legal{background-color:#fff;}.theme-light-blue .sidebar .legal .copyright a{color:#03a9f4 !important;}.theme-cyan .navbar{background-color:#00bcd4;}.theme-cyan .navbar-brand{color:#fff;}.theme-cyan .navbar-brand:hover{color:#fff;}.theme-cyan .navbar-brand:active{color:#fff;}.theme-cyan .navbar-brand:focus{color:#fff;}.theme-cyan .nav>li>a{color:#fff;}.theme-cyan .nav>li>a:hover{background-color:transparent;}.theme-cyan .nav>li>a:focus{background-color:transparent;}.theme-cyan .nav .open>a{background-color:transparent;}.theme-cyan .nav .open>a:hover{background-color:transparent;}.theme-cyan .nav .open>a:focus{background-color:transparent;}.theme-cyan .bars{color:#fff;}.theme-cyan .sidebar .menu .list li.active{background-color:transparent;}.theme-cyan .sidebar .menu .list li.active>:first-child i,.theme-cyan .sidebar .menu .list li.active>:first-child span{color:#00bcd4;}.theme-cyan .sidebar .menu .list .toggled{background-color:transparent;}.theme-cyan .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-cyan .sidebar .legal{background-color:#fff;}.theme-cyan .sidebar .legal .copyright a{color:#00bcd4 !important;}.theme-teal .navbar{background-color:#009688;}.theme-teal .navbar-brand{color:#fff;}.theme-teal .navbar-brand:hover{color:#fff;}.theme-teal .navbar-brand:active{color:#fff;}.theme-teal .navbar-brand:focus{color:#fff;}.theme-teal .nav>li>a{color:#fff;}.theme-teal .nav>li>a:hover{background-color:transparent;}.theme-teal .nav>li>a:focus{background-color:transparent;}.theme-teal .nav .open>a{background-color:transparent;}.theme-teal .nav .open>a:hover{background-color:transparent;}.theme-teal .nav .open>a:focus{background-color:transparent;}.theme-teal .bars{color:#fff;}.theme-teal .sidebar .menu .list li.active{background-color:transparent;}.theme-teal .sidebar .menu .list li.active>:first-child i,.theme-teal .sidebar .menu .list li.active>:first-child span{color:#009688;}.theme-teal .sidebar .menu .list .toggled{background-color:transparent;}.theme-teal .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-teal .sidebar .legal{background-color:#fff;}.theme-teal .sidebar .legal .copyright a{color:#009688 !important;}.theme-green .navbar{background-color:#4caf50;}.theme-green .navbar-brand{color:#fff;}.theme-green .navbar-brand:hover{color:#fff;}.theme-green .navbar-brand:active{color:#fff;}.theme-green .navbar-brand:focus{color:#fff;}.theme-green .nav>li>a{color:#fff;}.theme-green .nav>li>a:hover{background-color:transparent;}.theme-green .nav>li>a:focus{background-color:transparent;}.theme-green .nav .open>a{background-color:transparent;}.theme-green .nav .open>a:hover{background-color:transparent;}.theme-green .nav .open>a:focus{background-color:transparent;}.theme-green .bars{color:#fff;}.theme-green .sidebar .menu .list li.active{background-color:transparent;}.theme-green .sidebar .menu .list li.active>:first-child i,.theme-green .sidebar .menu .list li.active>:first-child span{color:#4caf50;}.theme-green .sidebar .menu .list .toggled{background-color:transparent;}.theme-green .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-green .sidebar .legal{background-color:#fff;}.theme-green .sidebar .legal .copyright a{color:#4caf50 !important;}.theme-light-green .navbar{background-color:#8bc34a;}.theme-light-green .navbar-brand{color:#fff;}.theme-light-green .navbar-brand:hover{color:#fff;}.theme-light-green .navbar-brand:active{color:#fff;}.theme-light-green .navbar-brand:focus{color:#fff;}.theme-light-green .nav>li>a{color:#fff;}.theme-light-green .nav>li>a:hover{background-color:transparent;}.theme-light-green .nav>li>a:focus{background-color:transparent;}.theme-light-green .nav .open>a{background-color:transparent;}.theme-light-green .nav .open>a:hover{background-color:transparent;}.theme-light-green .nav .open>a:focus{background-color:transparent;}.theme-light-green .bars{color:#fff;}.theme-light-green .sidebar .menu .list li.active{background-color:transparent;}.theme-light-green .sidebar .menu .list li.active>:first-child i,.theme-light-green .sidebar .menu .list li.active>:first-child span{color:#8bc34a;}.theme-light-green .sidebar .menu .list .toggled{background-color:transparent;}.theme-light-green .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-light-green .sidebar .legal{background-color:#fff;}.theme-light-green .sidebar .legal .copyright a{color:#8bc34a !important;}.theme-lime .navbar{background-color:#cddc39;}.theme-lime .navbar-brand{color:#fff;}.theme-lime .navbar-brand:hover{color:#fff;}.theme-lime .navbar-brand:active{color:#fff;}.theme-lime .navbar-brand:focus{color:#fff;}.theme-lime .nav>li>a{color:#fff;}.theme-lime .nav>li>a:hover{background-color:transparent;}.theme-lime .nav>li>a:focus{background-color:transparent;}.theme-lime .nav .open>a{background-color:transparent;}.theme-lime .nav .open>a:hover{background-color:transparent;}.theme-lime .nav .open>a:focus{background-color:transparent;}.theme-lime .bars{color:#fff;}.theme-lime .sidebar .menu .list li.active{background-color:transparent;}.theme-lime .sidebar .menu .list li.active>:first-child i,.theme-lime .sidebar .menu .list li.active>:first-child span{color:#cddc39;}.theme-lime .sidebar .menu .list .toggled{background-color:transparent;}.theme-lime .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-lime .sidebar .legal{background-color:#fff;}.theme-lime .sidebar .legal .copyright a{color:#cddc39 !important;}.theme-yellow .navbar{background-color:#ffeb3b;}.theme-yellow .navbar-brand{color:#fff;}.theme-yellow .navbar-brand:hover{color:#fff;}.theme-yellow .navbar-brand:active{color:#fff;}.theme-yellow .navbar-brand:focus{color:#fff;}.theme-yellow .nav>li>a{color:#fff;}.theme-yellow .nav>li>a:hover{background-color:transparent;}.theme-yellow .nav>li>a:focus{background-color:transparent;}.theme-yellow .nav .open>a{background-color:transparent;}.theme-yellow .nav .open>a:hover{background-color:transparent;}.theme-yellow .nav .open>a:focus{background-color:transparent;}.theme-yellow .bars{color:#fff;}.theme-yellow .sidebar .menu .list li.active{background-color:transparent;}.theme-yellow .sidebar .menu .list li.active>:first-child i,.theme-yellow .sidebar .menu .list li.active>:first-child span{color:#ffeb3b;}.theme-yellow .sidebar .menu .list .toggled{background-color:transparent;}.theme-yellow .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-yellow .sidebar .legal{background-color:#fff;}.theme-yellow .sidebar .legal .copyright a{color:#ffeb3b !important;}.theme-amber .navbar{background-color:#ffc107;}.theme-amber .navbar-brand{color:#fff;}.theme-amber .navbar-brand:hover{color:#fff;}.theme-amber .navbar-brand:active{color:#fff;}.theme-amber .navbar-brand:focus{color:#fff;}.theme-amber .nav>li>a{color:#fff;}.theme-amber .nav>li>a:hover{background-color:transparent;}.theme-amber .nav>li>a:focus{background-color:transparent;}.theme-amber .nav .open>a{background-color:transparent;}.theme-amber .nav .open>a:hover{background-color:transparent;}.theme-amber .nav .open>a:focus{background-color:transparent;}.theme-amber .bars{color:#fff;}.theme-amber .sidebar .menu .list li.active{background-color:transparent;}.theme-amber .sidebar .menu .list li.active>:first-child i,.theme-amber .sidebar .menu .list li.active>:first-child span{color:#ffc107;}.theme-amber .sidebar .menu .list .toggled{background-color:transparent;}.theme-amber .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-amber .sidebar .legal{background-color:#fff;}.theme-amber .sidebar .legal .copyright a{color:#ffc107 !important;}.theme-orange .navbar{background-color:#ff9800;}.theme-orange .navbar-brand{color:#fff;}.theme-orange .navbar-brand:hover{color:#fff;}.theme-orange .navbar-brand:active{color:#fff;}.theme-orange .navbar-brand:focus{color:#fff;}.theme-orange .nav>li>a{color:#fff;}.theme-orange .nav>li>a:hover{background-color:transparent;}.theme-orange .nav>li>a:focus{background-color:transparent;}.theme-orange .nav .open>a{background-color:transparent;}.theme-orange .nav .open>a:hover{background-color:transparent;}.theme-orange .nav .open>a:focus{background-color:transparent;}.theme-orange .bars{color:#fff;}.theme-orange .sidebar .menu .list li.active{background-color:transparent;}.theme-orange .sidebar .menu .list li.active>:first-child i,.theme-orange .sidebar .menu .list li.active>:first-child span{color:#ff9800;}.theme-orange .sidebar .menu .list .toggled{background-color:transparent;}.theme-orange .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-orange .sidebar .legal{background-color:#fff;}.theme-orange .sidebar .legal .copyright a{color:#ff9800 !important;}.theme-deep-orange .navbar{background-color:#ff5722;}.theme-deep-orange .navbar-brand{color:#fff;}.theme-deep-orange .navbar-brand:hover{color:#fff;}.theme-deep-orange .navbar-brand:active{color:#fff;}.theme-deep-orange .navbar-brand:focus{color:#fff;}.theme-deep-orange .nav>li>a{color:#fff;}.theme-deep-orange .nav>li>a:hover{background-color:transparent;}.theme-deep-orange .nav>li>a:focus{background-color:transparent;}.theme-deep-orange .nav .open>a{background-color:transparent;}.theme-deep-orange .nav .open>a:hover{background-color:transparent;}.theme-deep-orange .nav .open>a:focus{background-color:transparent;}.theme-deep-orange .bars{color:#fff;}.theme-deep-orange .sidebar .menu .list li.active{background-color:transparent;}.theme-deep-orange .sidebar .menu .list li.active>:first-child i,.theme-deep-orange .sidebar .menu .list li.active>:first-child span{color:#ff5722;}.theme-deep-orange .sidebar .menu .list .toggled{background-color:transparent;}.theme-deep-orange .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-deep-orange .sidebar .legal{background-color:#fff;}.theme-deep-orange .sidebar .legal .copyright a{color:#ff5722 !important;}.theme-brown .navbar{background-color:#795548;}.theme-brown .navbar-brand{color:#fff;}.theme-brown .navbar-brand:hover{color:#fff;}.theme-brown .navbar-brand:active{color:#fff;}.theme-brown .navbar-brand:focus{color:#fff;}.theme-brown .nav>li>a{color:#fff;}.theme-brown .nav>li>a:hover{background-color:transparent;}.theme-brown .nav>li>a:focus{background-color:transparent;}.theme-brown .nav .open>a{background-color:transparent;}.theme-brown .nav .open>a:hover{background-color:transparent;}.theme-brown .nav .open>a:focus{background-color:transparent;}.theme-brown .bars{color:#fff;}.theme-brown .sidebar .menu .list li.active{background-color:transparent;}.theme-brown .sidebar .menu .list li.active>:first-child i,.theme-brown .sidebar .menu .list li.active>:first-child span{color:#795548;}.theme-brown .sidebar .menu .list .toggled{background-color:transparent;}.theme-brown .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-brown .sidebar .legal{background-color:#fff;}.theme-brown .sidebar .legal .copyright a{color:#795548 !important;}.theme-grey .navbar{background-color:#9e9e9e;}.theme-grey .navbar-brand{color:#fff;}.theme-grey .navbar-brand:hover{color:#fff;}.theme-grey .navbar-brand:active{color:#fff;}.theme-grey .navbar-brand:focus{color:#fff;}.theme-grey .nav>li>a{color:#fff;}.theme-grey .nav>li>a:hover{background-color:transparent;}.theme-grey .nav>li>a:focus{background-color:transparent;}.theme-grey .nav .open>a{background-color:transparent;}.theme-grey .nav .open>a:hover{background-color:transparent;}.theme-grey .nav .open>a:focus{background-color:transparent;}.theme-grey .bars{color:#fff;}.theme-grey .sidebar .menu .list li.active{background-color:transparent;}.theme-grey .sidebar .menu .list li.active>:first-child i,.theme-grey .sidebar .menu .list li.active>:first-child span{color:#9e9e9e;}.theme-grey .sidebar .menu .list .toggled{background-color:transparent;}.theme-grey .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-grey .sidebar .legal{background-color:#fff;}.theme-grey .sidebar .legal .copyright a{color:#9e9e9e !important;}.theme-blue-grey .navbar{background-color:#607d8b;}.theme-blue-grey .navbar-brand{color:#fff;}.theme-blue-grey .navbar-brand:hover{color:#fff;}.theme-blue-grey .navbar-brand:active{color:#fff;}.theme-blue-grey .navbar-brand:focus{color:#fff;}.theme-blue-grey .nav>li>a{color:#fff;}.theme-blue-grey .nav>li>a:hover{background-color:transparent;}.theme-blue-grey .nav>li>a:focus{background-color:transparent;}.theme-blue-grey .nav .open>a{background-color:transparent;}.theme-blue-grey .nav .open>a:hover{background-color:transparent;}.theme-blue-grey .nav .open>a:focus{background-color:transparent;}.theme-blue-grey .bars{color:#fff;}.theme-blue-grey .sidebar .menu .list li.active{background-color:transparent;}.theme-blue-grey .sidebar .menu .list li.active>:first-child i,.theme-blue-grey .sidebar .menu .list li.active>:first-child span{color:#607d8b;}.theme-blue-grey .sidebar .menu .list .toggled{background-color:transparent;}.theme-blue-grey .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-blue-grey .sidebar .legal{background-color:#fff;}.theme-blue-grey .sidebar .legal .copyright a{color:#607d8b !important;}.theme-black .navbar{background-color:#000;}.theme-black .navbar-brand{color:#fff;}.theme-black .navbar-brand:hover{color:#fff;}.theme-black .navbar-brand:active{color:#fff;}.theme-black .navbar-brand:focus{color:#fff;}.theme-black .nav>li>a{color:#fff;}.theme-black .nav>li>a:hover{background-color:transparent;}.theme-black .nav>li>a:focus{background-color:transparent;}.theme-black .nav .open>a{background-color:transparent;}.theme-black .nav .open>a:hover{background-color:transparent;}.theme-black .nav .open>a:focus{background-color:transparent;}.theme-black .bars{color:#fff;}.theme-black .sidebar .menu .list li.active{background-color:transparent;}.theme-black .sidebar .menu .list li.active>:first-child i,.theme-black .sidebar .menu .list li.active>:first-child span{color:#000;}.theme-black .sidebar .menu .list .toggled{background-color:transparent;}.theme-black .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-black .sidebar .legal{background-color:#fff;}.theme-black .sidebar .legal .copyright a{color:#000 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-amber.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-amber.css new file mode 100644 index 0000000..f1b1b5f --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-amber.css @@ -0,0 +1,45 @@ +.theme-amber .navbar { + background-color: #FFC107; } + +.theme-amber .navbar-brand { + color: #fff; } + .theme-amber .navbar-brand:hover { + color: #fff; } + .theme-amber .navbar-brand:active { + color: #fff; } + .theme-amber .navbar-brand:focus { + color: #fff; } + +.theme-amber .nav > li > a { + color: #fff; } + .theme-amber .nav > li > a:hover { + background-color: transparent; } + .theme-amber .nav > li > a:focus { + background-color: transparent; } + +.theme-amber .nav .open > a { + background-color: transparent; } + .theme-amber .nav .open > a:hover { + background-color: transparent; } + .theme-amber .nav .open > a:focus { + background-color: transparent; } + +.theme-amber .bars { + color: #fff; } + +.theme-amber .sidebar .menu .list li.active { + background-color: transparent; } + .theme-amber .sidebar .menu .list li.active > :first-child i, .theme-amber .sidebar .menu .list li.active > :first-child span { + color: #FFC107; } + +.theme-amber .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-amber .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-amber .sidebar .legal { + background-color: #fff; } + .theme-amber .sidebar .legal .copyright a { + color: #FFC107 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-amber.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-amber.min.css new file mode 100644 index 0000000..d791a33 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-amber.min.css @@ -0,0 +1 @@ +.theme-amber .navbar{background-color:#ffc107;}.theme-amber .navbar-brand{color:#fff;}.theme-amber .navbar-brand:hover{color:#fff;}.theme-amber .navbar-brand:active{color:#fff;}.theme-amber .navbar-brand:focus{color:#fff;}.theme-amber .nav>li>a{color:#fff;}.theme-amber .nav>li>a:hover{background-color:transparent;}.theme-amber .nav>li>a:focus{background-color:transparent;}.theme-amber .nav .open>a{background-color:transparent;}.theme-amber .nav .open>a:hover{background-color:transparent;}.theme-amber .nav .open>a:focus{background-color:transparent;}.theme-amber .bars{color:#fff;}.theme-amber .sidebar .menu .list li.active{background-color:transparent;}.theme-amber .sidebar .menu .list li.active>:first-child i,.theme-amber .sidebar .menu .list li.active>:first-child span{color:#ffc107;}.theme-amber .sidebar .menu .list .toggled{background-color:transparent;}.theme-amber .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-amber .sidebar .legal{background-color:#fff;}.theme-amber .sidebar .legal .copyright a{color:#ffc107 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-black.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-black.css new file mode 100644 index 0000000..93263b7 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-black.css @@ -0,0 +1,45 @@ +.theme-black .navbar { + background-color: #000; } + +.theme-black .navbar-brand { + color: #fff; } + .theme-black .navbar-brand:hover { + color: #fff; } + .theme-black .navbar-brand:active { + color: #fff; } + .theme-black .navbar-brand:focus { + color: #fff; } + +.theme-black .nav > li > a { + color: #fff; } + .theme-black .nav > li > a:hover { + background-color: transparent; } + .theme-black .nav > li > a:focus { + background-color: transparent; } + +.theme-black .nav .open > a { + background-color: transparent; } + .theme-black .nav .open > a:hover { + background-color: transparent; } + .theme-black .nav .open > a:focus { + background-color: transparent; } + +.theme-black .bars { + color: #fff; } + +.theme-black .sidebar .menu .list li.active { + background-color: transparent; } + .theme-black .sidebar .menu .list li.active > :first-child i, .theme-black .sidebar .menu .list li.active > :first-child span { + color: #000; } + +.theme-black .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-black .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-black .sidebar .legal { + background-color: #fff; } + .theme-black .sidebar .legal .copyright a { + color: #000 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-black.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-black.min.css new file mode 100644 index 0000000..e763c92 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-black.min.css @@ -0,0 +1 @@ +.theme-black .navbar{background-color:#000;}.theme-black .navbar-brand{color:#fff;}.theme-black .navbar-brand:hover{color:#fff;}.theme-black .navbar-brand:active{color:#fff;}.theme-black .navbar-brand:focus{color:#fff;}.theme-black .nav>li>a{color:#fff;}.theme-black .nav>li>a:hover{background-color:transparent;}.theme-black .nav>li>a:focus{background-color:transparent;}.theme-black .nav .open>a{background-color:transparent;}.theme-black .nav .open>a:hover{background-color:transparent;}.theme-black .nav .open>a:focus{background-color:transparent;}.theme-black .bars{color:#fff;}.theme-black .sidebar .menu .list li.active{background-color:transparent;}.theme-black .sidebar .menu .list li.active>:first-child i,.theme-black .sidebar .menu .list li.active>:first-child span{color:#000;}.theme-black .sidebar .menu .list .toggled{background-color:transparent;}.theme-black .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-black .sidebar .legal{background-color:#fff;}.theme-black .sidebar .legal .copyright a{color:#000 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue-grey.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue-grey.css new file mode 100644 index 0000000..2cbb4a0 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue-grey.css @@ -0,0 +1,45 @@ +.theme-blue-grey .navbar { + background-color: #607D8B; } + +.theme-blue-grey .navbar-brand { + color: #fff; } + .theme-blue-grey .navbar-brand:hover { + color: #fff; } + .theme-blue-grey .navbar-brand:active { + color: #fff; } + .theme-blue-grey .navbar-brand:focus { + color: #fff; } + +.theme-blue-grey .nav > li > a { + color: #fff; } + .theme-blue-grey .nav > li > a:hover { + background-color: transparent; } + .theme-blue-grey .nav > li > a:focus { + background-color: transparent; } + +.theme-blue-grey .nav .open > a { + background-color: transparent; } + .theme-blue-grey .nav .open > a:hover { + background-color: transparent; } + .theme-blue-grey .nav .open > a:focus { + background-color: transparent; } + +.theme-blue-grey .bars { + color: #fff; } + +.theme-blue-grey .sidebar .menu .list li.active { + background-color: transparent; } + .theme-blue-grey .sidebar .menu .list li.active > :first-child i, .theme-blue-grey .sidebar .menu .list li.active > :first-child span { + color: #607D8B; } + +.theme-blue-grey .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-blue-grey .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-blue-grey .sidebar .legal { + background-color: #fff; } + .theme-blue-grey .sidebar .legal .copyright a { + color: #607D8B !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue-grey.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue-grey.min.css new file mode 100644 index 0000000..803f3a9 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue-grey.min.css @@ -0,0 +1 @@ +.theme-blue-grey .navbar{background-color:#607d8b;}.theme-blue-grey .navbar-brand{color:#fff;}.theme-blue-grey .navbar-brand:hover{color:#fff;}.theme-blue-grey .navbar-brand:active{color:#fff;}.theme-blue-grey .navbar-brand:focus{color:#fff;}.theme-blue-grey .nav>li>a{color:#fff;}.theme-blue-grey .nav>li>a:hover{background-color:transparent;}.theme-blue-grey .nav>li>a:focus{background-color:transparent;}.theme-blue-grey .nav .open>a{background-color:transparent;}.theme-blue-grey .nav .open>a:hover{background-color:transparent;}.theme-blue-grey .nav .open>a:focus{background-color:transparent;}.theme-blue-grey .bars{color:#fff;}.theme-blue-grey .sidebar .menu .list li.active{background-color:transparent;}.theme-blue-grey .sidebar .menu .list li.active>:first-child i,.theme-blue-grey .sidebar .menu .list li.active>:first-child span{color:#607d8b;}.theme-blue-grey .sidebar .menu .list .toggled{background-color:transparent;}.theme-blue-grey .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-blue-grey .sidebar .legal{background-color:#fff;}.theme-blue-grey .sidebar .legal .copyright a{color:#607d8b !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue.css new file mode 100644 index 0000000..01017c7 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue.css @@ -0,0 +1,45 @@ +.theme-blue .navbar { + background-color: #2196F3; } + +.theme-blue .navbar-brand { + color: #fff; } + .theme-blue .navbar-brand:hover { + color: #fff; } + .theme-blue .navbar-brand:active { + color: #fff; } + .theme-blue .navbar-brand:focus { + color: #fff; } + +.theme-blue .nav > li > a { + color: #fff; } + .theme-blue .nav > li > a:hover { + background-color: transparent; } + .theme-blue .nav > li > a:focus { + background-color: transparent; } + +.theme-blue .nav .open > a { + background-color: transparent; } + .theme-blue .nav .open > a:hover { + background-color: transparent; } + .theme-blue .nav .open > a:focus { + background-color: transparent; } + +.theme-blue .bars { + color: #fff; } + +.theme-blue .sidebar .menu .list li.active { + background-color: transparent; } + .theme-blue .sidebar .menu .list li.active > :first-child i, .theme-blue .sidebar .menu .list li.active > :first-child span { + color: #2196F3; } + +.theme-blue .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-blue .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-blue .sidebar .legal { + background-color: #fff; } + .theme-blue .sidebar .legal .copyright a { + color: #2196F3 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue.min.css new file mode 100644 index 0000000..36359d3 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-blue.min.css @@ -0,0 +1 @@ +.theme-blue .navbar{background-color:#2196f3;}.theme-blue .navbar-brand{color:#fff;}.theme-blue .navbar-brand:hover{color:#fff;}.theme-blue .navbar-brand:active{color:#fff;}.theme-blue .navbar-brand:focus{color:#fff;}.theme-blue .nav>li>a{color:#fff;}.theme-blue .nav>li>a:hover{background-color:transparent;}.theme-blue .nav>li>a:focus{background-color:transparent;}.theme-blue .nav .open>a{background-color:transparent;}.theme-blue .nav .open>a:hover{background-color:transparent;}.theme-blue .nav .open>a:focus{background-color:transparent;}.theme-blue .bars{color:#fff;}.theme-blue .sidebar .menu .list li.active{background-color:transparent;}.theme-blue .sidebar .menu .list li.active>:first-child i,.theme-blue .sidebar .menu .list li.active>:first-child span{color:#2196f3;}.theme-blue .sidebar .menu .list .toggled{background-color:transparent;}.theme-blue .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-blue .sidebar .legal{background-color:#fff;}.theme-blue .sidebar .legal .copyright a{color:#2196f3 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-brown.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-brown.css new file mode 100644 index 0000000..709c9e6 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-brown.css @@ -0,0 +1,45 @@ +.theme-brown .navbar { + background-color: #795548; } + +.theme-brown .navbar-brand { + color: #fff; } + .theme-brown .navbar-brand:hover { + color: #fff; } + .theme-brown .navbar-brand:active { + color: #fff; } + .theme-brown .navbar-brand:focus { + color: #fff; } + +.theme-brown .nav > li > a { + color: #fff; } + .theme-brown .nav > li > a:hover { + background-color: transparent; } + .theme-brown .nav > li > a:focus { + background-color: transparent; } + +.theme-brown .nav .open > a { + background-color: transparent; } + .theme-brown .nav .open > a:hover { + background-color: transparent; } + .theme-brown .nav .open > a:focus { + background-color: transparent; } + +.theme-brown .bars { + color: #fff; } + +.theme-brown .sidebar .menu .list li.active { + background-color: transparent; } + .theme-brown .sidebar .menu .list li.active > :first-child i, .theme-brown .sidebar .menu .list li.active > :first-child span { + color: #795548; } + +.theme-brown .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-brown .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-brown .sidebar .legal { + background-color: #fff; } + .theme-brown .sidebar .legal .copyright a { + color: #795548 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-brown.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-brown.min.css new file mode 100644 index 0000000..acc53fe --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-brown.min.css @@ -0,0 +1 @@ +.theme-brown .navbar{background-color:#795548;}.theme-brown .navbar-brand{color:#fff;}.theme-brown .navbar-brand:hover{color:#fff;}.theme-brown .navbar-brand:active{color:#fff;}.theme-brown .navbar-brand:focus{color:#fff;}.theme-brown .nav>li>a{color:#fff;}.theme-brown .nav>li>a:hover{background-color:transparent;}.theme-brown .nav>li>a:focus{background-color:transparent;}.theme-brown .nav .open>a{background-color:transparent;}.theme-brown .nav .open>a:hover{background-color:transparent;}.theme-brown .nav .open>a:focus{background-color:transparent;}.theme-brown .bars{color:#fff;}.theme-brown .sidebar .menu .list li.active{background-color:transparent;}.theme-brown .sidebar .menu .list li.active>:first-child i,.theme-brown .sidebar .menu .list li.active>:first-child span{color:#795548;}.theme-brown .sidebar .menu .list .toggled{background-color:transparent;}.theme-brown .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-brown .sidebar .legal{background-color:#fff;}.theme-brown .sidebar .legal .copyright a{color:#795548 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-cyan.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-cyan.css new file mode 100644 index 0000000..e05559d --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-cyan.css @@ -0,0 +1,45 @@ +.theme-cyan .navbar { + background-color: #00BCD4; } + +.theme-cyan .navbar-brand { + color: #fff; } + .theme-cyan .navbar-brand:hover { + color: #fff; } + .theme-cyan .navbar-brand:active { + color: #fff; } + .theme-cyan .navbar-brand:focus { + color: #fff; } + +.theme-cyan .nav > li > a { + color: #fff; } + .theme-cyan .nav > li > a:hover { + background-color: transparent; } + .theme-cyan .nav > li > a:focus { + background-color: transparent; } + +.theme-cyan .nav .open > a { + background-color: transparent; } + .theme-cyan .nav .open > a:hover { + background-color: transparent; } + .theme-cyan .nav .open > a:focus { + background-color: transparent; } + +.theme-cyan .bars { + color: #fff; } + +.theme-cyan .sidebar .menu .list li.active { + background-color: transparent; } + .theme-cyan .sidebar .menu .list li.active > :first-child i, .theme-cyan .sidebar .menu .list li.active > :first-child span { + color: #00BCD4; } + +.theme-cyan .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-cyan .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-cyan .sidebar .legal { + background-color: #fff; } + .theme-cyan .sidebar .legal .copyright a { + color: #00BCD4 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-cyan.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-cyan.min.css new file mode 100644 index 0000000..792c898 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-cyan.min.css @@ -0,0 +1 @@ +.theme-cyan .navbar{background-color:#00bcd4;}.theme-cyan .navbar-brand{color:#fff;}.theme-cyan .navbar-brand:hover{color:#fff;}.theme-cyan .navbar-brand:active{color:#fff;}.theme-cyan .navbar-brand:focus{color:#fff;}.theme-cyan .nav>li>a{color:#fff;}.theme-cyan .nav>li>a:hover{background-color:transparent;}.theme-cyan .nav>li>a:focus{background-color:transparent;}.theme-cyan .nav .open>a{background-color:transparent;}.theme-cyan .nav .open>a:hover{background-color:transparent;}.theme-cyan .nav .open>a:focus{background-color:transparent;}.theme-cyan .bars{color:#fff;}.theme-cyan .sidebar .menu .list li.active{background-color:transparent;}.theme-cyan .sidebar .menu .list li.active>:first-child i,.theme-cyan .sidebar .menu .list li.active>:first-child span{color:#00bcd4;}.theme-cyan .sidebar .menu .list .toggled{background-color:transparent;}.theme-cyan .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-cyan .sidebar .legal{background-color:#fff;}.theme-cyan .sidebar .legal .copyright a{color:#00bcd4 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-orange.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-orange.css new file mode 100644 index 0000000..5229daf --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-orange.css @@ -0,0 +1,45 @@ +.theme-deep-orange .navbar { + background-color: #FF5722; } + +.theme-deep-orange .navbar-brand { + color: #fff; } + .theme-deep-orange .navbar-brand:hover { + color: #fff; } + .theme-deep-orange .navbar-brand:active { + color: #fff; } + .theme-deep-orange .navbar-brand:focus { + color: #fff; } + +.theme-deep-orange .nav > li > a { + color: #fff; } + .theme-deep-orange .nav > li > a:hover { + background-color: transparent; } + .theme-deep-orange .nav > li > a:focus { + background-color: transparent; } + +.theme-deep-orange .nav .open > a { + background-color: transparent; } + .theme-deep-orange .nav .open > a:hover { + background-color: transparent; } + .theme-deep-orange .nav .open > a:focus { + background-color: transparent; } + +.theme-deep-orange .bars { + color: #fff; } + +.theme-deep-orange .sidebar .menu .list li.active { + background-color: transparent; } + .theme-deep-orange .sidebar .menu .list li.active > :first-child i, .theme-deep-orange .sidebar .menu .list li.active > :first-child span { + color: #FF5722; } + +.theme-deep-orange .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-deep-orange .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-deep-orange .sidebar .legal { + background-color: #fff; } + .theme-deep-orange .sidebar .legal .copyright a { + color: #FF5722 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-orange.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-orange.min.css new file mode 100644 index 0000000..160fc70 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-orange.min.css @@ -0,0 +1 @@ +.theme-deep-orange .navbar{background-color:#ff5722;}.theme-deep-orange .navbar-brand{color:#fff;}.theme-deep-orange .navbar-brand:hover{color:#fff;}.theme-deep-orange .navbar-brand:active{color:#fff;}.theme-deep-orange .navbar-brand:focus{color:#fff;}.theme-deep-orange .nav>li>a{color:#fff;}.theme-deep-orange .nav>li>a:hover{background-color:transparent;}.theme-deep-orange .nav>li>a:focus{background-color:transparent;}.theme-deep-orange .nav .open>a{background-color:transparent;}.theme-deep-orange .nav .open>a:hover{background-color:transparent;}.theme-deep-orange .nav .open>a:focus{background-color:transparent;}.theme-deep-orange .bars{color:#fff;}.theme-deep-orange .sidebar .menu .list li.active{background-color:transparent;}.theme-deep-orange .sidebar .menu .list li.active>:first-child i,.theme-deep-orange .sidebar .menu .list li.active>:first-child span{color:#ff5722;}.theme-deep-orange .sidebar .menu .list .toggled{background-color:transparent;}.theme-deep-orange .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-deep-orange .sidebar .legal{background-color:#fff;}.theme-deep-orange .sidebar .legal .copyright a{color:#ff5722 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-purple.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-purple.css new file mode 100644 index 0000000..8c21049 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-purple.css @@ -0,0 +1,45 @@ +.theme-deep-purple .navbar { + background-color: #673AB7; } + +.theme-deep-purple .navbar-brand { + color: #fff; } + .theme-deep-purple .navbar-brand:hover { + color: #fff; } + .theme-deep-purple .navbar-brand:active { + color: #fff; } + .theme-deep-purple .navbar-brand:focus { + color: #fff; } + +.theme-deep-purple .nav > li > a { + color: #fff; } + .theme-deep-purple .nav > li > a:hover { + background-color: transparent; } + .theme-deep-purple .nav > li > a:focus { + background-color: transparent; } + +.theme-deep-purple .nav .open > a { + background-color: transparent; } + .theme-deep-purple .nav .open > a:hover { + background-color: transparent; } + .theme-deep-purple .nav .open > a:focus { + background-color: transparent; } + +.theme-deep-purple .bars { + color: #fff; } + +.theme-deep-purple .sidebar .menu .list li.active { + background-color: transparent; } + .theme-deep-purple .sidebar .menu .list li.active > :first-child i, .theme-deep-purple .sidebar .menu .list li.active > :first-child span { + color: #673AB7; } + +.theme-deep-purple .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-deep-purple .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-deep-purple .sidebar .legal { + background-color: #fff; } + .theme-deep-purple .sidebar .legal .copyright a { + color: #673AB7 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-purple.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-purple.min.css new file mode 100644 index 0000000..fc96963 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-deep-purple.min.css @@ -0,0 +1 @@ +.theme-deep-purple .navbar{background-color:#673ab7;}.theme-deep-purple .navbar-brand{color:#fff;}.theme-deep-purple .navbar-brand:hover{color:#fff;}.theme-deep-purple .navbar-brand:active{color:#fff;}.theme-deep-purple .navbar-brand:focus{color:#fff;}.theme-deep-purple .nav>li>a{color:#fff;}.theme-deep-purple .nav>li>a:hover{background-color:transparent;}.theme-deep-purple .nav>li>a:focus{background-color:transparent;}.theme-deep-purple .nav .open>a{background-color:transparent;}.theme-deep-purple .nav .open>a:hover{background-color:transparent;}.theme-deep-purple .nav .open>a:focus{background-color:transparent;}.theme-deep-purple .bars{color:#fff;}.theme-deep-purple .sidebar .menu .list li.active{background-color:transparent;}.theme-deep-purple .sidebar .menu .list li.active>:first-child i,.theme-deep-purple .sidebar .menu .list li.active>:first-child span{color:#673ab7;}.theme-deep-purple .sidebar .menu .list .toggled{background-color:transparent;}.theme-deep-purple .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-deep-purple .sidebar .legal{background-color:#fff;}.theme-deep-purple .sidebar .legal .copyright a{color:#673ab7 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-green.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-green.css new file mode 100644 index 0000000..6f08cab --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-green.css @@ -0,0 +1,45 @@ +.theme-green .navbar { + background-color: #4CAF50; } + +.theme-green .navbar-brand { + color: #fff; } + .theme-green .navbar-brand:hover { + color: #fff; } + .theme-green .navbar-brand:active { + color: #fff; } + .theme-green .navbar-brand:focus { + color: #fff; } + +.theme-green .nav > li > a { + color: #fff; } + .theme-green .nav > li > a:hover { + background-color: transparent; } + .theme-green .nav > li > a:focus { + background-color: transparent; } + +.theme-green .nav .open > a { + background-color: transparent; } + .theme-green .nav .open > a:hover { + background-color: transparent; } + .theme-green .nav .open > a:focus { + background-color: transparent; } + +.theme-green .bars { + color: #fff; } + +.theme-green .sidebar .menu .list li.active { + background-color: transparent; } + .theme-green .sidebar .menu .list li.active > :first-child i, .theme-green .sidebar .menu .list li.active > :first-child span { + color: #4CAF50; } + +.theme-green .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-green .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-green .sidebar .legal { + background-color: #fff; } + .theme-green .sidebar .legal .copyright a { + color: #4CAF50 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-green.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-green.min.css new file mode 100644 index 0000000..7907971 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-green.min.css @@ -0,0 +1 @@ +.theme-green .navbar{background-color:#4caf50;}.theme-green .navbar-brand{color:#fff;}.theme-green .navbar-brand:hover{color:#fff;}.theme-green .navbar-brand:active{color:#fff;}.theme-green .navbar-brand:focus{color:#fff;}.theme-green .nav>li>a{color:#fff;}.theme-green .nav>li>a:hover{background-color:transparent;}.theme-green .nav>li>a:focus{background-color:transparent;}.theme-green .nav .open>a{background-color:transparent;}.theme-green .nav .open>a:hover{background-color:transparent;}.theme-green .nav .open>a:focus{background-color:transparent;}.theme-green .bars{color:#fff;}.theme-green .sidebar .menu .list li.active{background-color:transparent;}.theme-green .sidebar .menu .list li.active>:first-child i,.theme-green .sidebar .menu .list li.active>:first-child span{color:#4caf50;}.theme-green .sidebar .menu .list .toggled{background-color:transparent;}.theme-green .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-green .sidebar .legal{background-color:#fff;}.theme-green .sidebar .legal .copyright a{color:#4caf50 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-grey.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-grey.css new file mode 100644 index 0000000..658eacb --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-grey.css @@ -0,0 +1,45 @@ +.theme-grey .navbar { + background-color: #9E9E9E; } + +.theme-grey .navbar-brand { + color: #fff; } + .theme-grey .navbar-brand:hover { + color: #fff; } + .theme-grey .navbar-brand:active { + color: #fff; } + .theme-grey .navbar-brand:focus { + color: #fff; } + +.theme-grey .nav > li > a { + color: #fff; } + .theme-grey .nav > li > a:hover { + background-color: transparent; } + .theme-grey .nav > li > a:focus { + background-color: transparent; } + +.theme-grey .nav .open > a { + background-color: transparent; } + .theme-grey .nav .open > a:hover { + background-color: transparent; } + .theme-grey .nav .open > a:focus { + background-color: transparent; } + +.theme-grey .bars { + color: #fff; } + +.theme-grey .sidebar .menu .list li.active { + background-color: transparent; } + .theme-grey .sidebar .menu .list li.active > :first-child i, .theme-grey .sidebar .menu .list li.active > :first-child span { + color: #9E9E9E; } + +.theme-grey .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-grey .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-grey .sidebar .legal { + background-color: #fff; } + .theme-grey .sidebar .legal .copyright a { + color: #9E9E9E !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-grey.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-grey.min.css new file mode 100644 index 0000000..f437acb --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-grey.min.css @@ -0,0 +1 @@ +.theme-grey .navbar{background-color:#9e9e9e;}.theme-grey .navbar-brand{color:#fff;}.theme-grey .navbar-brand:hover{color:#fff;}.theme-grey .navbar-brand:active{color:#fff;}.theme-grey .navbar-brand:focus{color:#fff;}.theme-grey .nav>li>a{color:#fff;}.theme-grey .nav>li>a:hover{background-color:transparent;}.theme-grey .nav>li>a:focus{background-color:transparent;}.theme-grey .nav .open>a{background-color:transparent;}.theme-grey .nav .open>a:hover{background-color:transparent;}.theme-grey .nav .open>a:focus{background-color:transparent;}.theme-grey .bars{color:#fff;}.theme-grey .sidebar .menu .list li.active{background-color:transparent;}.theme-grey .sidebar .menu .list li.active>:first-child i,.theme-grey .sidebar .menu .list li.active>:first-child span{color:#9e9e9e;}.theme-grey .sidebar .menu .list .toggled{background-color:transparent;}.theme-grey .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-grey .sidebar .legal{background-color:#fff;}.theme-grey .sidebar .legal .copyright a{color:#9e9e9e !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-indigo.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-indigo.css new file mode 100644 index 0000000..9f8cfda --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-indigo.css @@ -0,0 +1,45 @@ +.theme-indigo .navbar { + background-color: #3F51B5; } + +.theme-indigo .navbar-brand { + color: #fff; } + .theme-indigo .navbar-brand:hover { + color: #fff; } + .theme-indigo .navbar-brand:active { + color: #fff; } + .theme-indigo .navbar-brand:focus { + color: #fff; } + +.theme-indigo .nav > li > a { + color: #fff; } + .theme-indigo .nav > li > a:hover { + background-color: transparent; } + .theme-indigo .nav > li > a:focus { + background-color: transparent; } + +.theme-indigo .nav .open > a { + background-color: transparent; } + .theme-indigo .nav .open > a:hover { + background-color: transparent; } + .theme-indigo .nav .open > a:focus { + background-color: transparent; } + +.theme-indigo .bars { + color: #fff; } + +.theme-indigo .sidebar .menu .list li.active { + background-color: transparent; } + .theme-indigo .sidebar .menu .list li.active > :first-child i, .theme-indigo .sidebar .menu .list li.active > :first-child span { + color: #3F51B5; } + +.theme-indigo .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-indigo .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-indigo .sidebar .legal { + background-color: #fff; } + .theme-indigo .sidebar .legal .copyright a { + color: #3F51B5 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-indigo.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-indigo.min.css new file mode 100644 index 0000000..28cdeb1 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-indigo.min.css @@ -0,0 +1 @@ +.theme-indigo .navbar{background-color:#3f51b5;}.theme-indigo .navbar-brand{color:#fff;}.theme-indigo .navbar-brand:hover{color:#fff;}.theme-indigo .navbar-brand:active{color:#fff;}.theme-indigo .navbar-brand:focus{color:#fff;}.theme-indigo .nav>li>a{color:#fff;}.theme-indigo .nav>li>a:hover{background-color:transparent;}.theme-indigo .nav>li>a:focus{background-color:transparent;}.theme-indigo .nav .open>a{background-color:transparent;}.theme-indigo .nav .open>a:hover{background-color:transparent;}.theme-indigo .nav .open>a:focus{background-color:transparent;}.theme-indigo .bars{color:#fff;}.theme-indigo .sidebar .menu .list li.active{background-color:transparent;}.theme-indigo .sidebar .menu .list li.active>:first-child i,.theme-indigo .sidebar .menu .list li.active>:first-child span{color:#3f51b5;}.theme-indigo .sidebar .menu .list .toggled{background-color:transparent;}.theme-indigo .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-indigo .sidebar .legal{background-color:#fff;}.theme-indigo .sidebar .legal .copyright a{color:#3f51b5 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-light-blue.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-light-blue.css new file mode 100644 index 0000000..50f4e0a --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-light-blue.css @@ -0,0 +1,45 @@ +.theme-light-blue .navbar { + background-color: #03A9F4; } + +.theme-light-blue .navbar-brand { + color: #fff; } + .theme-light-blue .navbar-brand:hover { + color: #fff; } + .theme-light-blue .navbar-brand:active { + color: #fff; } + .theme-light-blue .navbar-brand:focus { + color: #fff; } + +.theme-light-blue .nav > li > a { + color: #fff; } + .theme-light-blue .nav > li > a:hover { + background-color: transparent; } + .theme-light-blue .nav > li > a:focus { + background-color: transparent; } + +.theme-light-blue .nav .open > a { + background-color: transparent; } + .theme-light-blue .nav .open > a:hover { + background-color: transparent; } + .theme-light-blue .nav .open > a:focus { + background-color: transparent; } + +.theme-light-blue .bars { + color: #fff; } + +.theme-light-blue .sidebar .menu .list li.active { + background-color: transparent; } + .theme-light-blue .sidebar .menu .list li.active > :first-child i, .theme-light-blue .sidebar .menu .list li.active > :first-child span { + color: #03A9F4; } + +.theme-light-blue .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-light-blue .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-light-blue .sidebar .legal { + background-color: #fff; } + .theme-light-blue .sidebar .legal .copyright a { + color: #03A9F4 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-light-blue.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-light-blue.min.css new file mode 100644 index 0000000..d15a177 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-light-blue.min.css @@ -0,0 +1 @@ +.theme-light-blue .navbar{background-color:#03a9f4;}.theme-light-blue .navbar-brand{color:#fff;}.theme-light-blue .navbar-brand:hover{color:#fff;}.theme-light-blue .navbar-brand:active{color:#fff;}.theme-light-blue .navbar-brand:focus{color:#fff;}.theme-light-blue .nav>li>a{color:#fff;}.theme-light-blue .nav>li>a:hover{background-color:transparent;}.theme-light-blue .nav>li>a:focus{background-color:transparent;}.theme-light-blue .nav .open>a{background-color:transparent;}.theme-light-blue .nav .open>a:hover{background-color:transparent;}.theme-light-blue .nav .open>a:focus{background-color:transparent;}.theme-light-blue .bars{color:#fff;}.theme-light-blue .sidebar .menu .list li.active{background-color:transparent;}.theme-light-blue .sidebar .menu .list li.active>:first-child i,.theme-light-blue .sidebar .menu .list li.active>:first-child span{color:#03a9f4;}.theme-light-blue .sidebar .menu .list .toggled{background-color:transparent;}.theme-light-blue .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-light-blue .sidebar .legal{background-color:#fff;}.theme-light-blue .sidebar .legal .copyright a{color:#03a9f4 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-lime.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-lime.css new file mode 100644 index 0000000..82179e3 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-lime.css @@ -0,0 +1,45 @@ +.theme-lime .navbar { + background-color: #CDDC39; } + +.theme-lime .navbar-brand { + color: #fff; } + .theme-lime .navbar-brand:hover { + color: #fff; } + .theme-lime .navbar-brand:active { + color: #fff; } + .theme-lime .navbar-brand:focus { + color: #fff; } + +.theme-lime .nav > li > a { + color: #fff; } + .theme-lime .nav > li > a:hover { + background-color: transparent; } + .theme-lime .nav > li > a:focus { + background-color: transparent; } + +.theme-lime .nav .open > a { + background-color: transparent; } + .theme-lime .nav .open > a:hover { + background-color: transparent; } + .theme-lime .nav .open > a:focus { + background-color: transparent; } + +.theme-lime .bars { + color: #fff; } + +.theme-lime .sidebar .menu .list li.active { + background-color: transparent; } + .theme-lime .sidebar .menu .list li.active > :first-child i, .theme-lime .sidebar .menu .list li.active > :first-child span { + color: #CDDC39; } + +.theme-lime .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-lime .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-lime .sidebar .legal { + background-color: #fff; } + .theme-lime .sidebar .legal .copyright a { + color: #CDDC39 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-lime.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-lime.min.css new file mode 100644 index 0000000..3cc7c35 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-lime.min.css @@ -0,0 +1 @@ +.theme-lime .navbar{background-color:#cddc39;}.theme-lime .navbar-brand{color:#fff;}.theme-lime .navbar-brand:hover{color:#fff;}.theme-lime .navbar-brand:active{color:#fff;}.theme-lime .navbar-brand:focus{color:#fff;}.theme-lime .nav>li>a{color:#fff;}.theme-lime .nav>li>a:hover{background-color:transparent;}.theme-lime .nav>li>a:focus{background-color:transparent;}.theme-lime .nav .open>a{background-color:transparent;}.theme-lime .nav .open>a:hover{background-color:transparent;}.theme-lime .nav .open>a:focus{background-color:transparent;}.theme-lime .bars{color:#fff;}.theme-lime .sidebar .menu .list li.active{background-color:transparent;}.theme-lime .sidebar .menu .list li.active>:first-child i,.theme-lime .sidebar .menu .list li.active>:first-child span{color:#cddc39;}.theme-lime .sidebar .menu .list .toggled{background-color:transparent;}.theme-lime .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-lime .sidebar .legal{background-color:#fff;}.theme-lime .sidebar .legal .copyright a{color:#cddc39 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-orange.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-orange.css new file mode 100644 index 0000000..e855021 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-orange.css @@ -0,0 +1,45 @@ +.theme-orange .navbar { + background-color: #FF9800; } + +.theme-orange .navbar-brand { + color: #fff; } + .theme-orange .navbar-brand:hover { + color: #fff; } + .theme-orange .navbar-brand:active { + color: #fff; } + .theme-orange .navbar-brand:focus { + color: #fff; } + +.theme-orange .nav > li > a { + color: #fff; } + .theme-orange .nav > li > a:hover { + background-color: transparent; } + .theme-orange .nav > li > a:focus { + background-color: transparent; } + +.theme-orange .nav .open > a { + background-color: transparent; } + .theme-orange .nav .open > a:hover { + background-color: transparent; } + .theme-orange .nav .open > a:focus { + background-color: transparent; } + +.theme-orange .bars { + color: #fff; } + +.theme-orange .sidebar .menu .list li.active { + background-color: transparent; } + .theme-orange .sidebar .menu .list li.active > :first-child i, .theme-orange .sidebar .menu .list li.active > :first-child span { + color: #FF9800; } + +.theme-orange .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-orange .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-orange .sidebar .legal { + background-color: #fff; } + .theme-orange .sidebar .legal .copyright a { + color: #FF9800 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-orange.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-orange.min.css new file mode 100644 index 0000000..5b20f26 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-orange.min.css @@ -0,0 +1 @@ +.theme-orange .navbar{background-color:#ff9800;}.theme-orange .navbar-brand{color:#fff;}.theme-orange .navbar-brand:hover{color:#fff;}.theme-orange .navbar-brand:active{color:#fff;}.theme-orange .navbar-brand:focus{color:#fff;}.theme-orange .nav>li>a{color:#fff;}.theme-orange .nav>li>a:hover{background-color:transparent;}.theme-orange .nav>li>a:focus{background-color:transparent;}.theme-orange .nav .open>a{background-color:transparent;}.theme-orange .nav .open>a:hover{background-color:transparent;}.theme-orange .nav .open>a:focus{background-color:transparent;}.theme-orange .bars{color:#fff;}.theme-orange .sidebar .menu .list li.active{background-color:transparent;}.theme-orange .sidebar .menu .list li.active>:first-child i,.theme-orange .sidebar .menu .list li.active>:first-child span{color:#ff9800;}.theme-orange .sidebar .menu .list .toggled{background-color:transparent;}.theme-orange .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-orange .sidebar .legal{background-color:#fff;}.theme-orange .sidebar .legal .copyright a{color:#ff9800 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-pink.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-pink.css new file mode 100644 index 0000000..a0ff28f --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-pink.css @@ -0,0 +1,45 @@ +.theme-pink .navbar { + background-color: #E91E63; } + +.theme-pink .navbar-brand { + color: #fff; } + .theme-pink .navbar-brand:hover { + color: #fff; } + .theme-pink .navbar-brand:active { + color: #fff; } + .theme-pink .navbar-brand:focus { + color: #fff; } + +.theme-pink .nav > li > a { + color: #fff; } + .theme-pink .nav > li > a:hover { + background-color: transparent; } + .theme-pink .nav > li > a:focus { + background-color: transparent; } + +.theme-pink .nav .open > a { + background-color: transparent; } + .theme-pink .nav .open > a:hover { + background-color: transparent; } + .theme-pink .nav .open > a:focus { + background-color: transparent; } + +.theme-pink .bars { + color: #fff; } + +.theme-pink .sidebar .menu .list li.active { + background-color: transparent; } + .theme-pink .sidebar .menu .list li.active > :first-child i, .theme-pink .sidebar .menu .list li.active > :first-child span { + color: #E91E63; } + +.theme-pink .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-pink .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-pink .sidebar .legal { + background-color: #fff; } + .theme-pink .sidebar .legal .copyright a { + color: #E91E63 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-pink.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-pink.min.css new file mode 100644 index 0000000..c282520 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-pink.min.css @@ -0,0 +1 @@ +.theme-pink .navbar{background-color:#e91e63;}.theme-pink .navbar-brand{color:#fff;}.theme-pink .navbar-brand:hover{color:#fff;}.theme-pink .navbar-brand:active{color:#fff;}.theme-pink .navbar-brand:focus{color:#fff;}.theme-pink .nav>li>a{color:#fff;}.theme-pink .nav>li>a:hover{background-color:transparent;}.theme-pink .nav>li>a:focus{background-color:transparent;}.theme-pink .nav .open>a{background-color:transparent;}.theme-pink .nav .open>a:hover{background-color:transparent;}.theme-pink .nav .open>a:focus{background-color:transparent;}.theme-pink .bars{color:#fff;}.theme-pink .sidebar .menu .list li.active{background-color:transparent;}.theme-pink .sidebar .menu .list li.active>:first-child i,.theme-pink .sidebar .menu .list li.active>:first-child span{color:#e91e63;}.theme-pink .sidebar .menu .list .toggled{background-color:transparent;}.theme-pink .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-pink .sidebar .legal{background-color:#fff;}.theme-pink .sidebar .legal .copyright a{color:#e91e63 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-purple.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-purple.css new file mode 100644 index 0000000..76326c9 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-purple.css @@ -0,0 +1,45 @@ +.theme-purple .navbar { + background-color: #9C27B0; } + +.theme-purple .navbar-brand { + color: #fff; } + .theme-purple .navbar-brand:hover { + color: #fff; } + .theme-purple .navbar-brand:active { + color: #fff; } + .theme-purple .navbar-brand:focus { + color: #fff; } + +.theme-purple .nav > li > a { + color: #fff; } + .theme-purple .nav > li > a:hover { + background-color: transparent; } + .theme-purple .nav > li > a:focus { + background-color: transparent; } + +.theme-purple .nav .open > a { + background-color: transparent; } + .theme-purple .nav .open > a:hover { + background-color: transparent; } + .theme-purple .nav .open > a:focus { + background-color: transparent; } + +.theme-purple .bars { + color: #fff; } + +.theme-purple .sidebar .menu .list li.active { + background-color: transparent; } + .theme-purple .sidebar .menu .list li.active > :first-child i, .theme-purple .sidebar .menu .list li.active > :first-child span { + color: #9C27B0; } + +.theme-purple .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-purple .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-purple .sidebar .legal { + background-color: #fff; } + .theme-purple .sidebar .legal .copyright a { + color: #9C27B0 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-purple.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-purple.min.css new file mode 100644 index 0000000..f261c6b --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-purple.min.css @@ -0,0 +1 @@ +.theme-purple .navbar{background-color:#9c27b0;}.theme-purple .navbar-brand{color:#fff;}.theme-purple .navbar-brand:hover{color:#fff;}.theme-purple .navbar-brand:active{color:#fff;}.theme-purple .navbar-brand:focus{color:#fff;}.theme-purple .nav>li>a{color:#fff;}.theme-purple .nav>li>a:hover{background-color:transparent;}.theme-purple .nav>li>a:focus{background-color:transparent;}.theme-purple .nav .open>a{background-color:transparent;}.theme-purple .nav .open>a:hover{background-color:transparent;}.theme-purple .nav .open>a:focus{background-color:transparent;}.theme-purple .bars{color:#fff;}.theme-purple .sidebar .menu .list li.active{background-color:transparent;}.theme-purple .sidebar .menu .list li.active>:first-child i,.theme-purple .sidebar .menu .list li.active>:first-child span{color:#9c27b0;}.theme-purple .sidebar .menu .list .toggled{background-color:transparent;}.theme-purple .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-purple .sidebar .legal{background-color:#fff;}.theme-purple .sidebar .legal .copyright a{color:#9c27b0 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-red.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-red.css new file mode 100644 index 0000000..73c6a2d --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-red.css @@ -0,0 +1,45 @@ +.theme-red .navbar { + background-color: #F44336; } + +.theme-red .navbar-brand { + color: #fff; } + .theme-red .navbar-brand:hover { + color: #fff; } + .theme-red .navbar-brand:active { + color: #fff; } + .theme-red .navbar-brand:focus { + color: #fff; } + +.theme-red .nav > li > a { + color: #fff; } + .theme-red .nav > li > a:hover { + background-color: transparent; } + .theme-red .nav > li > a:focus { + background-color: transparent; } + +.theme-red .nav .open > a { + background-color: transparent; } + .theme-red .nav .open > a:hover { + background-color: transparent; } + .theme-red .nav .open > a:focus { + background-color: transparent; } + +.theme-red .bars { + color: #fff; } + +.theme-red .sidebar .menu .list li.active { + background-color: transparent; } + .theme-red .sidebar .menu .list li.active > :first-child i, .theme-red .sidebar .menu .list li.active > :first-child span { + color: #F44336; } + +.theme-red .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-red .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-red .sidebar .legal { + background-color: #fff; } + .theme-red .sidebar .legal .copyright a { + color: #F44336 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-red.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-red.min.css new file mode 100644 index 0000000..1431c29 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-red.min.css @@ -0,0 +1 @@ +.theme-red .navbar{background-color:#f44336;}.theme-red .navbar-brand{color:#fff;}.theme-red .navbar-brand:hover{color:#fff;}.theme-red .navbar-brand:active{color:#fff;}.theme-red .navbar-brand:focus{color:#fff;}.theme-red .nav>li>a{color:#fff;}.theme-red .nav>li>a:hover{background-color:transparent;}.theme-red .nav>li>a:focus{background-color:transparent;}.theme-red .nav .open>a{background-color:transparent;}.theme-red .nav .open>a:hover{background-color:transparent;}.theme-red .nav .open>a:focus{background-color:transparent;}.theme-red .bars{color:#fff;}.theme-red .sidebar .menu .list li.active{background-color:transparent;}.theme-red .sidebar .menu .list li.active>:first-child i,.theme-red .sidebar .menu .list li.active>:first-child span{color:#f44336;}.theme-red .sidebar .menu .list .toggled{background-color:transparent;}.theme-red .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-red .sidebar .legal{background-color:#fff;}.theme-red .sidebar .legal .copyright a{color:#f44336 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-teal.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-teal.css new file mode 100644 index 0000000..378fe9e --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-teal.css @@ -0,0 +1,45 @@ +.theme-teal .navbar { + background-color: #009688; } + +.theme-teal .navbar-brand { + color: #fff; } + .theme-teal .navbar-brand:hover { + color: #fff; } + .theme-teal .navbar-brand:active { + color: #fff; } + .theme-teal .navbar-brand:focus { + color: #fff; } + +.theme-teal .nav > li > a { + color: #fff; } + .theme-teal .nav > li > a:hover { + background-color: transparent; } + .theme-teal .nav > li > a:focus { + background-color: transparent; } + +.theme-teal .nav .open > a { + background-color: transparent; } + .theme-teal .nav .open > a:hover { + background-color: transparent; } + .theme-teal .nav .open > a:focus { + background-color: transparent; } + +.theme-teal .bars { + color: #fff; } + +.theme-teal .sidebar .menu .list li.active { + background-color: transparent; } + .theme-teal .sidebar .menu .list li.active > :first-child i, .theme-teal .sidebar .menu .list li.active > :first-child span { + color: #009688; } + +.theme-teal .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-teal .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-teal .sidebar .legal { + background-color: #fff; } + .theme-teal .sidebar .legal .copyright a { + color: #009688 !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-teal.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-teal.min.css new file mode 100644 index 0000000..27fe181 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-teal.min.css @@ -0,0 +1 @@ +.theme-teal .navbar{background-color:#009688;}.theme-teal .navbar-brand{color:#fff;}.theme-teal .navbar-brand:hover{color:#fff;}.theme-teal .navbar-brand:active{color:#fff;}.theme-teal .navbar-brand:focus{color:#fff;}.theme-teal .nav>li>a{color:#fff;}.theme-teal .nav>li>a:hover{background-color:transparent;}.theme-teal .nav>li>a:focus{background-color:transparent;}.theme-teal .nav .open>a{background-color:transparent;}.theme-teal .nav .open>a:hover{background-color:transparent;}.theme-teal .nav .open>a:focus{background-color:transparent;}.theme-teal .bars{color:#fff;}.theme-teal .sidebar .menu .list li.active{background-color:transparent;}.theme-teal .sidebar .menu .list li.active>:first-child i,.theme-teal .sidebar .menu .list li.active>:first-child span{color:#009688;}.theme-teal .sidebar .menu .list .toggled{background-color:transparent;}.theme-teal .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-teal .sidebar .legal{background-color:#fff;}.theme-teal .sidebar .legal .copyright a{color:#009688 !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-yellow.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-yellow.css new file mode 100644 index 0000000..92c573f --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-yellow.css @@ -0,0 +1,45 @@ +.theme-yellow .navbar { + background-color: #FFEB3B; } + +.theme-yellow .navbar-brand { + color: #fff; } + .theme-yellow .navbar-brand:hover { + color: #fff; } + .theme-yellow .navbar-brand:active { + color: #fff; } + .theme-yellow .navbar-brand:focus { + color: #fff; } + +.theme-yellow .nav > li > a { + color: #fff; } + .theme-yellow .nav > li > a:hover { + background-color: transparent; } + .theme-yellow .nav > li > a:focus { + background-color: transparent; } + +.theme-yellow .nav .open > a { + background-color: transparent; } + .theme-yellow .nav .open > a:hover { + background-color: transparent; } + .theme-yellow .nav .open > a:focus { + background-color: transparent; } + +.theme-yellow .bars { + color: #fff; } + +.theme-yellow .sidebar .menu .list li.active { + background-color: transparent; } + .theme-yellow .sidebar .menu .list li.active > :first-child i, .theme-yellow .sidebar .menu .list li.active > :first-child span { + color: #FFEB3B; } + +.theme-yellow .sidebar .menu .list .toggled { + background-color: transparent; } + +.theme-yellow .sidebar .menu .list .ml-menu { + background-color: transparent; } + +.theme-yellow .sidebar .legal { + background-color: #fff; } + .theme-yellow .sidebar .legal .copyright a { + color: #FFEB3B !important; } + diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-yellow.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-yellow.min.css new file mode 100644 index 0000000..73c0b78 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/css/themes/theme-yellow.min.css @@ -0,0 +1 @@ +.theme-yellow .navbar{background-color:#ffeb3b;}.theme-yellow .navbar-brand{color:#fff;}.theme-yellow .navbar-brand:hover{color:#fff;}.theme-yellow .navbar-brand:active{color:#fff;}.theme-yellow .navbar-brand:focus{color:#fff;}.theme-yellow .nav>li>a{color:#fff;}.theme-yellow .nav>li>a:hover{background-color:transparent;}.theme-yellow .nav>li>a:focus{background-color:transparent;}.theme-yellow .nav .open>a{background-color:transparent;}.theme-yellow .nav .open>a:hover{background-color:transparent;}.theme-yellow .nav .open>a:focus{background-color:transparent;}.theme-yellow .bars{color:#fff;}.theme-yellow .sidebar .menu .list li.active{background-color:transparent;}.theme-yellow .sidebar .menu .list li.active>:first-child i,.theme-yellow .sidebar .menu .list li.active>:first-child span{color:#ffeb3b;}.theme-yellow .sidebar .menu .list .toggled{background-color:transparent;}.theme-yellow .sidebar .menu .list .ml-menu{background-color:transparent;}.theme-yellow .sidebar .legal{background-color:#fff;}.theme-yellow .sidebar .legal .copyright a{color:#ffeb3b !important;} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/images/5.png b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/images/5.png new file mode 100644 index 0000000..1424567 Binary files /dev/null and b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/images/5.png differ diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/images/cloud.png b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/images/cloud.png new file mode 100644 index 0000000..fc20ac6 Binary files /dev/null and b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/images/cloud.png differ diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/js/admin.js b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/js/admin.js new file mode 100644 index 0000000..e2d23db --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/js/admin.js @@ -0,0 +1,469 @@ +if (typeof jQuery === "undefined") { + throw new Error("jQuery plugins need to be before this file"); +} + +$.AdminBSB = {}; +$.AdminBSB.options = { + colors: { + red: '#F44336', + pink: '#E91E63', + purple: '#9C27B0', + deepPurple: '#673AB7', + indigo: '#3F51B5', + blue: '#2196F3', + lightBlue: '#03A9F4', + cyan: '#00BCD4', + teal: '#009688', + green: '#4CAF50', + lightGreen: '#8BC34A', + lime: '#CDDC39', + yellow: '#ffe821', + amber: '#FFC107', + orange: '#FF9800', + deepOrange: '#FF5722', + brown: '#795548', + grey: '#9E9E9E', + blueGrey: '#607D8B', + black: '#000000', + white: '#ffffff' + }, + leftSideBar: { + scrollColor: 'rgba(0,0,0,0.5)', + scrollWidth: '4px', + scrollAlwaysVisible: false, + scrollBorderRadius: '0', + scrollRailBorderRadius: '0', + scrollActiveItemWhenPageLoad: true, + breakpointWidth: 1170 + }, + dropdownMenu: { + effectIn: 'fadeIn', + effectOut: 'fadeOut' + } +} + +/* Left Sidebar - Function ================================================================================================= +* You can manage the left sidebar menu options +* +*/ +$.AdminBSB.leftSideBar = { + activate: function () { + var _this = this; + var $body = $('body'); + var $overlay = $('.overlay'); + + //Close sidebar + $(window).click(function (e) { + var $target = $(e.target); + if (e.target.nodeName.toLowerCase() === 'i') { $target = $(e.target).parent(); } + + if (!$target.hasClass('bars') && _this.isOpen() && $target.parents('#leftsidebar').length === 0) { + if (!$target.hasClass('js-right-sidebar')) $overlay.fadeOut(); + $body.removeClass('overlay-open'); + } + }); + + $.each($('.menu-toggle.toggled'), function (i, val) { + $(val).next().slideToggle(0); + }); + + //When page load + $.each($('.menu .list li.active'), function (i, val) { + var $activeAnchors = $(val).find('a:eq(0)'); + + $activeAnchors.addClass('toggled'); + $activeAnchors.next().show(); + }); + + //Collapse or Expand Menu + $('.menu-toggle').on('click', function (e) { + var $this = $(this); + var $content = $this.next(); + + if ($($this.parents('ul')[0]).hasClass('list')) { + var $not = $(e.target).hasClass('menu-toggle') ? e.target : $(e.target).parents('.menu-toggle'); + + $.each($('.menu-toggle.toggled').not($not).next(), function (i, val) { + if ($(val).is(':visible')) { + $(val).prev().toggleClass('toggled'); + $(val).slideUp(); + } + }); + } + + $this.toggleClass('toggled'); + $content.slideToggle(320); + }); + + //Set menu height + _this.setMenuHeight(true); + _this.checkStatusForResize(true); + $(window).resize(function () { + _this.setMenuHeight(false); + _this.checkStatusForResize(false); + }); + + //Set Waves + Waves.attach('.menu .list a', ['waves-block']); + Waves.init(); + }, + setMenuHeight: function (isFirstTime) { + if (typeof $.fn.slimScroll != 'undefined') { + var configs = $.AdminBSB.options.leftSideBar; + var height = ($(window).height() - ($('.legal').outerHeight() + $('.user-info').outerHeight() + $('.navbar').innerHeight())); + var $el = $('.list'); + + if (!isFirstTime) { + $el.slimscroll({ + destroy: true + }); + } + + $el.slimscroll({ + height: height + "px", + color: configs.scrollColor, + size: configs.scrollWidth, + alwaysVisible: configs.scrollAlwaysVisible, + borderRadius: configs.scrollBorderRadius, + railBorderRadius: configs.scrollRailBorderRadius + }); + + //Scroll active menu item when page load, if option set = true + if ($.AdminBSB.options.leftSideBar.scrollActiveItemWhenPageLoad) { + var item = $('.menu .list li.active')[0]; + if (item) { + var activeItemOffsetTop = item.offsetTop; + if (activeItemOffsetTop > 150) $el.slimscroll({ scrollTo: activeItemOffsetTop + 'px' }); + } + } + } + }, + checkStatusForResize: function (firstTime) { + var $body = $('body'); + var $openCloseBar = $('.navbar .navbar-header .bars'); + var width = $body.width(); + + if (firstTime) { + $body.find('.content, .sidebar').addClass('no-animate').delay(1000).queue(function () { + $(this).removeClass('no-animate').dequeue(); + }); + } + + if (width < $.AdminBSB.options.leftSideBar.breakpointWidth) { + $body.addClass('ls-closed'); + $openCloseBar.fadeIn(); + } + else { + $body.removeClass('ls-closed'); + $openCloseBar.fadeOut(); + } + }, + isOpen: function () { + return $('body').hasClass('overlay-open'); + } +}; +//========================================================================================================================== + +/* Right Sidebar - Function ================================================================================================ +* You can manage the right sidebar menu options +* +*/ +$.AdminBSB.rightSideBar = { + activate: function () { + var _this = this; + var $sidebar = $('#rightsidebar'); + var $overlay = $('.overlay'); + + //Close sidebar + $(window).click(function (e) { + var $target = $(e.target); + if (e.target.nodeName.toLowerCase() === 'i') { $target = $(e.target).parent(); } + + if (!$target.hasClass('js-right-sidebar') && _this.isOpen() && $target.parents('#rightsidebar').length === 0) { + if (!$target.hasClass('bars')) $overlay.fadeOut(); + $sidebar.removeClass('open'); + } + }); + + $('.js-right-sidebar').on('click', function () { + $sidebar.toggleClass('open'); + if (_this.isOpen()) { $overlay.fadeIn(); } else { $overlay.fadeOut(); } + }); + }, + isOpen: function () { + return $('.right-sidebar').hasClass('open'); + } +} +//========================================================================================================================== + +/* Searchbar - Function ================================================================================================ +* You can manage the search bar +* +*/ +var $searchBar = $('.search-bar'); +$.AdminBSB.search = { + activate: function () { + var _this = this; + + //Search button click event + $('.js-search').on('click', function () { + _this.showSearchBar(); + }); + + //Close search click event + $searchBar.find('.close-search').on('click', function () { + _this.hideSearchBar(); + }); + + //ESC key on pressed + $searchBar.find('input[type="text"]').on('keyup', function (e) { + if (e.keyCode == 27) { + _this.hideSearchBar(); + } + }); + }, + showSearchBar: function () { + $searchBar.addClass('open'); + $searchBar.find('input[type="text"]').focus(); + }, + hideSearchBar: function () { + $searchBar.removeClass('open'); + $searchBar.find('input[type="text"]').val(''); + } +} +//========================================================================================================================== + +/* Navbar - Function ======================================================================================================= +* You can manage the navbar +* +*/ +$.AdminBSB.navbar = { + activate: function () { + var $body = $('body'); + var $overlay = $('.overlay'); + + //Open left sidebar panel + $('.bars').on('click', function () { + $body.toggleClass('overlay-open'); + if ($body.hasClass('overlay-open')) { $overlay.fadeIn(); } else { $overlay.fadeOut(); } + }); + + //Close collapse bar on click event + $('.nav [data-close="true"]').on('click', function () { + var isVisible = $('.navbar-toggle').is(':visible'); + var $navbarCollapse = $('.navbar-collapse'); + + if (isVisible) { + $navbarCollapse.slideUp(function () { + $navbarCollapse.removeClass('in').removeAttr('style'); + }); + } + }); + } +} +//========================================================================================================================== + +/* Input - Function ======================================================================================================== +* You can manage the inputs(also textareas) with name of class 'form-control' +* +*/ +$.AdminBSB.input = { + activate: function ($parentSelector) { + $parentSelector = $parentSelector || $('body'); + + //On focus event + $parentSelector.find('.form-control').focus(function () { + $(this).closest('.form-line').addClass('focused'); + }); + + //On focusout event + $parentSelector.find('.form-control').focusout(function () { + var $this = $(this); + if ($this.parents('.form-group').hasClass('form-float')) { + if ($this.val() == '') { $this.parents('.form-line').removeClass('focused'); } + } + else { + $this.parents('.form-line').removeClass('focused'); + } + }); + + //On label click + $parentSelector.on('click', '.form-float .form-line .form-label', function () { + $(this).parent().find('input').focus(); + }); + + //Not blank form + $parentSelector.find('.form-control').each(function () { + if ($(this).val() !== '') { + $(this).parents('.form-line').addClass('focused'); + } + }); + } +} +//========================================================================================================================== + +/* Form - Select - Function ================================================================================================ +* You can manage the 'select' of form elements +* +*/ +$.AdminBSB.select = { + activate: function () { + if ($.fn.selectpicker) { $('select:not(.ms)').selectpicker(); } + } +} +//========================================================================================================================== + +/* DropdownMenu - Function ================================================================================================= +* You can manage the dropdown menu +* +*/ + +$.AdminBSB.dropdownMenu = { + activate: function () { + var _this = this; + + $('.dropdown, .dropup, .btn-group').on({ + "show.bs.dropdown": function () { + var dropdown = _this.dropdownEffect(this); + _this.dropdownEffectStart(dropdown, dropdown.effectIn); + }, + "shown.bs.dropdown": function () { + var dropdown = _this.dropdownEffect(this); + if (dropdown.effectIn && dropdown.effectOut) { + _this.dropdownEffectEnd(dropdown, function () { }); + } + }, + "hide.bs.dropdown": function (e) { + var dropdown = _this.dropdownEffect(this); + if (dropdown.effectOut) { + e.preventDefault(); + _this.dropdownEffectStart(dropdown, dropdown.effectOut); + _this.dropdownEffectEnd(dropdown, function () { + dropdown.dropdown.removeClass('open'); + }); + } + } + }); + + //Set Waves + Waves.attach('.dropdown-menu li a', ['waves-block']); + Waves.init(); + }, + dropdownEffect: function (target) { + var effectIn = $.AdminBSB.options.dropdownMenu.effectIn, effectOut = $.AdminBSB.options.dropdownMenu.effectOut; + var dropdown = $(target), dropdownMenu = $('.dropdown-menu', target); + + if (dropdown.length > 0) { + var udEffectIn = dropdown.data('effect-in'); + var udEffectOut = dropdown.data('effect-out'); + if (udEffectIn !== undefined) { effectIn = udEffectIn; } + if (udEffectOut !== undefined) { effectOut = udEffectOut; } + } + + return { + target: target, + dropdown: dropdown, + dropdownMenu: dropdownMenu, + effectIn: effectIn, + effectOut: effectOut + }; + }, + dropdownEffectStart: function (data, effectToStart) { + if (effectToStart) { + data.dropdown.addClass('dropdown-animating'); + data.dropdownMenu.addClass('animated dropdown-animated'); + data.dropdownMenu.addClass(effectToStart); + } + }, + dropdownEffectEnd: function (data, callback) { + var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend'; + data.dropdown.one(animationEnd, function () { + data.dropdown.removeClass('dropdown-animating'); + data.dropdownMenu.removeClass('animated dropdown-animated'); + data.dropdownMenu.removeClass(data.effectIn); + data.dropdownMenu.removeClass(data.effectOut); + + if (typeof callback == 'function') { + callback(); + } + }); + } +} +//========================================================================================================================== + +/* Browser - Function ====================================================================================================== +* You can manage browser +* +*/ +var edge = 'Microsoft Edge'; +var ie10 = 'Internet Explorer 10'; +var ie11 = 'Internet Explorer 11'; +var opera = 'Opera'; +var firefox = 'Mozilla Firefox'; +var chrome = 'Google Chrome'; +var safari = 'Safari'; + +$.AdminBSB.browser = { + activate: function () { + var _this = this; + var className = _this.getClassName(); + + if (className !== '') $('html').addClass(_this.getClassName()); + }, + getBrowser: function () { + var userAgent = navigator.userAgent.toLowerCase(); + + if (/edge/i.test(userAgent)) { + return edge; + } else if (/rv:11/i.test(userAgent)) { + return ie11; + } else if (/msie 10/i.test(userAgent)) { + return ie10; + } else if (/opr/i.test(userAgent)) { + return opera; + } else if (/chrome/i.test(userAgent)) { + return chrome; + } else if (/firefox/i.test(userAgent)) { + return firefox; + } else if (!!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) { + return safari; + } + + return undefined; + }, + getClassName: function () { + var browser = this.getBrowser(); + + if (browser === edge) { + return 'edge'; + } else if (browser === ie11) { + return 'ie11'; + } else if (browser === ie10) { + return 'ie10'; + } else if (browser === opera) { + return 'opera'; + } else if (browser === chrome) { + return 'chrome'; + } else if (browser === firefox) { + return 'firefox'; + } else if (browser === safari) { + return 'safari'; + } else { + return ''; + } + } +} +//========================================================================================================================== + +$(function () { + $.AdminBSB.browser.activate(); + $.AdminBSB.leftSideBar.activate(); + $.AdminBSB.rightSideBar.activate(); + $.AdminBSB.navbar.activate(); + $.AdminBSB.dropdownMenu.activate(); + $.AdminBSB.input.activate(); + $.AdminBSB.select.activate(); + $.AdminBSB.search.activate(); + + setTimeout(function () { $('.page-loader-wrapper').fadeOut(); }, 50); +}); diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/js/script.js b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/js/script.js new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/admin-bsb/js/script.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/animate-css/animate.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/animate-css/animate.css new file mode 100644 index 0000000..188e78f --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/animate-css/animate.css @@ -0,0 +1,3340 @@ +@charset "UTF-8"; + +/*! + * animate.css -http://daneden.me/animate + * Version - 3.5.0 + * Licensed under the MIT license - http://opensource.org/licenses/MIT + * + * Copyright (c) 2016 Daniel Eden + */ + +.animated { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +.animated.flipOutX, +.animated.flipOutY, +.animated.bounceIn, +.animated.bounceOut { + -webkit-animation-duration: .75s; + animation-duration: .75s; +} + +@-webkit-keyframes bounce { + from, 20%, 53%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); + } + + 40%, 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0,-4px,0); + transform: translate3d(0,-4px,0); + } +} + +@keyframes bounce { + from, 20%, 53%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); + } + + 40%, 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0,-4px,0); + transform: translate3d(0,-4px,0); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-transform-origin: center bottom; + transform-origin: center bottom; +} + +@-webkit-keyframes flash { + from, 50%, to { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +@keyframes flash { + from, 50%, to { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(.95, 1.05, 1); + transform: scale3d(.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, .95, 1); + transform: scale3d(1.05, .95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(.95, 1.05, 1); + transform: scale3d(.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, .95, 1); + transform: scale3d(1.05, .95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + from, to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +@keyframes shake { + from, to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.headShake { + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + -webkit-animation-name: headShake; + animation-name: headShake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, 20% { + -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, 20% { + -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + from { + -webkit-transform: none; + transform: none; + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes wobble { + from { + -webkit-transform: none; + transform: none; + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes jello { + from, 11.1%, to { + -webkit-transform: none; + transform: none; + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +@keyframes jello { + from, 11.1%, to { + -webkit-transform: none; + transform: none; + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +.jello { + -webkit-animation-name: jello; + animation-name: jello; + -webkit-transform-origin: center; + transform-origin: center; +} + +@-webkit-keyframes bounceIn { + from, 20%, 40%, 60%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(.97, .97, .97); + transform: scale3d(.97, .97, .97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes bounceIn { + from, 20%, 40%, 60%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(.97, .97, .97); + transform: scale3d(.97, .97, .97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.bounceIn { + -webkit-animation-name: bounceIn; + animation-name: bounceIn; +} + +@-webkit-keyframes bounceInDown { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInDown { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInLeft { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInRight { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInUp { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 20% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 50%, 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } +} + +@keyframes bounceOut { + 20% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 50%, 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } +} + +.bounceOut { + -webkit-animation-name: bounceOut; + animation-name: bounceOut; +} + +@-webkit-keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(.95, .95, .95); + transform: perspective(400px) scale3d(.95, .95, .95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(.95, .95, .95); + transform: perspective(400px) scale3d(.95, .95, .95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; +} + +@-webkit-keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + opacity: 1; + } + + to { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + opacity: 1; + } + + to { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +.hinge { + -webkit-animation-name: hinge; + animation-name: hinge; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +@keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; +} + +@-webkit-keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 50% { + opacity: 1; + } +} + +@keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 50% { + opacity: 1; + } +} + +.zoomIn { + -webkit-animation-name: zoomIn; + animation-name: zoomIn; +} + +@-webkit-keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInDown { + -webkit-animation-name: zoomInDown; + animation-name: zoomInDown; +} + +@-webkit-keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInLeft { + -webkit-animation-name: zoomInLeft; + animation-name: zoomInLeft; +} + +@-webkit-keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInRight { + -webkit-animation-name: zoomInRight; + animation-name: zoomInRight; +} + +@-webkit-keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInUp { + -webkit-animation-name: zoomInUp; + animation-name: zoomInUp; +} + +@-webkit-keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + to { + opacity: 0; + } +} + +@keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + to { + opacity: 0; + } +} + +.zoomOut { + -webkit-animation-name: zoomOut; + animation-name: zoomOut; +} + +@-webkit-keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomOutDown { + -webkit-animation-name: zoomOutDown; + animation-name: zoomOutDown; +} + +@-webkit-keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); + transform: scale(.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +@keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); + transform: scale(.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +.zoomOutLeft { + -webkit-animation-name: zoomOutLeft; + animation-name: zoomOutLeft; +} + +@-webkit-keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(2000px, 0, 0); + transform: scale(.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +@keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(2000px, 0, 0); + transform: scale(.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +.zoomOutRight { + -webkit-animation-name: zoomOutRight; + animation-name: zoomOutRight; +} + +@-webkit-keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomOutUp { + -webkit-animation-name: zoomOutUp; + animation-name: zoomOutUp; +} + +@-webkit-keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; +} + +@-webkit-keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/animate-css/animate.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/animate-css/animate.min.css new file mode 100644 index 0000000..9959541 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/animate-css/animate.min.css @@ -0,0 +1,11 @@ +@charset "UTF-8"; + +/*! + * animate.css -http://daneden.me/animate + * Version - 3.5.0 + * Licensed under the MIT license - http://opensource.org/licenses/MIT + * + * Copyright (c) 2016 Daniel Eden + */ + +.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}.headShake{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-name:headShake;animation-name:headShake}@-webkit-keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}@keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{-webkit-animation-name:jello;animation-name:jello;-webkit-transform-origin:center;transform-origin:center}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}@keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}@keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp} \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap-notify/bootstrap-notify.js b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap-notify/bootstrap-notify.js new file mode 100644 index 0000000..0c05636 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap-notify/bootstrap-notify.js @@ -0,0 +1,353 @@ +/* +* Project: Bootstrap Notify = v3.1.3 +* Description: Turns standard Bootstrap alerts into "Growl-like" notifications. +* Author: Mouse0270 aka Robert McIntosh +* License: MIT License +* Website: https://github.com/mouse0270/bootstrap-growl +*/ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + // Create the defaults once + var defaults = { + element: 'body', + position: null, + type: "info", + allow_dismiss: true, + newest_on_top: false, + showProgressbar: false, + placement: { + from: "top", + align: "right" + }, + offset: 20, + spacing: 10, + z_index: 1031, + delay: 5000, + timer: 1000, + url_target: '_blank', + mouse_over: null, + animate: { + enter: 'animated fadeInDown', + exit: 'animated fadeOutUp' + }, + onShow: null, + onShown: null, + onClose: null, + onClosed: null, + icon_type: 'class', + template: '' + }; + + String.format = function() { + var str = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]); + } + return str; + }; + + function Notify ( element, content, options ) { + // Setup Content of Notify + var content = { + content: { + message: typeof content == 'object' ? content.message : content, + title: content.title ? content.title : '', + icon: content.icon ? content.icon : '', + url: content.url ? content.url : '#', + target: content.target ? content.target : '-' + } + }; + + options = $.extend(true, {}, content, options); + this.settings = $.extend(true, {}, defaults, options); + this._defaults = defaults; + if (this.settings.content.target == "-") { + this.settings.content.target = this.settings.url_target; + } + this.animations = { + start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart', + end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend' + } + + if (typeof this.settings.offset == 'number') { + this.settings.offset = { + x: this.settings.offset, + y: this.settings.offset + }; + } + + this.init(); + }; + + $.extend(Notify.prototype, { + init: function () { + var self = this; + + this.buildNotify(); + if (this.settings.content.icon) { + this.setIcon(); + } + if (this.settings.content.url != "#") { + this.styleURL(); + } + this.styleDismiss(); + this.placement(); + this.bind(); + + this.notify = { + $ele: this.$ele, + update: function(command, update) { + var commands = {}; + if (typeof command == "string") { + commands[command] = update; + }else{ + commands = command; + } + for (var command in commands) { + switch (command) { + case "type": + this.$ele.removeClass('alert-' + self.settings.type); + this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type); + self.settings.type = commands[command]; + this.$ele.addClass('alert-' + commands[command]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[command]); + break; + case "icon": + var $icon = this.$ele.find('[data-notify="icon"]'); + if (self.settings.icon_type.toLowerCase() == 'class') { + $icon.removeClass(self.settings.content.icon).addClass(commands[command]); + }else{ + if (!$icon.is('img')) { + $icon.find('img'); + } + $icon.attr('src', commands[command]); + } + break; + case "progress": + var newDelay = self.settings.delay - (self.settings.delay * (commands[command] / 100)); + this.$ele.data('notify-delay', newDelay); + this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[command]).css('width', commands[command] + '%'); + break; + case "url": + this.$ele.find('[data-notify="url"]').attr('href', commands[command]); + break; + case "target": + this.$ele.find('[data-notify="url"]').attr('target', commands[command]); + break; + default: + this.$ele.find('[data-notify="' + command +'"]').html(commands[command]); + }; + } + var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y); + self.reposition(posX); + }, + close: function() { + self.close(); + } + }; + }, + buildNotify: function () { + var content = this.settings.content; + this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target)); + this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align); + if (!this.settings.allow_dismiss) { + this.$ele.find('[data-notify="dismiss"]').css('display', 'none'); + } + if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) { + this.$ele.find('[data-notify="progressbar"]').remove(); + } + }, + setIcon: function() { + if (this.settings.icon_type.toLowerCase() == 'class') { + this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon); + }else{ + if (this.$ele.find('[data-notify="icon"]').is('img')) { + this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon); + }else{ + this.$ele.find('[data-notify="icon"]').append('Notify Icon'); + } + } + }, + styleDismiss: function() { + this.$ele.find('[data-notify="dismiss"]').css({ + position: 'absolute', + right: '10px', + top: '5px', + zIndex: this.settings.z_index + 2 + }); + }, + styleURL: function() { + this.$ele.find('[data-notify="url"]').css({ + backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)', + height: '100%', + left: '0px', + position: 'absolute', + top: '0px', + width: '100%', + zIndex: this.settings.z_index + 1 + }); + }, + placement: function() { + var self = this, + offsetAmt = this.settings.offset.y, + css = { + display: 'inline-block', + margin: '0px auto', + position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'), + transition: 'all .5s ease-in-out', + zIndex: this.settings.z_index + }, + hasAnimation = false, + settings = this.settings; + + $('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function() { + return offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing)); + }); + if (this.settings.newest_on_top == true) { + offsetAmt = this.settings.offset.y; + } + css[this.settings.placement.from] = offsetAmt+'px'; + + switch (this.settings.placement.align) { + case "left": + case "right": + css[this.settings.placement.align] = this.settings.offset.x+'px'; + break; + case "center": + css.left = 0; + css.right = 0; + break; + } + this.$ele.css(css).addClass(this.settings.animate.enter); + $.each(Array('webkit-', 'moz-', 'o-', 'ms-', ''), function(index, prefix) { + self.$ele[0].style[prefix+'AnimationIterationCount'] = 1; + }); + + $(this.settings.element).append(this.$ele); + + if (this.settings.newest_on_top == true) { + offsetAmt = (parseInt(offsetAmt)+parseInt(this.settings.spacing)) + this.$ele.outerHeight(); + this.reposition(offsetAmt); + } + + if ($.isFunction(self.settings.onShow)) { + self.settings.onShow.call(this.$ele); + } + + this.$ele.one(this.animations.start, function(event) { + hasAnimation = true; + }).one(this.animations.end, function(event) { + if ($.isFunction(self.settings.onShown)) { + self.settings.onShown.call(this); + } + }); + + setTimeout(function() { + if (!hasAnimation) { + if ($.isFunction(self.settings.onShown)) { + self.settings.onShown.call(this); + } + } + }, 600); + }, + bind: function() { + var self = this; + + this.$ele.find('[data-notify="dismiss"]').on('click', function() { + self.close(); + }) + + this.$ele.mouseover(function(e) { + $(this).data('data-hover', "true"); + }).mouseout(function(e) { + $(this).data('data-hover', "false"); + }); + this.$ele.data('data-hover', "false"); + + if (this.settings.delay > 0) { + self.$ele.data('notify-delay', self.settings.delay); + var timer = setInterval(function() { + var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer; + if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over == "pause") || self.settings.mouse_over != "pause") { + var percent = ((self.settings.delay - delay) / self.settings.delay) * 100; + self.$ele.data('notify-delay', delay); + self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%'); + } + if (delay <= -(self.settings.timer)) { + clearInterval(timer); + self.close(); + } + }, self.settings.timer); + } + }, + close: function() { + var self = this, + $successors = null, + posX = parseInt(this.$ele.css(this.settings.placement.from)), + hasAnimation = false; + + this.$ele.data('closing', 'true').addClass(this.settings.animate.exit); + self.reposition(posX); + + if ($.isFunction(self.settings.onClose)) { + self.settings.onClose.call(this.$ele); + } + + this.$ele.one(this.animations.start, function(event) { + hasAnimation = true; + }).one(this.animations.end, function(event) { + $(this).remove(); + if ($.isFunction(self.settings.onClosed)) { + self.settings.onClosed.call(this); + } + }); + + setTimeout(function() { + if (!hasAnimation) { + self.$ele.remove(); + if (self.settings.onClosed) { + self.settings.onClosed(self.$ele); + } + } + }, 600); + }, + reposition: function(posX) { + var self = this, + notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])', + $elements = this.$ele.nextAll(notifies); + if (this.settings.newest_on_top == true) { + $elements = this.$ele.prevAll(notifies); + } + $elements.each(function() { + $(this).css(self.settings.placement.from, posX); + posX = (parseInt(posX)+parseInt(self.settings.spacing)) + $(this).outerHeight(); + }); + } + }); + + $.notify = function ( content, options ) { + var plugin = new Notify( this, content, options ); + return plugin.notify; + }; + $.notifyDefaults = function( options ) { + defaults = $.extend(true, {}, defaults, options); + return defaults; + }; + $.notifyClose = function( command ) { + if (typeof command === "undefined" || command == "all") { + $('[data-notify]').find('[data-notify="dismiss"]').trigger('click'); + }else{ + $('[data-notify-position="'+command+'"]').find('[data-notify="dismiss"]').trigger('click'); + } + }; + +})); diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap-notify/bootstrap-notify.min.js b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap-notify/bootstrap-notify.min.js new file mode 100644 index 0000000..ea70a9f --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap-notify/bootstrap-notify.min.js @@ -0,0 +1 @@ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){function b(b,d,e){var d={content:{message:"object"==typeof d?d.message:d,title:d.title?d.title:"",icon:d.icon?d.icon:"",url:d.url?d.url:"#",target:d.target?d.target:"-"}};e=a.extend(!0,{},d,e),this.settings=a.extend(!0,{},c,e),this._defaults=c,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var c={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var a=arguments[0],b=1;b .progress-bar').removeClass("progress-bar-"+a.settings.type),a.settings.type=d[b],this.$ele.addClass("alert-"+d[b]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+d[b]);break;case"icon":var e=this.$ele.find('[data-notify="icon"]');"class"==a.settings.icon_type.toLowerCase()?e.removeClass(a.settings.content.icon).addClass(d[b]):(e.is("img")||e.find("img"),e.attr("src",d[b]));break;case"progress":var f=a.settings.delay-a.settings.delay*(d[b]/100);this.$ele.data("notify-delay",f),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",d[b]).css("width",d[b]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",d[b]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",d[b]);break;default:this.$ele.find('[data-notify="'+b+'"]').html(d[b])}var g=this.$ele.outerHeight()+parseInt(a.settings.spacing)+parseInt(a.settings.offset.y);a.reposition(g)},close:function(){a.close()}}},buildNotify:function(){var b=this.settings.content;this.$ele=a(String.format(this.settings.template,this.settings.type,b.title,b.message,b.url,b.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleDismiss:function(){this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1})},placement:function(){var b=this,c=this.settings.offset.y,d={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},e=!1,f=this.settings;switch(a('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return c=Math.max(c,parseInt(a(this).css(f.placement.from))+parseInt(a(this).outerHeight())+parseInt(f.spacing))}),1==this.settings.newest_on_top&&(c=this.settings.offset.y),d[this.settings.placement.from]=c+"px",this.settings.placement.align){case"left":case"right":d[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":d.left=0,d.right=0}this.$ele.css(d).addClass(this.settings.animate.enter),a.each(Array("webkit","moz","o","ms",""),function(a,c){b.$ele[0].style[c+"AnimationIterationCount"]=1}),a(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(c=parseInt(c)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(c)),a.isFunction(b.settings.onShow)&&b.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(a){e=!0}).one(this.animations.end,function(c){a.isFunction(b.settings.onShown)&&b.settings.onShown.call(this)}),setTimeout(function(){e||a.isFunction(b.settings.onShown)&&b.settings.onShown.call(this)},600)},bind:function(){var b=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){b.close()}),this.$ele.mouseover(function(b){a(this).data("data-hover","true")}).mouseout(function(b){a(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){b.$ele.data("notify-delay",b.settings.delay);var c=setInterval(function(){var a=parseInt(b.$ele.data("notify-delay"))-b.settings.timer;if("false"===b.$ele.data("data-hover")&&"pause"==b.settings.mouse_over||"pause"!=b.settings.mouse_over){var d=(b.settings.delay-a)/b.settings.delay*100;b.$ele.data("notify-delay",a),b.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",d).css("width",d+"%")}a<=-b.settings.timer&&(clearInterval(c),b.close())},b.settings.timer)}},close:function(){var b=this,c=parseInt(this.$ele.css(this.settings.placement.from)),d=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),b.reposition(c),a.isFunction(b.settings.onClose)&&b.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(a){d=!0}).one(this.animations.end,function(c){a(this).remove(),a.isFunction(b.settings.onClosed)&&b.settings.onClosed.call(this)}),setTimeout(function(){d||(b.$ele.remove(),b.settings.onClosed&&b.settings.onClosed(b.$ele))},600)},reposition:function(b){var c=this,d='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',e=this.$ele.nextAll(d);1==this.settings.newest_on_top&&(e=this.$ele.prevAll(d)),e.each(function(){a(this).css(c.settings.placement.from,b),b=parseInt(b)+parseInt(c.settings.spacing)+a(this).outerHeight()})}}),a.notify=function(a,c){var d=new b(this,a,c);return d.notify},a.notifyDefaults=function(b){return c=a.extend(!0,{},c,b)},a.notifyClose=function(b){"undefined"==typeof b||"all"==b?a("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):a('[data-notify-position="'+b+'"]').find('[data-notify="dismiss"]').trigger("click")}}); \ No newline at end of file diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.css new file mode 100644 index 0000000..31d8882 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.css @@ -0,0 +1,587 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 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, .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-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, .125); + box-shadow: inset 0 3px 5px rgba(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 { + text-shadow: 0 1px 0 #fff; + 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; + 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, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-color: #e8e8e8; + 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; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-color: #2e6da4; + 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; +} +.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); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + 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 > .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, .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); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + 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, .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-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, .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: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, .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.css.map */ diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.css.map b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.css.map new file mode 100644 index 0000000..d876f60 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/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;ACeH;;;;;;EAME,yCAAA;EC2CA,4FAAA;EACQ,oFAAA;CFvDT;ACgBC;;;;;;;;;;;;ECsCA,yDAAA;EACQ,iDAAA;CFxCT;ACMC;;;;;;;;;;;;;;;;;;ECiCA,yBAAA;EACQ,iBAAA;CFnBT;AC/BD;;;;;;EAuBI,kBAAA;CDgBH;ACyBC;;EAEE,uBAAA;CDvBH;AC4BD;EErEI,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;EAuC2C,0BAAA;EAA2B,mBAAA;CDjBvE;ACpBC;;EAEE,0BAAA;EACA,6BAAA;CDsBH;ACnBC;;EAEE,0BAAA;EACA,sBAAA;CDqBH;ACfG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6BL;ACbD;EEtEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8DD;AC5DC;;EAEE,0BAAA;EACA,6BAAA;CD8DH;AC3DC;;EAEE,0BAAA;EACA,sBAAA;CD6DH;ACvDG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqEL;ACpDD;EEvEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CDsGD;ACpGC;;EAEE,0BAAA;EACA,6BAAA;CDsGH;ACnGC;;EAEE,0BAAA;EACA,sBAAA;CDqGH;AC/FG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6GL;AC3FD;EExEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8ID;AC5IC;;EAEE,0BAAA;EACA,6BAAA;CD8IH;AC3IC;;EAEE,0BAAA;EACA,sBAAA;CD6IH;ACvIG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqJL;AClID;EEzEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CDsLD;ACpLC;;EAEE,0BAAA;EACA,6BAAA;CDsLH;ACnLC;;EAEE,0BAAA;EACA,sBAAA;CDqLH;AC/KG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6LL;ACzKD;EE1EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8ND;AC5NC;;EAEE,0BAAA;EACA,6BAAA;CD8NH;AC3NC;;EAEE,0BAAA;EACA,sBAAA;CD6NH;ACvNG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqOL;AC1MD;;EClCE,mDAAA;EACQ,2CAAA;CFgPT;ACrMD;;EE3FI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF0FF,0BAAA;CD2MD;ACzMD;;;EEhGI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFgGF,0BAAA;CD+MD;ACtMD;EE7GI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ECnBF,oEAAA;EH+HA,mBAAA;ECjEA,4FAAA;EACQ,oFAAA;CF8QT;ACjND;;EE7GI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ED2CF,yDAAA;EACQ,iDAAA;CFwRT;AC9MD;;EAEE,+CAAA;CDgND;AC5MD;EEhII,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EACA,4BAAA;EACA,uHAAA;ECnBF,oEAAA;EHkJA,mBAAA;CDkND;ACrND;;EEhII,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ED2CF,wDAAA;EACQ,gDAAA;CF+ST;AC/ND;;EAYI,0CAAA;CDuNH;AClND;;;EAGE,iBAAA;CDoND;AC/LD;EAfI;;;IAGE,YAAA;IE7JF,yEAAA;IACA,oEAAA;IACA,8FAAA;IAAA,uEAAA;IACA,4BAAA;IACA,uHAAA;GH+WD;CACF;AC3MD;EACE,8CAAA;EC3HA,2FAAA;EACQ,mFAAA;CFyUT;ACnMD;EEtLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CD+MD;AC1MD;EEvLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CDuND;ACjND;EExLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CD+ND;ACxND;EEzLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CDuOD;ACxND;EEjMI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH4ZH;ACrND;EE3MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHmaH;AC3ND;EE5MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH0aH;ACjOD;EE7MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHibH;ACvOD;EE9MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHwbH;AC7OD;EE/MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH+bH;AChPD;EElLI,8MAAA;EACA,yMAAA;EACA,sMAAA;CHqaH;AC5OD;EACE,mBAAA;EC9KA,mDAAA;EACQ,2CAAA;CF6ZT;AC7OD;;;EAGE,8BAAA;EEnOE,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFiOF,sBAAA;CDmPD;ACxPD;;;EAQI,kBAAA;CDqPH;AC3OD;ECnME,kDAAA;EACQ,0CAAA;CFibT;ACrOD;EE5PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHoeH;AC3OD;EE7PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH2eH;ACjPD;EE9PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHkfH;ACvPD;EE/PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHyfH;AC7PD;EEhQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHggBH;ACnQD;EEjQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHugBH;ACnQD;EExQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFsQF,sBAAA;EC3NA,0FAAA;EACQ,kFAAA;CFqeT","file":"bootstrap-theme.css","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\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.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 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 { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\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","// 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 word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\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","// Gradients\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 background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\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 background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\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 }\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 background-repeat: no-repeat;\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 }\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 background-repeat: no-repeat;\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 }\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/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.min.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.min.css new file mode 100644 index 0000000..5e39401 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 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{text-shadow:0 1px 0 #fff;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;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-color:#e8e8e8;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}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;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}.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);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;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);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;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/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.min.css.map b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.min.css.map new file mode 100644 index 0000000..94813e9 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap-theme.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 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 { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\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","// 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 word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\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","// Gradients\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 background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\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 background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\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 }\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 background-repeat: no-repeat;\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 }\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 background-repeat: no-repeat;\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 }\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/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap.css b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap.css new file mode 100644 index 0000000..6167622 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap.css @@ -0,0 +1,6757 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 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; + -webkit-text-size-adjust: 100%; + -ms-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: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +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 { + padding: 0; + border: 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-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +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: normal; + 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: #333; + 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 { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.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: normal; + line-height: 1; + color: #777; +} +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: .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: #777; +} +.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 #eee; +} +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; + margin-left: -5px; + list-style: none; +} +.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: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +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: #777; +} +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 #eee; + 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, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -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: #333; + 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; +} +.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; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + 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 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; +} +.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: .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: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +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: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-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; +} +.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, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .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: #eee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@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 label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + 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: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +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; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.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, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(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, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(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, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(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; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.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, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +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; + 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:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.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; + 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:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.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; + 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:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.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; + 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:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.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; + 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:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.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; + 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:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.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: normal; + 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: #777; + 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 .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .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-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.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; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(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: normal; + line-height: 1.42857143; + color: #333; + 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: #777; +} +.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: #777; + 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, .125); + box-shadow: inset 0 3px 5px rgba(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: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + 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: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + 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: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + 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; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.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-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; + } +} +.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-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@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; +} +.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-top: 8px; + margin-right: 15px; + 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-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@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-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-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-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-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-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-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: #777; +} +.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: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 > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.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: #777; + 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: #eee; +} +.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: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .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: #777; +} +.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: #777; + 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: #eee; +} +.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 .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .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: #333; +} +.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, .1); + box-shadow: inset 0 1px 2px rgba(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, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.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); + -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, .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); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + 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); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + 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); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + 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); +} +.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; +} +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.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.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: #777; +} +.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; +} +.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, .05); + box-shadow: 0 1px 1px rgba(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: #333; + 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: #333; +} +.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, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(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: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.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-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.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; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.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: .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, .5); + box-shadow: 0 5px 15px rgba(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-size: 12px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + 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; + filter: alpha(opacity=0); + opacity: 0; + + line-break: auto; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .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-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; +} +.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; +} +.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-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + 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; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + + line-break: auto; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.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; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(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: #999; + border-right-color: rgba(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: #999; + border-bottom-color: rgba(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: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .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 .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .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 { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 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, .6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(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, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(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; + filter: alpha(opacity=90); + outline: 0; + opacity: .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, .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 */ diff --git a/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap.css.map b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/css/bootstrap.css.map new file mode 100644 index 0000000..f010c82 --- /dev/null +++ b/engine-oauth2/oauth2-sdk-authentication-server/src/main/resources/static/plugins/bootstrap/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;ACG5E;EACE,wBAAA;EACA,2BAAA;EACA,+BAAA;CDDD;ACQD;EACE,UAAA;CDND;ACmBD;;;;;;;;;;;;;EAaE,eAAA;CDjBD;ACyBD;;;;EAIE,sBAAA;EACA,yBAAA;CDvBD;AC+BD;EACE,cAAA;EACA,UAAA;CD7BD;ACqCD;;EAEE,cAAA;CDnCD;AC6CD;EACE,8BAAA;CD3CD;ACmDD;;EAEE,WAAA;CDjDD;AC2DD;EACE,0BAAA;CDzDD;ACgED;;EAEE,kBAAA;CD9DD;ACqED;EACE,mBAAA;CDnED;AC2ED;EACE,eAAA;EACA,iBAAA;CDzED;ACgFD;EACE,iBAAA;EACA,YAAA;CD9ED;ACqFD;EACE,eAAA;CDnFD;AC0FD;;EAEE,eAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;CDxFD;AC2FD;EACE,YAAA;CDzFD;AC4FD;EACE,gBAAA;CD1FD;ACoGD;EACE,UAAA;CDlGD;ACyGD;EACE,iBAAA;CDvGD;ACiHD;EACE,iBAAA;CD/GD;ACsHD;EACE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,UAAA;CDpHD;AC2HD;EACE,eAAA;CDzHD;ACgID;;;;EAIE,kCAAA;EACA,eAAA;CD9HD;ACgJD;;;;;EAKE,eAAA;EACA,cAAA;EACA,UAAA;CD9ID;ACqJD;EACE,kBAAA;CDnJD;AC6JD;;EAEE,qBAAA;CD3JD;ACsKD;;;;EAIE,2BAAA;EACA,gBAAA;CDpKD;AC2KD;;EAEE,gBAAA;CDzKD;ACgLD;;EAEE,UAAA;EACA,WAAA;CD9KD;ACsLD;EACE,oBAAA;CDpLD;AC+LD;;EAEE,+BAAA;KAAA,4BAAA;UAAA,uBAAA;EACA,WAAA;CD7LD;ACsMD;;EAEE,aAAA;CDpMD;AC4MD;EACE,8BAAA;EACA,gCAAA;KAAA,6BAAA;UAAA,wBAAA;CD1MD;ACmND;;EAEE,yBAAA;CDjND;ACwND;EACE,0BAAA;EACA,cAAA;EACA,+BAAA;CDtND;AC8ND;EACE,UAAA;EACA,WAAA;CD5ND;ACmOD;EACE,eAAA;CDjOD;ACyOD;EACE,kBAAA;CDvOD;ACiPD;EACE,0BAAA;EACA,kBAAA;CD/OD;ACkPD;;EAEE,WAAA;CDhPD;AACD,qFAAqF;AElFrF;EA7FI;;;IAGI,mCAAA;IACA,uBAAA;IACA,oCAAA;YAAA,4BAAA;IACA,6BAAA;GFkLL;EE/KC;;IAEI,2BAAA;GFiLL;EE9KC;IACI,6BAAA;GFgLL;EE7KC;IACI,8BAAA;GF+KL;EE1KC;;IAEI,YAAA;GF4KL;EEzKC;;IAEI,uBAAA;IACA,yBAAA;GF2KL;EExKC;IACI,4BAAA;GF0KL;EEvKC;;IAEI,yBAAA;GFyKL;EEtKC;IACI,2BAAA;GFwKL;EErKC;;;IAGI,WAAA;IACA,UAAA;GFuKL;EEpKC;;IAEI,wBAAA;GFsKL;EEhKC;IACI,cAAA;GFkKL;EEhKC;;IAGQ,kCAAA;GFiKT;EE9JC;IACI,uBAAA;GFgKL;EE7JC;IACI,qCAAA;GF+JL;EEhKC;;IAKQ,kCAAA;GF+JT;EE5JC;;IAGQ,kCAAA;GF6JT;CACF;AGnPD;EACE,oCAAA;EACA,sDAAA;EACA,gYAAA;CHqPD;AG7OD;EACE,mBAAA;EACA,SAAA;EACA,sBAAA;EACA,oCAAA;EACA,mBAAA;EACA,oBAAA;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;AIthCD;ECgEE,+BAAA;EACG,4BAAA;EACK,uBAAA;CLy9BT;AIxhCD;;EC6DE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL+9BT;AIthCD;EACE,gBAAA;EACA,8CAAA;CJwhCD;AIrhCD;EACE,4DAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;CJuhCD;AInhCD;;;;EAIE,qBAAA;EACA,mBAAA;EACA,qBAAA;CJqhCD;AI/gCD;EACE,eAAA;EACA,sBAAA;CJihCD;AI/gCC;;EAEE,eAAA;EACA,2BAAA;CJihCH;AI9gCC;EEnDA,2CAAA;EACA,qBAAA;CNokCD;AIvgCD;EACE,UAAA;CJygCD;AIngCD;EACE,uBAAA;CJqgCD;AIjgCD;;;;;EGvEE,eAAA;EACA,gBAAA;EACA,aAAA;CP+kCD;AIrgCD;EACE,mBAAA;CJugCD;AIjgCD;EACE,aAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EC6FA,yCAAA;EACK,oCAAA;EACG,iCAAA;EEvLR,sBAAA;EACA,gBAAA;EACA,aAAA;CP+lCD;AIjgCD;EACE,mBAAA;CJmgCD;AI7/BD;EACE,iBAAA;EACA,oBAAA;EACA,UAAA;EACA,8BAAA;CJ+/BD;AIv/BD;EACE,mBAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,iBAAA;EACA,uBAAA;EACA,UAAA;CJy/BD;AIj/BC;;EAEE,iBAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;EACA,WAAA;CJm/BH;AIx+BD;EACE,gBAAA;CJ0+BD;AQjoCD;;;;;;;;;;;;EAEE,qBAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;CR6oCD;AQlpCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,oBAAA;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;AQ/rCD;EAwOA;IA1OI,gBAAA;GRqsCD;CACF;AQ7rCD;;EAEE,eAAA;CR+rCD;AQ5rCD;;EAEE,0BAAA;EACA,cAAA;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;ECrGE,eAAA;CT8yCD;AS7yCC;;EAEE,eAAA;CT+yCH;AQ1sCD;ECxGE,eAAA;CTqzCD;ASpzCC;;EAEE,eAAA;CTszCH;AQ9sCD;EC3GE,eAAA;CT4zCD;AS3zCC;;EAEE,eAAA;CT6zCH;AQltCD;EC9GE,eAAA;CTm0CD;ASl0CC;;EAEE,eAAA;CTo0CH;AQttCD;ECjHE,eAAA;CT00CD;ASz0CC;;EAEE,eAAA;CT20CH;AQttCD;EAGE,YAAA;EE3HA,0BAAA;CVk1CD;AUj1CC;;EAEE,0BAAA;CVm1CH;AQxtCD;EE9HE,0BAAA;CVy1CD;AUx1CC;;EAEE,0BAAA;CV01CH;AQ5tCD;EEjIE,0BAAA;CVg2CD;AU/1CC;;EAEE,0BAAA;CVi2CH;AQhuCD;EEpIE,0BAAA;CVu2CD;AUt2CC;;EAEE,0BAAA;CVw2CH;AQpuCD;EEvIE,0BAAA;CV82CD;AU72CC;;EAEE,0BAAA;CV+2CH;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,kBAAA;EACA,mBAAA;CR4tCH;AQvtCD;EACE,cAAA;EACA,oBAAA;CRytCD;AQvtCD;;EAEE,wBAAA;CRytCD;AQvtCD;EACE,kBAAA;CRytCD;AQvtCD;EACE,eAAA;CRytCD;AQhsCD;EA6EA;IAvFM,YAAA;IACA,aAAA;IACA,YAAA;IACA,kBAAA;IGtNJ,iBAAA;IACA,wBAAA;IACA,oBAAA;GXq6CC;EQ7nCH;IAhFM,mBAAA;GRgtCH;CACF;AQvsCD;;EAGE,aAAA;EACA,kCAAA;CRwsCD;AQtsCD;EACE,eAAA;EA9IqB,0BAAA;CRu1CtB;AQpsCD;EACE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,+BAAA;CRssCD;AQjsCG;;;EACE,iBAAA;CRqsCL;AQ/sCD;;;EAmBI,eAAA;EACA,eAAA;EACA,wBAAA;EACA,eAAA;CRisCH;AQ/rCG;;;EACE,uBAAA;CRmsCL;AQ3rCD;;EAEE,oBAAA;EACA,gBAAA;EACA,gCAAA;EACA,eAAA;EACA,kBAAA;CR6rCD;AQvrCG;;;;;;EAAW,YAAA;CR+rCd;AQ9rCG;;;;;;EACE,uBAAA;CRqsCL;AQ/rCD;EACE,oBAAA;EACA,mBAAA;EACA,wBAAA;CRisCD;AYv+CD;;;;EAIE,+DAAA;CZy+CD;AYr+CD;EACE,iBAAA;EACA,eAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CZu+CD;AYn+CD;EACE,iBAAA;EACA,eAAA;EACA,YAAA;EACA,uBAAA;EACA,mBAAA;EACA,uDAAA;UAAA,+CAAA;CZq+CD;AY3+CD;EASI,WAAA;EACA,gBAAA;EACA,kBAAA;EACA,yBAAA;UAAA,iBAAA;CZq+CH;AYh+CD;EACE,eAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,sBAAA;EACA,sBAAA;EACA,eAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;CZk+CD;AY7+CD;EAeI,WAAA;EACA,mBAAA;EACA,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,iBAAA;CZi+CH;AY59CD;EACE,kBAAA;EACA,mBAAA;CZ89CD;AaxhDD;ECHE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;Cd8hDD;AaxhDC;EAqEF;IAvEI,aAAA;Gb8hDD;CACF;Aa1hDC;EAkEF;IApEI,aAAA;GbgiDD;CACF;Aa5hDD;EA+DA;IAjEI,cAAA;GbkiDD;CACF;AazhDD;ECvBE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;CdmjDD;AathDD;ECvBE,mBAAA;EACA,oBAAA;CdgjDD;AehjDG;EACE,mBAAA;EAEA,gBAAA;EAEA,mBAAA;EACA,oBAAA;CfgjDL;AehiDG;EACE,YAAA;CfkiDL;Ae3hDC;EACE,YAAA;Cf6hDH;Ae9hDC;EACE,oBAAA;CfgiDH;AejiDC;EACE,oBAAA;CfmiDH;AepiDC;EACE,WAAA;CfsiDH;AeviDC;EACE,oBAAA;CfyiDH;Ae1iDC;EACE,oBAAA;Cf4iDH;Ae7iDC;EACE,WAAA;Cf+iDH;AehjDC;EACE,oBAAA;CfkjDH;AenjDC;EACE,oBAAA;CfqjDH;AetjDC;EACE,WAAA;CfwjDH;AezjDC;EACE,oBAAA;Cf2jDH;Ae5jDC;EACE,mBAAA;Cf8jDH;AehjDC;EACE,YAAA;CfkjDH;AenjDC;EACE,oBAAA;CfqjDH;AetjDC;EACE,oBAAA;CfwjDH;AezjDC;EACE,WAAA;Cf2jDH;Ae5jDC;EACE,oBAAA;Cf8jDH;Ae/jDC;EACE,oBAAA;CfikDH;AelkDC;EACE,WAAA;CfokDH;AerkDC;EACE,oBAAA;CfukDH;AexkDC;EACE,oBAAA;Cf0kDH;Ae3kDC;EACE,WAAA;Cf6kDH;Ae9kDC;EACE,oBAAA;CfglDH;AejlDC;EACE,mBAAA;CfmlDH;Ae/kDC;EACE,YAAA;CfilDH;AejmDC;EACE,WAAA;CfmmDH;AepmDC;EACE,mBAAA;CfsmDH;AevmDC;EACE,mBAAA;CfymDH;Ae1mDC;EACE,UAAA;Cf4mDH;Ae7mDC;EACE,mBAAA;Cf+mDH;AehnDC;EACE,mBAAA;CfknDH;AennDC;EACE,UAAA;CfqnDH;AetnDC;EACE,mBAAA;CfwnDH;AeznDC;EACE,mBAAA;Cf2nDH;Ae5nDC;EACE,UAAA;Cf8nDH;Ae/nDC;EACE,mBAAA;CfioDH;AeloDC;EACE,kBAAA;CfooDH;AehoDC;EACE,WAAA;CfkoDH;AepnDC;EACE,kBAAA;CfsnDH;AevnDC;EACE,0BAAA;CfynDH;Ae1nDC;EACE,0BAAA;Cf4nDH;Ae7nDC;EACE,iBAAA;Cf+nDH;AehoDC;EACE,0BAAA;CfkoDH;AenoDC;EACE,0BAAA;CfqoDH;AetoDC;EACE,iBAAA;CfwoDH;AezoDC;EACE,0BAAA;Cf2oDH;Ae5oDC;EACE,0BAAA;Cf8oDH;Ae/oDC;EACE,iBAAA;CfipDH;AelpDC;EACE,0BAAA;CfopDH;AerpDC;EACE,yBAAA;CfupDH;AexpDC;EACE,gBAAA;Cf0pDH;Aa1pDD;EElCI;IACE,YAAA;Gf+rDH;EexrDD;IACE,YAAA;Gf0rDD;Ee3rDD;IACE,oBAAA;Gf6rDD;Ee9rDD;IACE,oBAAA;GfgsDD;EejsDD;IACE,WAAA;GfmsDD;EepsDD;IACE,oBAAA;GfssDD;EevsDD;IACE,oBAAA;GfysDD;Ee1sDD;IACE,WAAA;Gf4sDD;Ee7sDD;IACE,oBAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,WAAA;GfqtDD;EettDD;IACE,oBAAA;GfwtDD;EeztDD;IACE,mBAAA;Gf2tDD;Ee7sDD;IACE,YAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,oBAAA;GfqtDD;EettDD;IACE,WAAA;GfwtDD;EeztDD;IACE,oBAAA;Gf2tDD;Ee5tDD;IACE,oBAAA;Gf8tDD;Ee/tDD;IACE,WAAA;GfiuDD;EeluDD;IACE,oBAAA;GfouDD;EeruDD;IACE,oBAAA;GfuuDD;EexuDD;IACE,WAAA;Gf0uDD;Ee3uDD;IACE,oBAAA;Gf6uDD;Ee9uDD;IACE,mBAAA;GfgvDD;Ee5uDD;IACE,YAAA;Gf8uDD;Ee9vDD;IACE,WAAA;GfgwDD;EejwDD;IACE,mBAAA;GfmwDD;EepwDD;IACE,mBAAA;GfswDD;EevwDD;IACE,UAAA;GfywDD;Ee1wDD;IACE,mBAAA;Gf4wDD;Ee7wDD;IACE,mBAAA;Gf+wDD;EehxDD;IACE,UAAA;GfkxDD;EenxDD;IACE,mBAAA;GfqxDD;EetxDD;IACE,mBAAA;GfwxDD;EezxDD;IACE,UAAA;Gf2xDD;Ee5xDD;IACE,mBAAA;Gf8xDD;Ee/xDD;IACE,kBAAA;GfiyDD;Ee7xDD;IACE,WAAA;Gf+xDD;EejxDD;IACE,kBAAA;GfmxDD;EepxDD;IACE,0BAAA;GfsxDD;EevxDD;IACE,0BAAA;GfyxDD;Ee1xDD;IACE,iBAAA;Gf4xDD;Ee7xDD;IACE,0BAAA;Gf+xDD;EehyDD;IACE,0BAAA;GfkyDD;EenyDD;IACE,iBAAA;GfqyDD;EetyDD;IACE,0BAAA;GfwyDD;EezyDD;IACE,0BAAA;Gf2yDD;Ee5yDD;IACE,iBAAA;Gf8yDD;Ee/yDD;IACE,0BAAA;GfizDD;EelzDD;IACE,yBAAA;GfozDD;EerzDD;IACE,gBAAA;GfuzDD;CACF;Aa/yDD;EE3CI;IACE,YAAA;Gf61DH;Eet1DD;IACE,YAAA;Gfw1DD;Eez1DD;IACE,oBAAA;Gf21DD;Ee51DD;IACE,oBAAA;Gf81DD;Ee/1DD;IACE,WAAA;Gfi2DD;Eel2DD;IACE,oBAAA;Gfo2DD;Eer2DD;IACE,oBAAA;Gfu2DD;Eex2DD;IACE,WAAA;Gf02DD;Ee32DD;IACE,oBAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,WAAA;Gfm3DD;Eep3DD;IACE,oBAAA;Gfs3DD;Eev3DD;IACE,mBAAA;Gfy3DD;Ee32DD;IACE,YAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,oBAAA;Gfm3DD;Eep3DD;IACE,WAAA;Gfs3DD;Eev3DD;IACE,oBAAA;Gfy3DD;Ee13DD;IACE,oBAAA;Gf43DD;Ee73DD;IACE,WAAA;Gf+3DD;Eeh4DD;IACE,oBAAA;Gfk4DD;Een4DD;IACE,oBAAA;Gfq4DD;Eet4DD;IACE,WAAA;Gfw4DD;Eez4DD;IACE,oBAAA;Gf24DD;Ee54DD;IACE,mBAAA;Gf84DD;Ee14DD;IACE,YAAA;Gf44DD;Ee55DD;IACE,WAAA;Gf85DD;Ee/5DD;IACE,mBAAA;Gfi6DD;Eel6DD;IACE,mBAAA;Gfo6DD;Eer6DD;IACE,UAAA;Gfu6DD;Eex6DD;IACE,mBAAA;Gf06DD;Ee36DD;IACE,mBAAA;Gf66DD;Ee96DD;IACE,UAAA;Gfg7DD;Eej7DD;IACE,mBAAA;Gfm7DD;Eep7DD;IACE,mBAAA;Gfs7DD;Eev7DD;IACE,UAAA;Gfy7DD;Ee17DD;IACE,mBAAA;Gf47DD;Ee77DD;IACE,kBAAA;Gf+7DD;Ee37DD;IACE,WAAA;Gf67DD;Ee/6DD;IACE,kBAAA;Gfi7DD;Eel7DD;IACE,0BAAA;Gfo7DD;Eer7DD;IACE,0BAAA;Gfu7DD;Eex7DD;IACE,iBAAA;Gf07DD;Ee37DD;IACE,0BAAA;Gf67DD;Ee97DD;IACE,0BAAA;Gfg8DD;Eej8DD;IACE,iBAAA;Gfm8DD;Eep8DD;IACE,0BAAA;Gfs8DD;Eev8DD;IACE,0BAAA;Gfy8DD;Ee18DD;IACE,iBAAA;Gf48DD;Ee78DD;IACE,0BAAA;Gf+8DD;Eeh9DD;IACE,yBAAA;Gfk9DD;Een9DD;IACE,gBAAA;Gfq9DD;CACF;Aa18DD;EE9CI;IACE,YAAA;Gf2/DH;Eep/DD;IACE,YAAA;Gfs/DD;Eev/DD;IACE,oBAAA;Gfy/DD;Ee1/DD;IACE,oBAAA;Gf4/DD;Ee7/DD;IACE,WAAA;Gf+/DD;EehgED;IACE,oBAAA;GfkgED;EengED;IACE,oBAAA;GfqgED;EetgED;IACE,WAAA;GfwgED;EezgED;IACE,oBAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,WAAA;GfihED;EelhED;IACE,oBAAA;GfohED;EerhED;IACE,mBAAA;GfuhED;EezgED;IACE,YAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,oBAAA;GfihED;EelhED;IACE,WAAA;GfohED;EerhED;IACE,oBAAA;GfuhED;EexhED;IACE,oBAAA;Gf0hED;Ee3hED;IACE,WAAA;Gf6hED;Ee9hED;IACE,oBAAA;GfgiED;EejiED;IACE,oBAAA;GfmiED;EepiED;IACE,WAAA;GfsiED;EeviED;IACE,oBAAA;GfyiED;Ee1iED;IACE,mBAAA;Gf4iED;EexiED;IACE,YAAA;Gf0iED;Ee1jED;IACE,WAAA;Gf4jED;Ee7jED;IACE,mBAAA;Gf+jED;EehkED;IACE,mBAAA;GfkkED;EenkED;IACE,UAAA;GfqkED;EetkED;IACE,mBAAA;GfwkED;EezkED;IACE,mBAAA;Gf2kED;Ee5kED;IACE,UAAA;Gf8kED;Ee/kED;IACE,mBAAA;GfilED;EellED;IACE,mBAAA;GfolED;EerlED;IACE,UAAA;GfulED;EexlED;IACE,mBAAA;Gf0lED;Ee3lED;IACE,kBAAA;Gf6lED;EezlED;IACE,WAAA;Gf2lED;Ee7kED;IACE,kBAAA;Gf+kED;EehlED;IACE,0BAAA;GfklED;EenlED;IACE,0BAAA;GfqlED;EetlED;IACE,iBAAA;GfwlED;EezlED;IACE,0BAAA;Gf2lED;Ee5lED;IACE,0BAAA;Gf8lED;Ee/lED;IACE,iBAAA;GfimED;EelmED;IACE,0BAAA;GfomED;EermED;IACE,0BAAA;GfumED;EexmED;IACE,iBAAA;Gf0mED;Ee3mED;IACE,0BAAA;Gf6mED;Ee9mED;IACE,yBAAA;GfgnED;EejnED;IACE,gBAAA;GfmnED;CACF;AgBvrED;EACE,8BAAA;ChByrED;AgBvrED;EACE,iBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ChByrED;AgBvrED;EACE,iBAAA;ChByrED;AgBnrED;EACE,YAAA;EACA,gBAAA;EACA,oBAAA;ChBqrED;AgBxrED;;;;;;EAWQ,aAAA;EACA,wBAAA;EACA,oBAAA;EACA,2BAAA;ChBqrEP;AgBnsED;EAoBI,uBAAA;EACA,8BAAA;ChBkrEH;AgBvsED;;;;;;EA8BQ,cAAA;ChBirEP;AgB/sED;EAoCI,2BAAA;ChB8qEH;AgBltED;EAyCI,uBAAA;ChB4qEH;AgBrqED;;;;;;EAOQ,aAAA;ChBsqEP;AgB3pED;EACE,uBAAA;ChB6pED;AgB9pED;;;;;;EAQQ,uBAAA;ChB8pEP;AgBtqED;;EAeM,yBAAA;ChB2pEL;AgBjpED;EAEI,0BAAA;ChBkpEH;AgBzoED;EAEI,0BAAA;ChB0oEH;AgBjoED;EACE,iBAAA;EACA,YAAA;EACA,sBAAA;ChBmoED;AgB9nEG;;EACE,iBAAA;EACA,YAAA;EACA,oBAAA;ChBioEL;AiB7wEC;;;;;;;;;;;;EAOI,0BAAA;CjBoxEL;AiB9wEC;;;;;EAMI,0BAAA;CjB+wEL;AiBlyEC;;;;;;;;;;;;EAOI,0BAAA;CjByyEL;AiBnyEC;;;;;EAMI,0BAAA;CjBoyEL;AiBvzEC;;;;;;;;;;;;EAOI,0BAAA;CjB8zEL;AiBxzEC;;;;;EAMI,0BAAA;CjByzEL;AiB50EC;;;;;;;;;;;;EAOI,0BAAA;CjBm1EL;AiB70EC;;;;;EAMI,0BAAA;CjB80EL;AiBj2EC;;;;;;;;;;;;EAOI,0BAAA;CjBw2EL;AiBl2EC;;;;;EAMI,0BAAA;CjBm2EL;AgBjtED;EACE,iBAAA;EACA,kBAAA;ChBmtED;AgBtpED;EACA;IA3DI,YAAA;IACA,oBAAA;IACA,mBAAA;IACA,6CAAA;IACA,uBAAA;GhBotED;EgB7pEH;IAnDM,iBAAA;GhBmtEH;EgBhqEH;;;;;;IA1CY,oBAAA;GhBktET;EgBxqEH;IAlCM,UAAA;GhB6sEH;EgB3qEH;;;;;;IAzBY,eAAA;GhB4sET;EgBnrEH;;;;;;IArBY,gBAAA;GhBgtET;EgB3rEH;;;;IARY,iBAAA;GhBysET;CACF;AkBn6ED;EACE,WAAA;EACA,UAAA;EACA,UAAA;EAIA,aAAA;ClBk6ED;AkB/5ED;EACE,eAAA;EACA,YAAA;EACA,WAAA;EACA,oBAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;EACA,UAAA;EACA,iCAAA;ClBi6ED;AkB95ED;EACE,sBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;ClBg6ED;AkBr5ED;Eb4BE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL43ET;AkBr5ED;;EAEE,gBAAA;EACA,mBAAA;EACA,oBAAA;ClBu5ED;AkBp5ED;EACE,eAAA;ClBs5ED;AkBl5ED;EACE,eAAA;EACA,YAAA;ClBo5ED;AkBh5ED;;EAEE,aAAA;ClBk5ED;AkB94ED;;;EZrEE,2CAAA;EACA,qBAAA;CNw9ED;AkB74ED;EACE,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;ClB+4ED;AkBr3ED;EACE,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EbxDA,yDAAA;EACQ,iDAAA;EAyHR,uFAAA;EACK,0EAAA;EACG,uEAAA;CLwzET;AmBh8EC;EACE,sBAAA;EACA,WAAA;EdUF,uFAAA;EACQ,+EAAA;CLy7ET;AKx5EC;EACE,YAAA;EACA,WAAA;CL05EH;AKx5EC;EAA0B,YAAA;CL25E3B;AK15EC;EAAgC,YAAA;CL65EjC;AkBj4EC;EACE,UAAA;EACA,8BAAA;ClBm4EH;AkB33EC;;;EAGE,0BAAA;EACA,WAAA;ClB63EH;AkB13EC;;EAEE,oBAAA;ClB43EH;AkBx3EC;EACE,aAAA;ClB03EH;AkB92ED;EACE,yBAAA;ClBg3ED;AkBx0ED;EAtBI;;;;IACE,kBAAA;GlBo2EH;EkBj2EC;;;;;;;;IAEE,kBAAA;GlBy2EH;EkBt2EC;;;;;;;;IAEE,kBAAA;GlB82EH;CACF;AkBp2ED;EACE,oBAAA;ClBs2ED;AkB91ED;;EAEE,mBAAA;EACA,eAAA;EACA,iBAAA;EACA,oBAAA;ClBg2ED;AkBr2ED;;EAQI,iBAAA;EACA,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,gBAAA;ClBi2EH;AkB91ED;;;;EAIE,mBAAA;EACA,mBAAA;EACA,mBAAA;ClBg2ED;AkB71ED;;EAEE,iBAAA;ClB+1ED;AkB31ED;;EAEE,mBAAA;EACA,sBAAA;EACA,mBAAA;EACA,iBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;ClB61ED;AkB31ED;;EAEE,cAAA;EACA,kBAAA;ClB61ED;AkBp1EC;;;;;;EAGE,oBAAA;ClBy1EH;AkBn1EC;;;;EAEE,oBAAA;ClBu1EH;AkBj1EC;;;;EAGI,oBAAA;ClBo1EL;AkBz0ED;EAEE,iBAAA;EACA,oBAAA;EAEA,iBAAA;EACA,iBAAA;ClBy0ED;AkBv0EC;;EAEE,gBAAA;EACA,iBAAA;ClBy0EH;AkB5zED;ECnQE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnBkkFD;AmBhkFC;EACE,aAAA;EACA,kBAAA;CnBkkFH;AmB/jFC;;EAEE,aAAA;CnBikFH;AkBx0ED;EAEI,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;ClBy0EH;AkB/0ED;EASI,aAAA;EACA,kBAAA;ClBy0EH;AkBn1ED;;EAcI,aAAA;ClBy0EH;AkBv1ED;EAiBI,aAAA;EACA,iBAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;ClBy0EH;AkBr0ED;EC/RE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBumFD;AmBrmFC;EACE,aAAA;EACA,kBAAA;CnBumFH;AmBpmFC;;EAEE,aAAA;CnBsmFH;AkBj1ED;EAEI,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ClBk1EH;AkBx1ED;EASI,aAAA;EACA,kBAAA;ClBk1EH;AkB51ED;;EAcI,aAAA;ClBk1EH;AkBh2ED;EAiBI,aAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;ClBk1EH;AkBz0ED;EAEE,mBAAA;ClB00ED;AkB50ED;EAMI,sBAAA;ClBy0EH;AkBr0ED;EACE,mBAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;ClBu0ED;AkBr0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBu0ED;AkBr0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBu0ED;AkBn0ED;;;;;;;;;;EC1ZI,eAAA;CnByuFH;AkB/0ED;ECtZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CL0rFT;AmBxuFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL+rFT;AkBz1ED;EC5YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBwuFH;AkB91ED;ECtYI,eAAA;CnBuuFH;AkB91ED;;;;;;;;;;EC7ZI,eAAA;CnBuwFH;AkB12ED;ECzZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLwtFT;AmBtwFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL6tFT;AkBp3ED;EC/YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBswFH;AkBz3ED;ECzYI,eAAA;CnBqwFH;AkBz3ED;;;;;;;;;;EChaI,eAAA;CnBqyFH;AkBr4ED;EC5ZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLsvFT;AmBpyFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL2vFT;AkB/4ED;EClZI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBoyFH;AkBp5ED;EC5YI,eAAA;CnBmyFH;AkBh5EC;EACE,UAAA;ClBk5EH;AkBh5EC;EACE,OAAA;ClBk5EH;AkBx4ED;EACE,eAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;ClB04ED;AkBvzED;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBy3EH;EkBrvEH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBu3EH;EkB1vEH;IAxHM,sBAAA;GlBq3EH;EkB7vEH;IApHM,sBAAA;IACA,uBAAA;GlBo3EH;EkBjwEH;;;IA9GQ,YAAA;GlBo3EL;EkBtwEH;IAxGM,YAAA;GlBi3EH;EkBzwEH;IApGM,iBAAA;IACA,uBAAA;GlBg3EH;EkB7wEH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB62EH;EkBpxEH;;IAtFQ,gBAAA;GlB82EL;EkBxxEH;;IAjFM,mBAAA;IACA,eAAA;GlB62EH;EkB7xEH;IA3EM,OAAA;GlB22EH;CACF;AkBj2ED;;;;EASI,cAAA;EACA,iBAAA;EACA,iBAAA;ClB81EH;AkBz2ED;;EAiBI,iBAAA;ClB41EH;AkB72ED;EJthBE,mBAAA;EACA,oBAAA;Cds4FD;AkB10EC;EAyBF;IAnCM,kBAAA;IACA,iBAAA;IACA,iBAAA;GlBw1EH;CACF;AkBx3ED;EAwCI,YAAA;ClBm1EH;AkBr0EC;EAUF;IAdQ,kBAAA;IACA,gBAAA;GlB60EL;CACF;AkBn0EC;EAEF;IANQ,iBAAA;IACA,gBAAA;GlB20EL;CACF;AoBp6FD;EACE,sBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,+BAAA;MAAA,2BAAA;EACA,gBAAA;EACA,uBAAA;EACA,8BAAA;EACA,oBAAA;EC0CA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,mBAAA;EhB+JA,0BAAA;EACG,uBAAA;EACC,sBAAA;EACI,kBAAA;CL+tFT;AoBv6FG;;;;;;EdnBF,2CAAA;EACA,qBAAA;CNk8FD;AoB16FC;;;EAGE,YAAA;EACA,sBAAA;CpB46FH;AoBz6FC;;EAEE,WAAA;EACA,uBAAA;Ef2BF,yDAAA;EACQ,iDAAA;CLi5FT;AoBz6FC;;;EAGE,oBAAA;EE7CF,cAAA;EAGA,0BAAA;EjB8DA,yBAAA;EACQ,iBAAA;CL05FT;AoBz6FG;;EAEE,qBAAA;CpB26FL;AoBl6FD;EC3DE,YAAA;EACA,uBAAA;EACA,mBAAA;CrBg+FD;AqB99FC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBs+FT;AqBn+FC;;;EAGE,uBAAA;CrBq+FH;AqBh+FG;;;;;;;;;EAGE,uBAAA;EACI,mBAAA;CrBw+FT;AoBv9FD;ECZI,YAAA;EACA,uBAAA;CrBs+FH;AoBx9FD;EC9DE,YAAA;EACA,0BAAA;EACA,sBAAA;CrByhGD;AqBvhGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB+hGT;AqB5hGC;;;EAGE,uBAAA;CrB8hGH;AqBzhGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBiiGT;AoB7gGD;ECfI,eAAA;EACA,uBAAA;CrB+hGH;AoB7gGD;EClEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBklGD;AqBhlGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBwlGT;AqBrlGC;;;EAGE,uBAAA;CrBulGH;AqBllGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB0lGT;AoBlkGD;ECnBI,eAAA;EACA,uBAAA;CrBwlGH;AoBlkGD;ECtEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB2oGD;AqBzoGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBipGT;AqB9oGC;;;EAGE,uBAAA;CrBgpGH;AqB3oGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBmpGT;AoBvnGD;ECvBI,eAAA;EACA,uBAAA;CrBipGH;AoBvnGD;EC1EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBosGD;AqBlsGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB0sGT;AqBvsGC;;;EAGE,uBAAA;CrBysGH;AqBpsGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB4sGT;AoB5qGD;EC3BI,eAAA;EACA,uBAAA;CrB0sGH;AoB5qGD;EC9EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6vGD;AqB3vGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBmwGT;AqBhwGC;;;EAGE,uBAAA;CrBkwGH;AqB7vGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBqwGT;AoBjuGD;EC/BI,eAAA;EACA,uBAAA;CrBmwGH;AoB5tGD;EACE,eAAA;EACA,oBAAA;EACA,iBAAA;CpB8tGD;AoB5tGC;;;;;EAKE,8BAAA;EfnCF,yBAAA;EACQ,iBAAA;CLkwGT;AoB7tGC;;;;EAIE,0BAAA;CpB+tGH;AoB7tGC;;EAEE,eAAA;EACA,2BAAA;EACA,8BAAA;CpB+tGH;AoB3tGG;;;;EAEE,eAAA;EACA,sBAAA;CpB+tGL;AoBttGD;;ECxEE,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CrBkyGD;AoBztGD;;EC5EE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrByyGD;AoB5tGD;;EChFE,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrBgzGD;AoB3tGD;EACE,eAAA;EACA,YAAA;CpB6tGD;AoBztGD;EACE,gBAAA;CpB2tGD;AoBptGC;;;EACE,YAAA;CpBwtGH;AuBl3GD;EACE,WAAA;ElBoLA,yCAAA;EACK,oCAAA;EACG,iCAAA;CLisGT;AuBr3GC;EACE,WAAA;CvBu3GH;AuBn3GD;EACE,cAAA;CvBq3GD;AuBn3GC;EAAY,eAAA;CvBs3Gb;AuBr3GC;EAAY,mBAAA;CvBw3Gb;AuBv3GC;EAAY,yBAAA;CvB03Gb;AuBv3GD;EACE,mBAAA;EACA,UAAA;EACA,iBAAA;ElBuKA,gDAAA;EACQ,2CAAA;KAAA,wCAAA;EAOR,mCAAA;EACQ,8BAAA;KAAA,2BAAA;EAGR,yCAAA;EACQ,oCAAA;KAAA,iCAAA;CL2sGT;AwBr5GD;EACE,sBAAA;EACA,SAAA;EACA,UAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,yBAAA;EACA,oCAAA;EACA,mCAAA;CxBu5GD;AwBn5GD;;EAEE,mBAAA;CxBq5GD;AwBj5GD;EACE,WAAA;CxBm5GD;AwB/4GD;EACE,mBAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,sCAAA;EACA,mBAAA;EnBsBA,oDAAA;EACQ,4CAAA;EmBrBR,qCAAA;UAAA,6BAAA;CxBk5GD;AwB74GC;EACE,SAAA;EACA,WAAA;CxB+4GH;AwBx6GD;ECzBE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBo8GD;AwB96GD;EAmCI,eAAA;EACA,kBAAA;EACA,YAAA;EACA,oBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxB84GH;AwBx4GC;;EAEE,sBAAA;EACA,eAAA;EACA,0BAAA;CxB04GH;AwBp4GC;;;EAGE,YAAA;EACA,sBAAA;EACA,WAAA;EACA,0BAAA;CxBs4GH;AwB73GC;;;EAGE,eAAA;CxB+3GH;AwB33GC;;EAEE,sBAAA;EACA,8BAAA;EACA,uBAAA;EE3GF,oEAAA;EF6GE,oBAAA;CxB63GH;AwBx3GD;EAGI,eAAA;CxBw3GH;AwB33GD;EAQI,WAAA;CxBs3GH;AwB92GD;EACE,WAAA;EACA,SAAA;CxBg3GD;AwBx2GD;EACE,QAAA;EACA,YAAA;CxB02GD;AwBt2GD;EACE,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxBw2GD;AwBp2GD;EACE,gBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,OAAA;EACA,aAAA;CxBs2GD;AwBl2GD;EACE,SAAA;EACA,WAAA;CxBo2GD;AwB51GD;;EAII,cAAA;EACA,0BAAA;EACA,4BAAA;EACA,YAAA;CxB41GH;AwBn2GD;;EAWI,UAAA;EACA,aAAA;EACA,mBAAA;CxB41GH;AwBv0GD;EAXE;IApEA,WAAA;IACA,SAAA;GxB05GC;EwBv1GD;IA1DA,QAAA;IACA,YAAA;GxBo5GC;CACF;A2BpiHD;;EAEE,mBAAA;EACA,sBAAA;EACA,uBAAA;C3BsiHD;A2B1iHD;;EAMI,mBAAA;EACA,YAAA;C3BwiHH;A2BtiHG;;;;;;;;EAIE,WAAA;C3B4iHL;A2BtiHD;;;;EAKI,kBAAA;C3BuiHH;A2BliHD;EACE,kBAAA;C3BoiHD;A2BriHD;;;EAOI,YAAA;C3BmiHH;A2B1iHD;;;EAYI,iBAAA;C3BmiHH;A2B/hHD;EACE,iBAAA;C3BiiHD;A2B7hHD;EACE,eAAA;C3B+hHD;A2B9hHC;EClDA,8BAAA;EACG,2BAAA;C5BmlHJ;A2B7hHD;;EC/CE,6BAAA;EACG,0BAAA;C5BglHJ;A2B5hHD;EACE,YAAA;C3B8hHD;A2B5hHD;EACE,iBAAA;C3B8hHD;A2B5hHD;;ECnEE,8BAAA;EACG,2BAAA;C5BmmHJ;A2B3hHD;ECjEE,6BAAA;EACG,0BAAA;C5B+lHJ;A2B1hHD;;EAEE,WAAA;C3B4hHD;A2B3gHD;EACE,kBAAA;EACA,mBAAA;C3B6gHD;A2B3gHD;EACE,mBAAA;EACA,oBAAA;C3B6gHD;A2BxgHD;EtB/CE,yDAAA;EACQ,iDAAA;CL0jHT;A2BxgHC;EtBnDA,yBAAA;EACQ,iBAAA;CL8jHT;A2BrgHD;EACE,eAAA;C3BugHD;A2BpgHD;EACE,wBAAA;EACA,uBAAA;C3BsgHD;A2BngHD;EACE,wBAAA;C3BqgHD;A2B9/GD;;;EAII,eAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;C3B+/GH;A2BtgHD;EAcM,YAAA;C3B2/GL;A2BzgHD;;;;EAsBI,iBAAA;EACA,eAAA;C3By/GH;A2Bp/GC;EACE,iBAAA;C3Bs/GH;A2Bp/GC;EC3KA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5B4pHF;A2Bt/GC;EC/KA,2BAAA;EACC,0BAAA;EAOD,gCAAA;EACC,+BAAA;C5BkqHF;A2Bv/GD;EACE,iBAAA;C3By/GD;A2Bv/GD;;EC/KE,8BAAA;EACC,6BAAA;C5B0qHF;A2Bt/GD;EC7LE,2BAAA;EACC,0BAAA;C5BsrHF;A2Bl/GD;EACE,eAAA;EACA,YAAA;EACA,oBAAA;EACA,0BAAA;C3Bo/GD;A2Bx/GD;;EAOI,YAAA;EACA,oBAAA;EACA,UAAA;C3Bq/GH;A2B9/GD;EAYI,YAAA;C3Bq/GH;A2BjgHD;EAgBI,WAAA;C3Bo/GH;A2Bn+GD;;;;EAKM,mBAAA;EACA,uBAAA;EACA,qBAAA;C3Bo+GL;A6B9sHD;EACE,mBAAA;EACA,eAAA;EACA,0BAAA;C7BgtHD;A6B7sHC;EACE,YAAA;EACA,gBAAA;EACA,iBAAA;C7B+sHH;A6BxtHD;EAeI,mBAAA;EACA,WAAA;EAKA,YAAA;EAEA,YAAA;EACA,iBAAA;C7BusHH;A6BrsHG;EACE,WAAA;C7BusHL;A6B7rHD;;;EV0BE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBwqHD;AmBtqHC;;;EACE,aAAA;EACA,kBAAA;CnB0qHH;AmBvqHC;;;;;;EAEE,aAAA;CnB6qHH;A6B/sHD;;;EVqBE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnB+rHD;AmB7rHC;;;EACE,aAAA;EACA,kBAAA;CnBisHH;AmB9rHC;;;;;;EAEE,aAAA;CnBosHH;A6B7tHD;;;EAGE,oBAAA;C7B+tHD;A6B7tHC;;;EACE,iBAAA;C7BiuHH;A6B7tHD;;EAEE,UAAA;EACA,oBAAA;EACA,uBAAA;C7B+tHD;A6B1tHD;EACE,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;C7B4tHD;A6BztHC;EACE,kBAAA;EACA,gBAAA;EACA,mBAAA;C7B2tHH;A6BztHC;EACE,mBAAA;EACA,gBAAA;EACA,mBAAA;C7B2tHH;A6B/uHD;;EA0BI,cAAA;C7BytHH;A6BptHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;C5Bi0HJ;A6BrtHD;EACE,gBAAA;C7ButHD;A6BrtHD;;;;;;;EDxGE,6BAAA;EACG,0BAAA;C5Bs0HJ;A6BttHD;EACE,eAAA;C7BwtHD;A6BntHD;EACE,mBAAA;EAGA,aAAA;EACA,oBAAA;C7BmtHD;A6BxtHD;EAUI,mBAAA;C7BitHH;A6B3tHD;EAYM,kBAAA;C7BktHL;A6B/sHG;;;EAGE,WAAA;C7BitHL;A6B5sHC;;EAGI,mBAAA;C7B6sHL;A6B1sHC;;EAGI,WAAA;EACA,kBAAA;C7B2sHL;A8B12HD;EACE,iBAAA;EACA,gBAAA;EACA,iBAAA;C9B42HD;A8B/2HD;EAOI,mBAAA;EACA,eAAA;C9B22HH;A8Bn3HD;EAWM,mBAAA;EACA,eAAA;EACA,mBAAA;C9B22HL;A8B12HK;;EAEE,sBAAA;EACA,0BAAA;C9B42HP;A8Bv2HG;EACE,eAAA;C9By2HL;A8Bv2HK;;EAEE,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,oBAAA;C9By2HP;A8Bl2HG;;;EAGE,0BAAA;EACA,sBAAA;C9Bo2HL;A8B74HD;ELHE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBm5HD;A8Bn5HD;EA0DI,gBAAA;C9B41HH;A8Bn1HD;EACE,8BAAA;C9Bq1HD;A8Bt1HD;EAGI,YAAA;EAEA,oBAAA;C9Bq1HH;A8B11HD;EASM,kBAAA;EACA,wBAAA;EACA,8BAAA;EACA,2BAAA;C9Bo1HL;A8Bn1HK;EACE,mCAAA;C9Bq1HP;A8B/0HK;;;EAGE,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,iCAAA;EACA,gBAAA;C9Bi1HP;A8B50HC;EAqDA,YAAA;EA8BA,iBAAA;C9B6vHD;A8Bh1HC;EAwDE,YAAA;C9B2xHH;A8Bn1HC;EA0DI,mBAAA;EACA,mBAAA;C9B4xHL;A8Bv1HC;EAgEE,UAAA;EACA,WAAA;C9B0xHH;A8B9wHD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9ByxHH;E8BztHH;IA9DQ,iBAAA;G9B0xHL;CACF;A8Bp2HC;EAuFE,gBAAA;EACA,mBAAA;C9BgxHH;A8Bx2HC;;;EA8FE,uBAAA;C9B+wHH;A8BjwHD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9B8wHH;E8B3uHH;;;IA9BM,0BAAA;G9B8wHH;CACF;A8B/2HD;EAEI,YAAA;C9Bg3HH;A8Bl3HD;EAMM,mBAAA;C9B+2HL;A8Br3HD;EASM,iBAAA;C9B+2HL;A8B12HK;;;EAGE,YAAA;EACA,0BAAA;C9B42HP;A8Bp2HD;EAEI,YAAA;C9Bq2HH;A8Bv2HD;EAIM,gBAAA;EACA,eAAA;C9Bs2HL;A8B11HD;EACE,YAAA;C9B41HD;A8B71HD;EAII,YAAA;C9B41HH;A8Bh2HD;EAMM,mBAAA;EACA,mBAAA;C9B61HL;A8Bp2HD;EAYI,UAAA;EACA,WAAA;C9B21HH;A8B/0HD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9B01HH;E8B1xHH;IA9DQ,iBAAA;G9B21HL;CACF;A8Bn1HD;EACE,iBAAA;C9Bq1HD;A8Bt1HD;EAKI,gBAAA;EACA,mBAAA;C9Bo1HH;A8B11HD;;;EAYI,uBAAA;C9Bm1HH;A8Br0HD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9Bk1HH;E8B/yHH;;;IA9BM,0BAAA;G9Bk1HH;CACF;A8Bz0HD;EAEI,cAAA;C9B00HH;A8B50HD;EAKI,eAAA;C9B00HH;A8Bj0HD;EAEE,iBAAA;EF3OA,2BAAA;EACC,0BAAA;C5B8iIF;A+BxiID;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,8BAAA;C/B0iID;A+BliID;EA8nBA;IAhoBI,mBAAA;G/BwiID;CACF;A+BzhID;EAgnBA;IAlnBI,YAAA;G/B+hID;CACF;A+BjhID;EACE,oBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,2DAAA;UAAA,mDAAA;EAEA,kCAAA;C/BkhID;A+BhhIC;EACE,iBAAA;C/BkhIH;A+Bt/HD;EA6jBA;IArlBI,YAAA;IACA,cAAA;IACA,yBAAA;YAAA,iBAAA;G/BkhID;E+BhhIC;IACE,0BAAA;IACA,wBAAA;IACA,kBAAA;IACA,6BAAA;G/BkhIH;E+B/gIC;IACE,oBAAA;G/BihIH;E+B5gIC;;;IAGE,gBAAA;IACA,iBAAA;G/B8gIH;CACF;A+B1gID;;EAGI,kBAAA;C/B2gIH;A+BtgIC;EAmjBF;;IArjBM,kBAAA;G/B6gIH;CACF;A+BpgID;;;;EAII,oBAAA;EACA,mBAAA;C/BsgIH;A+BhgIC;EAgiBF;;;;IAniBM,gBAAA;IACA,eAAA;G/B0gIH;CACF;A+B9/HD;EACE,cAAA;EACA,sBAAA;C/BggID;A+B3/HD;EA8gBA;IAhhBI,iBAAA;G/BigID;CACF;A+B7/HD;;EAEE,gBAAA;EACA,SAAA;EACA,QAAA;EACA,cAAA;C/B+/HD;A+Bz/HD;EAggBA;;IAlgBI,iBAAA;G/BggID;CACF;A+B9/HD;EACE,OAAA;EACA,sBAAA;C/BggID;A+B9/HD;EACE,UAAA;EACA,iBAAA;EACA,sBAAA;C/BggID;A+B1/HD;EACE,YAAA;EACA,mBAAA;EACA,gBAAA;EACA,kBAAA;EACA,aAAA;C/B4/HD;A+B1/HC;;EAEE,sBAAA;C/B4/HH;A+BrgID;EAaI,eAAA;C/B2/HH;A+Bl/HD;EALI;;IAEE,mBAAA;G/B0/HH;CACF;A+Bh/HD;EACE,mBAAA;EACA,aAAA;EACA,mBAAA;EACA,kBAAA;EC9LA,gBAAA;EACA,mBAAA;ED+LA,8BAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;C/Bm/HD;A+B/+HC;EACE,WAAA;C/Bi/HH;A+B//HD;EAmBI,eAAA;EACA,YAAA;EACA,YAAA;EACA,mBAAA;C/B++HH;A+BrgID;EAyBI,gBAAA;C/B++HH;A+Bz+HD;EAqbA;IAvbI,cAAA;G/B++HD;CACF;A+Bt+HD;EACE,oBAAA;C/Bw+HD;A+Bz+HD;EAII,kBAAA;EACA,qBAAA;EACA,kBAAA;C/Bw+HH;A+B58HC;EA2YF;IAjaM,iBAAA;IACA,YAAA;IACA,YAAA;IACA,cAAA;IACA,8BAAA;IACA,UAAA;IACA,yBAAA;YAAA,iBAAA;G/Bs+HH;E+B3kHH;;IAxZQ,2BAAA;G/Bu+HL;E+B/kHH;IArZQ,kBAAA;G/Bu+HL;E+Bt+HK;;IAEE,uBAAA;G/Bw+HP;CACF;A+Bt9HD;EA+XA;IA1YI,YAAA;IACA,UAAA;G/Bq+HD;E+B5lHH;IAtYM,YAAA;G/Bq+HH;E+B/lHH;IApYQ,kBAAA;IACA,qBAAA;G/Bs+HL;CACF;A+B39HD;EACE,mBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,qCAAA;E1B9NA,6FAAA;EACQ,qFAAA;E2B/DR,gBAAA;EACA,mBAAA;ChC4vID;AkBtuHD;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBwyHH;EkBpqHH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBsyHH;EkBzqHH;IAxHM,sBAAA;GlBoyHH;EkB5qHH;IApHM,sBAAA;IACA,uBAAA;GlBmyHH;EkBhrHH;;;IA9GQ,YAAA;GlBmyHL;EkBrrHH;IAxGM,YAAA;GlBgyHH;EkBxrHH;IApGM,iBAAA;IACA,uBAAA;GlB+xHH;EkB5rHH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB4xHH;EkBnsHH;;IAtFQ,gBAAA;GlB6xHL;EkBvsHH;;IAjFM,mBAAA;IACA,eAAA;GlB4xHH;EkB5sHH;IA3EM,OAAA;GlB0xHH;CACF;A+BpgIC;EAmWF;IAzWM,mBAAA;G/B8gIH;E+B5gIG;IACE,iBAAA;G/B8gIL;CACF;A+B7/HD;EAoVA;IA5VI,YAAA;IACA,UAAA;IACA,eAAA;IACA,gBAAA;IACA,eAAA;IACA,kBAAA;I1BzPF,yBAAA;IACQ,iBAAA;GLmwIP;CACF;A+BngID;EACE,cAAA;EHpUA,2BAAA;EACC,0BAAA;C5B00IF;A+BngID;EACE,iBAAA;EHzUA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5By0IF;A+B//HD;EChVE,gBAAA;EACA,mBAAA;ChCk1ID;A+BhgIC;ECnVA,iBAAA;EACA,oBAAA;ChCs1ID;A+BjgIC;ECtVA,iBAAA;EACA,oBAAA;ChC01ID;A+B3/HD;EChWE,iBAAA;EACA,oBAAA;ChC81ID;A+Bv/HD;EAsSA;IA1SI,YAAA;IACA,kBAAA;IACA,mBAAA;G/B+/HD;CACF;A+Bl+HD;EAhBE;IExWA,uBAAA;GjC81IC;E+Br/HD;IE5WA,wBAAA;IF8WE,oBAAA;G/Bu/HD;E+Bz/HD;IAKI,gBAAA;G/Bu/HH;CACF;A+B9+HD;EACE,0BAAA;EACA,sBAAA;C/Bg/HD;A+Bl/HD;EAKI,YAAA;C/Bg/HH;A+B/+HG;;EAEE,eAAA;EACA,8BAAA;C/Bi/HL;A+B1/HD;EAcI,YAAA;C/B++HH;A+B7/HD;EAmBM,YAAA;C/B6+HL;A+B3+HK;;EAEE,YAAA;EACA,8BAAA;C/B6+HP;A+Bz+HK;;;EAGE,YAAA;EACA,0BAAA;C/B2+HP;A+Bv+HK;;;EAGE,YAAA;EACA,8BAAA;C/By+HP;A+BjhID;EA8CI,mBAAA;C/Bs+HH;A+Br+HG;;EAEE,uBAAA;C/Bu+HL;A+BxhID;EAoDM,uBAAA;C/Bu+HL;A+B3hID;;EA0DI,sBAAA;C/Bq+HH;A+B99HK;;;EAGE,0BAAA;EACA,YAAA;C/Bg+HP;A+B/7HC;EAoKF;IA7LU,YAAA;G/B49HP;E+B39HO;;IAEE,YAAA;IACA,8BAAA;G/B69HT;E+Bz9HO;;;IAGE,YAAA;IACA,0BAAA;G/B29HT;E+Bv9HO;;;IAGE,YAAA;IACA,8BAAA;G/By9HT;CACF;A+B3jID;EA8GI,YAAA;C/Bg9HH;A+B/8HG;EACE,YAAA;C/Bi9HL;A+BjkID;EAqHI,YAAA;C/B+8HH;A+B98HG;;EAEE,YAAA;C/Bg9HL;A+B58HK;;;;EAEE,YAAA;C/Bg9HP;A+Bx8HD;EACE,uBAAA;EACA,sBAAA;C/B08HD;A+B58HD;EAKI,eAAA;C/B08HH;A+Bz8HG;;EAEE,YAAA;EACA,8BAAA;C/B28HL;A+Bp9HD;EAcI,eAAA;C/By8HH;A+Bv9HD;EAmBM,eAAA;C/Bu8HL;A+Br8HK;;EAEE,YAAA;EACA,8BAAA;C/Bu8HP;A+Bn8HK;;;EAGE,YAAA;EACA,0BAAA;C/Bq8HP;A+Bj8HK;;;EAGE,YAAA;EACA,8BAAA;C/Bm8HP;A+B3+HD;EA+CI,mBAAA;C/B+7HH;A+B97HG;;EAEE,uBAAA;C/Bg8HL;A+Bl/HD;EAqDM,uBAAA;C/Bg8HL;A+Br/HD;;EA2DI,sBAAA;C/B87HH;A+Bx7HK;;;EAGE,0BAAA;EACA,YAAA;C/B07HP;A+Bn5HC;EAwBF;IAvDU,sBAAA;G/Bs7HP;E+B/3HH;IApDU,0BAAA;G/Bs7HP;E+Bl4HH;IAjDU,eAAA;G/Bs7HP;E+Br7HO;;IAEE,YAAA;IACA,8BAAA;G/Bu7HT;E+Bn7HO;;;IAGE,YAAA;IACA,0BAAA;G/Bq7HT;E+Bj7HO;;;IAGE,YAAA;IACA,8BAAA;G/Bm7HT;CACF;A+B3hID;EA+GI,eAAA;C/B+6HH;A+B96HG;EACE,YAAA;C/Bg7HL;A+BjiID;EAsHI,eAAA;C/B86HH;A+B76HG;;EAEE,YAAA;C/B+6HL;A+B36HK;;;;EAEE,YAAA;C/B+6HP;AkCzjJD;EACE,kBAAA;EACA,oBAAA;EACA,iBAAA;EACA,0BAAA;EACA,mBAAA;ClC2jJD;AkChkJD;EAQI,sBAAA;ClC2jJH;AkCnkJD;EAWM,kBAAA;EACA,eAAA;EACA,YAAA;ClC2jJL;AkCxkJD;EAkBI,eAAA;ClCyjJH;AmC7kJD;EACE,sBAAA;EACA,gBAAA;EACA,eAAA;EACA,mBAAA;CnC+kJD;AmCnlJD;EAOI,gBAAA;CnC+kJH;AmCtlJD;;EAUM,mBAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,sBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,kBAAA;CnCglJL;AmC9kJG;;EAGI,eAAA;EPXN,+BAAA;EACG,4BAAA;C5B2lJJ;AmC7kJG;;EPvBF,gCAAA;EACG,6BAAA;C5BwmJJ;AmCxkJG;;;;EAEE,WAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CnC4kJL;AmCtkJG;;;;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;EACA,gBAAA;CnC2kJL;AmCloJD;;;;;;EAkEM,eAAA;EACA,uBAAA;EACA,mBAAA;EACA,oBAAA;CnCwkJL;AmC/jJD;;EC3EM,mBAAA;EACA,gBAAA;EACA,uBAAA;CpC8oJL;AoC5oJG;;ERKF,+BAAA;EACG,4BAAA;C5B2oJJ;AoC3oJG;;ERTF,gCAAA;EACG,6BAAA;C5BwpJJ;AmC1kJD;;EChFM,kBAAA;EACA,gBAAA;EACA,iBAAA;CpC8pJL;AoC5pJG;;ERKF,+BAAA;EACG,4BAAA;C5B2pJJ;AoC3pJG;;ERTF,gCAAA;EACG,6BAAA;C5BwqJJ;AqC3qJD;EACE,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,mBAAA;CrC6qJD;AqCjrJD;EAOI,gBAAA;CrC6qJH;AqCprJD;;EAUM,sBAAA;EACA,kBAAA;EACA,uBAAA;EACA,uBAAA;EACA,oBAAA;CrC8qJL;AqC5rJD;;EAmBM,sBAAA;EACA,0BAAA;CrC6qJL;AqCjsJD;;EA2BM,aAAA;CrC0qJL;AqCrsJD;;EAkCM,YAAA;CrCuqJL;AqCzsJD;;;;EA2CM,eAAA;EACA,uBAAA;EACA,oBAAA;CrCoqJL;AsCltJD;EACE,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,oBAAA;EACA,yBAAA;EACA,qBAAA;CtCotJD;AsChtJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CtCktJL;AsC7sJC;EACE,cAAA;CtC+sJH;AsC3sJC;EACE,mBAAA;EACA,UAAA;CtC6sJH;AsCtsJD;ECtCE,0BAAA;CvC+uJD;AuC5uJG;;EAEE,0BAAA;CvC8uJL;AsCzsJD;EC1CE,0BAAA;CvCsvJD;AuCnvJG;;EAEE,0BAAA;CvCqvJL;AsC5sJD;EC9CE,0BAAA;CvC6vJD;AuC1vJG;;EAEE,0BAAA;CvC4vJL;AsC/sJD;EClDE,0BAAA;CvCowJD;AuCjwJG;;EAEE,0BAAA;CvCmwJL;AsCltJD;ECtDE,0BAAA;CvC2wJD;AuCxwJG;;EAEE,0BAAA;CvC0wJL;AsCrtJD;EC1DE,0BAAA;CvCkxJD;AuC/wJG;;EAEE,0BAAA;CvCixJL;AwCnxJD;EACE,sBAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,eAAA;EACA,uBAAA;EACA,oBAAA;EACA,mBAAA;EACA,0BAAA;EACA,oBAAA;CxCqxJD;AwClxJC;EACE,cAAA;CxCoxJH;AwChxJC;EACE,mBAAA;EACA,UAAA;CxCkxJH;AwC/wJC;;EAEE,OAAA;EACA,iBAAA;CxCixJH;AwC5wJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CxC8wJL;AwCzwJC;;EAEE,eAAA;EACA,uBAAA;CxC2wJH;AwCxwJC;EACE,aAAA;CxC0wJH;AwCvwJC;EACE,kBAAA;CxCywJH;AwCtwJC;EACE,iBAAA;CxCwwJH;AyCl0JD;EACE,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,eAAA;EACA,0BAAA;CzCo0JD;AyCz0JD;;EASI,eAAA;CzCo0JH;AyC70JD;EAaI,oBAAA;EACA,gBAAA;EACA,iBAAA;CzCm0JH;AyCl1JD;EAmBI,0BAAA;CzCk0JH;AyC/zJC;;EAEE,mBAAA;EACA,mBAAA;EACA,oBAAA;CzCi0JH;AyC31JD;EA8BI,gBAAA;CzCg0JH;AyC9yJD;EACA;IAfI,kBAAA;IACA,qBAAA;GzCg0JD;EyC9zJC;;IAEE,mBAAA;IACA,oBAAA;GzCg0JH;EyCvzJH;;IAJM,gBAAA;GzC+zJH;CACF;A0C52JD;EACE,eAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;ErCiLA,4CAAA;EACK,uCAAA;EACG,oCAAA;CL8rJT;A0Cx3JD;;EAaI,kBAAA;EACA,mBAAA;C1C+2JH;A0C32JC;;;EAGE,sBAAA;C1C62JH;A0Cl4JD;EA0BI,aAAA;EACA,eAAA;C1C22JH;A2Cp4JD;EACE,cAAA;EACA,oBAAA;EACA,8BAAA;EACA,mBAAA;C3Cs4JD;A2C14JD;EAQI,cAAA;EAEA,eAAA;C3Co4JH;A2C94JD;EAeI,kBAAA;C3Ck4JH;A2Cj5JD;;EAqBI,iBAAA;C3Cg4JH;A2Cr5JD;EAyBI,gBAAA;C3C+3JH;A2Cv3JD;;EAEE,oBAAA;C3Cy3JD;A2C33JD;;EAMI,mBAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;C3Cy3JH;A2Cj3JD;ECvDE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C26JD;A2Ct3JD;EClDI,0BAAA;C5C26JH;A2Cz3JD;EC/CI,eAAA;C5C26JH;A2Cx3JD;EC3DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Cs7JD;A2C73JD;ECtDI,0BAAA;C5Cs7JH;A2Ch4JD;ECnDI,eAAA;C5Cs7JH;A2C/3JD;EC/DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Ci8JD;A2Cp4JD;EC1DI,0BAAA;C5Ci8JH;A2Cv4JD;ECvDI,eAAA;C5Ci8JH;A2Ct4JD;ECnEE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C48JD;A2C34JD;EC9DI,0BAAA;C5C48JH;A2C94JD;EC3DI,eAAA;C5C48JH;A6C98JD;EACE;IAAQ,4BAAA;G7Ci9JP;E6Ch9JD;IAAQ,yBAAA;G7Cm9JP;CACF;A6Ch9JD;EACE;IAAQ,4BAAA;G7Cm9JP;E6Cl9JD;IAAQ,yBAAA;G7Cq9JP;CACF;A6Cx9JD;EACE;IAAQ,4BAAA;G7Cm9JP;E6Cl9JD;IAAQ,yBAAA;G7Cq9JP;CACF;A6C98JD;EACE,iBAAA;EACA,aAAA;EACA,oBAAA;EACA,0BAAA;EACA,mBAAA;ExCsCA,uDAAA;EACQ,+CAAA;CL26JT;A6C78JD;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;CL+zJT;A6C18JD;;ECCI,8MAAA;EACA,yMAAA;EACA,sMAAA;EDAF,mCAAA;UAAA,2BAAA;C7C88JD;A6Cv8JD;;ExC5CE,2DAAA;EACK,sDAAA;EACG,mDAAA;CLu/JT;A6Cp8JD;EErEE,0BAAA;C/C4gKD;A+CzgKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C49JH;A6Cx8JD;EEzEE,0BAAA;C/CohKD;A+CjhKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Co+JH;A6C58JD;EE7EE,0BAAA;C/C4hKD;A+CzhKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C4+JH;A6Ch9JD;EEjFE,0BAAA;C/CoiKD;A+CjiKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Co/JH;AgD5iKD;EAEE,iBAAA;ChD6iKD;AgD3iKC;EACE,cAAA;ChD6iKH;AgDziKD;;EAEE,QAAA;EACA,iBAAA;ChD2iKD;AgDxiKD;EACE,eAAA;ChD0iKD;AgDviKD;EACE,eAAA;ChDyiKD;AgDtiKC;EACE,gBAAA;ChDwiKH;AgDpiKD;;EAEE,mBAAA;ChDsiKD;AgDniKD;;EAEE,oBAAA;ChDqiKD;AgDliKD;;;EAGE,oBAAA;EACA,oBAAA;ChDoiKD;AgDjiKD;EACE,uBAAA;ChDmiKD;AgDhiKD;EACE,uBAAA;ChDkiKD;AgD9hKD;EACE,cAAA;EACA,mBAAA;ChDgiKD;AgD1hKD;EACE,gBAAA;EACA,iBAAA;ChD4hKD;AiDnlKD;EAEE,oBAAA;EACA,gBAAA;CjDolKD;AiD5kKD;EACE,mBAAA;EACA,eAAA;EACA,mBAAA;EAEA,oBAAA;EACA,uBAAA;EACA,uBAAA;CjD6kKD;AiD1kKC;ErB3BA,6BAAA;EACC,4BAAA;C5BwmKF;AiD3kKC;EACE,iBAAA;ErBvBF,gCAAA;EACC,+BAAA;C5BqmKF;AiDpkKD;;EAEE,YAAA;CjDskKD;AiDxkKD;;EAKI,YAAA;CjDukKH;AiDnkKC;;;;EAEE,sBAAA;EACA,YAAA;EACA,0BAAA;CjDukKH;AiDnkKD;EACE,YAAA;EACA,iBAAA;CjDqkKD;AiDhkKC;;;EAGE,0BAAA;EACA,eAAA;EACA,oBAAA;CjDkkKH;AiDvkKC;;;EASI,eAAA;CjDmkKL;AiD5kKC;;;EAYI,eAAA;CjDqkKL;AiDhkKC;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;CjDkkKH;AiDxkKC;;;;;;;;;EAYI,eAAA;CjDukKL;AiDnlKC;;;EAeI,eAAA;CjDykKL;AkD3qKC;EACE,eAAA;EACA,0BAAA;ClD6qKH;AkD3qKG;;EAEE,eAAA;ClD6qKL;AkD/qKG;;EAKI,eAAA;ClD8qKP;AkD3qKK;;;;EAEE,eAAA;EACA,0BAAA;ClD+qKP;AkD7qKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDkrKP;AkDxsKC;EACE,eAAA;EACA,0BAAA;ClD0sKH;AkDxsKG;;EAEE,eAAA;ClD0sKL;AkD5sKG;;EAKI,eAAA;ClD2sKP;AkDxsKK;;;;EAEE,eAAA;EACA,0BAAA;ClD4sKP;AkD1sKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD+sKP;AkDruKC;EACE,eAAA;EACA,0BAAA;ClDuuKH;AkDruKG;;EAEE,eAAA;ClDuuKL;AkDzuKG;;EAKI,eAAA;ClDwuKP;AkDruKK;;;;EAEE,eAAA;EACA,0BAAA;ClDyuKP;AkDvuKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD4uKP;AkDlwKC;EACE,eAAA;EACA,0BAAA;ClDowKH;AkDlwKG;;EAEE,eAAA;ClDowKL;AkDtwKG;;EAKI,eAAA;ClDqwKP;AkDlwKK;;;;EAEE,eAAA;EACA,0BAAA;ClDswKP;AkDpwKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDywKP;AiDxqKD;EACE,cAAA;EACA,mBAAA;CjD0qKD;AiDxqKD;EACE,iBAAA;EACA,iBAAA;CjD0qKD;AmDpyKD;EACE,oBAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;E9C0DA,kDAAA;EACQ,0CAAA;CL6uKT;AmDnyKD;EACE,cAAA;CnDqyKD;AmDhyKD;EACE,mBAAA;EACA,qCAAA;EvBpBA,6BAAA;EACC,4BAAA;C5BuzKF;AmDtyKD;EAMI,eAAA;CnDmyKH;AmD9xKD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,eAAA;CnDgyKD;AmDpyKD;;;;;EAWI,eAAA;CnDgyKH;AmD3xKD;EACE,mBAAA;EACA,0BAAA;EACA,2BAAA;EvBxCA,gCAAA;EACC,+BAAA;C5Bs0KF;AmDrxKD;;EAGI,iBAAA;CnDsxKH;AmDzxKD;;EAMM,oBAAA;EACA,iBAAA;CnDuxKL;AmDnxKG;;EAEI,cAAA;EvBvEN,6BAAA;EACC,4BAAA;C5B61KF;AmDjxKG;;EAEI,iBAAA;EvBvEN,gCAAA;EACC,+BAAA;C5B21KF;AmD1yKD;EvB1DE,2BAAA;EACC,0BAAA;C5Bu2KF;AmD7wKD;EAEI,oBAAA;CnD8wKH;AmD3wKD;EACE,oBAAA;CnD6wKD;AmDrwKD;;;EAII,iBAAA;CnDswKH;AmD1wKD;;;EAOM,mBAAA;EACA,oBAAA;CnDwwKL;AmDhxKD;;EvBzGE,6BAAA;EACC,4BAAA;C5B63KF;AmDrxKD;;;;EAmBQ,4BAAA;EACA,6BAAA;CnDwwKP;AmD5xKD;;;;;;;;EAwBU,4BAAA;CnD8wKT;AmDtyKD;;;;;;;;EA4BU,6BAAA;CnDoxKT;AmDhzKD;;EvBjGE,gCAAA;EACC,+BAAA;C5Bq5KF;AmDrzKD;;;;EAyCQ,+BAAA;EACA,gCAAA;CnDkxKP;AmD5zKD;;;;;;;;EA8CU,+BAAA;CnDwxKT;AmDt0KD;;;;;;;;EAkDU,gCAAA;CnD8xKT;AmDh1KD;;;;EA2DI,2BAAA;CnD2xKH;AmDt1KD;;EA+DI,cAAA;CnD2xKH;AmD11KD;;EAmEI,UAAA;CnD2xKH;AmD91KD;;;;;;;;;;;;EA0EU,eAAA;CnDkyKT;AmD52KD;;;;;;;;;;;;EA8EU,gBAAA;CnD4yKT;AmD13KD;;;;;;;;EAuFU,iBAAA;CnD6yKT;AmDp4KD;;;;;;;;EAgGU,iBAAA;CnD8yKT;AmD94KD;EAsGI,UAAA;EACA,iBAAA;CnD2yKH;AmDjyKD;EACE,oBAAA;CnDmyKD;AmDpyKD;EAKI,iBAAA;EACA,mBAAA;CnDkyKH;AmDxyKD;EASM,gBAAA;CnDkyKL;AmD3yKD;EAcI,iBAAA;CnDgyKH;AmD9yKD;;EAkBM,2BAAA;CnDgyKL;AmDlzKD;EAuBI,cAAA;CnD8xKH;AmDrzKD;EAyBM,8BAAA;CnD+xKL;AmDxxKD;EC1PE,mBAAA;CpDqhLD;AoDnhLC;EACE,eAAA;EACA,0BAAA;EACA,mBAAA;CpDqhLH;AoDxhLC;EAMI,uBAAA;CpDqhLL;AoD3hLC;EASI,eAAA;EACA,0BAAA;CpDqhLL;AoDlhLC;EAEI,0BAAA;CpDmhLL;AmDvyKD;EC7PE,sBAAA;CpDuiLD;AoDriLC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CpDuiLH;AoD1iLC;EAMI,0BAAA;CpDuiLL;AoD7iLC;EASI,eAAA;EACA,uBAAA;CpDuiLL;AoDpiLC;EAEI,6BAAA;CpDqiLL;AmDtzKD;EChQE,sBAAA;CpDyjLD;AoDvjLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDyjLH;AoD5jLC;EAMI,0BAAA;CpDyjLL;AoD/jLC;EASI,eAAA;EACA,0BAAA;CpDyjLL;AoDtjLC;EAEI,6BAAA;CpDujLL;AmDr0KD;ECnQE,sBAAA;CpD2kLD;AoDzkLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD2kLH;AoD9kLC;EAMI,0BAAA;CpD2kLL;AoDjlLC;EASI,eAAA;EACA,0BAAA;CpD2kLL;AoDxkLC;EAEI,6BAAA;CpDykLL;AmDp1KD;ECtQE,sBAAA;CpD6lLD;AoD3lLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD6lLH;AoDhmLC;EAMI,0BAAA;CpD6lLL;AoDnmLC;EASI,eAAA;EACA,0BAAA;CpD6lLL;AoD1lLC;EAEI,6BAAA;CpD2lLL;AmDn2KD;ECzQE,sBAAA;CpD+mLD;AoD7mLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD+mLH;AoDlnLC;EAMI,0BAAA;CpD+mLL;AoDrnLC;EASI,eAAA;EACA,0BAAA;CpD+mLL;AoD5mLC;EAEI,6BAAA;CpD6mLL;AqD7nLD;EACE,mBAAA;EACA,eAAA;EACA,UAAA;EACA,WAAA;EACA,iBAAA;CrD+nLD;AqDpoLD;;;;;EAYI,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,aAAA;EACA,YAAA;EACA,UAAA;CrD+nLH;AqD1nLD;EACE,uBAAA;CrD4nLD;AqDxnLD;EACE,oBAAA;CrD0nLD;AsDrpLD;EACE,iBAAA;EACA,cAAA;EACA,oBAAA;EACA,0BAAA;EACA,0BAAA;EACA,mBAAA;EjDwDA,wDAAA;EACQ,gDAAA;CLgmLT;AsD/pLD;EASI,mBAAA;EACA,kCAAA;CtDypLH;AsDppLD;EACE,cAAA;EACA,mBAAA;CtDspLD;AsDppLD;EACE,aAAA;EACA,mBAAA;CtDspLD;AuD5qLD;EACE,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,0BAAA;EjCRA,aAAA;EAGA,0BAAA;CtBqrLD;AuD7qLC;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;EjCfF,aAAA;EAGA,0BAAA;CtB6rLD;AuDzqLC;EACE,WAAA;EACA,gBAAA;EACA,wBAAA;EACA,UAAA;EACA,yBAAA;CvD2qLH;AwDhsLD;EACE,iBAAA;CxDksLD;AwD9rLD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,kCAAA;EAIA,WAAA;CxD6rLD;AwD1rLC;EnD+GA,sCAAA;EACI,kCAAA;EACC,iCAAA;EACG,8BAAA;EAkER,oDAAA;EAEK,0CAAA;EACG,oCAAA;CL6gLT;AwDhsLC;EnD2GA,mCAAA;EACI,+BAAA;EACC,8BAAA;EACG,2BAAA;CLwlLT;AwDpsLD;EACE,mBAAA;EACA,iBAAA;CxDssLD;AwDlsLD;EACE,mBAAA;EACA,YAAA;EACA,aAAA;CxDosLD;AwDhsLD;EACE,mBAAA;EACA,uBAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EnDaA,iDAAA;EACQ,yCAAA;EmDZR,qCAAA;UAAA,6BAAA;EAEA,WAAA;CxDksLD;AwD9rLD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,uBAAA;CxDgsLD;AwD9rLC;ElCrEA,WAAA;EAGA,yBAAA;CtBowLD;AwDjsLC;ElCtEA,aAAA;EAGA,0BAAA;CtBwwLD;AwDhsLD;EACE,cAAA;EACA,iCAAA;CxDksLD;AwD9rLD;EACE,iBAAA;CxDgsLD;AwD5rLD;EACE,UAAA;EACA,wBAAA;CxD8rLD;AwDzrLD;EACE,mBAAA;EACA,cAAA;CxD2rLD;AwDvrLD;EACE,cAAA;EACA,kBAAA;EACA,8BAAA;CxDyrLD;AwD5rLD;EAQI,iBAAA;EACA,iBAAA;CxDurLH;AwDhsLD;EAaI,kBAAA;CxDsrLH;AwDnsLD;EAiBI,eAAA;CxDqrLH;AwDhrLD;EACE,mBAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;CxDkrLD;AwDhqLD;EAZE;IACE,aAAA;IACA,kBAAA;GxD+qLD;EwD7qLD;InDvEA,kDAAA;IACQ,0CAAA;GLuvLP;EwD5qLD;IAAY,aAAA;GxD+qLX;CACF;AwD1qLD;EAFE;IAAY,aAAA;GxDgrLX;CACF;AyD/zLD;EACE,mBAAA;EACA,cAAA;EACA,eAAA;ECRA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;EDHA,gBAAA;EnCVA,WAAA;EAGA,yBAAA;CtBs1LD;AyD30LC;EnCdA,aAAA;EAGA,0BAAA;CtB01LD;AyD90LC;EAAW,iBAAA;EAAmB,eAAA;CzDk1L/B;AyDj1LC;EAAW,iBAAA;EAAmB,eAAA;CzDq1L/B;AyDp1LC;EAAW,gBAAA;EAAmB,eAAA;CzDw1L/B;AyDv1LC;EAAW,kBAAA;EAAmB,eAAA;CzD21L/B;AyDv1LD;EACE,iBAAA;EACA,iBAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,mBAAA;CzDy1LD;AyDr1LD;EACE,mBAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;CzDu1LD;AyDn1LC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,UAAA;EACA,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,UAAA;EACA,UAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,SAAA;EACA,QAAA;EACA,iBAAA;EACA,4BAAA;EACA,yBAAA;CzDq1LH;AyDn1LC;EACE,SAAA;EACA,SAAA;EACA,iBAAA;EACA,4BAAA;EACA,wBAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,WAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,UAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;A2Dl7LD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;EACA,aAAA;EDXA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;ECAA,gBAAA;EAEA,uBAAA;EACA,qCAAA;UAAA,6BAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EtD8CA,kDAAA;EACQ,0CAAA;CLk5LT;A2D77LC;EAAY,kBAAA;C3Dg8Lb;A2D/7LC;EAAY,kBAAA;C3Dk8Lb;A2Dj8LC;EAAY,iBAAA;C3Do8Lb;A2Dn8LC;EAAY,mBAAA;C3Ds8Lb;A2Dn8LD;EACE,UAAA;EACA,kBAAA;EACA,gBAAA;EACA,0BAAA;EACA,iCAAA;EACA,2BAAA;C3Dq8LD;A2Dl8LD;EACE,kBAAA;C3Do8LD;A2D57LC;;EAEE,mBAAA;EACA,eAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;C3D87LH;A2D37LD;EACE,mBAAA;C3D67LD;A2D37LD;EACE,mBAAA;EACA,YAAA;C3D67LD;A2Dz7LC;EACE,UAAA;EACA,mBAAA;EACA,uBAAA;EACA,0BAAA;EACA,sCAAA;EACA,cAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,uBAAA;C3D47LL;A2Dz7LC;EACE,SAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,4BAAA;EACA,wCAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,UAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;C3D47LL;A2Dz7LC;EACE,UAAA;EACA,mBAAA;EACA,oBAAA;EACA,6BAAA;EACA,yCAAA;EACA,WAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,SAAA;EACA,mBAAA;EACA,oBAAA;EACA,0BAAA;C3D47LL;A2Dx7LC;EACE,SAAA;EACA,aAAA;EACA,kBAAA;EACA,sBAAA;EACA,2BAAA;EACA,uCAAA;C3D07LH;A2Dz7LG;EACE,aAAA;EACA,WAAA;EACA,sBAAA;EACA,wBAAA;EACA,cAAA;C3D27LL;A4DpjMD;EACE,mBAAA;C5DsjMD;A4DnjMD;EACE,mBAAA;EACA,iBAAA;EACA,YAAA;C5DqjMD;A4DxjMD;EAMI,cAAA;EACA,mBAAA;EvD6KF,0CAAA;EACK,qCAAA;EACG,kCAAA;CLy4LT;A4D/jMD;;EAcM,eAAA;C5DqjML;A4D3hMC;EA4NF;IvD3DE,uDAAA;IAEK,6CAAA;IACG,uCAAA;IA7JR,oCAAA;IAEQ,4BAAA;IA+GR,4BAAA;IAEQ,oBAAA;GL86LP;E4DzjMG;;IvDmHJ,2CAAA;IACQ,mCAAA;IuDjHF,QAAA;G5D4jML;E4D1jMG;;IvD8GJ,4CAAA;IACQ,oCAAA;IuD5GF,QAAA;G5D6jML;E4D3jMG;;;IvDyGJ,wCAAA;IACQ,gCAAA;IuDtGF,QAAA;G5D8jML;CACF;A4DpmMD;;;EA6CI,eAAA;C5D4jMH;A4DzmMD;EAiDI,QAAA;C5D2jMH;A4D5mMD;;EAsDI,mBAAA;EACA,OAAA;EACA,YAAA;C5D0jMH;A4DlnMD;EA4DI,WAAA;C5DyjMH;A4DrnMD;EA+DI,YAAA;C5DyjMH;A4DxnMD;;EAmEI,QAAA;C5DyjMH;A4D5nMD;EAuEI,YAAA;C5DwjMH;A4D/nMD;EA0EI,WAAA;C5DwjMH;A4DhjMD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EtC9FA,aAAA;EAGA,0BAAA;EsC6FA,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;EACA,mCAAA;C5DmjMD;A4D9iMC;EdnGE,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9CopMH;A4DljMC;EACE,WAAA;EACA,SAAA;EdxGA,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9C6pMH;A4DpjMC;;EAEE,WAAA;EACA,YAAA;EACA,sBAAA;EtCvHF,aAAA;EAGA,0BAAA;CtB4qMD;A4DtlMD;;;;EAuCI,mBAAA;EACA,SAAA;EACA,kBAAA;EACA,WAAA;EACA,sBAAA;C5DqjMH;A4DhmMD;;EA+CI,UAAA;EACA,mBAAA;C5DqjMH;A4DrmMD;;EAoDI,WAAA;EACA,oBAAA;C5DqjMH;A4D1mMD;;EAyDI,YAAA;EACA,aAAA;EACA,eAAA;EACA,mBAAA;C5DqjMH;A4DhjMG;EACE,iBAAA;C5DkjML;A4D9iMG;EACE,iBAAA;C5DgjML;A4DtiMD;EACE,mBAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;C5DwiMD;A4DjjMD;EAYI,sBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,oBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;EAWA,0BAAA;EACA,mCAAA;C5D8hMH;A4D7jMD;EAkCI,UAAA;EACA,YAAA;EACA,aAAA;EACA,uBAAA;C5D8hMH;A4DvhMD;EACE,mBAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;C5DyhMD;A4DxhMC;EACE,kBAAA;C5D0hMH;A4Dj/LD;EAhCE;;;;IAKI,YAAA;IACA,aAAA;IACA,kBAAA;IACA,gBAAA;G5DmhMH;E4D3hMD;;IAYI,mBAAA;G5DmhMH;E4D/hMD;;IAgBI,oBAAA;G5DmhMH;E4D9gMD;IACE,UAAA;IACA,WAAA;IACA,qBAAA;G5DghMD;E4D5gMD;IACE,aAAA;G5D8gMD;CACF;A6D7wMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,aAAA;EACA,eAAA;C7D6yMH;A6D3yMC;;;;;;;;;;;;;;;;EACE,YAAA;C7D4zMH;AiCp0MD;E6BRE,eAAA;EACA,kBAAA;EACA,mBAAA;C9D+0MD;AiCt0MD;EACE,wBAAA;CjCw0MD;AiCt0MD;EACE,uBAAA;CjCw0MD;AiCh0MD;EACE,yBAAA;CjCk0MD;AiCh0MD;EACE,0BAAA;CjCk0MD;AiCh0MD;EACE,mBAAA;CjCk0MD;AiCh0MD;E8BzBE,YAAA;EACA,mBAAA;EACA,kBAAA;EACA,8BAAA;EACA,UAAA;C/D41MD;AiC9zMD;EACE,yBAAA;CjCg0MD;AiCzzMD;EACE,gBAAA;CjC2zMD;AgE51MD;EACE,oBAAA;ChE81MD;AgEx1MD;;;;ECdE,yBAAA;CjE42MD;AgEv1MD;;;;;;;;;;;;EAYE,yBAAA;ChEy1MD;AgEl1MD;EA6IA;IC7LE,0BAAA;GjEs4MC;EiEr4MD;IAAU,0BAAA;GjEw4MT;EiEv4MD;IAAU,8BAAA;GjE04MT;EiEz4MD;;IACU,+BAAA;GjE44MT;CACF;AgE51MD;EAwIA;IA1II,0BAAA;GhEk2MD;CACF;AgE51MD;EAmIA;IArII,2BAAA;GhEk2MD;CACF;AgE51MD;EA8HA;IAhII,iCAAA;GhEk2MD;CACF;AgE31MD;EAwHA;IC7LE,0BAAA;GjEo6MC;EiEn6MD;IAAU,0BAAA;GjEs6MT;EiEr6MD;IAAU,8BAAA;GjEw6MT;EiEv6MD;;IACU,+BAAA;GjE06MT;CACF;AgEr2MD;EAmHA;IArHI,0BAAA;GhE22MD;CACF;AgEr2MD;EA8GA;IAhHI,2BAAA;GhE22MD;CACF;AgEr2MD;EAyGA;IA3GI,iCAAA;GhE22MD;CACF;AgEp2MD;EAmGA;IC7LE,0BAAA;GjEk8MC;EiEj8MD;IAAU,0BAAA;GjEo8MT;EiEn8MD;IAAU,8BAAA;GjEs8MT;EiEr8MD;;IACU,+BAAA;GjEw8MT;CACF;AgE92MD;EA8FA;IAhGI,0BAAA;GhEo3MD;CACF;AgE92MD;EAyFA;IA3FI,2BAAA;GhEo3MD;CACF;AgE92MD;EAoFA;IAtFI,iCAAA;GhEo3MD;CACF;AgE72MD;EA8EA;IC7LE,0BAAA;GjEg+MC;EiE/9MD;IAAU,0BAAA;GjEk+MT;EiEj+MD;IAAU,8BAAA;GjEo+MT;EiEn+MD;;IACU,+BAAA;GjEs+MT;CACF;AgEv3MD;EAyEA;IA3EI,0BAAA;GhE63MD;CACF;AgEv3MD;EAoEA;IAtEI,2BAAA;GhE63MD;CACF;AgEv3MD;EA+DA;IAjEI,iCAAA;GhE63MD;CACF;AgEt3MD;EAyDA;ICrLE,yBAAA;GjEs/MC;CACF;AgEt3MD;EAoDA;ICrLE,yBAAA;GjE2/MC;CACF;AgEt3MD;EA+CA;ICrLE,yBAAA;GjEggNC;CACF;AgEt3MD;EA0CA;ICrLE,yBAAA;GjEqgNC;CACF;AgEn3MD;ECnJE,yBAAA;CjEygND;AgEh3MD;EA4BA;IC7LE,0BAAA;GjEqhNC;EiEphND;IAAU,0BAAA;GjEuhNT;EiEthND;IAAU,8BAAA;GjEyhNT;EiExhND;;IACU,+BAAA;GjE2hNT;CACF;AgE93MD;EACE,yBAAA;ChEg4MD;AgE33MD;EAqBA;IAvBI,0BAAA;GhEi4MD;CACF;AgE/3MD;EACE,yBAAA;ChEi4MD;AgE53MD;EAcA;IAhBI,2BAAA;GhEk4MD;CACF;AgEh4MD;EACE,yBAAA;ChEk4MD;AgE73MD;EAOA;IATI,iCAAA;GhEm4MD;CACF;AgE53MD;EACA;ICrLE,yBAAA;GjEojNC;CACF","file":"bootstrap.css","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 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: 1px 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 background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-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: normal;\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 margin: -1px;\n padding: 0;\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: normal;\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 background-color: #fcf8e3;\n padding: .2em;\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-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\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 border-bottom: 1px dotted #777777;\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 border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\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: bold;\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 word-break: break-all;\n word-wrap: break-word;\n color: #333333;\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 margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\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 margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.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 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.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 {\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, .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 {\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, .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 {\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, .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 {\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}\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}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\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 overflow-x: auto;\n min-height: 0.01%;\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 padding: 0;\n margin: 0;\n border: 0;\n min-width: 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: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\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 border: 0;\n background-color: transparent;\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}\ninput[type=\"search\"] {\n -webkit-appearance: none;\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 label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\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-left: -20px;\n margin-top: 4px \\9;\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 vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\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}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 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 border-color: #3c763d;\n background-color: #dff0d8;\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 border-color: #8a6d3b;\n background-color: #fcf8e3;\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 border-color: #a94442;\n background-color: #f2dede;\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 margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\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 vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\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 outline: 0;\n background-image: none;\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 opacity: 0.65;\n filter: alpha(opacity=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 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:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\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 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:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\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 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:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\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 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:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\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 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:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\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 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:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\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 color: #337ab7;\n font-weight: normal;\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 list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #fff;\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 background-clip: padding-box;\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: normal;\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 text-decoration: none;\n color: #262626;\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 outline: 0;\n background-color: #337ab7;\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 background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\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 left: 0;\n right: 0;\n bottom: 0;\n top: 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 border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n content: \"\";\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 left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\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-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-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-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-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-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 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-right-radius: 4px;\n border-top-left-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-right-radius: 0;\n border-top-left-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-right-radius: 0;\n border-top-left-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 float: none;\n display: table-cell;\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-left: 0;\n padding-right: 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: normal;\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-bottom-right-radius: 0;\n border-top-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-bottom-left-radius: 0;\n border-top-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 margin-bottom: 0;\n padding-left: 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 background-color: transparent;\n cursor: not-allowed;\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 background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n cursor: default;\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 text-align: center;\n margin-bottom: 5px;\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 text-align: center;\n margin-bottom: 5px;\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-right-radius: 0;\n border-top-left-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 overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\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-left: 0;\n padding-right: 0;\n }\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.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-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\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.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\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 margin-right: 15px;\n padding: 9px 10px;\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 margin-left: -15px;\n margin-right: -15px;\n padding: 10px 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 border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 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-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-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-left: 15px;\n margin-right: 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-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-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555;\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-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-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-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #fff;\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-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 content: \"/\\00a0\";\n padding: 0 5px;\n color: #ccc;\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 line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #fff;\n border: 1px solid #ddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\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 > .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 background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\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 background-color: #fff;\n border-color: #ddd;\n cursor: not-allowed;\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-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-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-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\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 background-color: #fff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .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 color: #fff;\n line-height: 1;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\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 border-radius: 6px;\n padding-left: 15px;\n padding-right: 15px;\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-left: 60px;\n padding-right: 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-left: auto;\n margin-right: 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 background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\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 overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\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 zoom: 1;\n overflow: hidden;\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 margin-bottom: 20px;\n padding-left: 0;\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-right-radius: 4px;\n border-top-left-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}\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 text-decoration: none;\n color: #555;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\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}\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-right-radius: 3px;\n border-top-left-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-right-radius: 3px;\n border-top-left-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-right-radius: 0;\n border-top-left-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-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-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-left-radius: 3px;\n border-bottom-right-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 border: 0;\n margin-bottom: 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 left: 0;\n bottom: 0;\n height: 100%;\n width: 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 opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\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 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 background-clip: padding-box;\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 opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\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-left: 5px;\n margin-bottom: 0;\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: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 12px;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\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.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 bottom: 0;\n right: 5px;\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.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: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: 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-title {\n margin: 0;\n padding: 8px 14px;\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.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 {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #fff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #fff;\n}\n.popover.bottom > .arrow {\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 top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\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 content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #fff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\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 left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\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}\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\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 background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #fff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\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 margin-top: -10px;\n z-index: 5;\n display: inline-block;\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 line-height: 1;\n font-family: serif;\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 margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #fff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\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 left: 20%;\n right: 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 content: \" \";\n display: table;\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-left: auto;\n margin-right: 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 */","/*! 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// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\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","/*! 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 background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-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 // Bootstrap specific changes end\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: normal;\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// http://getbootstrap.com/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: http://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\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 http://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","// 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 word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\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","// Image Mixins\n// - Responsive image\n// - Retina image\n\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","//\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: normal;\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 background-color: @state-warning-bg;\n padding: .2em;\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-left: 5px;\n padding-right: 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: bold;\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\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\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 border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\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: bold;\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 word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\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\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 margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@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-left: (@gutter / 2);\n padding-right: (@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-left: (@gutter / 2);\n padding-right: (@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-left: (@gutter / 2);\n padding-right: (@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-left: (@gutter / 2);\n padding-right: (@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-left: ceil((@grid-gutter-width / 2));\n padding-right: floor((@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","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\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 cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\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 float: none;\n display: table-cell;\n }\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 overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.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","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\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}\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: bold;\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\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\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\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Unstyle the caret on ``\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n .opacity(.65);\n .box-shadow(none);\n }\n\n a& {\n &.disabled,\n fieldset[disabled] & {\n pointer-events: none; // Future-proof disabling of clicks on `` elements\n }\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-top: @caret-width-base solid ~\"\\9\"; // IE8\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base dashed;\n border-bottom: @caret-width-base solid ~\"\\9\"; // IE8\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\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","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn,\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\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) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n .border-top-radius(@btn-border-radius-base);\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n .border-top-radius(0);\n .border-bottom-radius(@btn-border-radius-base);\n }\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) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n\n &:focus {\n z-index: 3;\n }\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @input-border-radius;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @input-border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @input-border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\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-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-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n z-index: 2;\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\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; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .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 > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 3;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding-top: @jumbotron-padding;\n padding-bottom: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: @jumbotron-heading-font-size;\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\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(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\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 background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\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 background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\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 }\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 background-repeat: no-repeat;\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 }\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 background-repeat: no-repeat;\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 }\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","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n\n // Fix collapse in webkit from max-width: 100% and display: table-cell.\n &.img-thumbnail {\n max-width: none;\n }\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on