From 76cdec07b746d54c0d98b0ae9677d37dd5fddfe4 Mon Sep 17 00:00:00 2001
From: Administrator <895938232@qq.com>
Date: Tue, 3 May 2022 14:15:23 +0800
Subject: [PATCH] hm
---
.gitignore | 29 +
LICENSE | 201 +
README.md | 84 +
health.sql | 80 +
pom.xml | 239 ++
.../health/config/CrossXssFilter.java | 141 +
.../health/config/ExceptionManager.java | 43 +
.../beansprout/health/config/HTMLWrapper.java | 556 +++
.../MySimpleMappingExceptionResolver.java | 77 +
.../health/config/TaskExecutorConfig.java | 30 +
.../config/XssHttpServletRequestWrapper.java | 198 +
.../health/constant/SysConstant.java | 31 +
.../health/controller/BaseController.java | 36 +
.../health/controller/HealthController.java | 74 +
.../health/controller/MailController.java | 89 +
.../health/controller/PageController.java | 71 +
.../health/controller/UserController.java | 74 +
.../health/mapper/TBodyInfoMapper.java | 43 +
.../health/mapper/THealthConfigMapper.java | 12 +
.../beansprout/health/mapper/TUserMapper.java | 36 +
.../health/model/dto/BodyInfoQuery.java | 28 +
.../health/model/dto/BodyInfoSaveDto.java | 46 +
.../beansprout/health/model/dto/PageDto.java | 32 +
.../health/model/dto/UserLoginDto.java | 33 +
.../health/model/dto/UserRegisterDto.java | 36 +
.../health/model/dto/UserUpdateInfoDto.java | 39 +
.../model/dto/UserUpdatePasswordDto.java | 33 +
.../health/model/entity/BaseEntity.java | 24 +
.../health/model/entity/BaseInsertEntity.java | 30 +
.../health/model/entity/BaseUpdateEntity.java | 38 +
.../health/model/entity/TBodyInfo.java | 56 +
.../health/model/entity/THealthConfig.java | 35 +
.../beansprout/health/model/entity/TUser.java | 30 +
.../beansprout/health/model/vo/AuthVo.java | 26 +
.../health/model/vo/BodyInfoDetailVo.java | 65 +
.../health/model/vo/BodyInfoStatisticsVo.java | 43 +
.../health/model/vo/BusinessException.java | 34 +
.../beansprout/health/model/vo/PageVo.java | 73 +
.../top/beansprout/health/model/vo/R.java | 110 +
.../beansprout/health/model/vo/RequestVo.java | 31 +
.../health/model/vo/UserInfoVo.java | 36 +
.../health/model/vo/UserLoginVo.java | 33 +
.../health/service/HealthService.java | 40 +
.../health/service/UserService.java | 37 +
.../service/impl/HealthServiceImpl.java | 299 ++
.../health/service/impl/UserServiceImpl.java | 151 +
.../health/util/CollectionUtils.java | 69 +
.../top/beansprout/health/util/DateUtils.java | 286 ++
.../top/beansprout/health/util/JsonUtils.java | 57 +
.../top/beansprout/health/util/MailUtils.java | 74 +
.../beansprout/health/util/PublicUtils.java | 150 +
src/main/resources/application.properties | 4 +
src/main/resources/log4j.properties | 32 +
src/main/resources/mapper/TBodyInfoMapper.xml | 27 +
.../resources/mapper/THealthConfigMapper.xml | 12 +
src/main/resources/mapper/TUserMapper.xml | 17 +
src/main/resources/spring/mybatis-config.xml | 16 +
src/main/resources/spring/spring-dao.xml | 55 +
src/main/resources/spring/spring-mvc.xml | 51 +
src/main/resources/spring/spring-service.xml | 48 +
src/main/resources/spring/spring.xml | 25 +
src/main/webapp/WEB-INF/error/401.jsp | 71 +
src/main/webapp/WEB-INF/error/403.jsp | 90 +
src/main/webapp/WEB-INF/error/404.jsp | 60 +
src/main/webapp/WEB-INF/error/500.jsp | 12 +
src/main/webapp/WEB-INF/view/base.jsp | 8 +
.../webapp/WEB-INF/view/bodyInfoInput.jsp | 215 ++
src/main/webapp/WEB-INF/view/bodyInofList.jsp | 234 ++
.../WEB-INF/view/bodyInofStatistics.jsp | 175 +
src/main/webapp/WEB-INF/view/home.jsp | 181 +
src/main/webapp/WEB-INF/view/register.jsp | 150 +
src/main/webapp/WEB-INF/view/top.jsp | 55 +
.../webapp/WEB-INF/view/updatePassword.jsp | 120 +
src/main/webapp/WEB-INF/view/userInfo.jsp | 200 +
src/main/webapp/WEB-INF/web.xml | 89 +
src/main/webapp/index.jsp | 128 +
src/main/webapp/static/css/base.css | 3381 +++++++++++++++++
src/main/webapp/static/css/login.css | 120 +
.../css/util/bootstrap-datetimepicker.min.css | 9 +
src/main/webapp/static/imgs/default.png | Bin 0 -> 54350 bytes
src/main/webapp/static/imgs/login.jpg | Bin 0 -> 15369 bytes
src/main/webapp/static/js/common-css.js | 13 +
src/main/webapp/static/js/common-js.js | 43 +
src/main/webapp/static/js/login.js | 175 +
.../js/util/bootstrap-datetimepicker.min.js | 1 +
.../js/util/bootstrap-datetimepicker.zh-CN.js | 16 +
.../static/js/util/jquery.validate.min.js | 4 +
version_diary | 10 +
88 files changed, 10065 insertions(+)
create mode 100644 .gitignore
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 health.sql
create mode 100644 pom.xml
create mode 100644 src/main/java/top/beansprout/health/config/CrossXssFilter.java
create mode 100644 src/main/java/top/beansprout/health/config/ExceptionManager.java
create mode 100644 src/main/java/top/beansprout/health/config/HTMLWrapper.java
create mode 100644 src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java
create mode 100644 src/main/java/top/beansprout/health/config/TaskExecutorConfig.java
create mode 100644 src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java
create mode 100644 src/main/java/top/beansprout/health/constant/SysConstant.java
create mode 100644 src/main/java/top/beansprout/health/controller/BaseController.java
create mode 100644 src/main/java/top/beansprout/health/controller/HealthController.java
create mode 100644 src/main/java/top/beansprout/health/controller/MailController.java
create mode 100644 src/main/java/top/beansprout/health/controller/PageController.java
create mode 100644 src/main/java/top/beansprout/health/controller/UserController.java
create mode 100644 src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java
create mode 100644 src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java
create mode 100644 src/main/java/top/beansprout/health/mapper/TUserMapper.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/PageDto.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/UserLoginDto.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java
create mode 100644 src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java
create mode 100644 src/main/java/top/beansprout/health/model/entity/BaseEntity.java
create mode 100644 src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java
create mode 100644 src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java
create mode 100644 src/main/java/top/beansprout/health/model/entity/TBodyInfo.java
create mode 100644 src/main/java/top/beansprout/health/model/entity/THealthConfig.java
create mode 100644 src/main/java/top/beansprout/health/model/entity/TUser.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/AuthVo.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/BusinessException.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/PageVo.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/R.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/RequestVo.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/UserInfoVo.java
create mode 100644 src/main/java/top/beansprout/health/model/vo/UserLoginVo.java
create mode 100644 src/main/java/top/beansprout/health/service/HealthService.java
create mode 100644 src/main/java/top/beansprout/health/service/UserService.java
create mode 100644 src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java
create mode 100644 src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java
create mode 100644 src/main/java/top/beansprout/health/util/CollectionUtils.java
create mode 100644 src/main/java/top/beansprout/health/util/DateUtils.java
create mode 100644 src/main/java/top/beansprout/health/util/JsonUtils.java
create mode 100644 src/main/java/top/beansprout/health/util/MailUtils.java
create mode 100644 src/main/java/top/beansprout/health/util/PublicUtils.java
create mode 100644 src/main/resources/application.properties
create mode 100644 src/main/resources/log4j.properties
create mode 100644 src/main/resources/mapper/TBodyInfoMapper.xml
create mode 100644 src/main/resources/mapper/THealthConfigMapper.xml
create mode 100644 src/main/resources/mapper/TUserMapper.xml
create mode 100644 src/main/resources/spring/mybatis-config.xml
create mode 100644 src/main/resources/spring/spring-dao.xml
create mode 100644 src/main/resources/spring/spring-mvc.xml
create mode 100644 src/main/resources/spring/spring-service.xml
create mode 100644 src/main/resources/spring/spring.xml
create mode 100644 src/main/webapp/WEB-INF/error/401.jsp
create mode 100644 src/main/webapp/WEB-INF/error/403.jsp
create mode 100644 src/main/webapp/WEB-INF/error/404.jsp
create mode 100644 src/main/webapp/WEB-INF/error/500.jsp
create mode 100644 src/main/webapp/WEB-INF/view/base.jsp
create mode 100644 src/main/webapp/WEB-INF/view/bodyInfoInput.jsp
create mode 100644 src/main/webapp/WEB-INF/view/bodyInofList.jsp
create mode 100644 src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp
create mode 100644 src/main/webapp/WEB-INF/view/home.jsp
create mode 100644 src/main/webapp/WEB-INF/view/register.jsp
create mode 100644 src/main/webapp/WEB-INF/view/top.jsp
create mode 100644 src/main/webapp/WEB-INF/view/updatePassword.jsp
create mode 100644 src/main/webapp/WEB-INF/view/userInfo.jsp
create mode 100644 src/main/webapp/WEB-INF/web.xml
create mode 100644 src/main/webapp/index.jsp
create mode 100644 src/main/webapp/static/css/base.css
create mode 100644 src/main/webapp/static/css/login.css
create mode 100644 src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css
create mode 100644 src/main/webapp/static/imgs/default.png
create mode 100644 src/main/webapp/static/imgs/login.jpg
create mode 100644 src/main/webapp/static/js/common-css.js
create mode 100644 src/main/webapp/static/js/common-js.js
create mode 100644 src/main/webapp/static/js/login.js
create mode 100644 src/main/webapp/static/js/util/bootstrap-datetimepicker.min.js
create mode 100644 src/main/webapp/static/js/util/bootstrap-datetimepicker.zh-CN.js
create mode 100644 src/main/webapp/static/js/util/jquery.validate.min.js
create mode 100644 version_diary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4b1d0e3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+# Compiled class file
+*.class
+classes/
+
+# Log file
+*.log
+target/
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+*.iml
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+.idea/
+.settings/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /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 [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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..639c818
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+# health-manager
+
+#### 介绍
+个人健康信息管理系统
+
+#### 软件架构
+spring+springmvc+mybatis+mysql+jsp+bootstrap
+
+#### 安装教程
+1. 数据库导入sql文件
+2. eclipse导入maven项目
+3. 设置项目project facets,配置版本Dynamic Web Model为[3.0,)
+4. 配置web容器,导入项目到容器中,启动容器
+
+#### 使用说明
+1. 访问地址:(http://localhost:{web容器端口}/{项目名称}/)
+
+#### 项目效果
+![alt 登录](https://service.beansprout.top/image/health-manager/login.jpg)
+![alt 首页](https://service.beansprout.top/image/health-manager/home.jpg)
+![alt 健康列表](https://service.beansprout.top/image/health-manager/health_list.jpg)
+![alt 健康统计](https://service.beansprout.top/image/health-manager/health_statistics.jpg)
+
+#### 附录
+##### Mybatis字段类型映射
+```
+JDBCType | JavaType
+----------------------------
+CHAR String
+VARCHAR String
+LONGVARCHAR String
+NUMERIC java.math.BigDecimal
+DECIMAL java.math.BigDecimal
+BIT boolean
+BOOLEAN boolean
+TINYINT byte
+SMALLINT short
+INTEGER int
+BIGINT long
+REAL float
+FLOAT double
+DOUBLE double
+BINARY byte[]
+VARBINARY byte[]
+LONGVARBINARY byte[]
+DATE java.sql.Date
+TIME java.sql.Time
+TIMESTAMP java.sql.Timestamp
+CLOB Clob
+BLOB Blob
+ARRAY Array
+DISTINCT mapping of underlying type
+STRUCT Struct
+REF Ref
+DATALINK java.net.URL[color=red][/color]
+```
+
+##### SpringMVC 入参校验注解
+```
+校验注解 可校验类型 具体类型
+@AssertTrue Boolean、boolean 属性必须是true
+@AssertFalse Boolean、boolean 属性必须是false
+@Null 基本类型除外 属性必须为null
+@NotNull 基本类型除外 属性必须不能为null
+@NotEmpty CharSequence、Collection、Map 属性不能为null,字符串和集合长度不能为0(无法校验空字符串)
+@NotBlank CharSequence 属性不能为null,并不能为空字符串
+@Size CharSequence、Collection、Map 属性长度必须在指定范围
+@Length CharSequence 属性长度必须在指定范围
+@Min Number 属性必须大于指定最小值
+@Max Number 属性必须小于指定最大值
+@Range Number 属性在指定返回内
+@Past Date、Calender 属性时间必须大于当前时间
+@Future Date、Calender 属性时间必须小于当前时间
+@Email CharSequence 属性必须是合法邮件格式
+@Pattern CharSequence 属性必须匹配正则表达式
+```
+
+#### 码云特技
+1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
+2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
+3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
+4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
+5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
+6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
\ No newline at end of file
diff --git a/health.sql b/health.sql
new file mode 100644
index 0000000..496822c
--- /dev/null
+++ b/health.sql
@@ -0,0 +1,80 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server : 127.0.0.1
+ Source Server Type : MySQL
+ Source Server Version : 50644
+ Source Host : localhost:3306
+ Source Schema : health
+
+ Target Server Type : MySQL
+ Target Server Version : 50644
+ File Encoding : 65001
+
+ Date: 03/05/2020 15:46:09
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for t_body_info
+-- ----------------------------
+DROP TABLE IF EXISTS `t_body_info`;
+CREATE TABLE `t_body_info` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `creator` int(11) NULL DEFAULT NULL COMMENT '创建id',
+ `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
+ `updater` int(11) NULL DEFAULT NULL COMMENT '修改id',
+ `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
+ `low_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '舒张压',
+ `high_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '收缩压',
+ `heart_rate` int(10) NULL DEFAULT NULL COMMENT '心率',
+ `temperature` double(10, 2) NULL DEFAULT NULL COMMENT '体温',
+ `appetite` int(11) NULL DEFAULT NULL COMMENT '食欲',
+ `weight` double(10, 2) NULL DEFAULT NULL COMMENT '体重',
+ `number_of_step` bigint(20) NULL DEFAULT NULL COMMENT '步数',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '身体信息表' ROW_FORMAT = Compact;
+
+-- ----------------------------
+-- Table structure for t_health_config
+-- ----------------------------
+DROP TABLE IF EXISTS `t_health_config`;
+CREATE TABLE `t_health_config` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `creator` int(11) NULL DEFAULT NULL COMMENT '创建人id',
+ `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
+ `updater` int(11) NULL DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
+ `min_low_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最小低血压',
+ `max_low_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最高低血压',
+ `min_high_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最小高血压',
+ `max_high_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最大高血压',
+ `min_heart_rate` int(10) NULL DEFAULT NULL COMMENT '最小心率',
+ `max_heart_rate` int(10) NULL DEFAULT NULL COMMENT '最大心率',
+ `min_temperature` double(10, 2) NULL DEFAULT NULL COMMENT '最低体温',
+ `max_temperature` double(10, 2) NULL DEFAULT NULL COMMENT '最高体温',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '健康信息配置表' ROW_FORMAT = Compact;
+
+-- ----------------------------
+-- Table structure for t_user
+-- ----------------------------
+DROP TABLE IF EXISTS `t_user`;
+CREATE TABLE `t_user` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `creator` int(11) NULL DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
+ `updater` int(11) NULL DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
+ `nick_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '用户昵称',
+ `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '登录账户',
+ `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '登录密码',
+ `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '邮箱',
+ `head_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '头像',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE INDEX `user_name`(`user_name`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '用户表' ROW_FORMAT = Compact;
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..826ec24
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,239 @@
+
+ 4.0.0
+ top.beansprout
+ health
+ 0.0.1-SNAPSHOT
+ war
+ health-manager
+ 个人健康信息管理系统
+
+
+ UTF-8
+ 1.8
+ 5.1.8.RELEASE
+ 2.10.3
+
+
+
+
+
+ org.springframework
+ spring-core
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-context
+ ${spring.version}
+
+
+
+
+ org.springframework
+ spring-tx
+ ${spring.version}
+
+
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-web
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-context-support
+ ${spring.version}
+
+
+
+
+ org.springframework
+ spring-aspects
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-aop
+ ${spring.version}
+
+
+
+ aopalliance
+ aopalliance
+ 1.0
+
+
+
+ org.aspectj
+ aspectjrt
+ 1.6.8
+
+
+ org.aspectj
+ aspectjweaver
+ 1.6.8
+
+
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+ javax.servlet.jsp
+ jsp-api
+ 2.2
+ provided
+
+
+ javax.servlet.jsp.jstl
+ jstl-api
+ 1.2
+
+
+ org.glassfish.web
+ jstl-impl
+ 1.2
+
+
+
+
+ org.springframework
+ spring-orm
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-jdbc
+ ${spring.version}
+
+
+ mysql
+ mysql-connector-java
+ 8.0.16
+
+
+ com.alibaba
+ druid
+ 1.1.21
+
+
+ org.mybatis
+ mybatis
+ 3.5.3
+
+
+ org.mybatis
+ mybatis-spring
+ 2.0.4
+
+
+
+ com.github.pagehelper
+ pagehelper
+ 5.1.0
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.8
+ provider
+
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.30
+
+
+ org.slf4j
+ jcl-over-slf4j
+ 1.7.30
+
+
+ org.slf4j
+ jul-to-slf4j
+ 1.7.30
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.30
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${jackson.version}
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.0.7.Final
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ commons-fileupload
+ commons-fileupload
+ 1.4
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/config/CrossXssFilter.java b/src/main/java/top/beansprout/health/config/CrossXssFilter.java
new file mode 100644
index 0000000..cb665a1
--- /dev/null
+++ b/src/main/java/top/beansprout/health/config/CrossXssFilter.java
@@ -0,0 +1,141 @@
+package top.beansprout.health.config;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+import top.beansprout.health.constant.SysConstant;
+import top.beansprout.health.model.vo.RequestVo;
+import top.beansprout.health.model.vo.RequestVo.RequestVoBuilder;
+import top.beansprout.health.model.vo.UserLoginVo;
+import top.beansprout.health.util.JsonUtils;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ *
Title: CrossXssFilter
+ * Description: 对用户输入的表单信息进行检测过滤
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/22 22:47
+ */
+@Slf4j
+public class CrossXssFilter implements Filter {
+
+ /** 忽略资源地址 **/
+ private final String[] ignores = new String[] { "/druid", "/static" };
+ /** 忽略页面地址 **/
+ private final String[] filtrationViews = new String[] { "/login", "/user/login", "/register", "/user/register",
+ "/logout", "/user/logout" };
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
+ throws IOException, ServletException {
+ final HttpServletRequest request = init(servletRequest);
+ if (!isRequestValid(request)) {
+ chain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+ servletRequest.setCharacterEncoding("UTF-8");
+ servletResponse.setContentType("text/html;charset=utf-8");
+ // sql,xss过滤
+ log.info("URI:{}", request.getRequestURI());
+ log.info("METHOD:{}", request.getMethod());
+ log.info("ParameterMap:{}", JsonUtils.toJson(request.getParameterMap()));
+ if (isOauthValid(request)) {
+ // 获取身份
+ final Object user = request.getSession().getAttribute(SysConstant.INIT_FIELD_USER_VO);
+ if (PublicUtils.isBlank(user)) {
+ final HttpServletResponse response = (HttpServletResponse) servletResponse;
+ if ((request.getHeader("x-requested-with") != null)
+ && "XMLHttpRequest".equals(request.getHeader("x-requested-with"))) {
+ // ajax请求
+ response.setHeader("sessionstatus", "timeout");
+ response.setStatus(403);
+ response.addHeader("loginPath", "login");
+ chain.doFilter(request, response);
+ return;
+ }
+ // 页面请求
+ response.setHeader("X-Frame-Options", "DENY");
+ response.sendRedirect("login");
+ return;
+ }
+ }
+ final XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper(request);
+ chain.doFilter(xssHttpServletRequestWrapper, servletResponse);
+ }
+
+ /** 初始化 **/
+ private HttpServletRequest init(ServletRequest servletRequest) {
+ final HttpServletRequest request = (HttpServletRequest) servletRequest;
+
+ final String basePath = PublicUtils.join(request.getScheme(), "://", request.getServerName(),
+ ":" + request.getServerPort(), request.getContextPath(), "/");
+
+ final RequestVoBuilder requestVo = RequestVo.builder().basePath(basePath);
+ final Object userStr = request.getSession().getAttribute(SysConstant.INIT_FIELD_USER_VO);
+ if (PublicUtils.isNotBlank(userStr)) {
+ requestVo.user((UserLoginVo) userStr);
+ }
+ request.setAttribute(SysConstant.INIT_FIELD_REQUEST_VO, JsonUtils.toJson(requestVo.build()));
+ return request;
+ }
+
+ /** 校验合法地址请求 **/
+ private boolean isRequestValid(HttpServletRequest request) {
+ URI uri;
+ try {
+ uri = new URI(request.getRequestURL().toString());
+ } catch (final URISyntaxException ex) {
+ return false;
+ }
+
+ if (uri.getHost() == null)
+ return false;
+ if (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https"))
+ return false;
+ // 忽略指定地址
+ final String path = StringUtils.removeStart(uri.getPath(), request.getContextPath());
+ for (final String ignore : ignores) {
+ if (path.startsWith(ignore))
+ return false;
+ }
+ // 忽略swagger的根路径
+ if (path.equalsIgnoreCase("/"))
+ return false;
+ return true;
+ }
+
+ /** 是否校验合法身份请求 **/
+ private boolean isOauthValid(HttpServletRequest request) {
+ // 忽略指定地址
+ final String path = StringUtils.removeStart(request.getRequestURI(), request.getContextPath());
+ for (final String filtrationView : filtrationViews) {
+ if (path.startsWith(filtrationView))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/config/ExceptionManager.java b/src/main/java/top/beansprout/health/config/ExceptionManager.java
new file mode 100644
index 0000000..1afb95e
--- /dev/null
+++ b/src/main/java/top/beansprout/health/config/ExceptionManager.java
@@ -0,0 +1,43 @@
+package top.beansprout.health.config;
+
+import java.util.List;
+
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import top.beansprout.health.model.vo.R;
+
+/**
+ * Title: ExceptionManager
+ * Description:
+ * @author cyy
+ * @date 2020年4月28日
+ */
+@ControllerAdvice
+public class ExceptionManager {
+
+ @ResponseBody
+ @ExceptionHandler({ BindException.class, MethodArgumentNotValidException.class })
+ public R handleException(Exception e) {
+ List fieldErrs = null;
+ if (e instanceof BindException) {
+ fieldErrs = ((BindException) e).getBindingResult().getFieldErrors();
+ }
+ if (e instanceof MethodArgumentNotValidException) {
+ fieldErrs = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
+ }
+ Object r = e.getMessage();
+ if (fieldErrs != null) {
+ for (final FieldError err : fieldErrs) {
+ r = err.getDefaultMessage();
+ break;
+ }
+ }
+ return R.budil().result(false).message(r.toString()).data(r);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/config/HTMLWrapper.java b/src/main/java/top/beansprout/health/config/HTMLWrapper.java
new file mode 100644
index 0000000..3d4eaa1
--- /dev/null
+++ b/src/main/java/top/beansprout/health/config/HTMLWrapper.java
@@ -0,0 +1,556 @@
+package top.beansprout.health.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Title: HTMLWrapper
+ * Description: HTML内容过滤
+ * HTML filtering utility for protecting against XSS (Cross Site Scripting).
+ *
+ * This code is licensed LGPLv3
+ *
+ * This code is a Java port of the original work in PHP by Cal Hendersen.
+ * http://code.iamcal.com/php/lib_filter/
+ *
+ * The trickiest part of the translation was handling the differences in regex handling
+ * between PHP and Java. These resources were helpful in the process:
+ *
+ * http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html
+ * http://us2.php.net/manual/en/reference.pcre.pattern.modifiers.php
+ * http://www.regular-expressions.info/modifiers.html
+ *
+ * A note on naming conventions: instance variables are prefixed with a "v"; global
+ * constants are in all caps.
+ *
+ * Sample use:
+ * String input = ...
+ * String clean = new HTMLWrapper().filter( input );
+ *
+ * The class is not thread safe. Create a new instance if in doubt.
+ *
+ * If you find bugs or have suggestions on improvement (especially regarding
+ * performance), please contact us. The latest version of this
+ * source, and our contact details, can be found at http://xss-html-filter.sf.net
+ *
+ * @author beansprout
+ * @date 2020/3/22 23:06
+ * @version 1.0
+ */
+@Slf4j
+public final class HTMLWrapper {
+
+ private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
+ private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL);
+ private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
+ private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
+ private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
+ private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
+ private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
+ private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
+ private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
+ private static final Pattern P_ENTITY = Pattern.compile("(\\d+);?");
+ private static final Pattern P_ENTITY_UNICODE = Pattern.compile("([0-9a-f]+);?");
+ private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
+ private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
+ private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
+ private static final Pattern P_END_ARROW = Pattern.compile("^>");
+ private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
+ private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
+ private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
+ private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
+ private static final Pattern P_AMP = Pattern.compile("&");
+ private static final Pattern P_QUOTE = Pattern.compile("<");
+ private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
+ private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
+ private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
+
+ // @xxx could grow large... maybe use sesat's ReferenceMap
+ private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap();
+ private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap();
+
+ /**
+ * set of allowed html elements, along with allowed attributes for each element
+ **/
+ private final Map> vAllowed;
+ /**
+ * counts of open tags for each (allowable) html element
+ **/
+ private final Map vTagCounts = new HashMap();
+
+ /**
+ * html elements which must always be self-closing (e.g. " ")
+ **/
+ private final String[] vSelfClosingTags;
+ /**
+ * html elements which must always have separate opening and closing tags (e.g. " ")
+ **/
+ private final String[] vNeedClosingTags;
+ /**
+ * set of disallowed html elements
+ **/
+ private final String[] vDisallowed;
+ /**
+ * attributes which should be checked for valid protocols
+ **/
+ private final String[] vProtocolAtts;
+ /**
+ * allowed protocols
+ **/
+ private final String[] vAllowedProtocols;
+ /**
+ * tags which should be removed if they contain no content (e.g. " " or " ")
+ **/
+ private final String[] vRemoveBlanks;
+ /**
+ * entities allowed within html markup
+ **/
+ private final String[] vAllowedEntities;
+ /**
+ * flag determining whether comments are allowed in input String.
+ */
+ private final boolean stripComment;
+ private final boolean encodeQuotes;
+ private boolean vDebug = false;
+ /**
+ * flag determining whether to try to make tags when presented with "unbalanced"
+ * angle brackets (e.g. "" becomes " text "). If set to false,
+ * unbalanced angle brackets will be html escaped.
+ */
+ private final boolean alwaysMakeTags;
+
+ /**
+ * Default constructor.
+ */
+ public HTMLWrapper() {
+ vAllowed = new HashMap<>();
+
+ final ArrayList a_atts = new ArrayList();
+ a_atts.add("href");
+ a_atts.add("target");
+ vAllowed.put("a", a_atts);
+
+ final ArrayList img_atts = new ArrayList();
+ img_atts.add("src");
+ img_atts.add("width");
+ img_atts.add("height");
+ img_atts.add("alt");
+ vAllowed.put("img", img_atts);
+
+ final ArrayList no_atts = new ArrayList();
+ vAllowed.put("b", no_atts);
+ vAllowed.put("strong", no_atts);
+ vAllowed.put("i", no_atts);
+ vAllowed.put("em", no_atts);
+
+ vSelfClosingTags = new String[]{"img"};
+ vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
+ vDisallowed = new String[]{};
+ vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp.
+ vProtocolAtts = new String[]{"src", "href"};
+ vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
+ vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
+ stripComment = true;
+ encodeQuotes = true;
+ alwaysMakeTags = true;
+ }
+
+ /**
+ * Set debug flag to true. Otherwise use default settings. See the default constructor.
+ *
+ * @param debug turn debug on with a true argument
+ */
+ public HTMLWrapper(final boolean debug) {
+ this();
+ vDebug = debug;
+ }
+
+ /**
+ * Map-parameter configurable constructor.
+ *
+ * @param conf map containing configuration. keys match field names.
+ */
+ @SuppressWarnings("unchecked")
+ public HTMLWrapper(final Map conf) {
+
+ assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
+ assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
+ assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
+ assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
+ assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
+ assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
+ assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
+ assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
+
+ vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed"));
+ vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
+ vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
+ vDisallowed = (String[]) conf.get("vDisallowed");
+ vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
+ vProtocolAtts = (String[]) conf.get("vProtocolAtts");
+ vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
+ vAllowedEntities = (String[]) conf.get("vAllowedEntities");
+ stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
+ encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
+ alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
+ }
+
+ private void reset() {
+ vTagCounts.clear();
+ }
+
+ private void debug(final String msg) {
+ if (vDebug) {
+ log.info(msg);
+ }
+ }
+
+ //---------------------------------------------------------------
+ // my versions of some PHP library functions
+ public static String chr(final int decimal) {
+ return String.valueOf((char) decimal);
+ }
+
+ public static String htmlSpecialChars(final String s) {
+ String result = s;
+ result = regexReplace(P_AMP, "&", result);
+ result = regexReplace(P_QUOTE, """, result);
+ result = regexReplace(P_LEFT_ARROW, "<", result);
+ result = regexReplace(P_RIGHT_ARROW, ">", result);
+ return result;
+ }
+
+ //---------------------------------------------------------------
+
+ /**
+ * given a user submitted input String, filter out any invalid or restricted
+ * html.
+ *
+ * @param input text (i.e. submitted by a user) than may contain html
+ * @return "clean" version of input, with only valid, whitelisted html elements allowed
+ */
+ public String filter(final String input) {
+ reset();
+ String s = input;
+
+ debug("************************************************");
+ debug(" INPUT: " + input);
+
+ s = escapeComments(s);
+ debug(" escapeComments: " + s);
+
+ s = balanceHTML(s);
+ debug(" balanceHTML: " + s);
+
+ s = checkTags(s);
+ debug(" checkTags: " + s);
+
+ s = processRemoveBlanks(s);
+ debug("processRemoveBlanks: " + s);
+
+ s = validateEntities(s);
+ debug(" validateEntites: " + s);
+
+ debug("************************************************\n\n");
+ return s;
+ }
+
+ public boolean isAlwaysMakeTags() {
+ return alwaysMakeTags;
+ }
+
+ public boolean isStripComments() {
+ return stripComment;
+ }
+
+ private String escapeComments(final String s) {
+ final Matcher m = P_COMMENTS.matcher(s);
+ final StringBuffer buf = new StringBuffer();
+ if (m.find()) {
+ final String match = m.group(1); //(.*?)
+ m.appendReplacement(buf, Matcher.quoteReplacement(""));
+ }
+ m.appendTail(buf);
+
+ return buf.toString();
+ }
+
+ private String balanceHTML(String s) {
+ if (alwaysMakeTags) {
+ //
+ // try and form html
+ //
+ s = regexReplace(P_END_ARROW, "", s);
+ s = regexReplace(P_BODY_TO_END, "<$1>", s);
+ s = regexReplace(P_XML_CONTENT, "$1<$2", s);
+
+ } else {
+ //
+ // escape stray brackets
+ //
+ s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s);
+ s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s);
+
+ //
+ // the last regexp causes '<>' entities to appear
+ // (we need to do a lookahead assertion so that the last bracket can
+ // be used in the next pass of the regexp)
+ //
+ s = regexReplace(P_BOTH_ARROWS, "", s);
+ }
+
+ return s;
+ }
+
+ private String checkTags(String s) {
+ final Matcher m = P_TAGS.matcher(s);
+
+ final StringBuffer buf = new StringBuffer();
+ while (m.find()) {
+ String replaceStr = m.group(1);
+ replaceStr = processTag(replaceStr);
+ m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
+ }
+ m.appendTail(buf);
+
+ s = buf.toString();
+
+ // these get tallied in processTag
+ // (remember to reset before subsequent calls to filter method)
+ for (final String key : vTagCounts.keySet()) {
+ for (int ii = 0; ii < vTagCounts.get(key); ii++) {
+ s += "" + key + ">";
+ }
+ }
+
+ return s;
+ }
+
+ private String processRemoveBlanks(final String s) {
+ String result = s;
+ for (final String tag : vRemoveBlanks) {
+ if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
+ P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>" + tag + ">"));
+ }
+ result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
+ if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
+ P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
+ }
+ result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
+ }
+
+ return result;
+ }
+
+ private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
+ final Matcher m = regex_pattern.matcher(s);
+ return m.replaceAll(replacement);
+ }
+
+ private String processTag(final String s) {
+ // ending tags
+ Matcher m = P_END_TAG.matcher(s);
+ if (m.find()) {
+ final String name = m.group(1).toLowerCase();
+ if (allowed(name)) {
+ if (!inArray(name, vSelfClosingTags)) {
+ if (vTagCounts.containsKey(name)) {
+ vTagCounts.put(name, vTagCounts.get(name) - 1);
+ return "" + name + ">";
+ }
+ }
+ }
+ }
+
+ // starting tags
+ m = P_START_TAG.matcher(s);
+ if (m.find()) {
+ final String name = m.group(1).toLowerCase();
+ final String body = m.group(2);
+ String ending = m.group(3);
+
+ //debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
+ if (allowed(name)) {
+ String params = "";
+
+ final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
+ final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
+ final List paramNames = new ArrayList();
+ final List paramValues = new ArrayList();
+ while (m2.find()) {
+ paramNames.add(m2.group(1)); //([a-z0-9]+)
+ paramValues.add(m2.group(3)); //(.*?)
+ }
+ while (m3.find()) {
+ paramNames.add(m3.group(1)); //([a-z0-9]+)
+ paramValues.add(m3.group(3)); //([^\"\\s']+)
+ }
+
+ String paramName, paramValue;
+ for (int ii = 0; ii < paramNames.size(); ii++) {
+ paramName = paramNames.get(ii).toLowerCase();
+ paramValue = paramValues.get(ii);
+
+// debug( "paramName='" + paramName + "'" );
+// debug( "paramValue='" + paramValue + "'" );
+// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
+
+ if (allowedAttribute(name, paramName)) {
+ if (inArray(paramName, vProtocolAtts)) {
+ paramValue = processParamProtocol(paramValue);
+ }
+ params += " " + paramName + "=\"" + paramValue + "\"";
+ }
+ }
+
+ if (inArray(name, vSelfClosingTags)) {
+ ending = " /";
+ }
+
+ if (inArray(name, vNeedClosingTags)) {
+ ending = "";
+ }
+
+ if ((ending == null) || (ending.length() < 1)) {
+ if (vTagCounts.containsKey(name)) {
+ vTagCounts.put(name, vTagCounts.get(name) + 1);
+ } else {
+ vTagCounts.put(name, 1);
+ }
+ } else {
+ ending = " /";
+ }
+ return "<" + name + params + ending + ">";
+ } else
+ return "";
+ }
+
+ // comments
+ m = P_COMMENT.matcher(s);
+ if (!stripComment && m.find())
+ return "<" + m.group() + ">";
+
+ return "";
+ }
+
+ private String processParamProtocol(String s) {
+ s = decodeEntities(s);
+ final Matcher m = P_PROTOCOL.matcher(s);
+ if (m.find()) {
+ final String protocol = m.group(1);
+ if (!inArray(protocol, vAllowedProtocols)) {
+ // bad protocol, turn into local anchor link instead
+ s = "#" + s.substring(protocol.length() + 1, s.length());
+ if (s.startsWith("#//")) {
+ s = "#" + s.substring(3, s.length());
+ }
+ }
+ }
+
+ return s;
+ }
+
+ private String decodeEntities(String s) {
+ StringBuffer buf = new StringBuffer();
+
+ Matcher m = P_ENTITY.matcher(s);
+ while (m.find()) {
+ final String match = m.group(1);
+ final int decimal = Integer.decode(match).intValue();
+ m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+ }
+ m.appendTail(buf);
+ s = buf.toString();
+
+ buf = new StringBuffer();
+ m = P_ENTITY_UNICODE.matcher(s);
+ while (m.find()) {
+ final String match = m.group(1);
+ final int decimal = Integer.valueOf(match, 16).intValue();
+ m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+ }
+ m.appendTail(buf);
+ s = buf.toString();
+
+ buf = new StringBuffer();
+ m = P_ENCODE.matcher(s);
+ while (m.find()) {
+ final String match = m.group(1);
+ final int decimal = Integer.valueOf(match, 16).intValue();
+ m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+ }
+ m.appendTail(buf);
+ s = buf.toString();
+
+ s = validateEntities(s);
+ return s;
+ }
+
+ private String validateEntities(final String s) {
+ final StringBuffer buf = new StringBuffer();
+
+ // validate entities throughout the string
+ final Matcher m = P_VALID_ENTITIES.matcher(s);
+ while (m.find()) {
+ final String one = m.group(1); //([^&;]*)
+ final String two = m.group(2); //(?=(;|&|$))
+ m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
+ }
+ m.appendTail(buf);
+
+ return encodeQuotes(buf.toString());
+ }
+
+ private String encodeQuotes(final String s) {
+ if (encodeQuotes) {
+ final StringBuffer buf = new StringBuffer();
+ final Matcher m = P_VALID_QUOTES.matcher(s);
+ while (m.find()) {
+ final String one = m.group(1); //(>|^)
+ final String two = m.group(2); //([^<]+?)
+ final String three = m.group(3); //(<|$)
+ m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, """, two) + three));
+ }
+ m.appendTail(buf);
+ return buf.toString();
+ } else
+ return s;
+ }
+
+ private String checkEntity(final String preamble, final String term) {
+
+ return ";".equals(term) && isValidEntity(preamble)
+ ? '&' + preamble
+ : "&" + preamble;
+ }
+
+ private boolean isValidEntity(final String entity) {
+ return inArray(entity, vAllowedEntities);
+ }
+
+ private static boolean inArray(final String s, final String[] array) {
+ for (final String item : array) {
+ if ((item != null) && item.equals(s))
+ return true;
+ }
+ return false;
+ }
+
+ private boolean allowed(final String name) {
+ return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
+ }
+
+ private boolean allowedAttribute(final String name, final String paramName) {
+ return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java b/src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java
new file mode 100644
index 0000000..799b200
--- /dev/null
+++ b/src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java
@@ -0,0 +1,77 @@
+package top.beansprout.health.config;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ModelAndView;
+
+import top.beansprout.health.model.vo.BusinessException;
+import top.beansprout.health.model.vo.R;
+import top.beansprout.health.util.JsonUtils;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ * Title: MySimpleMappingExceptionResolver
+ * Description: 异常处理
+ * @author cyy
+ * @date 2020年4月23日
+ */
+public class MySimpleMappingExceptionResolver implements HandlerExceptionResolver {
+
+ @Override
+ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
+ Exception exception) {
+ // 判断是否ajax请求
+ if (!((request.getHeader("accept").indexOf("application/json") > -1)
+ || ((request.getHeader("X-Requested-With") != null)
+ && (request.getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1)))) {
+ // 这里需要手动将异常打印出来,由于没有配置log,实际生产环境应该打印到log里面
+ exception.printStackTrace();
+ if (exception instanceof BusinessException) {
+ final BusinessException be = (BusinessException) exception;
+ String path = be.getR().getPath();
+ if (PublicUtils.isBlank(path)) {
+ path = request.getHeader("Referer");
+ path = StringUtils.removeStart(path, request.getHeader("Origin"));
+ path = StringUtils.removeStart(path, request.getContextPath());
+ }
+ return R.failed(path, be.getR());
+ } else
+ // 对于非ajax请求,我们都统一跳转到error.jsp页面
+ return R.failed(R.budil().result(false).message("系统异常!"));
+ } else {
+ // 如果是ajax请求,JSON格式返回
+ if (exception instanceof BusinessException) {
+ final BusinessException be = (BusinessException) exception;
+ response(response, be.getR());
+ } else {
+ response(response, R.budil().result(false).message("系统异常!"));
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("deprecation")
+ private void response(HttpServletResponse response, Object data) {
+ response.setContentType("application/json;charset=UTF-8");
+ PrintWriter writer = null;
+ try {
+ response.setStatus(HttpStatus.OK.value());
+ writer = response.getWriter();
+ writer.write(JsonUtils.toJson(data));
+ writer.flush();
+ } catch (final IOException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/config/TaskExecutorConfig.java b/src/main/java/top/beansprout/health/config/TaskExecutorConfig.java
new file mode 100644
index 0000000..9e58ca1
--- /dev/null
+++ b/src/main/java/top/beansprout/health/config/TaskExecutorConfig.java
@@ -0,0 +1,30 @@
+package top.beansprout.health.config;
+
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+@Configuration
+@EnableAsync
+public class TaskExecutorConfig implements AsyncConfigurer { // 1
+
+ @Override
+ public Executor getAsyncExecutor() { // 2
+ ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+ taskExecutor.setCorePoolSize(10);
+ taskExecutor.setMaxPoolSize(15);
+ taskExecutor.setQueueCapacity(25);
+ taskExecutor.initialize();
+ return taskExecutor;
+ }
+
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return new SimpleAsyncUncaughtExceptionHandler();
+ }
+}
diff --git a/src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java b/src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java
new file mode 100644
index 0000000..6f94e85
--- /dev/null
+++ b/src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java
@@ -0,0 +1,198 @@
+package top.beansprout.health.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.MediaType;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ * Title: XssHttpServletRequestWrapper
+ * Description: XSS过滤处理
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/22 22:51
+ */
+@Slf4j
+public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private HttpServletRequest orgRequest;
+ private final String requestUri;
+ private static HTMLWrapper htmlWrapper;
+ @Getter
+ private String requestBody;
+
+ private static String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+";
+ private static Set notAllowedKeyWords = new HashSet<>(0);
+ private static String replacedString = "INVALID";
+ // 指定uri需要进行html过滤
+ private static String []htmlUri = { };
+ private static List ignoreParams = new ArrayList<>();
+
+ static {
+ final String keyStr[] = key.split("\\|");
+ for (final String str : keyStr) {
+ notAllowedKeyWords.add(str);
+ }
+ htmlWrapper = new HTMLWrapper(true);
+ }
+
+ public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
+ super(request);
+ orgRequest = request;
+ if (request instanceof XssHttpServletRequestWrapper) {
+ orgRequest = ((XssHttpServletRequestWrapper) request).getOrgRequest();
+ }
+ requestUri = orgRequest.getRequestURI();
+ requestBody = getAnalysisRequestBody();
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ // 为空,直接返回
+ if (PublicUtils.isBlank(requestBody))
+ return super.getInputStream();
+
+ // xss过滤
+ requestBody = xssEncode(requestBody);
+ final ByteArrayInputStream bis = new ByteArrayInputStream(requestBody.getBytes("UTF-8"));
+ return new ServletInputStream() {
+ @Override
+ public boolean isFinished() {
+ return true;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+
+ @Override
+ public int read() {
+ return bis.read();
+ }
+ };
+ }
+
+ @Override
+ public String getParameter(String name) {
+ final String value = super.getParameter(name);
+ if (value == null)
+ return null;
+ return xssEncode(value);
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ final String[] parameters = super.getParameterValues(name);
+ if ((parameters == null) || (parameters.length == 0))
+ return null;
+ final int count = parameters.length;
+ final String[] encodedValues = new String[count];
+ for (int i = 0; i < count; i++) {
+ encodedValues[i] = xssEncode(parameters[i]);
+ }
+ return encodedValues;
+ }
+
+ @Override
+ public Map getParameterMap() {
+ final Map parameters = super.getParameterMap();
+ if (parameters == null)
+ return null;
+ final Map result = new HashMap<>();
+ for (final String key : parameters.keySet()) {
+ final String encodedKey = xssEncode(key);
+ final int count = parameters.get(key).length;
+ final String[] encodedValues = new String[count];
+ for (int i = 0; i < count; i++) {
+ encodedValues[i] = xssEncode(parameters.get(key)[i]);
+ }
+ result.put(encodedKey, encodedValues);
+ }
+ return result;
+ }
+
+ /**
+ * 覆盖getHeader方法,将参数名和参数值都做xss过滤。
+ * 如果需要获得原始的值,则通过super.getHeaders(name)来获取
+ * getHeaderNames 也可能需要覆盖
+ */
+ @Override
+ public String getHeader(String name) {
+ final String value = super.getHeader(name);
+ if (value == null)
+ return null;
+ // 忽略指定字段不进行xss
+ if (ignoreParams.contains(name)) return value;
+ return xssEncode(value);
+ }
+
+ /** 获取请求body json 内容 **/
+ private String getAnalysisRequestBody() throws IOException {
+ final String contentType = super.getHeader("Content-Type");
+ if (PublicUtils.isBlank(contentType) || !MediaType.APPLICATION_JSON_VALUE.contentEquals(contentType))
+ return null;
+
+ return IOUtils.toString(super.getInputStream(), "UTF-8");
+ }
+
+ /** 获取最原始request **/
+ private HttpServletRequest getOrgRequest() {
+ return orgRequest;
+ }
+
+ /** 过滤 */
+ private String xssEncode(String value) {
+ // You'll need to remove the spaces from the html entities below
+ String result = value.replaceAll("<", "<").replaceAll(">", ">");
+ result = result.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
+ result = result.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
+ result = result.replaceAll("'", "& #39;");
+ result = result.replaceAll("eval\\((.*)\\)", "");
+ result = result.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
+ result = result.replaceAll("script", "");
+ result = sqlKeyFilterWords(result);
+ for (int i = 0; i < htmlUri.length; i ++) {
+ if (requestUri.contains(htmlUri[i])) {
+ result = htmlWrapper.filter(result);
+ }
+ }
+ return result;
+ }
+
+ /** sql注入过滤 **/
+ private String sqlKeyFilterWords(String value) {
+ String paramValue = value;
+ for (final String keyword : notAllowedKeyWords) {
+ if ((paramValue.length() > (keyword.length() + 1))
+ && (paramValue.contains(" " + keyword) || paramValue.contains(keyword + " ") || paramValue.contains(" " + keyword + " "))) {
+ paramValue = StringUtils.replace(paramValue, keyword, replacedString);
+ log.warn("CrosXssFilter 请求参数中包含不允许sql的关键词({});参数:{};过滤后的参数:{}", keyword, value, paramValue);
+ }
+ }
+ return paramValue;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/constant/SysConstant.java b/src/main/java/top/beansprout/health/constant/SysConstant.java
new file mode 100644
index 0000000..91905c2
--- /dev/null
+++ b/src/main/java/top/beansprout/health/constant/SysConstant.java
@@ -0,0 +1,31 @@
+package top.beansprout.health.constant;
+
+import javax.servlet.http.HttpServletRequest;
+
+import top.beansprout.health.model.vo.RequestVo;
+import top.beansprout.health.util.JsonUtils;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ * Title: SysConstant
+ * Description: 系统常量
+ * @author cyy
+ * @date 2020年4月23日
+ */
+public class SysConstant {
+
+ public static final String CHARACHER = "UTF-8";
+
+ public static final String INIT_FIELD_REQUEST_IP = "ip";
+ public static final String INIT_FIELD_REQUEST_REQUEST_ID = "requestId";
+ public static final String INIT_FIELD_REQUEST_USER_AGENT = "userAgent";
+ public static final String INIT_FIELD_REQUEST_BASE_PATH = "basePath";
+ public static final String INIT_FIELD_REQUEST_VO = "requestVo";
+ public static final String INIT_FIELD_USER_VO = "user";
+
+ /** 获取初始化数据 **/
+ public static RequestVo getRequestVo(HttpServletRequest request) {
+ return JsonUtils.toObj(PublicUtils.getAttribute(request, INIT_FIELD_REQUEST_VO), RequestVo.class);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/controller/BaseController.java b/src/main/java/top/beansprout/health/controller/BaseController.java
new file mode 100644
index 0000000..ec37435
--- /dev/null
+++ b/src/main/java/top/beansprout/health/controller/BaseController.java
@@ -0,0 +1,36 @@
+package top.beansprout.health.controller;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import top.beansprout.health.constant.SysConstant;
+import top.beansprout.health.model.vo.BusinessException;
+import top.beansprout.health.model.vo.UserLoginVo;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ * Title: BaseController
+ * Description: 基本信息处理
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月27日 上午12:36:51
+ * @Version: 1.0.0
+ */
+public class BaseController {
+
+ @Autowired
+ private HttpServletRequest request;
+
+ public UserLoginVo getUserInfo() {
+ final Object userObject = request.getSession().getAttribute(SysConstant.INIT_FIELD_USER_VO);
+ if (PublicUtils.isNotBlank(userObject))
+ return (UserLoginVo) userObject;
+ throw new BusinessException("login", "身份信息已过期,请重新登录");
+ }
+
+ public int getUserId() {
+ return getUserInfo().getId();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/controller/HealthController.java b/src/main/java/top/beansprout/health/controller/HealthController.java
new file mode 100644
index 0000000..998727d
--- /dev/null
+++ b/src/main/java/top/beansprout/health/controller/HealthController.java
@@ -0,0 +1,74 @@
+package top.beansprout.health.controller;
+
+import java.util.Date;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import top.beansprout.health.model.dto.BodyInfoQuery;
+import top.beansprout.health.model.dto.BodyInfoSaveDto;
+import top.beansprout.health.model.vo.R;
+import top.beansprout.health.service.HealthService;
+
+/**
+ * Title: HealthController
+ * Description: 健康管理接口
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+@Controller
+@RequestMapping("/health")
+public class HealthController extends BaseController {
+
+ @Autowired
+ private HealthService healthService;
+
+ // 保存身体信息
+ @ResponseBody
+ @PostMapping("/saveBodyInfo")
+ public R saveBodyInfo(@RequestBody @Valid BodyInfoSaveDto bodyInfoSaveDto) {
+ healthService.saveBodyInfo(getUserId(), bodyInfoSaveDto);
+ return R.okAsAjax();
+ }
+
+ // 身体信息列表
+ @ResponseBody
+ @GetMapping("/bodyInfoList")
+ public R bodyInfoList(@Valid BodyInfoQuery bodyInfoQuery) {
+ return R.okAsAjax(healthService.bodyInfoList(getUserId(), bodyInfoQuery));
+ }
+
+ // 删除身体信息
+ @ResponseBody
+ @DeleteMapping("/{id}")
+ public R saveBodyInfo(@PathVariable int id) {
+ healthService.deleteBodyInfo(getUserId(), id);
+ return R.okAsAjax();
+ }
+
+ // 获取身体信息
+ @ResponseBody
+ @GetMapping("/{id}")
+ public R getBodyInfo(@PathVariable int id) {
+ return R.okAsAjax(healthService.getBodyInfo(getUserId(), id));
+ }
+
+ // 身体信息统计
+ @ResponseBody
+ @GetMapping("/bodyInfoStatistics")
+ public R bodyInfoStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
+ return R.okAsAjax(healthService.getBodyStatistics(getUserId(), date));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/controller/MailController.java b/src/main/java/top/beansprout/health/controller/MailController.java
new file mode 100644
index 0000000..c47001b
--- /dev/null
+++ b/src/main/java/top/beansprout/health/controller/MailController.java
@@ -0,0 +1,89 @@
+package top.beansprout.health.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import top.beansprout.health.model.dto.BodyInfoSaveDto;
+import top.beansprout.health.model.dto.UserUpdateInfoDto;
+import top.beansprout.health.model.entity.TBodyInfo;
+import top.beansprout.health.model.vo.R;
+import top.beansprout.health.service.HealthService;
+import top.beansprout.health.util.MailUtils;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * Title: MailController
+ * Description: 发送邮箱接口
+ *
+ * @author ateenliu
+ * @date 2022年4月6日
+ */
+@Controller
+@RequestMapping("/mail")
+public class MailController extends BaseController {
+
+ @Autowired
+ private HealthService healthService;
+
+ @ResponseBody
+ @PostMapping(value = "/verification")
+ public R sendVerificationMail(@Valid String email) {
+ MailUtils mailUtils = new MailUtils();
+ String verificationCode = mailUtils.createVertifyCode();
+ String mailSubject = "个人健康管理平台验证码";
+ String mailBody = "欢迎使用个人健康管理平台,您的邮箱验证码为:"+verificationCode;
+ if(mailUtils.sendingMimeMail(email,mailSubject,mailBody))
+ {
+ System.out.println("邮件发送成功!");
+ return R.okAsAjax(verificationCode);
+ }
+ else {
+ System.out.println("邮件发送失败!");
+ return R.okAsAjax();
+ }
+
+ }
+
+ @ResponseBody
+ @PostMapping(value = "/recentPush")
+ public R recentPushMail(){
+ if(getUserInfo().getEmail()==null)
+ return R.ofAsAjax(true,"暂未绑定邮箱","");
+
+ List tBodyInfoList = healthService.recentBodyInfoList(getUserId());
+ TBodyInfo t = new TBodyInfo();
+ MailUtils mailUtils = new MailUtils();
+ String mailSubject = "近期健康报告";
+ StringBuilder content = new StringBuilder("您的近期报告如下: ");
+ content.append("");
+ content.append("打卡时间 收缩压 舒张压 " +
+ "心率 体温 食欲 体重 步数 ");
+ for(int i=0;i"+t.getCreateTime()+" "+ ""+t.getHighBloodPressure()+" "
+ + ""+t.getLowBloodPressure()+" "+ ""+t.getHeartRate()+" "
+ + ""+t.getTemperature()+"度 "+ ""+t.getAppetiteDes(t.getAppetite())+" "
+ + ""+t.getWeight()+"kg "+ ""+t.getNumberOfStep()+"步 ");
+ }
+ content.append("
");
+ content.append("");
+
+
+ if(mailUtils.sendingMimeMail(getUserInfo().getEmail(), mailSubject,content.toString()))
+ {
+ System.out.println("邮件发送成功!");
+ return R.okAsAjax();
+ }
+ else {
+ System.out.println("邮件发送失败!");
+ return R.okAsAjax();
+ }
+ }
+
+}
diff --git a/src/main/java/top/beansprout/health/controller/PageController.java b/src/main/java/top/beansprout/health/controller/PageController.java
new file mode 100644
index 0000000..eacc217
--- /dev/null
+++ b/src/main/java/top/beansprout/health/controller/PageController.java
@@ -0,0 +1,71 @@
+package top.beansprout.health.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ * Title: PageController
+ * Description: 页面管理
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月23日 下午11:15:18
+ * @Version: 1.0.0
+ */
+@Controller
+public class PageController {
+
+ // 根页面
+ @GetMapping("/")
+ public String rootView() {
+ return "redirect:/login";
+ }
+
+ // 登录页面
+ @GetMapping("/login")
+ public String loginView() {
+ return "../../index";
+ }
+
+ // 注册页面
+ @GetMapping("/register")
+ public String registerView() {
+ return "register";
+ }
+
+ // 主页面
+ @GetMapping("/home")
+ public String homeView() {
+ return "home";
+ }
+
+ // 用户信息页面
+ @GetMapping("/userInfo")
+ public String userInfoView() {
+ return "userInfo";
+ }
+
+ // 用户信息页面
+ @GetMapping("/updatePassword")
+ public String updatePasswordView() {
+ return "updatePassword";
+ }
+
+ // 用户身体信息录入页面
+ @GetMapping("/bodyInfoInput")
+ public String bodyInfoInputView() {
+ return "bodyInfoInput";
+ }
+
+ // 用户身体信息列表页面
+ @GetMapping("/bodyInofList")
+ public String bodyInofListView() {
+ return "bodyInofList";
+ }
+
+ // 用户身体信息统计页面
+ @GetMapping("/bodyInofStatistics")
+ public String bodyInofStatisticsView() {
+ return "bodyInofStatistics";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/controller/UserController.java b/src/main/java/top/beansprout/health/controller/UserController.java
new file mode 100644
index 0000000..19d0bac
--- /dev/null
+++ b/src/main/java/top/beansprout/health/controller/UserController.java
@@ -0,0 +1,74 @@
+package top.beansprout.health.controller;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import top.beansprout.health.model.dto.UserLoginDto;
+import top.beansprout.health.model.dto.UserRegisterDto;
+import top.beansprout.health.model.dto.UserUpdateInfoDto;
+import top.beansprout.health.model.dto.UserUpdatePasswordDto;
+import top.beansprout.health.model.vo.R;
+import top.beansprout.health.service.UserService;
+
+/**
+ * Title: UserController
+ * Description: 用户管理接口
+ *
+ * @author cyy
+ * @date 2020年4月23日
+ */
+@Controller
+@RequestMapping("/user")
+public class UserController extends BaseController {
+
+ @Autowired
+ private UserService userService;
+
+ // 登录
+ @ResponseBody
+ @PostMapping("/login")
+ public R login(@Valid UserLoginDto userLoginDto) {
+ return R.okAsAjax(userService.login(userLoginDto));
+ }
+
+ // 注册
+ @ResponseBody
+ @PostMapping("/register")
+ public R register(@Valid UserRegisterDto userRegisterDto) {
+ userService.register(userRegisterDto);
+ return R.okAsAjax();
+ }
+
+ // 登出
+ @GetMapping("/logout")
+ public String logout(HttpServletRequest request) {
+ userService.logout(request);
+ return "redirect:/login?target=redirect";
+ }
+
+ // 修改密码
+ @ResponseBody
+ @PutMapping("/updatePassword")
+ public R updatePassword(HttpServletRequest request, @RequestBody @Valid UserUpdatePasswordDto updatePasswordDto) {
+ userService.updatePassword(request, getUserId(), updatePasswordDto);
+ return R.okAsAjax();
+ }
+
+ // 修改用户信息
+ @ResponseBody
+ @PostMapping(value = "/updateUserInfo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public R updateUserInfo(@Valid UserUpdateInfoDto userUpdateInfoDto) {
+ return R.okAsAjax(userService.updateUserInfo(getUserId(), userUpdateInfoDto));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java b/src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java
new file mode 100644
index 0000000..73ee646
--- /dev/null
+++ b/src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java
@@ -0,0 +1,43 @@
+package top.beansprout.health.mapper;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import top.beansprout.health.model.entity.TBodyInfo;
+
+/**
+ * Title: TBodyInfoMapper
+ * Description: 身体健康操作
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+public interface TBodyInfoMapper {
+
+ @Insert("INSERT INTO t_body_info (creator, create_time, updater, update_time, low_blood_pressure, "
+ + "high_blood_pressure, heart_rate, temperature, appetite, weight, number_of_step) VALUE "
+ + "( #{creator},NOW(),#{updater},NOW(),#{lowBloodPressure},#{highBloodPressure},"
+ + "#{heartRate},#{temperature},#{appetite},#{weight},#{numberOfStep} )")
+ int insertByOne(TBodyInfo bodyInfo);
+
+ @Select("SELECT COUNT(*) FROM t_body_info WHERE creator = #{creator} AND create_time >= CURDATE() AND create_time <= CONCAT(CURDATE(), '23:59:59')")
+ int countByOneAsToDay(@Param("creator") int userId);
+
+ List selectByUserId(@Param("creator") int userId, @Param("minDate") Date minDate,
+ @Param("maxDate") Date maxDate);
+
+ @Delete("DELETE FROM t_body_info WHERE id = #{id} AND creator = #{creator}")
+ int deleteByOne(@Param("id") int id, @Param("creator") int userId);
+
+ @Select("SELECT * FROM t_body_info WHERE id = #{id} AND creator = #{creator}")
+ TBodyInfo selectByUserOne(@Param("id") int id, @Param("creator") int userId);
+
+ @Select("SELECT * FROM t_body_info WHERE creator = #{creator} LIMIT 7 ")
+ List selectRecentByUserId(@Param("creator") int userId);
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java b/src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java
new file mode 100644
index 0000000..713ca05
--- /dev/null
+++ b/src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java
@@ -0,0 +1,12 @@
+package top.beansprout.health.mapper;
+
+/**
+ * Title: THealthConfigMapper
+ * Description: 健康配置操作
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+public interface THealthConfigMapper {
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/mapper/TUserMapper.java b/src/main/java/top/beansprout/health/mapper/TUserMapper.java
new file mode 100644
index 0000000..82b3dd9
--- /dev/null
+++ b/src/main/java/top/beansprout/health/mapper/TUserMapper.java
@@ -0,0 +1,36 @@
+package top.beansprout.health.mapper;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import top.beansprout.health.model.entity.TUser;
+
+/**
+ * Title: TUserMapper
+ * Description: 用户数据操作
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月23日 下午9:24:05
+ * @Version: 1.0.0
+ */
+public interface TUserMapper {
+
+ @Select("SELECT * FROM t_user WHERE user_name = #{userName}")
+ TUser selectByUserName(@Param("userName") String userName);
+
+ @Select("SELECT * FROM t_user WHERE id = #{userId}")
+ TUser selectById(@Param("userId") int id);
+
+ @Insert("INSERT INTO t_user (creator, create_time, updater, update_time, nick_name, user_name, password, head_url) "
+ + "VALUE (0, NOW(), 0, NOW(), #{userName}, #{userName}, #{password}, #{headUrl})")
+ int insertByOne(TUser tUser);
+
+ @Update("UPDATE t_user SET updater = #{id}, update_time = NOW(), password = #{password} WHERE id = #{id}")
+ int updateByOne(TUser tUser);
+
+ @Update("UPDATE t_user SET updater = #{id}, update_time = NOW(), nick_name = #{nickName}, email = #{email}, head_url = #{headUrl} WHERE id = #{id}")
+ int updateByUserInfoOne(TUser tUser);
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java b/src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java
new file mode 100644
index 0000000..a411f91
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java
@@ -0,0 +1,28 @@
+package top.beansprout.health.model.dto;
+
+import java.util.Date;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: BodyInfoQuery
+ * Description: 身体信息查询
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+@Setter
+@Getter
+public class BodyInfoQuery extends PageDto {
+
+ private static final long serialVersionUID = 1L;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ private Date minDate;
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ private Date maxDate;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java b/src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java
new file mode 100644
index 0000000..d3cbbeb
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java
@@ -0,0 +1,46 @@
+package top.beansprout.health.model.dto;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.NotNull;
+
+import org.hibernate.validator.constraints.Range;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: BodyInfoSaveDto
+ * Description: 身体信息保存
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+@Setter
+@Getter
+public class BodyInfoSaveDto implements Serializable {
+
+ private static final long serialVersionUID = 80577054311531170L;
+
+ @NotNull(message = "低血压不能为空")
+ @Range(min = 0, max = 200, message = "低血压只能在0到200之间")
+ private Integer lowBloodPressure;
+ @NotNull(message = "高血压不能为空")
+ @Range(min = 0, max = 200, message = "高血压只能在0到200之间")
+ private Integer highBloodPressure;
+ @NotNull(message = "心率不能为空")
+ @Range(min = 40, max = 200, message = "心率只能在40到200之间")
+ private Integer heartRate;
+ @NotNull(message = "体温不能为空")
+ @Range(min = 33, max = 45, message = "体温只能在33到45之间")
+ private Double temperature;
+ @NotNull(message = "食欲不能为空")
+ @Range(min = 1, max = 6, message = "食欲只能在1到6之间")
+ private Integer appetite;
+ @NotNull(message = "体重不能为空")
+ @Range(min = 0, max = 200, message = "体重只能在33到40之间")
+ private Double weight;
+ // 步数
+ private int numberOfStep;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/PageDto.java b/src/main/java/top/beansprout/health/model/dto/PageDto.java
new file mode 100644
index 0000000..571b989
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/PageDto.java
@@ -0,0 +1,32 @@
+package top.beansprout.health.model.dto;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: PageDto
+ * Description: 列表请求
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/22 22:32
+ */
+@Setter
+@Getter
+public class PageDto implements Serializable {
+
+ private static final long serialVersionUID = -1796187511693230421L;
+
+ @Min(value = 1, message = "当前页最小页码为1")
+ private int page = 1;
+
+ @Min(value = 1, message = "每页展示条数最小为1")
+ @Max(value = 100, message = "每页展示条数最大为100")
+ private int pageSize = 10;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/UserLoginDto.java b/src/main/java/top/beansprout/health/model/dto/UserLoginDto.java
new file mode 100644
index 0000000..5e8ccce
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/UserLoginDto.java
@@ -0,0 +1,33 @@
+package top.beansprout.health.model.dto;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.NotBlank;
+
+import org.hibernate.validator.constraints.Length;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: UserLoginDto
+ * Description: 用户登录
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月25日 下午8:11:06
+ * @Version: 1.0.0
+ */
+@Setter
+@Getter
+public class UserLoginDto implements Serializable {
+
+ private static final long serialVersionUID = 6865880222397602631L;
+
+ @NotBlank(message = "账户不能为空")
+ @Length(max = 20, message = "账户不能超过20位")
+ private String userName;
+ @NotBlank(message = "密码不能为空")
+ @Length(max = 20, message = "密码不能超过20位")
+ private String passWord;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java b/src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java
new file mode 100644
index 0000000..28720df
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java
@@ -0,0 +1,36 @@
+package top.beansprout.health.model.dto;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.NotBlank;
+
+import org.hibernate.validator.constraints.Length;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: UserRegisterDto
+ * Description: 用户注册
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月26日 上午12:54:09
+ * @Version: 1.0.0
+ */
+@Setter
+@Getter
+public class UserRegisterDto implements Serializable {
+
+ private static final long serialVersionUID = -4003225859721099921L;
+
+ @NotBlank(message = "用户名或姓名不能为空")
+ @Length(min = 2, max = 10, message = "用户名或姓名必须在2~10位之间")
+ private String userName;
+ @NotBlank(message = "密码不能为空")
+ @Length(min = 6, max = 10, message = "密码必须在6~10位之间")
+ private String passWord;
+ @NotBlank(message = "确认密码不能为空")
+ @Length(min = 6, max = 10, message = "确认密码必须在6~10位之间")
+ private String confirmPassWord;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java b/src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java
new file mode 100644
index 0000000..1e9fdb4
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java
@@ -0,0 +1,39 @@
+package top.beansprout.health.model.dto;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+import org.hibernate.validator.constraints.Length;
+import org.springframework.web.multipart.MultipartFile;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: UserUpdateInfoDto
+ * Description: 用户信息修改
+ *
+ * @author cyy
+ * @date 2020年4月30日
+ */
+@Setter
+@Getter
+public class UserUpdateInfoDto implements Serializable {
+
+ private static final long serialVersionUID = 7815632813092576575L;
+
+ // 昵称
+ @NotBlank(message = "昵称不能为空")
+ @Length(min = 2, max = 10, message = "昵称只能为2~10个字符")
+ private String nickName;
+ // 邮件
+ @Email(message = "邮箱格式不正确")
+ private String email;
+ // 头像
+ @NotNull(message = "头像不能为空")
+ private MultipartFile headUrl;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java b/src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java
new file mode 100644
index 0000000..e4f3d1f
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java
@@ -0,0 +1,33 @@
+package top.beansprout.health.model.dto;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.NotBlank;
+
+import org.hibernate.validator.constraints.Length;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: UserUpdatePasswordDto
+ * Description: 用户修改密码
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月27日 上午12:32:39
+ * @Version: 1.0.0
+ */
+@Setter
+@Getter
+public class UserUpdatePasswordDto implements Serializable {
+
+ private static final long serialVersionUID = 8674837059264557849L;
+
+ @NotBlank(message = "密码不能为空")
+ @Length(min = 6, max = 10, message = "密码必须在6~10位之间")
+ private String passWord;
+ @NotBlank(message = "确认密码不能为空")
+ @Length(min = 6, max = 10, message = "确认密码必须在6~10位之间")
+ private String confirmPassWord;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/entity/BaseEntity.java b/src/main/java/top/beansprout/health/model/entity/BaseEntity.java
new file mode 100644
index 0000000..e8dde47
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/entity/BaseEntity.java
@@ -0,0 +1,24 @@
+package top.beansprout.health.model.entity;
+
+import java.io.Serializable;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: BaseEntity
+ * Description: 实体类对象的父类-公共字段
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/22 16:50
+ */
+@Setter
+@Getter
+public abstract class BaseEntity implements Serializable {
+
+ private static final long serialVersionUID = 4402927657355642335L;
+
+ private int id;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java b/src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java
new file mode 100644
index 0000000..049e75c
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java
@@ -0,0 +1,30 @@
+package top.beansprout.health.model.entity;
+
+import java.util.Date;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: BaseUpdateEntity
+ * Description: 实体类插入对象的父类-公共字段
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/23 23:52
+ */
+@Setter
+@Getter
+public abstract class BaseInsertEntity extends BaseEntity {
+
+ private static final long serialVersionUID = -7314584145079382702L;
+
+ private Long creator;// 创建人id
+ private Date createTime;// 创建时间
+
+ public void init(long userId) {
+ this.creator = userId;
+ this.createTime = new Date();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java b/src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java
new file mode 100644
index 0000000..6e63e16
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java
@@ -0,0 +1,38 @@
+package top.beansprout.health.model.entity;
+
+import java.util.Date;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: BaseUpdateEntity
+ * Description: 实体类用户对象的父类-公共字段
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/23 23:52
+ */
+@Setter
+@Getter
+public abstract class BaseUpdateEntity extends BaseEntity {
+
+ private static final long serialVersionUID = 1620758104430484420L;
+
+ private int creator;// 创建人id
+ private Date createTime;// 创建时间
+ private int updater;// 更新者id
+ private Date updateTime;// 更新时间
+
+ public void init(int userId) {
+ this.creator = userId;
+ this.createTime = new Date();
+ this.update(userId);
+ }
+
+ public void update(int userId) {
+ this.updater = userId;
+ this.updateTime = new Date();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/entity/TBodyInfo.java b/src/main/java/top/beansprout/health/model/entity/TBodyInfo.java
new file mode 100644
index 0000000..a4a9b9b
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/entity/TBodyInfo.java
@@ -0,0 +1,56 @@
+package top.beansprout.health.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * Title: TBodyInfo
+ * Description: 身体信息表
+ * @author cyy
+ * @date 2020年4月27日
+ */
+@Setter
+@Getter
+public class TBodyInfo extends BaseUpdateEntity {
+
+ private static final long serialVersionUID = -7707228772072459981L;
+
+ // 打开时间
+ private Date createTime;
+
+ // 舒张压
+ private int lowBloodPressure;
+ // 收缩压
+ private int highBloodPressure;
+ // 心率
+ private int heartRate;
+ // 体温
+ private double temperature;
+ // 食欲
+ private int appetite;
+ // 体重
+ private double weight;
+ // 步数
+ private int numberOfStep;
+
+ public String getAppetiteDes(int appetite){
+ switch (appetite){
+ case 1:
+ return "非常差";
+ case 2:
+ return "差";
+ case 3:
+ return "一般";
+ case 4:
+ return "良好";
+ case 5:
+ return "很好";
+ case 6:
+ return "非常好";
+ }
+ return "";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/entity/THealthConfig.java b/src/main/java/top/beansprout/health/model/entity/THealthConfig.java
new file mode 100644
index 0000000..cacfb3c
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/entity/THealthConfig.java
@@ -0,0 +1,35 @@
+package top.beansprout.health.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: THealthConfig
+ * Description: 健康信息配置表
+ * @author cyy
+ * @date 2020年4月27日
+ */
+@Setter
+@Getter
+public class THealthConfig extends BaseUpdateEntity {
+
+ private static final long serialVersionUID = 6928075281678415808L;
+
+ // 最小低血压
+ private int minLowBloodPressure;
+ // 最大低血压
+ private int maxLowBloodPressure;
+ // 最小高血压
+ private int minHighBloodPressure;
+ // 最大高血压
+ private int maxHighBloodPressure;
+ // 最小心率
+ private int minHeartRate;
+ // 最大心率
+ private int maxHeartRate;
+ // 最小体温
+ private double minTemperature;
+ // 最大体温
+ private double maxTemperature;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/entity/TUser.java b/src/main/java/top/beansprout/health/model/entity/TUser.java
new file mode 100644
index 0000000..1105b63
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/entity/TUser.java
@@ -0,0 +1,30 @@
+package top.beansprout.health.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: TUser
+ * Description: 用户表
+ *
+ * @author beansprout
+ * @date 2020年4月22日
+ */
+@Setter
+@Getter
+public class TUser extends BaseUpdateEntity {
+
+ private static final long serialVersionUID = 7849174171033853904L;
+
+ // 用户名
+ private String nickName;
+ // 账户
+ private String userName;
+ // 密码
+ private String password;
+ // 邮箱
+ private String email;
+ // 头像
+ private String headUrl;
+
+}
diff --git a/src/main/java/top/beansprout/health/model/vo/AuthVo.java b/src/main/java/top/beansprout/health/model/vo/AuthVo.java
new file mode 100644
index 0000000..1253a15
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/AuthVo.java
@@ -0,0 +1,26 @@
+package top.beansprout.health.model.vo;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+
+/**
+ * Title: AuthVo
+ * Description: 发送邮箱用户
+ *
+ * @author ateenliu
+ * @date 2022年4月6日
+ */
+
+public class AuthVo extends Authenticator {
+ private String username = "";
+ private String password = "";
+
+ public AuthVo(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+ public PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+
+}
diff --git a/src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java b/src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java
new file mode 100644
index 0000000..e51c4c3
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java
@@ -0,0 +1,65 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: BodyInfoDetailVo
+ * Description: 身体信息详情
+ *
+ * @author cyy
+ * @date 2020年4月28日
+ */
+@Setter
+@Getter
+public class BodyInfoDetailVo implements Serializable {
+
+ private static final long serialVersionUID = -2831120593213278473L;
+
+ // id
+ private int id;
+ // 打卡时间
+ private Date createTime;
+
+ // 舒张压
+ private int lowBloodPressure;
+ // 舒张压状态
+ private int lowBloodPressureStatus;
+ // 舒张压描述
+ private String lowBloodPressureDesc;
+
+ // 收缩压
+ private int highBloodPressure;
+ // 收缩压状态
+ private int highBloodPressureStatus;
+ // 收缩压描述
+ private String highBloodPressureDesc;
+
+ // 血压
+ private String bloodPressureDesc;
+
+ // 心率
+ private int heartRate;
+ // 心率状态
+ private int heartRateStatus;
+ // 心率描述
+ private String heartRateDesc;
+
+ // 体温
+ private double temperature;
+ // 体温状态
+ private int temperatureStatus;
+ // 体温描述
+ private String temperatureDesc;
+
+ // 食欲
+ private int appetite;
+ // 体重
+ private double weight;
+ // 步数
+ private int numberOfStep;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java b/src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java
new file mode 100644
index 0000000..5e895a8
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java
@@ -0,0 +1,43 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Title: BodyInfoStatisticsVo
+ * Description: 健康信息统计
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月28日 下午11:32:05
+ * @Version: 1.0.0
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public class BodyInfoStatisticsVo implements Serializable {
+
+ private static final long serialVersionUID = 8627681890953284164L;
+
+ // 类别名字
+ private List typeNames;
+ // 类别数据
+ private List bodyInfoVos;
+
+ @Setter
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BodyInfoVo {
+ // 类别名
+ private String typeName;
+ // 数据
+ private List datas;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/BusinessException.java b/src/main/java/top/beansprout/health/model/vo/BusinessException.java
new file mode 100644
index 0000000..8bbf34f
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/BusinessException.java
@@ -0,0 +1,34 @@
+package top.beansprout.health.model.vo;
+
+import lombok.Getter;
+
+/**
+ * Title: BusinessException
+ * Description: 业务异常
+ * @author cyy
+ * @date 2020年4月23日
+ */
+@Getter
+public class BusinessException extends RuntimeException {
+
+ private static final long serialVersionUID = -5846808029174051527L;
+
+ private final R r;
+
+ public BusinessException() {
+ r = R.budil().result(false).message("系统异常");
+ }
+
+ public BusinessException(String message) {
+ r = R.budil().result(false).message(message);
+ }
+
+ public BusinessException(String path, String message) {
+ r = R.budil().result(false).message(message).path(path);
+ }
+
+ public BusinessException(String message, Object data) {
+ r = R.budil().result(false).message(message).data(data);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/PageVo.java b/src/main/java/top/beansprout/health/model/vo/PageVo.java
new file mode 100644
index 0000000..253d76f
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/PageVo.java
@@ -0,0 +1,73 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.github.pagehelper.PageInfo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: PageVo
+ * Description: 列表返回
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/22 22:15
+ */
+@Setter
+@Getter
+public class PageVo implements Serializable {
+
+ private static final long serialVersionUID = 7039024431017840683L;
+
+ // 当前页
+ private long page;
+ // 每页展示数量
+ private long pageSize;
+ // 是否是第一页true第一
+ private boolean first = false;
+ // 是否是最后一页true最后
+ private boolean last = false;
+ // 总页数
+ private int totalPages;
+ // 总数据条数
+ private int totalElements;
+ // 当前页数据条数
+ private int numberOfElements;
+ // 数据集
+ private List> content;
+
+ public PageVo(int page, int pageSize, int totalElements, List content) {
+ this.pageSize = pageSize;
+ this.totalElements = totalElements;
+ this.page = page <= 0 ? 1 : page;
+ this.totalPages = totalElements == 0 ? 0
+ : (totalElements % pageSize) == 0 ? totalElements / pageSize : (totalElements / pageSize) + 1;
+ this.first = page == 1 ? true : false;
+ this.last = ((page == this.totalPages) || (content.size() < 1)) ? true : false;
+ this.content = content;
+ this.numberOfElements = this.content.size();
+ }
+
+ public PageVo(PageInfo pageInfo) {
+ this.page = pageInfo.getPageNum();
+ this.pageSize = pageInfo.getPageSize();
+ this.totalElements = (int) pageInfo.getTotal();
+ this.totalPages = pageInfo.getPages();
+ this.first = page == 1 ? true : false;
+ this.content = pageInfo.getList();
+ this.last = ((page == this.totalPages) || (content.size() < 1)) ? true : false;
+ this.numberOfElements = this.content.size();
+ }
+
+ public static PageVo of(int page, int pageSize, int totalElements, List content) {
+ return new PageVo(page, pageSize, totalElements, content);
+ }
+
+ public static PageVo of(PageInfo pageInfo) {
+ return new PageVo(pageInfo);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/R.java b/src/main/java/top/beansprout/health/model/vo/R.java
new file mode 100644
index 0000000..d2d9c7c
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/R.java
@@ -0,0 +1,110 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.web.servlet.ModelAndView;
+
+import lombok.Getter;
+import lombok.Setter;
+import top.beansprout.health.constant.SysConstant;
+
+/**
+ * Title: R
+ * Description: 返回结果
+ * @author cyy
+ * @date 2020年4月23日
+ */
+@Setter
+@Getter
+public class R extends HashMap implements Serializable {
+
+ private static final long serialVersionUID = -7261945562236369523L;
+
+ public static final String FIELD_RESULT = "result";
+ public static final String FIELD_MESSAGE = "message";
+ public static final String FIELD_DATA = "data";
+ public static final String FIELD_PATH = "path";
+
+ public static final String FAILED_FIELD_REQUEST_ID = SysConstant.INIT_FIELD_REQUEST_REQUEST_ID;
+ public static final String FAILED_FIELD_TIME_STAMP = "timestamp";
+ public static final String FAILED_FIELD_PATH = "path";
+ public static final String FAILED_FIELD_ERROR = "error";
+ public static final String FAILED_FIELD_ERRORS = "errors";
+
+ // 结果
+ private boolean result;
+ // 描述
+ private String message;
+ // 数据
+ private Object data;
+ // 页面
+ private String path;
+
+ public static R budil() {
+ return new R();
+ }
+
+ public R result(boolean result) {
+ this.put(FIELD_RESULT, result);
+ this.result = result;
+ return this;
+ }
+
+ public R message(String message) {
+ this.put(FIELD_MESSAGE, message);
+ this.message = message;
+ return this;
+ }
+
+ public R data(Object data) {
+ this.put(FIELD_DATA, data);
+ this.data = data;
+ return this;
+ }
+
+ public R path(String path) {
+ this.put(FIELD_PATH, path);
+ this.path = path;
+ return this;
+ }
+
+ public static ModelAndView ok() {
+ return of("/", null);
+ }
+
+ public static R okAsAjax() {
+ return okAsAjax(null);
+ }
+
+ public static ModelAndView ok(Map data) {
+ return of("/", data);
+ }
+
+ public static R okAsAjax(Object data) {
+ return ofAsAjax(true, "成功", data);
+ }
+
+ public static ModelAndView failed() {
+ return of("/500", null);
+ }
+
+ public static ModelAndView failed(Map data) {
+ return of("/500", data);
+ }
+
+ public static ModelAndView failed(String path, Map data) {
+ return of(path, data);
+ }
+
+ public static ModelAndView of(String path, Map data) {
+ final ModelAndView modelAndView = new ModelAndView(path, data);
+ return modelAndView;
+ }
+
+ public static R ofAsAjax(boolean result, String message, Object data) {
+ return budil().result(result).message(message).data(data);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/RequestVo.java b/src/main/java/top/beansprout/health/model/vo/RequestVo.java
new file mode 100644
index 0000000..e2b6885
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/RequestVo.java
@@ -0,0 +1,31 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Title: RequestVo
+ * Description: 请求数据
+ * @author cyy
+ * @date 2020年4月23日
+ */
+@Setter
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RequestVo implements Serializable {
+
+ private static final long serialVersionUID = -2460516900904070899L;
+
+ // 基本项目地址
+ private String basePath;
+ // 用户信息
+ private UserLoginVo user;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/UserInfoVo.java b/src/main/java/top/beansprout/health/model/vo/UserInfoVo.java
new file mode 100644
index 0000000..45b3137
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/UserInfoVo.java
@@ -0,0 +1,36 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Title: UserInfoVo
+ * Description: 用户信息
+ *
+ * @author cyy
+ * @date 2020年4月30日
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserInfoVo implements Serializable {
+
+ private static final long serialVersionUID = 8389631895704876733L;
+
+ // 用户id
+ private int id;
+ // 用户名
+ private String nickName;
+ // 账户
+ private String userName;
+ // 邮箱
+ private String email;
+ // 头像
+ private String headUrl;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/model/vo/UserLoginVo.java b/src/main/java/top/beansprout/health/model/vo/UserLoginVo.java
new file mode 100644
index 0000000..40496d5
--- /dev/null
+++ b/src/main/java/top/beansprout/health/model/vo/UserLoginVo.java
@@ -0,0 +1,33 @@
+package top.beansprout.health.model.vo;
+
+import java.io.Serializable;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Title: UserLoginVo
+ * Description:
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月25日 下午8:49:19
+ * @Version: 1.0.0
+ */
+@Setter
+@Getter
+public class UserLoginVo implements Serializable {
+
+ private static final long serialVersionUID = -7027379021523096696L;
+
+ // 用户id
+ private int id;
+ // 用户名
+ private String nickName;
+ // 账户
+ private String userName;
+ // 邮箱
+ private String email;
+ // 头像
+ private String headUrl;
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/service/HealthService.java b/src/main/java/top/beansprout/health/service/HealthService.java
new file mode 100644
index 0000000..595b26c
--- /dev/null
+++ b/src/main/java/top/beansprout/health/service/HealthService.java
@@ -0,0 +1,40 @@
+package top.beansprout.health.service;
+
+import java.util.Date;
+import java.util.List;
+
+import top.beansprout.health.model.dto.BodyInfoQuery;
+import top.beansprout.health.model.dto.BodyInfoSaveDto;
+import top.beansprout.health.model.entity.TBodyInfo;
+import top.beansprout.health.model.vo.BodyInfoDetailVo;
+import top.beansprout.health.model.vo.BodyInfoStatisticsVo;
+import top.beansprout.health.model.vo.PageVo;
+
+/**
+ * Title: HealthService
+ * Description: 健康业务接口
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+public interface HealthService {
+
+ /** 保存身体信息 **/
+ void saveBodyInfo(int userId, BodyInfoSaveDto bodyInfoSaveDto);
+
+ /** 获取身体信息列表 **/
+ PageVo bodyInfoList(int userId, BodyInfoQuery bodyInfoQuery);
+
+ /** 删除身体信息 **/
+ void deleteBodyInfo(int userId, int id);
+
+ /** 获取身体信息 **/
+ BodyInfoDetailVo getBodyInfo(int userId, int id);
+
+ /** 获取身体信息统计 **/
+ BodyInfoStatisticsVo getBodyStatistics(int userId, Date date);
+
+ /** 获取惊奇身体信息统计 **/
+ List recentBodyInfoList(int userId);
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/service/UserService.java b/src/main/java/top/beansprout/health/service/UserService.java
new file mode 100644
index 0000000..ff2610e
--- /dev/null
+++ b/src/main/java/top/beansprout/health/service/UserService.java
@@ -0,0 +1,37 @@
+package top.beansprout.health.service;
+
+import javax.servlet.http.HttpServletRequest;
+
+import top.beansprout.health.model.dto.UserLoginDto;
+import top.beansprout.health.model.dto.UserRegisterDto;
+import top.beansprout.health.model.dto.UserUpdateInfoDto;
+import top.beansprout.health.model.dto.UserUpdatePasswordDto;
+import top.beansprout.health.model.vo.UserInfoVo;
+import top.beansprout.health.model.vo.UserLoginVo;
+
+/**
+ * Title: UserService
+ * Description: 用户业务接口
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月25日 下午8:01:14
+ * @Version: 1.0.0
+ */
+public interface UserService {
+
+ /** 登录逻辑 **/
+ UserLoginVo login(UserLoginDto userLoginDto);
+
+ /** 注册逻辑 **/
+ void register(UserRegisterDto userRegisterDto);
+
+ /** 登出 **/
+ void logout(HttpServletRequest request);
+
+ /** 修改密码 **/
+ void updatePassword(HttpServletRequest request, int userId, UserUpdatePasswordDto updatePasswordDto);
+
+ /** 修改用户信息 **/
+ UserInfoVo updateUserInfo(int userId, UserUpdateInfoDto userUpdateInfoDto);
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java b/src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java
new file mode 100644
index 0000000..752b636
--- /dev/null
+++ b/src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java
@@ -0,0 +1,299 @@
+package top.beansprout.health.service.impl;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+
+import top.beansprout.health.mapper.TBodyInfoMapper;
+import top.beansprout.health.mapper.THealthConfigMapper;
+import top.beansprout.health.model.dto.BodyInfoQuery;
+import top.beansprout.health.model.dto.BodyInfoSaveDto;
+import top.beansprout.health.model.entity.TBodyInfo;
+import top.beansprout.health.model.vo.BodyInfoDetailVo;
+import top.beansprout.health.model.vo.BodyInfoStatisticsVo;
+import top.beansprout.health.model.vo.BodyInfoStatisticsVo.BodyInfoVo;
+import top.beansprout.health.model.vo.BusinessException;
+import top.beansprout.health.model.vo.PageVo;
+import top.beansprout.health.service.HealthService;
+import top.beansprout.health.util.DateUtils;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ * Title: HealthServiceImpl
+ * Description: 健康业务逻辑操作
+ *
+ * @author cyy
+ * @date 2020年4月27日
+ */
+@Service
+public class HealthServiceImpl implements HealthService {
+
+ @Autowired
+ private THealthConfigMapper healthConfigMapper;
+ @Autowired
+ private TBodyInfoMapper bodyInfoMapper;
+
+ @Override
+ @Transactional
+ public void saveBodyInfo(int userId, BodyInfoSaveDto bodyInfoSaveDto) {
+ // 判断今天是否已经录入过
+ final int countByOneAsToDay = bodyInfoMapper.countByOneAsToDay(userId);
+ if (countByOneAsToDay > 0)
+ throw new BusinessException("今天已经打过卡了");
+
+ final TBodyInfo tBodyInfo = new TBodyInfo();
+ PublicUtils.copyBean(bodyInfoSaveDto, tBodyInfo);
+ tBodyInfo.init(userId);
+ bodyInfoMapper.insertByOne(tBodyInfo);
+ }
+
+ @Override
+ public List recentBodyInfoList(int userId){
+ List tBodyInfoList = bodyInfoMapper.selectRecentByUserId(userId);
+ return tBodyInfoList;
+ }
+
+ @Override
+ public PageVo bodyInfoList(int userId, BodyInfoQuery bodyInfoQuery) {
+ if ((bodyInfoQuery.getMinDate() != null) && (bodyInfoQuery.getMaxDate() != null)) {
+ if (bodyInfoQuery.getMinDate().after(bodyInfoQuery.getMaxDate()))
+ throw new BusinessException("最小时间不能大于最大时间");
+ }
+ PageHelper.startPage(bodyInfoQuery.getPage(), bodyInfoQuery.getPageSize());
+ final PageInfo pageInfo = new PageInfo<>(
+ bodyInfoMapper.selectByUserId(userId, bodyInfoQuery.getMinDate(), bodyInfoQuery.getMaxDate()));
+ return PageVo.of(pageInfo);
+ }
+
+ @Override
+ @Transactional
+ public void deleteBodyInfo(int userId, int id) {
+ bodyInfoMapper.deleteByOne(id, userId);
+ }
+
+ @Override
+ public BodyInfoDetailVo getBodyInfo(int userId, int id) {
+ final TBodyInfo bodyInfo = bodyInfoMapper.selectByUserOne(id, userId);
+ if (bodyInfo == null)
+ throw new BusinessException("该身体信息不存在");
+
+ BodyInfoDetailVo bodyInfoDetailVo = new BodyInfoDetailVo();
+ PublicUtils.copyBean(bodyInfo, bodyInfoDetailVo);
+ bodyInfoDetailVo = bodyInfoCompare(bodyInfoDetailVo);
+ return bodyInfoDetailVo;
+ }
+
+ /** 健康信息比较 **/
+ private BodyInfoDetailVo bodyInfoCompare(BodyInfoDetailVo bodyInfoDetailVo) {
+ // TODO
+ // 血压
+ bodyInfoDetailVo = bloodPressureCompare(bodyInfoDetailVo);
+ // 心率
+ final int heartRate = bodyInfoDetailVo.getHeartRate();
+ bodyInfoDetailVo.setHeartRateStatus(heartRateStatus(heartRate));
+ bodyInfoDetailVo.setHeartRateDesc(statusDesc(bodyInfoDetailVo.getHeartRateStatus()));
+ // 体温
+ final double temperature = bodyInfoDetailVo.getTemperature();
+ bodyInfoDetailVo.setTemperatureStatus(temperatureStatus(temperature));
+ bodyInfoDetailVo.setTemperatureDesc(statusDesc(bodyInfoDetailVo.getTemperatureStatus()));
+ return bodyInfoDetailVo;
+ }
+
+ /** 血压判断 **/
+ private BodyInfoDetailVo bloodPressureCompare(BodyInfoDetailVo bodyInfoDetailVo) {
+ // 收缩压
+ // 正常范围收缩压90~139mmHg
+ final int minHighBloodPressure = 90;
+ final int maxHighBloodPressure = 139;
+ // 舒张压
+ // 舒张压60~89mmHg
+ final int minLowBloodPressure = 60;
+ final int maxLowBloodPressure = 89;
+ // 高血压:成人收缩压≥140mmHg和(或)舒张压≥90mmHg
+ // 低血压:血压低于90/60mmHg
+
+ if (bodyInfoDetailVo.getLowBloodPressure() > maxLowBloodPressure) {
+ bodyInfoDetailVo.setLowBloodPressureStatus(1);
+ bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.getLowBloodPressureStatus()));
+ } else if (bodyInfoDetailVo.getLowBloodPressure() < minLowBloodPressure) {
+ bodyInfoDetailVo.setLowBloodPressureStatus(-1);
+ bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.getLowBloodPressureStatus()));
+ } else {
+ bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.getLowBloodPressureStatus()));
+ }
+
+ if (bodyInfoDetailVo.getHighBloodPressure() > maxHighBloodPressure) {
+ bodyInfoDetailVo.setHighBloodPressureStatus(1);
+ bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.getHighBloodPressureStatus()));
+ } else if (bodyInfoDetailVo.getHighBloodPressure() < minHighBloodPressure) {
+ bodyInfoDetailVo.setHighBloodPressureStatus(-1);
+ bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.getHighBloodPressureStatus()));
+ } else {
+ bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.getHighBloodPressureStatus()));
+ }
+
+ if ((bodyInfoDetailVo.getLowBloodPressureStatus() == 1)
+ && (bodyInfoDetailVo.getHighBloodPressureStatus() == 1)) {
+ bodyInfoDetailVo.setBloodPressureDesc("该用户为高血压");
+ } else if ((bodyInfoDetailVo.getLowBloodPressureStatus() == -1)
+ && (bodyInfoDetailVo.getHighBloodPressureStatus() == -1)) {
+ bodyInfoDetailVo.setBloodPressureDesc("该用户为低血压");
+ } else if ((bodyInfoDetailVo.getLowBloodPressureStatus() == 0)
+ && (bodyInfoDetailVo.getHighBloodPressureStatus() == 0)) {
+ bodyInfoDetailVo.setBloodPressureDesc("该用户血压正常");
+ }
+
+ /*
+ * if ((bodyInfoDetailVo.getLowBloodPressure() > maxLowBloodPressure) ||
+ * (bodyInfoDetailVo.getHighBloodPressure() > maxHighBloodPressure)) {
+ * bodyInfoDetailVo.setLowBloodPressureStatus(1);
+ * bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.
+ * getLowBloodPressureStatus()));
+ * bodyInfoDetailVo.setHighBloodPressureStatus(1);
+ * bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.
+ * getHighBloodPressureStatus())); } else if
+ * ((bodyInfoDetailVo.getLowBloodPressure() < minLowBloodPressure) ||
+ * (bodyInfoDetailVo.getHighBloodPressure() < minHighBloodPressure)) {
+ * bodyInfoDetailVo.setLowBloodPressureStatus(-1);
+ * bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.
+ * getLowBloodPressureStatus()));
+ * bodyInfoDetailVo.setHighBloodPressureStatus(-1);
+ * bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.
+ * getHighBloodPressureStatus())); } else {
+ * bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.
+ * getLowBloodPressureStatus()));
+ * bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.
+ * getHighBloodPressureStatus())); }
+ */
+ return bodyInfoDetailVo;
+ }
+
+ /** 心率判断 **/
+ private int heartRateStatus(int heartRate) {
+ final int min = 60;
+ final int max = 80;
+ final int max1 = 100;
+ final int max2 = 120;
+ if (heartRate < min)
+ return -1;
+ else if ((heartRate >= min) && (heartRate <= max))
+ return 0;
+ else if ((heartRate > max) && (heartRate < max1))
+ return 1;
+ else if ((heartRate > max1) && (heartRate < max2))
+ return 2;
+ else
+ return 4;
+ }
+
+ /** 体温判断 **/
+ private int temperatureStatus(double temperature) {
+ final Double min = 36.1;
+ final Double max = 37.3;
+ final Double max1 = 38.1;
+ final Double max2 = 39.1;
+ final Double max3 = 41.1;
+ if (temperature < min)
+ return -1;
+ else if ((temperature >= min) && (temperature <= max))
+ return 0;
+ else if ((temperature > max) && (temperature < max1))
+ return 1;
+ else if ((temperature > max1) && (temperature < max2))
+ return 2;
+ else if ((temperature > max2) && (temperature < max3))
+ return 3;
+ else
+ return 4;
+ }
+
+ /** 状态描述语 **/
+ private String statusDesc(int status) {
+ switch (status) {
+ case -1:
+ return "低";
+ case 1:
+ return "高";
+ case 2:
+ return "很高";
+ case 3:
+ return "严重";
+ case 4:
+ return "非常严重";
+ default:
+ return "正常";
+ }
+ }
+
+ @Override
+ public BodyInfoStatisticsVo getBodyStatistics(int userId, Date date) {
+ LocalDateTime now = null;
+ if (date == null) {
+ now = LocalDateTime.now();
+ } else {
+ now = DateUtils.convertDateToLDT(date);
+ }
+ final LocalDateTime startWeekDateTime = DateUtils.getDayStart(DateUtils.getWeekBegin(now));
+ final Date startWeek = DateUtils.convertLDTToDate(startWeekDateTime);
+ final Date endWeek = DateUtils.convertLDTToDate(DateUtils.getDayEnd(DateUtils.getWeekEnd(now)));
+ final List bodyInfos = bodyInfoMapper.selectByUserId(userId, startWeek, endWeek);
+
+ final List bodyInfoVos = fillBodyInfoVo(startWeekDateTime, bodyInfos);
+
+ return new BodyInfoStatisticsVo(bodyInfoVos.stream().map(BodyInfoVo::getTypeName).collect(Collectors.toList()),
+ bodyInfoVos);
+ }
+
+ /** 填充数据 **/
+ private List fillBodyInfoVo(LocalDateTime startWeekDateTime, List bodyInfos) {
+ final List bodyInfoVos = new ArrayList<>();
+ final List lowBloodPressures = new ArrayList<>();
+ final List highBloodPressures = new ArrayList<>();
+ final List heartRates = new ArrayList<>();
+ final List temperatures = new ArrayList<>();
+ final List numberOfSetps = new ArrayList<>();
+ for (int i = 0; i < 7; i++) {
+ final Long targetTime = DateUtils.getMilliByTime(DateUtils.plus(startWeekDateTime, i, ChronoUnit.DAYS));
+ boolean isSign = false;
+ for (final TBodyInfo tBodyInfo : bodyInfos) {
+ if (targetTime.equals(DateUtils.getMilliByTime(
+ DateUtils.getDayStart(DateUtils.convertDateToLDT(tBodyInfo.getCreateTime()))))) {
+ // 已打卡
+ isSign = true;
+ lowBloodPressures.add(tBodyInfo.getLowBloodPressure());
+ highBloodPressures.add(tBodyInfo.getHighBloodPressure());
+ heartRates.add(tBodyInfo.getHeartRate());
+ temperatures.add(tBodyInfo.getTemperature());
+ numberOfSetps.add(tBodyInfo.getNumberOfStep());
+ break;
+ }
+ }
+ if (!isSign) {
+ // 未打卡
+ lowBloodPressures.add(0);
+ highBloodPressures.add(0);
+ heartRates.add(0);
+ temperatures.add(0);
+ numberOfSetps.add(0);
+ }
+ }
+ bodyInfoVos.add(new BodyInfoVo("舒张压", lowBloodPressures));
+ bodyInfoVos.add(new BodyInfoVo("收缩压", highBloodPressures));
+ bodyInfoVos.add(new BodyInfoVo("心率", heartRates));
+ bodyInfoVos.add(new BodyInfoVo("体温", temperatures));
+ bodyInfoVos.add(new BodyInfoVo("步数", numberOfSetps));
+
+ return bodyInfoVos;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java b/src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..5b97b5a
--- /dev/null
+++ b/src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java
@@ -0,0 +1,151 @@
+package top.beansprout.health.service.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import top.beansprout.health.constant.SysConstant;
+import top.beansprout.health.mapper.TUserMapper;
+import top.beansprout.health.model.dto.UserLoginDto;
+import top.beansprout.health.model.dto.UserRegisterDto;
+import top.beansprout.health.model.dto.UserUpdateInfoDto;
+import top.beansprout.health.model.dto.UserUpdatePasswordDto;
+import top.beansprout.health.model.entity.TUser;
+import top.beansprout.health.model.vo.BusinessException;
+import top.beansprout.health.model.vo.UserInfoVo;
+import top.beansprout.health.model.vo.UserLoginVo;
+import top.beansprout.health.service.UserService;
+import top.beansprout.health.util.PublicUtils;
+
+/**
+ * Title: UserServiceImpl
+ * Description: 用户业务逻辑操作
+ *
+ * @Auther: cyy
+ * @Date: 2020年4月25日 下午8:02:42
+ * @Version: 1.0.0
+ */
+@Service
+public class UserServiceImpl implements UserService {
+
+ @Autowired
+ private HttpServletRequest request;
+
+ @Autowired
+ private TUserMapper userMapper;
+
+ @Override
+ public UserLoginVo login(UserLoginDto userLoginDto) {
+ final TUser user = userMapper.selectByUserName(userLoginDto.getUserName());
+ if (PublicUtils.isBlank(user))
+ throw new BusinessException("用户不存在");
+ if (!user.getPassword().equalsIgnoreCase(userLoginDto.getPassWord()))
+ throw new BusinessException("密码不正确");
+
+ final UserLoginVo userLoginVo = new UserLoginVo();
+ PublicUtils.copyBean(user, userLoginVo);
+ request.getSession().setAttribute(SysConstant.INIT_FIELD_USER_VO, userLoginVo);
+ return userLoginVo;
+ }
+
+ @Override
+ @Transactional
+ public void register(UserRegisterDto userRegisterDto) {
+ if (!userRegisterDto.getPassWord().equalsIgnoreCase(userRegisterDto.getConfirmPassWord()))
+ throw new BusinessException("两次密码不一致");
+ final TUser user = userMapper.selectByUserName(userRegisterDto.getUserName());
+ if (PublicUtils.isNotBlank(user))
+ throw new BusinessException("该账户已经注册");
+ final TUser tUser = new TUser();
+ tUser.setUserName(userRegisterDto.getUserName());
+ tUser.setNickName(tUser.getUserName());
+ tUser.setPassword(userRegisterDto.getPassWord());
+ tUser.setHeadUrl("static/imgs/default.png"); //默认头像
+ userMapper.insertByOne(tUser);
+ }
+
+ @Override
+ public void logout(HttpServletRequest request) {
+ // 清除session
+ final Enumeration em = request.getSession().getAttributeNames();
+ while (em.hasMoreElements()) {
+ request.getSession().removeAttribute(em.nextElement().toString());
+ }
+ request.getSession().invalidate();
+ // 请求初始化信息
+ request.removeAttribute(SysConstant.INIT_FIELD_REQUEST_VO);
+ }
+
+ @Override
+ public void updatePassword(HttpServletRequest request, int userId, UserUpdatePasswordDto updatePasswordDto) {
+ if (!updatePasswordDto.getPassWord().equalsIgnoreCase(updatePasswordDto.getConfirmPassWord()))
+ throw new BusinessException("两次密码不一致");
+ final TUser user = userMapper.selectById(userId);
+ if (PublicUtils.isBlank(user))
+ throw new BusinessException("该账户不存在");
+ user.setPassword(updatePasswordDto.getPassWord());
+ userMapper.updateByOne(user);
+ // 清空session数据
+ logout(request);
+ }
+
+ @Override
+ public UserInfoVo updateUserInfo(int userId, UserUpdateInfoDto userUpdateInfoDto) {
+ // 原始文件名
+ String sourceName = userUpdateInfoDto.getHeadUrl().getOriginalFilename();
+ final String fileType = sourceName.substring(sourceName.lastIndexOf("."));
+ if (!".jpg".equals(fileType.toLowerCase()) && !".png".equals(fileType.toLowerCase()))
+ throw new BusinessException("只能上传jpg、png格式的图片");
+
+ // 获取文件上传的路径,在webapp下的static/upload/中
+ final String base = "E:\\Workspaces\\HM_upload\\";
+ final File fileDir = new File(base);
+ if (!fileDir.exists()) {
+ fileDir.mkdirs();
+ }
+
+ // 讲文件上传到upload目录
+ sourceName = PublicUtils.join(PublicUtils.randomString(10), fileType);
+ final File upload = new File(PublicUtils.join(base, sourceName));
+ //System.out.println(upload.toString());
+ try {
+ userUpdateInfoDto.getHeadUrl().transferTo(upload);
+ } catch (final IOException e) {
+ throw new BusinessException("上传错误");
+ }
+
+ final String relativePath = PublicUtils.join("/HM_upload/", sourceName);
+
+ final TUser user = userMapper.selectById(userId);
+ if (PublicUtils.isBlank(user))
+ throw new BusinessException("该账户不存在");
+
+ user.setNickName(userUpdateInfoDto.getNickName());
+ user.setEmail(userUpdateInfoDto.getEmail());
+ user.setHeadUrl(relativePath);
+ userMapper.updateByUserInfoOne(user);
+
+ final UserInfoVo userInfo = new UserInfoVo();
+ PublicUtils.copyBean(user, userInfo);
+ refreshSessionUserInfo(userInfo);
+
+ return userInfo;
+ }
+
+ /** 刷新session中的用户信息 **/
+ private void refreshSessionUserInfo(UserInfoVo userInfo) {
+ final HttpSession session = request.getSession();
+ final UserLoginVo userLoginVo = (UserLoginVo) session.getAttribute(SysConstant.INIT_FIELD_USER_VO);
+ PublicUtils.copyBean(userInfo, userLoginVo);
+
+ session.setAttribute(SysConstant.INIT_FIELD_USER_VO, userLoginVo);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/util/CollectionUtils.java b/src/main/java/top/beansprout/health/util/CollectionUtils.java
new file mode 100644
index 0000000..aaa591e
--- /dev/null
+++ b/src/main/java/top/beansprout/health/util/CollectionUtils.java
@@ -0,0 +1,69 @@
+package top.beansprout.health.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Title: CollectionUtils
+ * Description: 集合工具类
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/19 22:37
+ */
+public class CollectionUtils {
+
+ /** 判断集合是否为null或者size=0 **/
+ public static boolean isEmpty(Collection coll) {
+ return CollectionUtils.isEmpty(coll);
+ }
+
+ public static boolean isNotEmpty(Collection coll) {
+ return !isEmpty(coll);
+ }
+
+ /**
+ * 将Iterable映射成列表
+ *
+ * @param src
+ * @param mapper
+ * @return
+ */
+ public static List map(final Iterable src, final Function super T, ? extends R> mapper) {
+ final List r = new ArrayList<>();
+ for (final T t : src) {
+ r.add(mapper.apply(t));
+ }
+ return r;
+ }
+
+ /**
+ * 数组转集合
+ * @param key
+ * @return
+ */
+ public static List arrayToList(T... key) {
+ return Arrays.asList(key);
+ }
+
+ /**
+ * element 转换成指定集合
+ *
+ * @param sourceCollection 源集合
+ * @param collectionFactory 目标集合
+ * @return DEST
+ */
+ public static , DEST extends Collection> DEST
+ transformElements(SOURCETYPE sourceCollection, Supplier collectionFactory) {
+ final DEST dest = collectionFactory.get();
+ sourceCollection.forEach(t -> {
+ dest.add(t);
+ });
+ return dest;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/util/DateUtils.java b/src/main/java/top/beansprout/health/util/DateUtils.java
new file mode 100644
index 0000000..e9afc26
--- /dev/null
+++ b/src/main/java/top/beansprout/health/util/DateUtils.java
@@ -0,0 +1,286 @@
+package top.beansprout.health.util;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.time.temporal.TemporalUnit;
+import java.util.Date;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Title: DateUtils
+ * Description: 时间工具类
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/19 23:14
+ */
+@Slf4j
+public class DateUtils {
+
+ /** HH:mm **/
+ public static final String HHmm = "HH:mm";
+ /** HH:mm:ss **/
+ public static final String HHmmss = "HH:mm:ss";
+ /** HH:mm:ss a **/
+ public static final String HHmmssa = "HH:mm:ss a";
+
+ /** yyyyMM **/
+ public static final String yyyyMM = "yyyyMM";
+ /** yyyyMMdd **/
+ public static final String yyyyMMdd = "yyyyMMdd";
+ /** yyyyMMddHH **/
+ public static final String yyyyMMddHH = "yyyyMMddHH";
+ /** yyyyMMddHHmm **/
+ public static final String yyyyMMddHHmm = "yyyyMMddHHmm";
+ /** yyyyMMddHHmmss **/
+ public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss";
+ /** yyyyMMddHHmmss.SSS **/
+ public static final String yyyyMMddHHmmssSSS = "yyyyMMddHHmmss.SSS";
+ /** yyyyMMddTHHmmss.SSSZ **/
+ public static final String yyyyMMddTHHmmssSSSZ = "yyyyMMdd'T'HHmmss.SSS'Z'";
+
+ /** MM-dd HH:mm **/
+ public static final String MMddHHmm_LINE = "MM-dd HH:mm";
+ /** yyyy-MM **/
+ public static final String yyyyMM_LINE = "yyyy-MM";
+ /** yyyy-MM-dd **/
+ public static final String yyyyMMdd_LINE = "yyyy-MM-dd";
+ /** yyyy-MM-dd HH **/
+ public static final String yyyyMMddHH_LINE = "yyyy-MM-dd HH";
+ /** yyyy-MM-dd HH:mm **/
+ public static final String yyyyMMddHHmm_LINE = "yyyy-MM-dd HH:mm";
+ /** yyyy-MM-dd HH:mm:ss **/
+ public static final String yyyyMMddHHmmss_LINE = "yyyy-MM-dd HH:mm:ss";
+ /** yyyy-MM-ddTHH:mm:ss **/
+ public static final String yyyyMMddTHHmmss_LINE = "yyyy-MM-dd'T'HH:mm:ss";
+ /** yyyy-MM-dd HH:mm:ss.SSS **/
+ public static final String yyyyMMddHHmmssSSS_LINE = "yyyy-MM-dd HH:mm:ss.SSS";
+ /** yyyy-MM-ddTHH:mm:ss.SSSZ **/
+ public static final String yyyyMMddTHHmmssSSSZ_LINE = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
+ /** yyyy.MM **/
+ public static final String yyyyMM_POINT = "yyyy.MM";
+ /** yyyy.MM.dd **/
+ public static final String yyyyMMdd_POINT = "yyyy.MM.dd";
+ /** yyyy.MM.dd HH **/
+ public static final String yyyyMMddHH_POINT = "yyyy.MM.dd HH";
+ /** yyyy.MM.dd HH:mm **/
+ public static final String yyyyMMddHHmm_POINT = "yyyy.MM.dd HH:mm";
+ /** yyyy.MM.dd HH:mm:ss **/
+ public static final String yyyyMMddHHmmss_POINT = "yyyy.MM.dd HH:mm:ss";
+ /** yyyy.MM.dd HH:mm:ss.SSS **/
+ public static final String yyyyMMddHHmmssSSS_POINT = "yyyy.MM.dd HH:mm:ss.SSS";
+ /** yyyy.MM.ddTHH:mm:ss.SSSZ **/
+ public static final String yyyyMMddTHHmmssSSSZ_POINT = "yyyy.MM.dd'T'HH:mm:ss.SSS'Z'";
+
+ /** MM/dd/yyyy, HH:mm:ss a **/
+ public static final String MMddyyyyHHmmssa_SLASH = "MM/dd/yyyy, HH:mm:ss a";
+ /** MM月MM **/
+ public static final String yyyyMM_SLASH = "yyyy/MM";
+ /** yyyy/MM/dd **/
+ public static final String yyyyMMdd_SLASH = "yyyy/MM/dd";
+ /** yyyy/MM/dd HH **/
+ public static final String yyyyMMddHH_SLASH = "yyyy/MM/dd HH";
+ /** yyyy/MM/dd HH:mm **/
+ public static final String yyyyMMddHHmm_SLASH = "yyyy/MM/dd HH:mm";
+ /** yyyy/MM/dd HH:mm:ss **/
+ public static final String yyyyMMddHHmmss_SLASH = "yyyy/MM/dd HH:mm:ss";
+ /** yyyy/MM/dd HH:mm:ss.SSS **/
+ public static final String yyyyMMddHHmmssSSS_SLASH = "yyyy/MM/dd HH:mm:ss.SSS";
+ /** yyyy/MM/ddTHH:mm:ss.SSSZ **/
+ public static final String yyyyMMddTHHmmssSSSZ_SLASH = "yyyy/MM/dd'T'HH:mm:ss.SSS'Z'";
+
+ /** yyyy年MM月 **/
+ public static final String yyyyMM_UNIT = "yyyy年MM月";
+ /** yyyy年MM月dd日 **/
+ public static final String yyyyMMdd_UNIT = "yyyy年MM月dd日";
+ /** yyyy年MM月dd日 HH **/
+ public static final String yyyyMMddHH_UNIT = "yyyy年MM月dd日 HH";
+ /** yyyy年MM月dd日 HH:mm **/
+ public static final String yyyyMMddHHmm_UNIT = "yyyy年MM月dd日 HH:mm";
+ /** yyyy年MM月dd日 HH:mm:ss **/
+ public static final String yyyyMMddHHmmss_UNIT = "yyyy年MM月dd日 HH:mm:ss";
+ /** yyyy年MM月dd日 HH:mm:ss.SSS **/
+ public static final String yyyyMMddHHmmssSSS_UNIT = "yyyy年MM月dd日 HH:mm:ss.SSS";
+ /** yyyy年MM月dd日THH:mm:ss.SSSZ **/
+ public static final String yyyyMMddTHHmmssSSSZ_UNIT = "yyyy年MM月dd日'T'HH:mm:ss.SSS'Z'";
+
+ public static final long SECONDS_TO_MINUTE = 60;
+ public static final long MINUTES_TO_HOUR = 60;
+ public static final long HOUR_TO_DAY = 24;
+ public static final long DAYS_TO_WEEK = 7;
+ public static final long DAYS_TO_MONTH = 31;
+ public static final long DAYS_TO_YEAR = 365;
+ public static final long MONTHES_TO_YEAR = 12;
+
+ public static final long SECONDS_TO_HOUR = SECONDS_TO_MINUTE * MINUTES_TO_HOUR;
+ public static final long SECONDS_TO_DAY = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY;
+ public static final long SECONDS_TO_WEEK = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY * DAYS_TO_WEEK;
+ public static final long SECONDS_TO_MONTH = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY * DAYS_TO_MONTH;// 暂定义位每月31天
+ public static final long SECONDS_TO_YEAR = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY * DAYS_TO_YEAR;
+
+ private static final String[] weekDaysName = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
+ private static final String[] weekDaysName_en = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
+ "Saturday" };
+ private static final String[] monthName = { "一月份", "二月份", "三月份", "四月份", "五月份", "六月份", "七月份", "八月份", "九月份", "十月份",
+ "十一月份", "十二月份" };
+
+ /**
+ * 格式化时间为LocalDateTime
+ * @param str
+ * @param pattern
+ * @return
+ */
+ public static LocalDateTime parse(String str, String pattern) {
+ final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+ return LocalDateTime.parse(str, formatter);
+ }
+
+ /**
+ * 格式化时间为Date
+ * @param str
+ * @param pattern
+ * @return
+ */
+ public static Date parseDate(String str, String pattern) {
+ return convertLDTToDate(parse(str, pattern));
+ }
+
+ /**
+ * Date转换为LocalDateTime
+ * @param date
+ * @return
+ */
+ public static LocalDateTime convertDateToLDT(Date date) {
+ return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+ }
+
+ /**
+ * LocalDateTime转换为Date
+ *
+ * @param time
+ * @return
+ */
+ public static Date convertLDTToDate(LocalDateTime time) {
+ return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
+ }
+
+ /**
+ * 获取指定日期的毫秒
+ * @param time
+ * @return
+ */
+ public static Long getMilliByTime(LocalDateTime time) {
+ return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ }
+
+ /**
+ * 获取指定日期的秒
+ * @param time
+ * @return
+ */
+ public static Long getSecondsByTime(LocalDateTime time) {
+ return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
+ }
+
+ /**
+ * 获取指定时间的指定格式
+ * @param time
+ * @param pattern
+ * @return
+ */
+ public static String formatTime(LocalDateTime time, String pattern) {
+ return time.format(DateTimeFormatter.ofPattern(pattern));
+ }
+
+ /**
+ * 获取当前时间的指定格式
+ * @param pattern
+ * @return
+ */
+ public static String formatNow(String pattern) {
+ return formatTime(LocalDateTime.now(), pattern);
+ }
+
+ /**
+ * 日期加上一个数,根据field不同加不同值,field为ChronoUnit.*
+ * @param time
+ * @param number
+ * @param field
+ * @return
+ */
+ public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {
+ return time.plus(number, field);
+ }
+
+ /**
+ * 日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.*
+ * @param time
+ * @param number
+ * @param field
+ * @return
+ */
+ public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field) {
+ return time.minus(number, field);
+ }
+
+ /**
+ * 获取两个日期的差 field参数为ChronoUnit.*
+ * @param startTime
+ * @param endTime
+ * @param field 单位(年月日时分秒)
+ * @return
+ */
+ public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) {
+ final Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime));
+ if (field == ChronoUnit.YEARS)
+ return period.getYears();
+ if (field == ChronoUnit.MONTHS)
+ return period.getYears() * 12 + period.getMonths();
+ return field.between(startTime, endTime);
+ }
+
+ /**
+ * 获取一天的开始时间,2017,7,22 00:00
+ * @param time
+ * @return
+ */
+ public static LocalDateTime getDayStart(LocalDateTime time) {
+ return time.withHour(0).withMinute(0).withSecond(0).withNano(0);
+ }
+
+ /**
+ * 获取一天的结束时间,2017,7,22 23:59:59.999999999
+ * @param time
+ * @return
+ */
+ public static LocalDateTime getDayEnd(LocalDateTime time) {
+ return time.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
+ }
+
+ /**
+ * 获得指定时间的周一日期
+ * @param time
+ * @return
+ */
+ public static LocalDateTime getWeekBegin(LocalDateTime time) {
+ return time.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
+ }
+
+ /**
+ * 获得指定时间的周日日期
+ * @param time
+ * @return
+ */
+ public static LocalDateTime getWeekEnd(LocalDateTime time) {
+ return time.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/util/JsonUtils.java b/src/main/java/top/beansprout/health/util/JsonUtils.java
new file mode 100644
index 0000000..53d1b49
--- /dev/null
+++ b/src/main/java/top/beansprout/health/util/JsonUtils.java
@@ -0,0 +1,57 @@
+package top.beansprout.health.util;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Title: JsonUtils
+ * Description: Json工具类,依赖 jackson
+ * @author beansprout
+ * @date 2020/3/18 23:39
+ * @version 1.0
+ */
+@Slf4j
+public class JsonUtils {
+
+ private final static ObjectMapper objMapper = new ObjectMapper();
+
+ /**
+ * Json字符串转化成对象
+ * @param jsonString
+ * @param clazz
+ * @param
+ * @return
+ */
+ public static T toObj(String jsonString, Class clazz) {
+ objMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ try {
+ return objMapper.readValue(jsonString, clazz);
+ } catch (final IOException e) {
+ log.error("Json string conversion object failed {}", e);
+ }
+ return null;
+ }
+
+ /**
+ * javaBean 转化成json字符串
+ * @param obj
+ * @return
+ */
+ public static String toJson(Object obj) {
+ if((obj instanceof Integer) || (obj instanceof Long) || (obj instanceof Float) ||
+ (obj instanceof Double) || (obj instanceof Boolean) || (obj instanceof String))
+ return String.valueOf(obj);
+ try {
+ return objMapper.writeValueAsString(obj);
+ } catch (final JsonProcessingException e) {
+ log.error("Json object conversion string failed {}", e);
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/top/beansprout/health/util/MailUtils.java b/src/main/java/top/beansprout/health/util/MailUtils.java
new file mode 100644
index 0000000..782e453
--- /dev/null
+++ b/src/main/java/top/beansprout/health/util/MailUtils.java
@@ -0,0 +1,74 @@
+package top.beansprout.health.util;
+
+import top.beansprout.health.model.vo.AuthVo;
+
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.util.Properties;
+
+public class MailUtils {
+ private Properties props; //系统属性
+ private Session mailSession; //邮件会话对象
+ private MimeMessage mimeMsg; //MIME邮件对象
+
+ //获取6位随机验证码
+ public String createVertifyCode(){
+ String[] letters = new String[] {
+ "q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m",
+ "A","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M",
+ "0","1","2","3","4","5","6","7","8","9"};
+ String stringBuilder ="";
+ for (int i = 0; i < 6; i++) {
+ stringBuilder = stringBuilder + letters[(int)Math.floor(Math.random()*letters.length)];
+ }
+ return stringBuilder;
+ }
+
+ public MailUtils() {
+ AuthVo au = new AuthVo("ateenliu@hunnu.edu.cn","jiAQr2Qp84xWn7GD");
+ //设置系统属性
+ props= System.getProperties(); //获得系统属性对象
+ props.put("mail.smtp.host", "smtp.exmail.qq.com"); //设置SMTP主机
+ props.put("mail.smtp.port", "465"); //设置服务端口号
+ props.put("mail.smtp.auth", "true"); //同时通过验证
+ props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+ //获得邮件会话对象
+ mailSession = Session.getInstance(props, au);
+ }
+
+ public boolean sendingMimeMail(String MailTo, String MailSubject,
+ String MailBody) {
+ try {
+ //创建MIME邮件对象
+ mimeMsg=new MimeMessage(mailSession);
+ //设置发信人
+ mimeMsg.setFrom(new InternetAddress("ateenliu@hunnu.edu.cn"));
+ //设置收信人
+ if(MailTo!=null){
+ mimeMsg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(MailTo));
+ }
+// //设置抄送人
+// if(MailCopyTo!=null){
+// mimeMsg.setRecipients(Message.RecipientType.CC,InternetAddress.parse(MailCopyTo));
+// }
+// //设置暗送人
+// if(MailBCopyTo!=null){
+// mimeMsg.setRecipients(Message.RecipientType.BCC,InternetAddress.parse(MailBCopyTo));
+// }
+ //设置邮件主题
+ mimeMsg.setSubject(MailSubject,"utf-8");
+ //设置邮件内容,将邮件body部分转化为HTML格式
+ mimeMsg.setContent(MailBody,"text/html;charset=utf-8");
+ //发送邮件
+ Transport.send(mimeMsg);
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/top/beansprout/health/util/PublicUtils.java b/src/main/java/top/beansprout/health/util/PublicUtils.java
new file mode 100644
index 0000000..5211a90
--- /dev/null
+++ b/src/main/java/top/beansprout/health/util/PublicUtils.java
@@ -0,0 +1,150 @@
+package top.beansprout.health.util;
+
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * Title: PublicUtils
+ * Description: 公共方法
+ *
+ * @author beansprout
+ * @version 1.0
+ * @date 2020/3/22 17:41
+ */
+public class PublicUtils {
+
+ /**
+ * 为空
+ * @param obj
+ * @return
+ */
+ public static boolean isBlank(Object obj) {
+ if (obj == null)
+ return true;
+ if (obj instanceof String)
+ return ((String) obj).trim().equals("");
+ if (obj instanceof CharSequence)
+ return ((CharSequence) obj).length() == 0;
+ if (obj instanceof Object[])
+ return ((Object[]) obj).length == 0;
+ if (obj instanceof Collection)
+ return ((Collection>) obj).isEmpty();
+ if (obj instanceof Map)
+ return ((Map, ?>) obj).isEmpty();
+ return false;
+ }
+
+ /**
+ * 不为空
+ * @param obj
+ * @return
+ */
+ public static boolean isNotBlank(Object obj) {
+ return !isBlank(obj);
+ }
+
+ /**
+ * 随机指定长度的数字+字母字符串
+ * @param leng
+ * @return
+ */
+ public static String randomString(int leng) {
+ return RandomStringUtils.randomAlphanumeric(leng);
+ }
+
+ /**
+ * 拼接字符串
+ * @param values
+ * @return
+ */
+ public static String join(Object ...values) {
+ if (values.length <= 0) return "";
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (final Object value : values) {
+ stringBuilder.append(value);
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Base64加密
+ * @param value 原文
+ * @return
+ */
+ public static String encryptBase64(String value) {
+ if (isBlank(value)) return null;
+ final byte[] encode = Base64.getEncoder().encode(value.getBytes());
+ return new String(encode);
+ }
+
+ /**
+ * Base64解密
+ * @param encrypt 密文
+ * @return
+ */
+ public static String decryptBase64(String encrypt) {
+ if (isBlank(encrypt)) return null;
+ final byte[] decode = Base64.getDecoder().decode(encrypt);
+ return new String(decode);
+ }
+
+ /**
+ * 获取请求域参数
+ * @param request
+ * @param key
+ * @return
+ */
+ public static String getAttribute(HttpServletRequest request, String key) {
+ return getAttribute(request, key, String.class);
+ }
+
+ public static T getAttribute(HttpServletRequest request, String key, Class requiredType) {
+ if ((request == null) || isBlank(key)) return null;
+ final Object value = request.getAttribute(key);
+ return isBlank(value) ? null : castValue(value, requiredType);
+ }
+
+ /** Object类型转换 **/
+ private static T castValue(Object value, Class requiredType) {
+ if ((requiredType == Date.class) && (value instanceof Long)) {
+ value = new Date((Long)value);
+ }
+
+ if (value instanceof Integer) {
+ final int intValue = (Integer)value;
+ if (requiredType == Long.class) {
+ value = (long)intValue;
+ } else if ((requiredType == Short.class) && (-32768 <= intValue) && (intValue <= 32767)) {
+ value = (short)intValue;
+ } else if ((requiredType == Byte.class) && (-128 <= intValue) && (intValue <= 127)) {
+ value = (byte)intValue;
+ }
+ }
+
+ if (!requiredType.isInstance(value))
+ throw new RuntimeException(
+ "Expected value to be of type: " + requiredType + ", but was " + value.getClass());
+ else
+ return requiredType.cast(value);
+ }
+
+ /**
+ * Bean字段值复制
+ * @param source 源
+ * @param target 目标
+ * @return 目标
+ */
+ public static T copyBean(T source, T target) {
+ if (isBlank(source) && isBlank(target)) return null;
+ BeanUtils.copyProperties(source, target);
+ return target;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..77538f0
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,4 @@
+jdbc.driver=com.mysql.cj.jdbc.Driver
+jdbc.url=jdbc:mysql://localhost:3306/health?useUnicode=true&characterEncoding=UTF-8
+jdbc.username=health
+jdbc.password=health
\ No newline at end of file
diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties
new file mode 100644
index 0000000..12b5cc8
--- /dev/null
+++ b/src/main/resources/log4j.properties
@@ -0,0 +1,32 @@
+### set log levels ###
+log4j.rootLogger=DEBUG , console , debug , error
+### console ###
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Target=System.out
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n
+### log file ###
+#log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender
+#log4j.appender.debug.File=log.log
+#log4j.appender.debug.Append=true
+#log4j.appender.debug.Threshold=INFO
+#log4j.appender.debug.layout=org.apache.log4j.PatternLayout
+#log4j.appender.debug.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n
+### exception ###
+log4j.appender.error=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.error.File=error-log.log
+log4j.appender.error.Append=true
+log4j.appender.error.Threshold=ERROR
+log4j.appender.error.layout=org.apache.log4j.PatternLayout
+log4j.appender.error.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n
+###需要声明,然后下方才可以使druid sql输出,否则会抛出log4j.error.key not found
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %l %c%n%p: %m%n
+### druid sql ###
+log4j.logger.druid.sql=warn,stdout
+log4j.logger.druid.sql.DataSource=warn,stdout
+log4j.logger.druid.sql.Connection=warn,stdout
+log4j.logger.druid.sql.Statement=warn,stdout
+log4j.logger.druid.sql.ResultSet=warn,stdout
\ No newline at end of file
diff --git a/src/main/resources/mapper/TBodyInfoMapper.xml b/src/main/resources/mapper/TBodyInfoMapper.xml
new file mode 100644
index 0000000..9b8625f
--- /dev/null
+++ b/src/main/resources/mapper/TBodyInfoMapper.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT * FROM t_body_info
+
+ creator = #{ creator }
+
+ AND create_time>=#{minDate}
+
+
+
+
+
+ ORDER BY create_time DESC
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/mapper/THealthConfigMapper.xml b/src/main/resources/mapper/THealthConfigMapper.xml
new file mode 100644
index 0000000..05aafe4
--- /dev/null
+++ b/src/main/resources/mapper/THealthConfigMapper.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/mapper/TUserMapper.xml b/src/main/resources/mapper/TUserMapper.xml
new file mode 100644
index 0000000..65f8c68
--- /dev/null
+++ b/src/main/resources/mapper/TUserMapper.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/mybatis-config.xml b/src/main/resources/spring/mybatis-config.xml
new file mode 100644
index 0000000..a200d4f
--- /dev/null
+++ b/src/main/resources/spring/mybatis-config.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-dao.xml b/src/main/resources/spring/spring-dao.xml
new file mode 100644
index 0000000..507b330
--- /dev/null
+++ b/src/main/resources/spring/spring-dao.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ helperDialect=mysql
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml
new file mode 100644
index 0000000..e30d3cc
--- /dev/null
+++ b/src/main/resources/spring/spring-mvc.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring-service.xml b/src/main/resources/spring/spring-service.xml
new file mode 100644
index 0000000..948320a
--- /dev/null
+++ b/src/main/resources/spring/spring-service.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/spring/spring.xml b/src/main/resources/spring/spring.xml
new file mode 100644
index 0000000..28afb2b
--- /dev/null
+++ b/src/main/resources/spring/spring.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/error/401.jsp b/src/main/webapp/WEB-INF/error/401.jsp
new file mode 100644
index 0000000..047766b
--- /dev/null
+++ b/src/main/webapp/WEB-INF/error/401.jsp
@@ -0,0 +1,71 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+<%@ page session="false" trimDirectiveWhitespaces="true" %>
+
+
+
+ 401 Unauthorized
+
+
+
+ 401 Unauthorized
+
+ You are not authorized to view this page. If you have not changed
+ any configuration files, please examine the file
+ conf/tomcat-users.xml in your installation. That
+ file must contain the credentials to let you use this webapp.
+
+
+ For example, to add the admin-gui role to a user named
+ tomcat with a password of s3cret , add the following to the
+ config file listed above.
+
+
+<role rolename="admin-gui"/>
+<user username="tomcat" password="s3cret" roles="admin-gui"/>
+
+
+ Note that for Tomcat 7 onwards, the roles required to use the host manager
+ application were changed from the single admin role to the
+ following two roles. You will need to assign the role(s) required for
+ the functionality you wish to access.
+
+
+ admin-gui - allows access to the HTML GUI
+ admin-script - allows access to the text interface
+
+
+ The HTML interface is protected against CSRF but the text interface is not.
+ To maintain the CSRF protection:
+
+
+ Users with the admin-gui role should not be granted the
+ admin-script role.
+ If the text interface is accessed through a browser (e.g. for testing
+ since this interface is intended for tools not humans) then the browser
+ must be closed afterwards to terminate the session.
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/error/403.jsp b/src/main/webapp/WEB-INF/error/403.jsp
new file mode 100644
index 0000000..74e1e2d
--- /dev/null
+++ b/src/main/webapp/WEB-INF/error/403.jsp
@@ -0,0 +1,90 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+<%@ page session="false" trimDirectiveWhitespaces="true" %>
+
+
+
+ 403 Access Denied
+
+
+
+ 403 Access Denied
+
+ You are not authorized to view this page.
+
+
+ By default the Host Manager is only accessible from a browser running on the
+ same machine as Tomcat. If you wish to modify this restriction, you'll need
+ to edit the Host Manager's context.xml file.
+
+
+ If you have already configured the Host Manager application to allow access
+ and you have used your browsers back button, used a saved book-mark or
+ similar then you may have triggered the cross-site request forgery (CSRF)
+ protection that has been enabled for the HTML interface of the Host Manager
+ application. You will need to reset this protection by returning to the
+ main Host Manager page .
+ Once you return to this page, you will be able to continue using the Host
+ Manager application's HTML interface normally. If you continue to see this
+ access denied message, check that you have the necessary permissions to
+ access this application.
+
+ If you have not changed
+ any configuration files, please examine the file
+ conf/tomcat-users.xml in your installation. That
+ file must contain the credentials to let you use this webapp.
+
+
+ For example, to add the admin-gui role to a user named
+ tomcat with a password of s3cret , add the following to the
+ config file listed above.
+
+
+<role rolename="admin-gui"/>
+<user username="tomcat" password="s3cret" roles="admin-gui"/>
+
+
+ Note that for Tomcat 7 onwards, the roles required to use the host manager
+ application were changed from the single admin role to the
+ following two roles. You will need to assign the role(s) required for
+ the functionality you wish to access.
+
+
+ admin-gui - allows access to the HTML GUI
+ admin-script - allows access to the text interface
+
+
+ The HTML interface is protected against CSRF but the text interface is not.
+ To maintain the CSRF protection:
+
+
+ Users with the admin-gui role should not be granted the
+ admin-script role.
+ If the text interface is accessed through a browser (e.g. for testing
+ since this interface is intended for tools not humans) then the browser
+ must be closed afterwards to terminate the session.
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/error/404.jsp b/src/main/webapp/WEB-INF/error/404.jsp
new file mode 100644
index 0000000..2bf507a
--- /dev/null
+++ b/src/main/webapp/WEB-INF/error/404.jsp
@@ -0,0 +1,60 @@
+
+
+
+404 Not found
+
+
+
+ 404 Not found
+
+ The page you tried to access (<%=request.getAttribute("javax.servlet.error.request_uri")%>)
+ ${ baseurl }
+ does not exist.
+
+ The Host Manager application has been re-structured for Tomcat 7
+ onwards and some URLs have changed. All URLs used to access the
+ Manager application should now start with one of the following
+ options:
+
+ <%=request.getContextPath()%>/html for the HTML GUI
+ <%=request.getContextPath()%>/text for the text interface
+
+
+ Note that the URL for the text interface has changed from "<%=request.getContextPath()%>"
+ to "<%=request.getContextPath()%>/text".
+
+ You probably need to adjust the URL you are using to access the
+ Host Manager application. However, there is always a chance you have
+ found a bug in the Host Manager application. If you are sure you have
+ found a bug, and that the bug has not already been reported, please
+ report it to the Apache Tomcat team.
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/error/500.jsp b/src/main/webapp/WEB-INF/error/500.jsp
new file mode 100644
index 0000000..522b3d0
--- /dev/null
+++ b/src/main/webapp/WEB-INF/error/500.jsp
@@ -0,0 +1,12 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+
+
+
+
+错误页面
+
+
+<% System.out.print(request.getAttribute("message")); %>
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/view/base.jsp b/src/main/webapp/WEB-INF/view/base.jsp
new file mode 100644
index 0000000..5a5a108
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/base.jsp
@@ -0,0 +1,8 @@
+
+<%-- <%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%> --%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+
+
diff --git a/src/main/webapp/WEB-INF/view/bodyInfoInput.jsp b/src/main/webapp/WEB-INF/view/bodyInfoInput.jsp
new file mode 100644
index 0000000..7405480
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/bodyInfoInput.jsp
@@ -0,0 +1,215 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp"%>
+
+
+
+
+ 身体信息录入
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/view/bodyInofList.jsp b/src/main/webapp/WEB-INF/view/bodyInofList.jsp
new file mode 100644
index 0000000..d3090f8
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/bodyInofList.jsp
@@ -0,0 +1,234 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp" %>
+
+
+
+ 用户身体信息列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp b/src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp
new file mode 100644
index 0000000..8e21eda
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp
@@ -0,0 +1,175 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp" %>
+
+
+
+ 用户身体信息统计
+
+
+
+
+
+
+ 时间
+
+ 搜索
+ 刷新
+ 近期推送
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/view/home.jsp b/src/main/webapp/WEB-INF/view/home.jsp
new file mode 100644
index 0000000..f8e7963
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/home.jsp
@@ -0,0 +1,181 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp"%>
+
+
+
+
+ 首页
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/view/register.jsp b/src/main/webapp/WEB-INF/view/register.jsp
new file mode 100644
index 0000000..9c4c065
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/register.jsp
@@ -0,0 +1,150 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp" %>
+
+
+
+ 注册
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/view/top.jsp b/src/main/webapp/WEB-INF/view/top.jsp
new file mode 100644
index 0000000..7b81120
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/top.jsp
@@ -0,0 +1,55 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+
+
+
+
+ 头部
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/view/updatePassword.jsp b/src/main/webapp/WEB-INF/view/updatePassword.jsp
new file mode 100644
index 0000000..72fcadc
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/updatePassword.jsp
@@ -0,0 +1,120 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp"%>
+
+
+
+
+ 修改密码
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/view/userInfo.jsp b/src/main/webapp/WEB-INF/view/userInfo.jsp
new file mode 100644
index 0000000..aaf3bd7
--- /dev/null
+++ b/src/main/webapp/WEB-INF/view/userInfo.jsp
@@ -0,0 +1,200 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="base.jsp"%>
+
+
+
+
+ 用户信息
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..eee39cd
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,89 @@
+
+
+
+ 个人健康信息管理
+
+
+
+ contextConfigLocation
+ classpath:spring/spring.xml
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+
+ crossXssFilter
+ top.beansprout.health.config.CrossXssFilter
+
+
+ crossXssFilter
+ /*
+
+
+
+
+ characterEncodingFilter
+ org.springframework.web.filter.CharacterEncodingFilter
+
+
+ encoding
+ UTF-8
+
+
+
+ forceEncoding
+ true
+
+
+
+ characterEncodingFilter
+ /*
+
+
+
+
+ dispatcher
+ org.springframework.web.servlet.DispatcherServlet
+
+
+ contextConfigLocation
+ classpath:spring/spring-mvc.xml
+
+ 1
+
+
+ dispatcher
+ /
+
+
+
+ index.jsp
+
+
+ java.lang.Exception
+ /WEB-INF/error/500.jsp
+
+
+ 401
+ /WEB-INF/error/401.jsp
+
+
+ 403
+ /WEB-INF/error/403.jsp
+
+
+ 404
+ /WEB-INF/error/404.jsp
+
+
+ 405
+ /WEB-INF/error/404.jsp
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp
new file mode 100644
index 0000000..bfc8fec
--- /dev/null
+++ b/src/main/webapp/index.jsp
@@ -0,0 +1,128 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+ pageEncoding="UTF-8"%>
+<%@include file="WEB-INF/view/base.jsp"%>
+
+
+
+ 登录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/static/css/base.css b/src/main/webapp/static/css/base.css
new file mode 100644
index 0000000..934a655
--- /dev/null
+++ b/src/main/webapp/static/css/base.css
@@ -0,0 +1,3381 @@
+/*
+==========================================================
+==========================================================
+
+Bootstrap 4 Admin Template
+
+https://bootstrapious.com/p/admin-template
+
+==========================================================
+==========================================================
+*/
+/*
+* ==========================================================
+* GENERAL STYLES
+* ==========================================================
+*/
+body {
+ overflow-x: hidden;
+}
+
+a,
+i,
+span {
+ display: inline-block;
+ text-decoration: none;
+ -webkit-transition: all 0.3s;
+ transition: all 0.3s;
+}
+
+a:hover, a:focus,
+i:hover,
+i:focus,
+span:hover,
+span:focus {
+ text-decoration: none;
+}
+
+section {
+ padding: 50px 0;
+}
+
+canvas {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.container-fluid {
+ padding: 0 30px;
+}
+
+@media (max-width: 575px) {
+ .container-fluid {
+ padding: 0 15px;
+ }
+}
+
+header.page-header {
+ padding: 20px 0;
+}
+
+table {
+ font-size: 0.9em;
+ color: #666;
+}
+
+.card-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+}
+
+.card-close .dropdown-toggle {
+ color: #999;
+ background: none;
+ border: none;
+}
+
+.card-close .dropdown-toggle:after {
+ display: none;
+}
+
+.card-close .dropdown-menu {
+ border: none;
+ min-width: auto;
+ font-size: 0.9em;
+ border-radius: 0;
+ -webkit-box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.1), -2px -2px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.1), -2px -2px 3px rgba(0, 0, 0, 0.1);
+}
+
+.card-close .dropdown-menu a {
+ color: #999 !important;
+}
+
+.card-close .dropdown-menu a:hover {
+ background: #796AEE;
+ color: #fff !important;
+}
+
+.card-close .dropdown-menu a i {
+ margin-right: 10px;
+ -webkit-transition: none;
+ transition: none;
+}
+
+.content-inner {
+ position: relative;
+ width: calc(100% - 250px);
+ min-height: calc(100vh - 70px);
+ padding-bottom: 60px;
+}
+
+.content-inner.active {
+ width: calc(100% - 90px);
+}
+
+.page-header {
+ background: #fff;
+ padding: 20px;
+ -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1);
+ position: relative;
+ z-index: 8;
+}
+
+*[class*="icon-"] {
+ -webkit-transform: translateY(3px);
+ transform: translateY(3px);
+}
+
+button,
+input {
+ outline: none !important;
+}
+
+.card {
+ margin-bottom: 30px;
+ -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05);
+}
+
+.card-header {
+ -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.05);
+}
+
+.card-header h1,
+.card-header h2,
+.card-header h3,
+.card-header h4,
+.card-header h5,
+.card-header h6 {
+ margin-bottom: 0;
+}
+
+.breadcrumb-holder {
+ background: #fff;
+}
+
+.breadcrumb {
+ background: #fff;
+ position: relative;
+ z-index: 7;
+ border-radius: 0;
+ padding: 15px 0;
+ margin-bottom: 0;
+}
+
+.breadcrumb li.breadcrumb-item {
+ color: #aaa;
+ font-weight: 300;
+}
+
+/*=== Helpers ===*/
+.text-bold {
+ font-weight: 700;
+}
+
+.text-small {
+ font-size: 0.9rem;
+}
+
+.text-xsmall {
+ font-size: 0.8rem;
+}
+
+.bg-red {
+ background: #ff7676 !important;
+ color: #fff;
+}
+
+.bg-red:hover {
+ color: #fff;
+}
+
+.bg-blue {
+ background: #85b4f2 !important;
+ color: #fff;
+}
+
+.bg-blue:hover {
+ color: #fff;
+}
+
+.bg-yellow {
+ background: #eef157 !important;
+ color: #fff;
+}
+
+.bg-yellow:hover {
+ color: #fff;
+}
+
+.bg-green {
+ background: #54e69d !important;
+ color: #fff;
+}
+
+.bg-green:hover {
+ color: #fff;
+}
+
+.bg-orange {
+ background: #ffc36d !important;
+ color: #fff;
+}
+
+.bg-orange:hover {
+ color: #fff;
+}
+
+.bg-violet {
+ background: #796AEE !important;
+ color: #fff;
+}
+
+.bg-violet:hover {
+ color: #fff;
+}
+
+.bg-gray {
+ background: #ced4da !important;
+}
+
+.bg-white {
+ background: #fff !important;
+}
+
+.text-red {
+ color: #ff7676;
+}
+
+.text-red:hover {
+ color: #ff7676;
+}
+
+.text-yellow {
+ color: #eef157;
+}
+
+.text-yellow:hover {
+ color: #eef157;
+}
+
+.text-green {
+ color: #54e69d;
+}
+
+.text-green:hover {
+ color: #54e69d;
+}
+
+.text-orange {
+ color: #ffc36d;
+}
+
+.text-orange:hover {
+ color: #ffc36d;
+}
+
+.text-violet {
+ color: #796AEE;
+}
+
+.text-violet:hover {
+ color: #796AEE;
+}
+
+.text-blue {
+ color: #85b4f2;
+}
+
+.text-blue:hover {
+ color: #85b4f2;
+}
+
+.text-gray {
+ color: #ced4da;
+}
+
+.text-gray:hover {
+ color: #ced4da;
+}
+
+.text-uppercase {
+ letter-spacing: 0.2em;
+}
+
+.lh-2 {
+ line-height: 2;
+}
+
+.page {
+ background: #EEF5F9;
+}
+
+.page .text-white {
+ color: #fff;
+}
+
+.no-padding {
+ padding: 0 !important;
+}
+
+.no-padding-bottom {
+ padding-bottom: 0 !important;
+}
+
+.no-padding-top {
+ padding-top: 0 !important;
+}
+
+.no-margin {
+ margin: 0 !important;
+}
+
+.no-margin-bottom {
+ margin-bottom: 0 !important;
+}
+
+.no-margin-top {
+ margin-top: 0 !important;
+}
+
+.page {
+ overflow-x: hidden;
+}
+
+.has-shadow {
+ -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05);
+}
+
+.badge {
+ font-weight: 400;
+}
+
+.badge-rounded {
+ border-radius: 50px;
+}
+
+.list-group-item {
+ border-right: 0;
+ border-left: 0;
+}
+
+.list-group-item:first-child, .list-group-item:last-child {
+ border-radius: 0;
+}
+
+.overflow-hidden {
+ overflow: hidden;
+}
+
+.tile-link {
+ position: absolute;
+ cursor: pointer;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ z-index: 30;
+}
+
+/*
+* ==========================================================
+* SIDEBAR
+* ==========================================================
+*/
+nav.side-navbar {
+ background: #fff;
+ min-width: 250px;
+ max-width: 250px;
+ color: #686a76;
+ -webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
+ box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
+ z-index: 9;
+ /*==== Sidebar Header ====*/
+ /*==== Sidebar Menu ====*/
+ /*==== Shrinked Sidebar ====*/
+}
+
+nav.side-navbar a {
+ color: inherit;
+ position: relative;
+ font-size: 0.9em;
+}
+
+nav.side-navbar a[data-toggle="collapse"]::before {
+ content: '\f104';
+ display: inline-block;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ font-family: 'FontAwesome';
+ position: absolute;
+ top: 50%;
+ right: 20px;
+}
+
+nav.side-navbar a[aria-expanded="true"] {
+ background: #EEF5F9;
+}
+
+nav.side-navbar a[aria-expanded="true"]::before {
+ content: '\f107';
+}
+
+nav.side-navbar a i {
+ font-size: 1.2em;
+ margin-right: 10px;
+ -webkit-transition: none;
+ transition: none;
+}
+
+nav.side-navbar .sidebar-header {
+ padding: 30px 15px;
+}
+
+nav.side-navbar .avatar {
+ width: 55px;
+ height: 55px;
+}
+
+nav.side-navbar .title {
+ margin-left: 10px;
+}
+
+nav.side-navbar .title h1 {
+ color: #333;
+}
+
+nav.side-navbar .title p {
+ font-size: 0.9em;
+ font-weight: 200;
+ margin-bottom: 0;
+ color: #aaa;
+}
+
+nav.side-navbar span.heading {
+ text-transform: uppercase;
+ font-weight: 400;
+ margin-left: 20px;
+ color: #ccc;
+ font-size: 0.8em;
+}
+
+nav.side-navbar ul {
+ padding: 15px 0;
+}
+
+nav.side-navbar ul li {
+ /* submenu item active */
+}
+
+nav.side-navbar ul li a {
+ padding: 10px 15px;
+ text-decoration: none;
+ display: block;
+ font-weight: 300;
+ border-left: 4px solid transparent;
+}
+
+nav.side-navbar ul li a:hover {
+ background: #796AEE;
+ border-left: 4px solid #3b25e6;
+ color: #fff;
+}
+
+nav.side-navbar ul li li a {
+ padding-left: 50px;
+ background: #EEF5F9;
+}
+
+nav.side-navbar ul li.active > a {
+ background: #796AEE;
+ color: #fff;
+ border-left: 4px solid #3b25e6;
+}
+
+nav.side-navbar ul li.active > a:hover {
+ background: #796AEE;
+}
+
+nav.side-navbar ul li li.active > a {
+ background: #8e81f1;
+}
+
+nav.side-navbar ul li ul {
+ padding: 0;
+}
+
+nav.side-navbar.shrinked {
+ min-width: 90px;
+ max-width: 90px;
+ text-align: center;
+}
+
+nav.side-navbar.shrinked span.heading {
+ margin: 0;
+}
+
+nav.side-navbar.shrinked ul li a {
+ padding: 15px 2px;
+ border: none;
+ font-size: 0.8em;
+ color: #aaa;
+ -webkit-transition: color 0.3s, background 0.3s;
+ transition: color 0.3s, background 0.3s;
+}
+
+nav.side-navbar.shrinked ul li a[data-toggle="collapse"]::before {
+ content: '\f107';
+ -webkit-transform: translateX(50%);
+ transform: translateX(50%);
+ position: absolute;
+ top: auto;
+ right: 50%;
+ bottom: 0;
+ left: auto;
+}
+
+nav.side-navbar.shrinked ul li a[data-toggle="collapse"][aria-expanded="true"]::before {
+ content: '\f106';
+}
+
+nav.side-navbar.shrinked ul li a:hover {
+ color: #fff;
+ border: none;
+}
+
+nav.side-navbar.shrinked ul li a:hover i {
+ color: #fff;
+}
+
+nav.side-navbar.shrinked ul li a i {
+ margin-right: 0;
+ margin-bottom: 2px;
+ display: block;
+ font-size: 1rem;
+ color: #333;
+ -webkit-transition: color 0.3s;
+ transition: color 0.3s;
+}
+
+nav.side-navbar.shrinked ul li.active > a {
+ color: #fff;
+}
+
+nav.side-navbar.shrinked ul li.active > a i {
+ color: #fff;
+}
+
+nav.side-navbar.shrinked .sidebar-header .title {
+ display: none;
+}
+
+/* SIDEBAR MEDIAQUERIES ----------------------------------- */
+@media (max-width: 1199px) {
+ nav.side-navbar {
+ margin-left: -90px;
+ min-width: 90px;
+ max-width: 90px;
+ text-align: center;
+ overflow: hidden;
+ }
+ nav.side-navbar span.heading {
+ margin: 0;
+ }
+ nav.side-navbar ul li a {
+ padding: 15px 2px;
+ border: none;
+ font-size: 0.8em;
+ color: #aaa;
+ -webkit-transition: color 0.3s, background 0.3s;
+ transition: color 0.3s, background 0.3s;
+ }
+ nav.side-navbar ul li a[data-toggle="collapse"]::before {
+ content: '\f107';
+ -webkit-transform: translateX(50%);
+ transform: translateX(50%);
+ position: absolute;
+ top: auto;
+ right: 50%;
+ bottom: 0;
+ left: auto;
+ }
+ nav.side-navbar ul li a[data-toggle="collapse"][aria-expanded="true"]::before {
+ content: '\f106';
+ }
+ nav.side-navbar ul li a:hover {
+ color: #fff;
+ border: none;
+ }
+ nav.side-navbar ul li a:hover i {
+ color: #fff;
+ }
+ nav.side-navbar ul li a i {
+ margin-right: 0;
+ margin-bottom: 5px;
+ display: block;
+ font-size: 1.6em;
+ color: #333;
+ -webkit-transition: color 0.3s;
+ transition: color 0.3s;
+ }
+ nav.side-navbar ul li.active > a {
+ color: #fff;
+ }
+ nav.side-navbar ul li.active > a i {
+ color: #fff;
+ }
+ nav.side-navbar .sidebar-header .title {
+ display: none;
+ }
+ nav.side-navbar.shrinked {
+ margin-left: 0;
+ }
+ .content-inner {
+ width: 100%;
+ }
+ .content-inner.active {
+ width: calc(100% - 90px);
+ }
+}
+
+/*
+* ==========================================================
+* MAIN NAVBAR
+* ==========================================================
+*/
+nav.navbar {
+ background: #2f333e;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ color: #fff;
+ position: relative;
+ border-radius: 0;
+ -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
+ z-index: 10;
+ padding-left: 0;
+ padding-right: 0;
+ /*==== Toggle Sidebar Btn ====*/
+ /*==== Navbar Items ====*/
+ /*==== Search Box ====*/
+ /*==== Dropdowns ====*/
+}
+
+nav.navbar .badge {
+ width: 22px;
+ height: 22px;
+ line-height: 22px;
+ text-align: center;
+ padding: 0;
+ border-radius: 50%;
+}
+
+nav.navbar .navbar-holder {
+ width: 100%;
+}
+
+nav.navbar a {
+ color: inherit;
+}
+
+nav.navbar .container-fluid {
+ width: 100%;
+}
+
+nav.navbar .menu-btn {
+ margin-right: 20px;
+ font-size: 1.2em;
+ -webkit-transition: all 0.7s;
+ transition: all 0.7s;
+}
+
+nav.navbar .menu-btn span {
+ width: 20px;
+ height: 2px;
+ background: #fff;
+ display: block;
+ margin: 4px auto 0;
+ -webkit-transition: all 0.3s cubic-bezier(0.81, -0.33, 0.345, 1.375);
+ transition: all 0.3s cubic-bezier(0.81, -0.33, 0.345, 1.375);
+}
+
+nav.navbar .menu-btn span:nth-of-type(2) {
+ position: relative;
+ width: 35px;
+ -webkit-transform: rotateY(180deg);
+ transform: rotateY(180deg);
+}
+
+nav.navbar .menu-btn span:nth-of-type(2)::before, nav.navbar .menu-btn span:nth-of-type(2)::after {
+ content: '';
+ width: 6px;
+ height: 2px;
+ display: block;
+ background: #fff;
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ position: absolute;
+ top: 2px;
+ left: 0;
+ -webkit-transition: all 0.7s;
+ transition: all 0.7s;
+}
+
+nav.navbar .menu-btn span:nth-of-type(2)::after {
+ -webkit-transform: rotate(145deg);
+ transform: rotate(145deg);
+ position: absolute;
+ top: -2px;
+ left: 0;
+}
+
+nav.navbar .menu-btn.active span:first-of-type {
+ -webkit-transform: translateY(12px);
+ transform: translateY(12px);
+}
+
+nav.navbar .menu-btn.active span:nth-of-type(2) {
+ -webkit-transform: none;
+ transform: none;
+}
+
+nav.navbar .menu-btn.active span:last-of-type {
+ -webkit-transform: translateY(-12px);
+ transform: translateY(-12px);
+}
+
+nav.navbar .nav-link {
+ position: relative;
+}
+
+nav.navbar .nav-link span.badge-corner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ font-weight: 400;
+ font-size: 0.7em;
+}
+
+nav.navbar .nav-link.language span {
+ margin-left: .3rem;
+ vertical-align: middle;
+}
+
+nav.navbar .nav-link.logout i {
+ margin-left: 10px;
+}
+
+nav.navbar .nav-menu {
+ margin-bottom: 0;
+}
+
+nav.navbar .search-box {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 0;
+ background: #fff;
+ z-index: 12;
+ border-radius: 0;
+ display: none;
+}
+
+nav.navbar .search-box .dismiss {
+ position: absolute;
+ top: 50%;
+ right: 20px;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 1.5em;
+ color: #999;
+}
+
+nav.navbar .search-box form {
+ height: 100%;
+}
+
+nav.navbar .search-box form input {
+ height: 100%;
+ border: none;
+ padding: 20px;
+}
+
+nav.navbar .dropdown-toggle::after {
+ border: none !important;
+ content: '\f107';
+ vertical-align: baseline;
+ font-family: 'FontAwesome';
+ margin-left: .3rem;
+}
+
+nav.navbar .dropdown-menu {
+ right: 0;
+ min-width: 250px;
+ left: auto;
+ margin-top: 15px;
+ margin-bottom: 0;
+ padding: 15px 0;
+ max-width: 400px;
+ border-radius: 0;
+ border: none;
+ -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -2px 0 2px rgba(0, 0, 0, 0.1);
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -2px 0 2px rgba(0, 0, 0, 0.1);
+}
+
+nav.navbar .dropdown-menu .dropdown-item {
+ background: #fff;
+ padding: 10px 20px;
+ font-size: 0.8rem;
+ color: #777;
+ width: 100%;
+}
+
+nav.navbar .dropdown-menu .dropdown-item:hover {
+ background: #f5f5f5;
+}
+
+nav.navbar .dropdown-menu .dropdown-item i {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ background: #796AEE;
+ text-align: center;
+ color: #fff;
+ border-radius: 50%;
+ margin-right: 10px;
+}
+
+nav.navbar .dropdown-menu .dropdown-item small {
+ margin-left: 40px;
+}
+
+nav.navbar .dropdown-menu span {
+ position: static;
+ font-size: 0.9em;
+ color: #999;
+}
+
+nav.navbar .dropdown-menu strong {
+ font-weight: 700;
+}
+
+nav.navbar .dropdown-menu .msg-profile {
+ width: 45px;
+ height: 45px;
+ margin-right: 10px;
+}
+
+nav.navbar .dropdown-menu h3 {
+ font-weight: 500;
+}
+
+/* MAIN NAVBAR MEDIAQUERIES ----------------------------------- */
+@media (max-width: 1199px) {
+ nav.navbar {
+ /*==== Toggle Sidebar Btn ====*/
+ }
+ nav.navbar .menu-btn {
+ margin-right: 20px;
+ font-size: 1.2em;
+ -webkit-transition: all 0.7s;
+ transition: all 0.7s;
+ }
+ nav.navbar .menu-btn span:first-of-type {
+ -webkit-transform: translateY(12px);
+ transform: translateY(12px);
+ }
+ nav.navbar .menu-btn span:nth-of-type(2) {
+ -webkit-transform: none;
+ transform: none;
+ }
+ nav.navbar .menu-btn span:nth-of-type(2)::before, nav.navbar .menu-btn span:nth-of-type(2)::after {
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+ nav.navbar .menu-btn span:nth-of-type(2)::after {
+ -webkit-transform: rotate(145deg);
+ transform: rotate(145deg);
+ position: absolute;
+ top: -2px;
+ left: 0;
+ }
+ nav.navbar .menu-btn span:last-of-type {
+ -webkit-transform: translateY(-12px);
+ transform: translateY(-12px);
+ }
+ nav.navbar .menu-btn.active span:first-of-type {
+ -webkit-transform: none;
+ transform: none;
+ }
+ nav.navbar .menu-btn.active span:nth-of-type(2) {
+ -webkit-transform: rotateY(180deg);
+ transform: rotateY(180deg);
+ }
+ nav.navbar .menu-btn.active span:last-of-type {
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+@media (max-width: 575px) {
+ nav.navbar {
+ font-size: 14px;
+ }
+ nav.navbar .badge {
+ width: 19px;
+ height: 19px;
+ }
+ nav.navbar .nav-item > a {
+ font-size: 13px;
+ }
+ nav.navbar .dropdown-menu {
+ right: auto;
+ left: -50%;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+ }
+}
+
+/*
+* ==========================================================
+* DASHBOARD COUNTS SECTION
+* ==========================================================
+*/
+section.dashboard-counts .icon {
+ width: 40px;
+ height: 40px;
+ line-height: 40px;
+ text-align: center;
+ min-width: 40px;
+ max-width: 40px;
+ border-radius: 50%;
+}
+
+section.dashboard-counts .title {
+ font-size: 1.1em;
+ font-weight: 300;
+ color: #777;
+ margin: 0 20px;
+}
+
+section.dashboard-counts .progress {
+ margin-top: 10px;
+ height: 4px;
+}
+
+section.dashboard-counts .number {
+ font-size: 1.8em;
+ font-weight: 300;
+}
+
+section.dashboard-counts .number strong {
+ font-weight: 700;
+}
+
+section.dashboard-counts .row {
+ padding: 30px 15px;
+ margin: 0;
+}
+
+section.dashboard-counts div[class*='col-'] .item {
+ border-right: 1px solid #eee;
+ padding: 15px 0;
+}
+
+section.dashboard-counts div[class*='col-']:last-of-type .item {
+ border-right: none;
+}
+
+/* DASHBOARD COUNTS MEDIAQUERIES ------------------------ */
+@media (max-width: 1199px) {
+ section.dashboard-counts div[class*='col-']:nth-of-type(2) .item {
+ border-right: none;
+ }
+}
+
+@media (max-width: 575px) {
+ section.dashboard-counts div[class*='col-'] .item {
+ border-right: none;
+ }
+}
+
+/*
+* ==========================================================
+* DASHBOARD HEADER SECTION
+* ==========================================================
+*/
+.statistic {
+ padding: 20px 15px;
+ margin-bottom: 15px;
+}
+
+.statistic:last-of-type {
+ margin-bottom: 0;
+}
+
+.statistic strong {
+ font-size: 1.5em;
+ color: #333;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.statistic small {
+ color: #aaa;
+ text-transform: uppercase;
+}
+
+.statistic .icon {
+ width: 40px;
+ height: 40px;
+ line-height: 40px;
+ text-align: center;
+ min-width: 40px;
+ max-width: 40px;
+ color: #fff;
+ border-radius: 50%;
+ margin-right: 15px;
+}
+
+.chart .title {
+ padding: 15px 0 0 15px;
+}
+
+.chart .title strong {
+ font-weight: 700;
+ font-size: 1.2em;
+}
+
+.chart .title small {
+ color: #aaa;
+ text-transform: uppercase;
+}
+
+.chart .line-chart {
+ width: 100%;
+ height: 100%;
+ padding: 20px 0;
+}
+
+.chart .line-chart canvas {
+ width: calc(100% - 30px) !important;
+}
+
+.chart .bar-chart {
+ margin-bottom: 15px;
+}
+
+.chart .bar-chart canvas {
+ padding: 15px;
+ width: 100%;
+ margin: 0;
+}
+
+/* DASHBOARD HEADER MEDIAQUERIES ------------------------*/
+@media (max-width: 991px) {
+ section.dashboard-header div[class*='col-'] {
+ margin-bottom: 20px;
+ }
+}
+
+/*
+* ==========================================================
+* PROJECTS SECTION
+* ==========================================================
+*/
+.project .row {
+ margin: 0;
+ padding: 15px 0;
+ margin-bottom: 15px;
+}
+
+.project div[class*='col-'] {
+ border-right: 1px solid #eee;
+}
+
+.project .text h3 {
+ margin-bottom: 0;
+ color: #555;
+}
+
+.project .text small {
+ color: #aaa;
+ font-size: 0.75em;
+}
+
+.project .project-date span {
+ font-size: 0.9em;
+ color: #999;
+}
+
+.project .image {
+ max-width: 50px;
+ min-width: 50px;
+ height: 50px;
+ margin-right: 15px;
+}
+
+.project .time,
+.project .comments,
+.project .project-progress {
+ color: #999;
+ font-size: 0.9em;
+ margin-right: 20px;
+}
+
+.project .time i,
+.project .comments i,
+.project .project-progress i {
+ margin-right: 5px;
+}
+
+.project .project-progress {
+ width: 200px;
+}
+
+.project .project-progress .progress {
+ height: 4px;
+}
+
+.project .card {
+ margin-bottom: 0;
+}
+
+/* PROJECTS SECTION ------------------------------------- */
+@media (max-width: 991px) {
+ .project .right-col {
+ margin-top: 20px;
+ margin-left: 65px;
+ }
+ .project .project-progress {
+ width: 150px;
+ }
+}
+
+@media (max-width: 480px) {
+ .project .project-progress {
+ display: none;
+ }
+}
+
+/*
+* ==========================================================
+* CLIENT SECTION
+* ==========================================================
+*/
+/*====== Work Amount Box ======*/
+.work-amount .chart {
+ margin: 40px auto;
+ position: relative;
+}
+
+.work-amount .chart .text {
+ display: inline-block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+.work-amount .chart strong {
+ font-size: 1.5rem;
+}
+
+.work-amount .chart span {
+ color: #999;
+ font-weight: 300;
+}
+
+.work-amount li span {
+ font-size: 0.85em;
+ margin-bottom: 10px;
+ color: #777;
+ display: block;
+}
+
+.work-amount li span::before {
+ content: '';
+ display: inline-block;
+ margin-right: 10px;
+ width: 7px;
+ height: 7px;
+ line-height: 7px;
+ background: #85b4f2;
+ border-radius: 50%;
+}
+
+/*====== Client Profile Box ======*/
+.client .client-avatar {
+ width: 100px;
+ height: 100px;
+ margin: 0 auto;
+ position: relative;
+}
+
+.client .client-avatar .status {
+ content: '';
+ display: block;
+ width: 18px;
+ height: 18px;
+ border: 3px solid #fff;
+ border-radius: 50%;
+ position: absolute;
+ right: 4px;
+ bottom: 4px;
+}
+
+.client .client-title {
+ margin-top: 20px;
+}
+
+.client .client-title h3 {
+ font-weight: 500;
+ color: #555;
+}
+
+.client .client-title span {
+ font-size: 0.9em;
+ color: #aaa;
+ display: block;
+}
+
+.client .client-title a {
+ padding: 2px 30px;
+ border-radius: 40px;
+ background: #54e69d;
+ color: #fff;
+ margin-top: 5px;
+ font-size: 0.9em;
+ text-decoration: none;
+}
+
+.client .client-title a:hover {
+ background: #85b4f2;
+}
+
+.client .client-info {
+ margin-top: 20px;
+}
+
+.client .client-info strong {
+ font-weight: 700;
+}
+
+.client .client-social {
+ margin-top: 20px;
+}
+
+.client .client-social a {
+ color: #aaa;
+}
+
+/*====== Total Overdue Box ======*/
+.overdue .chart canvas {
+ width: 100% !important;
+ z-index: 1;
+}
+
+.overdue .card {
+ margin-bottom: 0;
+}
+
+.overdue .card-body {
+ padding: 20px;
+}
+
+.overdue .card-body small {
+ font-weight: 300;
+ color: #aaa;
+}
+
+.overdue .card-body h3 {
+ margin-bottom: 5px;
+}
+
+.overdue .number {
+ font-size: 1.8em;
+ font-weight: 400;
+ color: #555;
+ margin: 35px 0;
+}
+
+/*
+* ==========================================================
+* FEEDS SECTION
+* ==========================================================
+*/
+/*====== Checklist Box ======*/
+.checklist label {
+ font-size: 0.8em;
+ color: #999;
+ line-height: 1.8em;
+ margin-bottom: 0;
+}
+
+.checklist .item {
+ padding: 20px;
+}
+
+.checklist .item:nth-of-type(even) {
+ background: #fafafa;
+}
+
+/*====== Trending Articles Box ======*/
+.articles a {
+ text-decoration: none !important;
+ display: block;
+ margin-bottom: 0;
+ color: #555;
+}
+
+.articles .badge {
+ font-size: 0.7em;
+ padding: 5px 10px;
+ line-height: 1;
+ margin-left: 10px;
+}
+
+.articles .item {
+ padding: 20px;
+}
+
+.articles .item:nth-of-type(even) {
+ background: #fafafa;
+}
+
+.articles .item .image {
+ min-width: 50px;
+ max-width: 50px;
+ height: 50px;
+ margin-right: 15px;
+}
+
+.articles .item img {
+ padding: 3px;
+ border: 1px solid #28a745;
+}
+
+.articles .item h3 {
+ color: #555;
+ font-weight: 400;
+ margin-bottom: 0;
+}
+
+.articles .item small {
+ color: #aaa;
+ font-size: 0.75em;
+}
+
+/*
+* ==========================================================
+* UPDATES SECTION
+* ==========================================================
+*/
+/*====== Recent Updates Box ======*/
+.recent-updates .item {
+ padding: 20px;
+}
+
+.recent-updates .item:nth-of-type(even) {
+ background: #fafafa;
+}
+
+.recent-updates .icon {
+ margin-right: 10px;
+}
+
+.recent-updates h5 {
+ margin-bottom: 5px;
+ color: #333;
+ font-weight: 400;
+}
+
+.recent-updates p {
+ font-size: 0.8em;
+ color: #aaa;
+ margin-bottom: 0;
+}
+
+.recent-updates .date {
+ font-size: 0.9em;
+ color: #adadad;
+}
+
+.recent-updates .date strong {
+ font-size: 1.4em;
+ line-height: 0.8em;
+ display: block;
+}
+
+.recent-updates .date span {
+ font-size: 0.9em;
+ font-weight: 300;
+}
+
+/*====== Daily Feeds Box ======*/
+.daily-feeds .item {
+ padding: 20px;
+ border-bottom: 1px solid #eee;
+}
+
+.daily-feeds .feed-profile {
+ max-width: 50px;
+ min-width: 50px;
+ margin-right: 10px;
+}
+
+.daily-feeds h5 {
+ margin-bottom: 0;
+ color: #555;
+}
+
+.daily-feeds span {
+ font-size: 0.8em;
+ color: #999;
+}
+
+.daily-feeds .full-date {
+ font-size: 0.85em;
+ color: #aaa;
+ font-weight: 300;
+}
+
+.daily-feeds .CTAs {
+ margin-top: 5px;
+}
+
+.daily-feeds .CTAs a {
+ font-size: 0.7em;
+ padding: 3px 8px;
+ margin-right: 5px;
+}
+
+.daily-feeds .CTAs a i {
+ margin-right: 5px;
+}
+
+.daily-feeds .quote {
+ background: #fafafa;
+ margin-top: 5px;
+ border-radius: 0;
+ padding: 15px;
+ margin-left: 60px;
+}
+
+.daily-feeds .quote small {
+ font-size: 0.75em;
+ color: #777;
+}
+
+.daily-feeds .date {
+ font-size: 0.9em;
+ color: #aaa;
+}
+
+/*====== Recent Activities Box ======*/
+.recent-activities .item {
+ padding: 0 15px;
+ border-bottom: 1px solid #eee;
+}
+
+.recent-activities .item div[class*='col-'] {
+ padding: 15px;
+}
+
+.recent-activities h5 {
+ font-weight: 400;
+ color: #333;
+}
+
+.recent-activities p {
+ font-size: 0.75em;
+ color: #999;
+}
+
+.recent-activities .icon {
+ width: 35px;
+ height: 35px;
+ line-height: 35px;
+ background: #f5f5f5;
+ text-align: center;
+ display: inline-block;
+}
+
+.recent-activities .date {
+ font-size: 0.75em;
+ color: #999;
+ padding: 10px;
+}
+
+.recent-activities .date-holder {
+ padding: 0 !important;
+ border-right: 1px solid #eee;
+}
+
+/*
+* ==========================================================
+* FOOTER
+* ==========================================================
+*/
+footer.main-footer {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ background: #2f333e;
+ color: #fff;
+ padding: 20px 10px;
+}
+
+footer.main-footer a {
+ color: inherit;
+}
+
+footer.main-footer p {
+ margin: 0;
+ font-size: 0.8em;
+}
+
+/* FOOTER MEDIAQUERIES --------------------------------- */
+@media (max-width: 575px) {
+ footer.main-footer div[class*='col'] {
+ text-align: center !important;
+ }
+}
+
+section.charts div[class*="col-"] {
+ margin-bottom: 30px;
+}
+
+section.charts header {
+ padding: 30px 0;
+}
+
+section.charts div[class*="col-"] {
+ margin-bottom: 0;
+}
+
+/*
+* ==========================================================
+* LINE CHART
+* ==========================================================
+*/
+.line-chart-example .card-block {
+ padding: 20px;
+}
+
+/*
+* ==========================================================
+* POLAR CHART
+* ==========================================================
+*/
+/*
+* ==========================================================
+* POLAR CHART
+* ==========================================================
+*/
+.polar-chart-example canvas {
+ max-width: 350px;
+ max-height: 350px;
+ margin: 20px auto;
+}
+
+/*
+* ==========================================================
+* PIE CHART
+* ==========================================================
+*/
+.pie-chart-example canvas {
+ max-width: 350px;
+ max-height: 350px;
+ margin: 15px auto;
+}
+
+/*
+* ==========================================================
+* RADAR CHART
+* ==========================================================
+*/
+.radar-chart-example canvas {
+ max-width: 350px;
+ max-height: 350px;
+ margin: 20px auto;
+}
+
+/* FORMS MEDIAQUERIES -------------------------- */
+@media (max-width: 991px) {
+ section.charts h2 {
+ font-size: 1rem;
+ }
+}
+
+/*
+* ==========================================================
+* FORMS
+* ==========================================================
+*/
+.form-control {
+ height: calc(2.25rem + 2px);
+ border: 1px solid #dee2e6;
+ border-radius: 0;
+ padding: 0.375rem 0.75rem;
+}
+
+.form-control::-moz-placeholder {
+ font-size: 0.9em;
+ font-weight: 300;
+ font-family: "Poppins", sans-serif;
+ color: #aaa;
+}
+
+.form-control::-webkit-input-placeholder {
+ font-size: 0.9em;
+ font-weight: 300;
+ font-family: "Poppins", sans-serif;
+ color: #aaa;
+}
+
+.form-control:-ms-input-placeholder {
+ font-size: 0.9em;
+ font-weight: 300;
+ font-family: "Poppins", sans-serif;
+ color: #aaa;
+}
+
+.form-control:focus {
+ color: #495057;
+ background-color: #fff;
+ border-color: #796AEE;
+ outline: none;
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25);
+}
+
+.form-control-sm {
+ height: calc(1.8125rem + 2px);
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.5;
+}
+
+.form-control-lg {
+ height: calc(2.875rem + 2px);
+ padding: 0.5rem 1rem;
+ font-size: 1.25rem;
+ line-height: 1.5;
+}
+
+textarea.form-control {
+ height: auto;
+}
+
+select.form-control[size], select.form-control[multiple] {
+ height: auto;
+}
+
+select.form-control option {
+ color: #999;
+}
+
+.input-group .dropdown-menu {
+ padding: 15px;
+ color: #777;
+ border-radius: 0;
+}
+
+.input-group .dropdown-menu a {
+ padding: 5px 0;
+ color: inherit;
+ text-decoration: none;
+}
+
+.input-group .dropdown-menu a:hover {
+ color: #796AEE;
+ background: none;
+}
+
+.input-group-text {
+ color: #868e96;
+ background: #f8f9fa;
+ border-color: #dee2e6;
+ border-radius: 0;
+}
+
+.input-group-text .checkbox-template, .input-group-text .radio-template {
+ -webkit-transform: none;
+ transform: none;
+}
+
+.form-control-label {
+ font-size: .9rem;
+ color: #777;
+}
+
+button, input[type='submit'] {
+ cursor: pointer;
+ font-family: inherit;
+ font-weight: 300 !important;
+}
+
+.input-group .dropdown-toggle {
+ background: #f5f5f5;
+ color: #777;
+}
+
+.checkbox-template, .radio-template {
+ -webkit-transform: translateY(3px);
+ transform: translateY(3px);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ cursor: pointer;
+ position: relative;
+}
+
+.checkbox-template + label, .radio-template + label {
+ margin-left: 10px;
+}
+
+.checkbox-template::before, .radio-template::before {
+ margin-right: 10px;
+ content: '';
+ display: inline-block;
+ -webkit-transform: translate(-2px, -2px);
+ transform: translate(-2px, -2px);
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+ text-align: center;
+ background: #dae2e7;
+ -webkit-transition: all 0.2s;
+ transition: all 0.2s;
+}
+
+.checkbox-template::after, .radio-template::after {
+ content: '\f00c';
+ width: 12px;
+ height: 12px;
+ line-height: 12px;
+ text-align: center;
+ display: block;
+ font-family: 'FontAwesome';
+ position: absolute;
+ top: 1px;
+ left: 1px;
+ font-size: 0.7em;
+ opacity: 0;
+ -webkit-transition: all 0.2s;
+ transition: all 0.2s;
+ color: #fff;
+}
+
+.checkbox-template:checked::before, .radio-template:checked::before {
+ background: #796AEE;
+}
+
+.checkbox-template:checked::after, .radio-template:checked::after {
+ opacity: 1;
+}
+
+.radio-template::before {
+ border-radius: 50%;
+ -webkit-transform: translate(-3px, -3px);
+ transform: translate(-3px, -3px);
+}
+
+.radio-template::after {
+ width: 6px;
+ height: 6px;
+ line-height: 6px;
+ text-align: center;
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ border-radius: 50%;
+ content: '';
+}
+
+.radio-template:checked::after {
+ background: #fff;
+}
+
+input.input-material {
+ width: 100%;
+ border: none;
+ border-bottom: 1px solid #eee;
+ padding: 10px 0;
+}
+
+input.input-material.is-invalid {
+ border-color: #dc3545 !important;
+}
+
+input.input-material:focus {
+ border-color: #796AEE;
+}
+
+input.input-material ~ label {
+ color: #aaa;
+ position: absolute;
+ top: 14px;
+ left: 0;
+ cursor: text;
+ -webkit-transition: all 0.2s;
+ transition: all 0.2s;
+ font-weight: 300;
+}
+
+input.input-material ~ label.active {
+ font-size: 0.8rem;
+ top: -10px;
+ color: #796AEE;
+}
+
+input.input-material.is-invalid ~ label {
+ color: #dc3545;
+}
+
+.form-group-material {
+ position: relative;
+ margin-bottom: 30px;
+}
+
+.modal-content {
+ border-radius: 0;
+}
+
+.i-checks {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+}
+
+/*
+* ==========================================================
+* FORM PAGE
+* ==========================================================
+*/
+.forms p {
+ font-size: 0.9em;
+ color: #555;
+}
+
+.forms form small {
+ font-size: 0.8em;
+ color: #999;
+ font-weight: 300;
+}
+
+.forms .line {
+ width: 100%;
+ height: 1px;
+ border-bottom: 1px dashed #eee;
+ margin: 30px 0;
+}
+
+/*
+
+=====================
+STYLE SWITCHER FOR DEMO
+=====================
+
+*/
+#style-switch-button {
+ position: fixed;
+ top: 120px;
+ right: 0px;
+ border-radius: 0;
+ z-index: 12;
+}
+
+#style-switch {
+ width: 300px;
+ padding: 20px;
+ position: fixed;
+ top: 160px;
+ right: 0;
+ background: #fff;
+ border: solid 1px #ced4da;
+ z-index: 12;
+}
+
+#style-switch h4 {
+ color: #495057;
+}
+
+/* =========================================
+ THEMING OF BOOTSTRAP COMPONENTS
+ ========================================= */
+/*
+ * 1. NAVBAR
+ */
+.navbar {
+ padding: 0.5rem 1rem;
+}
+
+.navbar-brand {
+ display: inline-block;
+ padding-top: 0.3125rem;
+ padding-bottom: 0.3125rem;
+ margin-right: 1rem;
+ font-size: 1.25rem;
+}
+
+.navbar-toggler {
+ padding: 0.25rem 0.75rem;
+ font-size: 1.25rem;
+ line-height: 1;
+ border: 1px solid transparent;
+ border-radius: 0.25rem;
+}
+
+.navbar-light .navbar-brand {
+ color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover {
+ color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-nav .nav-link {
+ color: rgba(0, 0, 0, 0.5);
+}
+
+.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover {
+ color: rgba(0, 0, 0, 0.7);
+}
+
+.navbar-light .navbar-nav .nav-link.disabled {
+ color: rgba(0, 0, 0, 0.3);
+}
+
+.navbar-light .navbar-nav .show > .nav-link,
+.navbar-light .navbar-nav .active > .nav-link,
+.navbar-light .navbar-nav .nav-link.show,
+.navbar-light .navbar-nav .nav-link.active {
+ color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-toggler {
+ color: rgba(0, 0, 0, 0.5);
+ border-color: rgba(0, 0, 0, 0.1);
+}
+
+.navbar-light .navbar-toggler-icon {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
+}
+
+.navbar-light .navbar-text {
+ color: rgba(0, 0, 0, 0.5);
+}
+
+.navbar-dark .navbar-brand {
+ color: #fff;
+}
+
+.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover {
+ color: #fff;
+}
+
+.navbar-dark .navbar-nav .nav-link {
+ color: rgba(255, 255, 255, 0.5);
+}
+
+.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover {
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.navbar-dark .navbar-nav .nav-link.disabled {
+ color: rgba(255, 255, 255, 0.25);
+}
+
+.navbar-dark .navbar-nav .show > .nav-link,
+.navbar-dark .navbar-nav .active > .nav-link,
+.navbar-dark .navbar-nav .nav-link.show,
+.navbar-dark .navbar-nav .nav-link.active {
+ color: #fff;
+}
+
+.navbar-dark .navbar-toggler {
+ color: rgba(255, 255, 255, 0.5);
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.navbar-dark .navbar-toggler-icon {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
+}
+
+.navbar-dark .navbar-text {
+ color: rgba(255, 255, 255, 0.5);
+}
+
+/*
+ * 2. BUTTONS
+ */
+.btn {
+ font-weight: 400;
+ border: 1px solid transparent;
+ padding: 0.375rem 0.75rem;
+ font-size: 1rem;
+ line-height: 1.5;
+ border-radius: 0.25rem;
+ -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+
+.btn:focus, .btn.focus {
+ outline: 0;
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25);
+}
+
+.btn.disabled, .btn:disabled {
+ opacity: .65;
+}
+
+.btn:not([disabled]):not(.disabled):active, .btn:not([disabled]):not(.disabled).active {
+ background-image: none;
+}
+
+.btn-primary {
+ color: color-yiq(#796AEE);
+ background-color: #796AEE;
+ border-color: #796AEE;
+}
+
+.btn-primary:hover {
+ color: color-yiq(#5a48ea);
+ background-color: #5a48ea;
+ border-color: #503ce9;
+}
+
+.btn-primary:focus, .btn-primary.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+}
+
+.btn-primary.disabled, .btn-primary:disabled {
+ color: color-yiq(#796AEE);
+ background-color: #796AEE;
+ border-color: #796AEE;
+}
+
+.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,
+.show > .btn-primary.dropdown-toggle {
+ color: color-yiq(#503ce9);
+ background-color: #503ce9;
+ border-color: #4631e7;
+}
+
+.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-primary.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+}
+
+.btn-secondary {
+ color: color-yiq(#868e96);
+ background-color: #868e96;
+ border-color: #868e96;
+}
+
+.btn-secondary:hover {
+ color: color-yiq(#727b84);
+ background-color: #727b84;
+ border-color: #6c757d;
+}
+
+.btn-secondary:focus, .btn-secondary.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+}
+
+.btn-secondary.disabled, .btn-secondary:disabled {
+ color: color-yiq(#868e96);
+ background-color: #868e96;
+ border-color: #868e96;
+}
+
+.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,
+.show > .btn-secondary.dropdown-toggle {
+ color: color-yiq(#6c757d);
+ background-color: #6c757d;
+ border-color: #666e76;
+}
+
+.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-secondary.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+}
+
+.btn-success {
+ color: color-yiq(#28a745);
+ background-color: #28a745;
+ border-color: #28a745;
+}
+
+.btn-success:hover {
+ color: color-yiq(#218838);
+ background-color: #218838;
+ border-color: #1e7e34;
+}
+
+.btn-success:focus, .btn-success.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-success.disabled, .btn-success:disabled {
+ color: color-yiq(#28a745);
+ background-color: #28a745;
+ border-color: #28a745;
+}
+
+.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,
+.show > .btn-success.dropdown-toggle {
+ color: color-yiq(#1e7e34);
+ background-color: #1e7e34;
+ border-color: #1c7430;
+}
+
+.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,
+.show > .btn-success.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-info {
+ color: color-yiq(#17a2b8);
+ background-color: #17a2b8;
+ border-color: #17a2b8;
+}
+
+.btn-info:hover {
+ color: color-yiq(#138496);
+ background-color: #138496;
+ border-color: #117a8b;
+}
+
+.btn-info:focus, .btn-info.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-info.disabled, .btn-info:disabled {
+ color: color-yiq(#17a2b8);
+ background-color: #17a2b8;
+ border-color: #17a2b8;
+}
+
+.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,
+.show > .btn-info.dropdown-toggle {
+ color: color-yiq(#117a8b);
+ background-color: #117a8b;
+ border-color: #10707f;
+}
+
+.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,
+.show > .btn-info.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-warning {
+ color: color-yiq(#ffc107);
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+
+.btn-warning:hover {
+ color: color-yiq(#e0a800);
+ background-color: #e0a800;
+ border-color: #d39e00;
+}
+
+.btn-warning:focus, .btn-warning.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.btn-warning.disabled, .btn-warning:disabled {
+ color: color-yiq(#ffc107);
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+
+.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,
+.show > .btn-warning.dropdown-toggle {
+ color: color-yiq(#d39e00);
+ background-color: #d39e00;
+ border-color: #c69500;
+}
+
+.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,
+.show > .btn-warning.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.btn-danger {
+ color: color-yiq(#dc3545);
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-danger:hover {
+ color: color-yiq(#c82333);
+ background-color: #c82333;
+ border-color: #bd2130;
+}
+
+.btn-danger:focus, .btn-danger.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-danger.disabled, .btn-danger:disabled {
+ color: color-yiq(#dc3545);
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,
+.show > .btn-danger.dropdown-toggle {
+ color: color-yiq(#bd2130);
+ background-color: #bd2130;
+ border-color: #b21f2d;
+}
+
+.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,
+.show > .btn-danger.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-light {
+ color: color-yiq(#f8f9fa);
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+
+.btn-light:hover {
+ color: color-yiq(#e2e6ea);
+ background-color: #e2e6ea;
+ border-color: #dae0e5;
+}
+
+.btn-light:focus, .btn-light.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-light.disabled, .btn-light:disabled {
+ color: color-yiq(#f8f9fa);
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+
+.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,
+.show > .btn-light.dropdown-toggle {
+ color: color-yiq(#dae0e5);
+ background-color: #dae0e5;
+ border-color: #d3d9df;
+}
+
+.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,
+.show > .btn-light.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-dark {
+ color: color-yiq(#343a40);
+ background-color: #343a40;
+ border-color: #343a40;
+}
+
+.btn-dark:hover {
+ color: color-yiq(#23272b);
+ background-color: #23272b;
+ border-color: #1d2124;
+}
+
+.btn-dark:focus, .btn-dark.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-dark.disabled, .btn-dark:disabled {
+ color: color-yiq(#343a40);
+ background-color: #343a40;
+ border-color: #343a40;
+}
+
+.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,
+.show > .btn-dark.dropdown-toggle {
+ color: color-yiq(#1d2124);
+ background-color: #1d2124;
+ border-color: #171a1d;
+}
+
+.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,
+.show > .btn-dark.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-default {
+ color: color-yiq(#ced4da);
+ background-color: #ced4da;
+ border-color: #ced4da;
+}
+
+.btn-default:hover {
+ color: color-yiq(#b8c1ca);
+ background-color: #b8c1ca;
+ border-color: #b1bbc4;
+}
+
+.btn-default:focus, .btn-default.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5);
+}
+
+.btn-default.disabled, .btn-default:disabled {
+ color: color-yiq(#ced4da);
+ background-color: #ced4da;
+ border-color: #ced4da;
+}
+
+.btn-default:not(:disabled):not(.disabled):active, .btn-default:not(:disabled):not(.disabled).active,
+.show > .btn-default.dropdown-toggle {
+ color: color-yiq(#b1bbc4);
+ background-color: #b1bbc4;
+ border-color: #aab4bf;
+}
+
+.btn-default:not(:disabled):not(.disabled):active:focus, .btn-default:not(:disabled):not(.disabled).active:focus,
+.show > .btn-default.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5);
+}
+
+.btn-outline-primary {
+ color: #796AEE;
+ background-color: transparent;
+ background-image: none;
+ border-color: #796AEE;
+}
+
+.btn-outline-primary:hover {
+ color: #fff;
+ background-color: #796AEE;
+ border-color: #796AEE;
+}
+
+.btn-outline-primary:focus, .btn-outline-primary.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+}
+
+.btn-outline-primary.disabled, .btn-outline-primary:disabled {
+ color: #796AEE;
+ background-color: transparent;
+}
+
+.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,
+.show > .btn-outline-primary.dropdown-toggle {
+ color: color-yiq(#796AEE);
+ background-color: #796AEE;
+ border-color: #796AEE;
+}
+
+.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-primary.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+}
+
+.btn-outline-secondary {
+ color: #868e96;
+ background-color: transparent;
+ background-image: none;
+ border-color: #868e96;
+}
+
+.btn-outline-secondary:hover {
+ color: #fff;
+ background-color: #868e96;
+ border-color: #868e96;
+}
+
+.btn-outline-secondary:focus, .btn-outline-secondary.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+}
+
+.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
+ color: #868e96;
+ background-color: transparent;
+}
+
+.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,
+.show > .btn-outline-secondary.dropdown-toggle {
+ color: color-yiq(#868e96);
+ background-color: #868e96;
+ border-color: #868e96;
+}
+
+.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-secondary.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5);
+}
+
+.btn-outline-success {
+ color: #28a745;
+ background-color: transparent;
+ background-image: none;
+ border-color: #28a745;
+}
+
+.btn-outline-success:hover {
+ color: #fff;
+ background-color: #28a745;
+ border-color: #28a745;
+}
+
+.btn-outline-success:focus, .btn-outline-success.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-outline-success.disabled, .btn-outline-success:disabled {
+ color: #28a745;
+ background-color: transparent;
+}
+
+.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,
+.show > .btn-outline-success.dropdown-toggle {
+ color: color-yiq(#28a745);
+ background-color: #28a745;
+ border-color: #28a745;
+}
+
+.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-success.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-outline-info {
+ color: #17a2b8;
+ background-color: transparent;
+ background-image: none;
+ border-color: #17a2b8;
+}
+
+.btn-outline-info:hover {
+ color: #fff;
+ background-color: #17a2b8;
+ border-color: #17a2b8;
+}
+
+.btn-outline-info:focus, .btn-outline-info.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-outline-info.disabled, .btn-outline-info:disabled {
+ color: #17a2b8;
+ background-color: transparent;
+}
+
+.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,
+.show > .btn-outline-info.dropdown-toggle {
+ color: color-yiq(#17a2b8);
+ background-color: #17a2b8;
+ border-color: #17a2b8;
+}
+
+.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-info.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-outline-warning {
+ color: #ffc107;
+ background-color: transparent;
+ background-image: none;
+ border-color: #ffc107;
+}
+
+.btn-outline-warning:hover {
+ color: #fff;
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+
+.btn-outline-warning:focus, .btn-outline-warning.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.btn-outline-warning.disabled, .btn-outline-warning:disabled {
+ color: #ffc107;
+ background-color: transparent;
+}
+
+.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,
+.show > .btn-outline-warning.dropdown-toggle {
+ color: color-yiq(#ffc107);
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+
+.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-warning.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.btn-outline-danger {
+ color: #dc3545;
+ background-color: transparent;
+ background-image: none;
+ border-color: #dc3545;
+}
+
+.btn-outline-danger:hover {
+ color: #fff;
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-outline-danger:focus, .btn-outline-danger.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-outline-danger.disabled, .btn-outline-danger:disabled {
+ color: #dc3545;
+ background-color: transparent;
+}
+
+.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,
+.show > .btn-outline-danger.dropdown-toggle {
+ color: color-yiq(#dc3545);
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-danger.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-outline-light {
+ color: #f8f9fa;
+ background-color: transparent;
+ background-image: none;
+ border-color: #f8f9fa;
+}
+
+.btn-outline-light:hover {
+ color: #fff;
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+
+.btn-outline-light:focus, .btn-outline-light.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-outline-light.disabled, .btn-outline-light:disabled {
+ color: #f8f9fa;
+ background-color: transparent;
+}
+
+.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,
+.show > .btn-outline-light.dropdown-toggle {
+ color: color-yiq(#f8f9fa);
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+
+.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-light.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-outline-dark {
+ color: #343a40;
+ background-color: transparent;
+ background-image: none;
+ border-color: #343a40;
+}
+
+.btn-outline-dark:hover {
+ color: #fff;
+ background-color: #343a40;
+ border-color: #343a40;
+}
+
+.btn-outline-dark:focus, .btn-outline-dark.focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-outline-dark.disabled, .btn-outline-dark:disabled {
+ color: #343a40;
+ background-color: transparent;
+}
+
+.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,
+.show > .btn-outline-dark.dropdown-toggle {
+ color: color-yiq(#343a40);
+ background-color: #343a40;
+ border-color: #343a40;
+}
+
+.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-dark.dropdown-toggle:focus {
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-lg {
+ padding: 0.5rem 1rem;
+ font-size: 1.25rem;
+ line-height: 1.5;
+ border-radius: 0.3rem;
+}
+
+.btn-sm {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ border-radius: 0.2rem;
+}
+
+/*
+ * 3. TYPE
+ */
+body {
+ font-family: "Poppins", sans-serif;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ background-color: #fff;
+}
+
+a {
+ color: #796AEE;
+ text-decoration: none;
+}
+
+a:focus, a:hover {
+ color: #3b25e6;
+ text-decoration: underline;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+ margin-bottom: 0.5rem;
+ font-family: inherit;
+ font-weight: 700;
+ line-height: 1.1;
+ color: inherit;
+}
+
+h1,
+.h1 {
+ font-size: 1.5rem;
+}
+
+h2,
+.h2 {
+ font-size: 1.3rem;
+}
+
+h3,
+.h3 {
+ font-size: 1.2rem;
+}
+
+h4,
+.h4 {
+ font-size: 1.1rem;
+}
+
+h5,
+.h5 {
+ font-size: 1rem;
+}
+
+h6,
+.h6 {
+ font-size: 0.95rem;
+}
+
+.lead {
+ font-size: 1.25rem;
+ font-weight: 300;
+}
+
+.display-1 {
+ font-size: 6rem;
+ font-weight: 300;
+ line-height: 1.1;
+}
+
+.display-2 {
+ font-size: 5.5rem;
+ font-weight: 300;
+ line-height: 1.1;
+}
+
+.display-3 {
+ font-size: 4.5rem;
+ font-weight: 300;
+ line-height: 1.1;
+}
+
+.display-4 {
+ font-size: 3.5rem;
+ font-weight: 300;
+ line-height: 1.1;
+}
+
+hr {
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+small,
+.small {
+ font-size: 80%;
+ font-weight: 400;
+}
+
+mark,
+.mark {
+ padding: 0.2em;
+ background-color: #fcf8e3;
+}
+
+.blockquote {
+ padding: 0.5rem 1rem;
+ margin-bottom: 2rem;
+ font-size: 1.25rem;
+ border-left: 5px solid #796AEE;
+}
+
+.blockquote-footer {
+ color: #868e96;
+}
+
+.blockquote-footer::before {
+ content: "\2014 \00A0";
+}
+
+.text-primary {
+ color: #796AEE !important;
+}
+
+a.text-primary:focus, a.text-primary:hover {
+ color: #503ce9 !important;
+}
+
+/*
+ * 4. PAGINATION
+ */
+.page-item:first-child .page-link {
+ border-top-left-radius: 0.25rem;
+ border-bottom-left-radius: 0.25rem;
+}
+
+.page-item:last-child .page-link {
+ border-top-right-radius: 0.25rem;
+ border-bottom-right-radius: 0.25rem;
+}
+
+.page-item.active .page-link {
+ color: #fff;
+ background-color: #796AEE;
+ border-color: #796AEE;
+}
+
+.page-item.disabled .page-link {
+ color: #868e96;
+ background-color: #fff;
+ border-color: #ddd;
+}
+
+.page-link {
+ padding: 0.5rem 0.75rem;
+ margin-left: -1px;
+ line-height: 1.25;
+ color: #796AEE;
+ background-color: #fff;
+ border: 1px solid #ddd;
+}
+
+.page-link:hover {
+ z-index: 2;
+ color: #3b25e6;
+ background-color: #e9ecef;
+ border-color: #ddd;
+}
+
+.page-link:focus {
+ z-index: 2;
+ outline: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ text-decoration: none;
+}
+
+.pagination-lg .page-link {
+ padding: 0.75rem 1.5rem;
+ font-size: 1.25rem;
+ line-height: 1.5;
+}
+
+.pagination-lg .page-item:first-child .page-link {
+ border-top-left-radius: 0.3rem;
+ border-bottom-left-radius: 0.3rem;
+}
+
+.pagination-lg .page-item:last-child .page-link {
+ border-top-right-radius: 0.3rem;
+ border-bottom-right-radius: 0.3rem;
+}
+
+.pagination-sm .page-link {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.5;
+}
+
+.pagination-sm .page-item:first-child .page-link {
+ border-top-left-radius: 0.2rem;
+ border-bottom-left-radius: 0.2rem;
+}
+
+.pagination-sm .page-item:last-child .page-link {
+ border-top-right-radius: 0.2rem;
+ border-bottom-right-radius: 0.2rem;
+}
+
+/*
+* 5. UTILITIES
+*/
+.bg-primary {
+ background-color: #796AEE !important;
+}
+
+a.bg-primary:focus, a.bg-primary:hover {
+ background-color: #503ce9 !important;
+}
+
+.bg-secondary {
+ background-color: #868e96 !important;
+}
+
+a.bg-secondary:focus, a.bg-secondary:hover {
+ background-color: #6c757d !important;
+}
+
+.bg-success {
+ background-color: #28a745 !important;
+}
+
+a.bg-success:focus, a.bg-success:hover {
+ background-color: #1e7e34 !important;
+}
+
+.bg-info {
+ background-color: #17a2b8 !important;
+}
+
+a.bg-info:focus, a.bg-info:hover {
+ background-color: #117a8b !important;
+}
+
+.bg-warning {
+ background-color: #ffc107 !important;
+}
+
+a.bg-warning:focus, a.bg-warning:hover {
+ background-color: #d39e00 !important;
+}
+
+.bg-danger {
+ background-color: #dc3545 !important;
+}
+
+a.bg-danger:focus, a.bg-danger:hover {
+ background-color: #bd2130 !important;
+}
+
+.bg-light {
+ background-color: #f8f9fa !important;
+}
+
+a.bg-light:focus, a.bg-light:hover {
+ background-color: #dae0e5 !important;
+}
+
+.bg-dark {
+ background-color: #343a40 !important;
+}
+
+a.bg-dark:focus, a.bg-dark:hover {
+ background-color: #1d2124 !important;
+}
+
+.border-primary {
+ border-color: #796AEE !important;
+}
+
+.border-secondary {
+ border-color: #868e96 !important;
+}
+
+.border-success {
+ border-color: #28a745 !important;
+}
+
+.border-info {
+ border-color: #17a2b8 !important;
+}
+
+.border-warning {
+ border-color: #ffc107 !important;
+}
+
+.border-danger {
+ border-color: #dc3545 !important;
+}
+
+.border-light {
+ border-color: #f8f9fa !important;
+}
+
+.border-dark {
+ border-color: #343a40 !important;
+}
+
+.text-primary {
+ color: #796AEE !important;
+}
+
+a.text-primary:focus, a.text-primary:hover {
+ color: #503ce9 !important;
+}
+
+.text-secondary {
+ color: #868e96 !important;
+}
+
+a.text-secondary:focus, a.text-secondary:hover {
+ color: #6c757d !important;
+}
+
+.text-success {
+ color: #28a745 !important;
+}
+
+a.text-success:focus, a.text-success:hover {
+ color: #1e7e34 !important;
+}
+
+.text-info {
+ color: #17a2b8 !important;
+}
+
+a.text-info:focus, a.text-info:hover {
+ color: #117a8b !important;
+}
+
+.text-warning {
+ color: #ffc107 !important;
+}
+
+a.text-warning:focus, a.text-warning:hover {
+ color: #d39e00 !important;
+}
+
+.text-danger {
+ color: #dc3545 !important;
+}
+
+a.text-danger:focus, a.text-danger:hover {
+ color: #bd2130 !important;
+}
+
+.text-light {
+ color: #f8f9fa !important;
+}
+
+a.text-light:focus, a.text-light:hover {
+ color: #dae0e5 !important;
+}
+
+.text-dark {
+ color: #343a40 !important;
+}
+
+a.text-dark:focus, a.text-dark:hover {
+ color: #1d2124 !important;
+}
+
+.badge-primary {
+ color: color-yiq(#796AEE);
+ background-color: #796AEE;
+}
+
+.badge-primary[href]:focus, .badge-primary[href]:hover {
+ color: color-yiq(#796AEE);
+ text-decoration: none;
+ background-color: #503ce9;
+}
+
+.badge-secondary {
+ color: color-yiq(#868e96);
+ background-color: #868e96;
+}
+
+.badge-secondary[href]:focus, .badge-secondary[href]:hover {
+ color: color-yiq(#868e96);
+ text-decoration: none;
+ background-color: #6c757d;
+}
+
+.badge-success {
+ color: color-yiq(#28a745);
+ background-color: #28a745;
+}
+
+.badge-success[href]:focus, .badge-success[href]:hover {
+ color: color-yiq(#28a745);
+ text-decoration: none;
+ background-color: #1e7e34;
+}
+
+.badge-info {
+ color: color-yiq(#17a2b8);
+ background-color: #17a2b8;
+}
+
+.badge-info[href]:focus, .badge-info[href]:hover {
+ color: color-yiq(#17a2b8);
+ text-decoration: none;
+ background-color: #117a8b;
+}
+
+.badge-warning {
+ color: color-yiq(#ffc107);
+ background-color: #ffc107;
+}
+
+.badge-warning[href]:focus, .badge-warning[href]:hover {
+ color: color-yiq(#ffc107);
+ text-decoration: none;
+ background-color: #d39e00;
+}
+
+.badge-danger {
+ color: color-yiq(#dc3545);
+ background-color: #dc3545;
+}
+
+.badge-danger[href]:focus, .badge-danger[href]:hover {
+ color: color-yiq(#dc3545);
+ text-decoration: none;
+ background-color: #bd2130;
+}
+
+.badge-light {
+ color: color-yiq(#f8f9fa);
+ background-color: #f8f9fa;
+}
+
+.badge-light[href]:focus, .badge-light[href]:hover {
+ color: color-yiq(#f8f9fa);
+ text-decoration: none;
+ background-color: #dae0e5;
+}
+
+.badge-dark {
+ color: color-yiq(#343a40);
+ background-color: #343a40;
+}
+
+.badge-dark[href]:focus, .badge-dark[href]:hover {
+ color: color-yiq(#343a40);
+ text-decoration: none;
+ background-color: #1d2124;
+}
+
+/*
+* 6.CODE
+*/
+code {
+ padding: 0.2rem 0.4rem;
+ font-size: 90%;
+ color: #bd4147;
+ background-color: #f8f9fa;
+ border-radius: 0.25rem;
+}
+
+a > code {
+ padding: 0;
+ color: inherit;
+ background-color: inherit;
+}
+
+/*
+* 7. NAV
+*/
+.nav-link {
+ padding: 0.5rem 1rem;
+}
+
+.nav-link.disabled {
+ color: #868e96;
+}
+
+.nav-tabs .nav-item {
+ margin-bottom: -1px;
+}
+
+.nav-tabs .nav-link {
+ border: 1px solid transparent;
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+}
+
+.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {
+ border-color: #e9ecef #e9ecef #dee2e6;
+}
+
+.nav-tabs .nav-link.disabled {
+ color: #868e96;
+}
+
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-item.show .nav-link {
+ color: #495057;
+ background-color: #fff;
+}
+
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+}
+
+.nav-pills .nav-link {
+ border-radius: 0.25rem;
+}
+
+.nav-pills .nav-link.active,
+.nav-pills .show > .nav-link {
+ color: #fff;
+ background-color: #796AEE;
+}
+
+/*
+* 8. CARD
+*/
+.card {
+ background-color: #fff;
+ border: 0 solid #eee;
+ border-radius: 0;
+}
+
+.card > .list-group:first-child .list-group-item:first-child {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.card > .list-group:last-child .list-group-item:last-child {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.card-body {
+ padding: 1.25rem;
+}
+
+.card-title {
+ margin-bottom: 1rem;
+}
+
+.card-subtitle {
+ margin-top: -0.5rem;
+}
+
+.card-link + .card-link {
+ margin-left: 1.25rem;
+}
+
+.card-header {
+ padding: 1rem 1.25rem;
+ background-color: #fff;
+ border-bottom: 1px solid #eee;
+}
+
+.card-header:first-child {
+ border-radius: 0 0 0 0;
+}
+
+.card-header-transparent {
+ background-color: rgba(0, 0, 0, 0.3);
+ border-bottom: none;
+}
+
+.card-footer {
+ padding: 1rem 1.25rem;
+ background-color: #f8f9fa;
+ border-top: 1px solid #eee;
+}
+
+.card-footer:last-child {
+ border-radius: 0 0 0 0;
+}
+
+.card-header-tabs {
+ margin-right: -0.625rem;
+ margin-bottom: -1rem;
+ margin-left: -0.625rem;
+ border-bottom: 0;
+}
+
+.card-header-pills {
+ margin-right: -0.625rem;
+ margin-left: -0.625rem;
+}
+
+.card-img-overlay {
+ padding: 1.25rem;
+}
+
+.card-img-overlay-opacity {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.card-img {
+ border-radius: 0;
+}
+
+.card-img-top {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.card-img-bottom {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.card-deck .card {
+ margin-bottom: 15px;
+}
+
+@media (min-width: 576px) {
+ .card-deck {
+ margin-right: -15px;
+ margin-left: -15px;
+ }
+ .card-deck .card {
+ margin-right: 15px;
+ margin-left: 15px;
+ }
+}
+
+.custom-select {
+ height: calc(2.25rem + 2px);
+ padding: 0.375rem 1.75rem 0.375rem 0.75rem;
+ line-height: 1.5;
+ color: #495057;
+ vertical-align: middle;
+ background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center;
+ background-size: 8px 10px;
+ border: 1px solid #ced4da;
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.custom-select:focus {
+ border-color: #796AEE;
+ outline: 0;
+ -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+ box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5);
+}
+
+.custom-select:focus::-ms-value {
+ color: #495057;
+ background-color: #fff;
+}
+
+.custom-select[multiple], .custom-select[size]:not([size="1"]) {
+ height: auto;
+ padding-right: 0.75rem;
+ background-image: none;
+}
+
+.custom-select:disabled {
+ color: #868e96;
+ background-color: #e9ecef;
+}
+
+.custom-select::-ms-expand {
+ opacity: 0;
+}
+
+.custom-select-sm {
+ height: calc(1.8125rem + 2px);
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
+ font-size: 75%;
+}
+
+.custom-select-lg {
+ height: calc(2.875rem + 2px);
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
+ font-size: 125%;
+}
\ No newline at end of file
diff --git a/src/main/webapp/static/css/login.css b/src/main/webapp/static/css/login.css
new file mode 100644
index 0000000..f520ab5
--- /dev/null
+++ b/src/main/webapp/static/css/login.css
@@ -0,0 +1,120 @@
+/*
+* ==========================================================
+* LOGIN PAGE
+* ==========================================================
+*/
+.login-page {
+ position: relative;
+}
+
+.login-page::before {
+ content: '';
+ width: 100%;
+ height: 100%;
+ display: block;
+ z-index: -1;
+ background: url(../imgs/login.jpg);
+ background-size: cover;
+ -webkit-filter: blur(10px);
+ filter: blur(10px);
+ z-index: 1;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.login-page .container {
+ min-height: 100vh;
+ z-index: 999;
+ padding: 20px;
+ position: relative;
+}
+
+.login-page .form-holder {
+ width: 100%;
+ border-radius: 5px;
+ overflow: hidden;
+ margin-bottom: 50px;
+}
+
+.login-page .form-holder .info,
+.login-page .form-holder .form {
+ min-height: 70vh;
+ padding: 40px;
+ height: 100%;
+}
+
+.login-page .form-holder div[class*='col-'] {
+ padding: 0;
+}
+
+.login-page .form-holder .info {
+ background: rgba(121, 106, 238, 0.9);
+ color: #fff;
+}
+
+.login-page .form-holder .info h1 {
+ font-size: 2.5em;
+ font-weight: 600;
+}
+
+.login-page .form-holder .info p {
+ font-weight: 300;
+}
+
+.login-page .form-holder .form .form-group {
+ position: relative;
+ margin-bottom: 30px;
+}
+
+.login-page .form-holder .form .content {
+ width: 100%;
+}
+
+.login-page .form-holder .form form {
+ width: 100%;
+ max-width: 400px;
+}
+
+.login-page .form-holder .form #login,
+.login-page .form-holder .form #register {
+ margin-bottom: 20px;
+ cursor: pointer;
+}
+
+.login-page .form-holder .form a.forgot-pass,
+.login-page .form-holder .form a.signup {
+ font-size: 0.9em;
+ color: #85b4f2;
+}
+
+.login-page .form-holder .form small {
+ color: #aaa;
+}
+
+.login-page .form-holder .form .terms-conditions label {
+ cursor: pointer;
+ color: #aaa;
+ font-size: 0.9em;
+}
+
+.login-page .copyrights {
+ width: 100%;
+ z-index: 9999;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ color: #fff;
+}
+
+@media (max-width: 991px) {
+ .login-page .info,
+ .login-page .form {
+ min-height: auto !important;
+ }
+
+ .login-page .info {
+ padding-top: 100px !important;
+ padding-bottom: 100px !important;
+ }
+}
diff --git a/src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css b/src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css
new file mode 100644
index 0000000..78485fe
--- /dev/null
+++ b/src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Datetimepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */.datetimepicker{padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datetimepicker-inline{width:220px}.datetimepicker.datetimepicker-rtl{direction:rtl}.datetimepicker.datetimepicker-rtl table tr td span{float:right}.datetimepicker-dropdown,.datetimepicker-dropdown-left{top:0;left:0}[class*=" datetimepicker-dropdown"]:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute}[class*=" datetimepicker-dropdown"]:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute}[class*=" datetimepicker-dropdown-top"]:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);border-bottom:0}[class*=" datetimepicker-dropdown-top"]:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;border-bottom:0}.datetimepicker-dropdown-bottom-left:before{top:-7px;right:6px}.datetimepicker-dropdown-bottom-left:after{top:-6px;right:7px}.datetimepicker-dropdown-bottom-right:before{top:-7px;left:6px}.datetimepicker-dropdown-bottom-right:after{top:-6px;left:7px}.datetimepicker-dropdown-top-left:before{bottom:-7px;right:6px}.datetimepicker-dropdown-top-left:after{bottom:-6px;right:7px}.datetimepicker-dropdown-top-right:before{bottom:-7px;left:6px}.datetimepicker-dropdown-top-right:after{bottom:-6px;left:7px}.datetimepicker>div{display:none}.datetimepicker.minutes div.datetimepicker-minutes{display:block}.datetimepicker.hours div.datetimepicker-hours{display:block}.datetimepicker.days div.datetimepicker-days{display:block}.datetimepicker.months div.datetimepicker-months{display:block}.datetimepicker.years div.datetimepicker-years{display:block}.datetimepicker table{margin:0}.datetimepicker td,.datetimepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0}.table-striped .datetimepicker table tr td,.table-striped .datetimepicker table tr th{background-color:transparent}.datetimepicker table tr td.minute:hover{background:#eee;cursor:pointer}.datetimepicker table tr td.hour:hover{background:#eee;cursor:pointer}.datetimepicker table tr td.day:hover{background:#eee;cursor:pointer}.datetimepicker table tr td.old,.datetimepicker table tr td.new{color:#999}.datetimepicker table tr td.disabled,.datetimepicker table tr td.disabled:hover{background:0;color:#999;cursor:default}.datetimepicker table tr td.today,.datetimepicker table tr td.today:hover,.datetimepicker table tr td.today.disabled,.datetimepicker table tr td.today.disabled:hover{background-color:#fde19a;background-image:-moz-linear-gradient(top,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(top,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(top,#fdd49a,#fdf59a);background-image:-o-linear-gradient(top,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a',endColorstr='#fdf59a',GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.datetimepicker table tr td.today:hover,.datetimepicker table tr td.today:hover:hover,.datetimepicker table tr td.today.disabled:hover,.datetimepicker table tr td.today.disabled:hover:hover,.datetimepicker table tr td.today:active,.datetimepicker table tr td.today:hover:active,.datetimepicker table tr td.today.disabled:active,.datetimepicker table tr td.today.disabled:hover:active,.datetimepicker table tr td.today.active,.datetimepicker table tr td.today:hover.active,.datetimepicker table tr td.today.disabled.active,.datetimepicker table tr td.today.disabled:hover.active,.datetimepicker table tr td.today.disabled,.datetimepicker table tr td.today:hover.disabled,.datetimepicker table tr td.today.disabled.disabled,.datetimepicker table tr td.today.disabled:hover.disabled,.datetimepicker table tr td.today[disabled],.datetimepicker table tr td.today:hover[disabled],.datetimepicker table tr td.today.disabled[disabled],.datetimepicker table tr td.today.disabled:hover[disabled]{background-color:#fdf59a}.datetimepicker table tr td.today:active,.datetimepicker table tr td.today:hover:active,.datetimepicker table tr td.today.disabled:active,.datetimepicker table tr td.today.disabled:hover:active,.datetimepicker table tr td.today.active,.datetimepicker table tr td.today:hover.active,.datetimepicker table tr td.today.disabled.active,.datetimepicker table tr td.today.disabled:hover.active{background-color:#fbf069}.datetimepicker table tr td.active,.datetimepicker table tr td.active:hover,.datetimepicker table tr td.active.disabled,.datetimepicker table tr td.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-ms-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc',endColorstr='#0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.datetimepicker table tr td.active:hover,.datetimepicker table tr td.active:hover:hover,.datetimepicker table tr td.active.disabled:hover,.datetimepicker table tr td.active.disabled:hover:hover,.datetimepicker table tr td.active:active,.datetimepicker table tr td.active:hover:active,.datetimepicker table tr td.active.disabled:active,.datetimepicker table tr td.active.disabled:hover:active,.datetimepicker table tr td.active.active,.datetimepicker table tr td.active:hover.active,.datetimepicker table tr td.active.disabled.active,.datetimepicker table tr td.active.disabled:hover.active,.datetimepicker table tr td.active.disabled,.datetimepicker table tr td.active:hover.disabled,.datetimepicker table tr td.active.disabled.disabled,.datetimepicker table tr td.active.disabled:hover.disabled,.datetimepicker table tr td.active[disabled],.datetimepicker table tr td.active:hover[disabled],.datetimepicker table tr td.active.disabled[disabled],.datetimepicker table tr td.active.disabled:hover[disabled]{background-color:#04c}.datetimepicker table tr td.active:active,.datetimepicker table tr td.active:hover:active,.datetimepicker table tr td.active.disabled:active,.datetimepicker table tr td.active.disabled:hover:active,.datetimepicker table tr td.active.active,.datetimepicker table tr td.active:hover.active,.datetimepicker table tr td.active.disabled.active,.datetimepicker table tr td.active.disabled:hover.active{background-color:#039}.datetimepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datetimepicker .datetimepicker-hours span{height:26px;line-height:26px}.datetimepicker .datetimepicker-hours table tr td span.hour_am,.datetimepicker .datetimepicker-hours table tr td span.hour_pm{width:14.6%}.datetimepicker .datetimepicker-hours fieldset legend,.datetimepicker .datetimepicker-minutes fieldset legend{margin-bottom:inherit;line-height:30px}.datetimepicker .datetimepicker-minutes span{height:26px;line-height:26px}.datetimepicker table tr td span:hover{background:#eee}.datetimepicker table tr td span.disabled,.datetimepicker table tr td span.disabled:hover{background:0;color:#999;cursor:default}.datetimepicker table tr td span.active,.datetimepicker table tr td span.active:hover,.datetimepicker table tr td span.active.disabled,.datetimepicker table tr td span.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-ms-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc',endColorstr='#0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.datetimepicker table tr td span.active:hover,.datetimepicker table tr td span.active:hover:hover,.datetimepicker table tr td span.active.disabled:hover,.datetimepicker table tr td span.active.disabled:hover:hover,.datetimepicker table tr td span.active:active,.datetimepicker table tr td span.active:hover:active,.datetimepicker table tr td span.active.disabled:active,.datetimepicker table tr td span.active.disabled:hover:active,.datetimepicker table tr td span.active.active,.datetimepicker table tr td span.active:hover.active,.datetimepicker table tr td span.active.disabled.active,.datetimepicker table tr td span.active.disabled:hover.active,.datetimepicker table tr td span.active.disabled,.datetimepicker table tr td span.active:hover.disabled,.datetimepicker table tr td span.active.disabled.disabled,.datetimepicker table tr td span.active.disabled:hover.disabled,.datetimepicker table tr td span.active[disabled],.datetimepicker table tr td span.active:hover[disabled],.datetimepicker table tr td span.active.disabled[disabled],.datetimepicker table tr td span.active.disabled:hover[disabled]{background-color:#04c}.datetimepicker table tr td span.active:active,.datetimepicker table tr td span.active:hover:active,.datetimepicker table tr td span.active.disabled:active,.datetimepicker table tr td span.active.disabled:hover:active,.datetimepicker table tr td span.active.active,.datetimepicker table tr td span.active:hover.active,.datetimepicker table tr td span.active.disabled.active,.datetimepicker table tr td span.active.disabled:hover.active{background-color:#039}.datetimepicker table tr td span.old{color:#999}.datetimepicker th.switch{width:145px}.datetimepicker th span.glyphicon{pointer-events:none}.datetimepicker thead tr:first-child th,.datetimepicker tfoot th{cursor:pointer}.datetimepicker thead tr:first-child th:hover,.datetimepicker tfoot th:hover{background:#eee}.input-append.date .add-on i,.input-prepend.date .add-on i,.input-group.date .input-group-addon span{cursor:pointer;width:14px;height:14px}
\ No newline at end of file
diff --git a/src/main/webapp/static/imgs/default.png b/src/main/webapp/static/imgs/default.png
new file mode 100644
index 0000000000000000000000000000000000000000..17410cbdf7a8780dd6d135942c48103fed0c4fd6
GIT binary patch
literal 54350
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