Browse Source

公众号新项目

master
yangfuda 2 years ago
commit
850f6da0a4
100 changed files with 6252 additions and 0 deletions
  1. +32
    -0
      .gitignore
  2. +13
    -0
      .travis.yml
  3. +14
    -0
      Dockerfile
  4. +201
    -0
      LICENSE
  5. +43
    -0
      README.md
  6. +390
    -0
      db/mysql-0.7.0.sql
  7. +195
    -0
      pom.xml
  8. +18
    -0
      src/main/java/com/github/niefy/BootApplication.java
  9. +19
    -0
      src/main/java/com/github/niefy/common/annotation/SysLog.java
  10. +95
    -0
      src/main/java/com/github/niefy/common/aspect/SysLogAspect.java
  11. +52
    -0
      src/main/java/com/github/niefy/common/exception/RRException.java
  12. +62
    -0
      src/main/java/com/github/niefy/common/exception/RRExceptionHandler.java
  13. +34
    -0
      src/main/java/com/github/niefy/common/handler/JSONArrayTypeHandler.java
  14. +34
    -0
      src/main/java/com/github/niefy/common/handler/JSONObjectTypeHandler.java
  15. +12
    -0
      src/main/java/com/github/niefy/common/utils/ConfigConstant.java
  16. +122
    -0
      src/main/java/com/github/niefy/common/utils/Constant.java
  17. +64
    -0
      src/main/java/com/github/niefy/common/utils/CookieUtil.java
  18. +88
    -0
      src/main/java/com/github/niefy/common/utils/DateUtils.java
  19. +25
    -0
      src/main/java/com/github/niefy/common/utils/HttpContextUtils.java
  20. +48
    -0
      src/main/java/com/github/niefy/common/utils/IPUtils.java
  21. +20
    -0
      src/main/java/com/github/niefy/common/utils/Json.java
  22. +75
    -0
      src/main/java/com/github/niefy/common/utils/MD5Util.java
  23. +20
    -0
      src/main/java/com/github/niefy/common/utils/MapUtils.java
  24. +101
    -0
      src/main/java/com/github/niefy/common/utils/PageUtils.java
  25. +68
    -0
      src/main/java/com/github/niefy/common/utils/Query.java
  26. +61
    -0
      src/main/java/com/github/niefy/common/utils/R.java
  27. +35
    -0
      src/main/java/com/github/niefy/common/utils/SHA1Util.java
  28. +57
    -0
      src/main/java/com/github/niefy/common/utils/SmsUtils.java
  29. +42
    -0
      src/main/java/com/github/niefy/common/utils/SpringContextUtils.java
  30. +456
    -0
      src/main/java/com/github/niefy/common/utils/StringUtils.java
  31. +39
    -0
      src/main/java/com/github/niefy/common/validator/ValidatorUtils.java
  32. +8
    -0
      src/main/java/com/github/niefy/common/validator/group/AddGroup.java
  33. +8
    -0
      src/main/java/com/github/niefy/common/validator/group/AliyunGroup.java
  34. +12
    -0
      src/main/java/com/github/niefy/common/validator/group/Group.java
  35. +8
    -0
      src/main/java/com/github/niefy/common/validator/group/QcloudGroup.java
  36. +8
    -0
      src/main/java/com/github/niefy/common/validator/group/QiniuGroup.java
  37. +11
    -0
      src/main/java/com/github/niefy/common/validator/group/UpdateGroup.java
  38. +542
    -0
      src/main/java/com/github/niefy/common/xss/HTMLFilter.java
  39. +41
    -0
      src/main/java/com/github/niefy/common/xss/SQLFilter.java
  40. +29
    -0
      src/main/java/com/github/niefy/common/xss/XssFilter.java
  41. +139
    -0
      src/main/java/com/github/niefy/common/xss/XssHttpServletRequestWrapper.java
  42. +17
    -0
      src/main/java/com/github/niefy/config/CorsConfig.java
  43. +41
    -0
      src/main/java/com/github/niefy/config/FilterConfig.java
  44. +30
    -0
      src/main/java/com/github/niefy/config/KaptchaConfig.java
  45. +26
    -0
      src/main/java/com/github/niefy/config/MybatisPlusConfig.java
  46. +68
    -0
      src/main/java/com/github/niefy/config/ShiroConfig.java
  47. +38
    -0
      src/main/java/com/github/niefy/config/SwaggerConfig.java
  48. +68
    -0
      src/main/java/com/github/niefy/config/TaskExcutor.java
  49. +69
    -0
      src/main/java/com/github/niefy/modules/oss/cloud/AbstractCloudStorageService.java
  50. +54
    -0
      src/main/java/com/github/niefy/modules/oss/cloud/AliyunAbstractCloudStorageService.java
  51. +85
    -0
      src/main/java/com/github/niefy/modules/oss/cloud/CloudStorageConfig.java
  52. +35
    -0
      src/main/java/com/github/niefy/modules/oss/cloud/OSSFactory.java
  53. +81
    -0
      src/main/java/com/github/niefy/modules/oss/cloud/QcloudAbstractCloudStorageService.java
  54. +68
    -0
      src/main/java/com/github/niefy/modules/oss/cloud/QiniuAbstractCloudStorageService.java
  55. +135
    -0
      src/main/java/com/github/niefy/modules/oss/controller/SysOssController.java
  56. +14
    -0
      src/main/java/com/github/niefy/modules/oss/dao/SysOssDao.java
  57. +27
    -0
      src/main/java/com/github/niefy/modules/oss/entity/SysOssEntity.java
  58. +20
    -0
      src/main/java/com/github/niefy/modules/oss/service/SysOssService.java
  59. +27
    -0
      src/main/java/com/github/niefy/modules/oss/service/impl/SysOssServiceImpl.java
  60. +22
    -0
      src/main/java/com/github/niefy/modules/sys/controller/AbstractController.java
  61. +97
    -0
      src/main/java/com/github/niefy/modules/sys/controller/SysConfigController.java
  62. +43
    -0
      src/main/java/com/github/niefy/modules/sys/controller/SysLogController.java
  63. +98
    -0
      src/main/java/com/github/niefy/modules/sys/controller/SysLoginController.java
  64. +198
    -0
      src/main/java/com/github/niefy/modules/sys/controller/SysMenuController.java
  65. +129
    -0
      src/main/java/com/github/niefy/modules/sys/controller/SysRoleController.java
  66. +155
    -0
      src/main/java/com/github/niefy/modules/sys/controller/SysUserController.java
  67. +14
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysCaptchaDao.java
  68. +28
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysConfigDao.java
  69. +15
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysLogDao.java
  70. +35
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysMenuDao.java
  71. +22
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysRoleDao.java
  72. +32
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysRoleMenuDao.java
  73. +29
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysUserDao.java
  74. +28
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysUserRoleDao.java
  75. +18
    -0
      src/main/java/com/github/niefy/modules/sys/dao/SysUserTokenDao.java
  76. +28
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysCaptchaEntity.java
  77. +27
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysConfigEntity.java
  78. +46
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysLogEntity.java
  79. +76
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysMenuEntity.java
  80. +53
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysRoleEntity.java
  81. +31
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysRoleMenuEntity.java
  82. +81
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysUserEntity.java
  83. +31
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysUserRoleEntity.java
  84. +31
    -0
      src/main/java/com/github/niefy/modules/sys/entity/SysUserTokenEntity.java
  85. +20
    -0
      src/main/java/com/github/niefy/modules/sys/form/PasswordForm.java
  86. +22
    -0
      src/main/java/com/github/niefy/modules/sys/form/SysLoginForm.java
  87. +100
    -0
      src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Filter.java
  88. +69
    -0
      src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Realm.java
  89. +27
    -0
      src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Token.java
  90. +43
    -0
      src/main/java/com/github/niefy/modules/sys/oauth2/TokenGenerator.java
  91. +26
    -0
      src/main/java/com/github/niefy/modules/sys/service/ShiroService.java
  92. +26
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysCaptchaService.java
  93. +61
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysConfigService.java
  94. +24
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysLogService.java
  95. +50
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysMenuService.java
  96. +32
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysRoleMenuService.java
  97. +34
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysRoleService.java
  98. +26
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysUserRoleService.java
  99. +56
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysUserService.java
  100. +25
    -0
      src/main/java/com/github/niefy/modules/sys/service/SysUserTokenService.java

+ 32
- 0
.gitignore View File

@@ -0,0 +1,32 @@
# Compiled class file
*.class
*.iml
.idea/
target/
src/main/resources/static/
.settings/
.vscode/
.classpath
.factorypath
.project
# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
wx-api.iml

+ 13
- 0
.travis.yml View File

@@ -0,0 +1,13 @@
language: java
jdk:
- openjdk8

script: "mvn clean package -Dmaven.test.skip=true"

branches:
only:
- master

notifications:
email:
- niefy@qq.com

+ 14
- 0
Dockerfile View File

@@ -0,0 +1,14 @@
#设置镜像使用的基础镜像
FROM openjdk:8u322-jre-buster
# 作者
MAINTAINER niefy <niefy@qq.com>
#设置镜像暴露的端口 这里要与application.properties中的server.port保持一致
EXPOSE 80
#设置容器的挂载卷
VOLUME /tmp
#编译镜像时将springboot生成的jar文件复制到镜像中
ADD target/wx-api.jar /wx-api.jar
#编译镜像时运行脚本
RUN bash -c 'touch /wx-api.jar'
#容器的入口程序,这里注意如果要指定外部配置文件需要使用-spring.config.location指定配置文件存放目录
ENTRYPOINT ["java","-jar","/wx-api.jar"]

+ 201
- 0
LICENSE View File

@@ -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.

+ 43
- 0
README.md View File

@@ -0,0 +1,43 @@
# wx-api
微信公众号管理系统,支持多公众号接入。提供公众号菜单、自动回复、公众号素材、模板消息、CMS等管理功能

### [📖使用文档](https://www.yuque.com/nifury/wx) | [Github仓库](https://github.com/niefy/wx-api) | [码云仓库](https://gitee.com/niefy/wx-api)

## 项目说明
- wx-api是一个轻量级的公众号开发种子项目,可快速接入微信公众号管理功能
- 管理后台前端项目wx-manage:https://github.com/niefy/wx-manage
- 移动端示例wx-client: https://github.com/niefy/wx-client
- swagger文档(启动wx-api后查看):http://localhost:8088/wx/swagger-ui/index.html

## [docker方式启动文档](https://www.yuque.com/nifury/wx/nf1rvm)
## [开发环境启动文档](https://www.yuque.com/nifury/wx/guobb7)
## [生产环境部署步骤](https://www.yuque.com/nifury/wx/ofehhv)

## 技术选型
- 核心框架:Spring Boot
- 安全框架:Apache Shiro
- 持久层框架:[MyBatis-Plus](https://baomidou.oschina.io/mybatis-plus-doc/#/quick-start)
- 公众号开发框架:[WxJava](https://github.com/Wechat-Group/WxJava)
- 项目脚手架:[renren-fast](https://gitee.com/renrenio/renren-fast)
- 页面交互:Vue2.x、ElementUI、TinyMce Editor、Vuex


## 截图
![公众号账号](https://s1.ax1x.com/2020/06/23/NUTQAg.png)
![公众号菜单](https://s1.ax1x.com/2020/06/23/NUTlNQ.png)
![自动回复](https://s1.ax1x.com/2020/04/10/GTqyQA.png)
![模板消息配置](https://s1.ax1x.com/2020/04/18/JnKZhF.jpg)
![模板消息发送](https://s1.ax1x.com/2020/04/18/JnKEkT.jpg)
![粉丝管理](https://s1.ax1x.com/2020/04/18/JnKVtU.jpg)
![带参二维码](https://s1.ax1x.com/2020/04/18/JnKF00.jpg)
![素材管理](https://s1.ax1x.com/2020/05/20/Y7djHI.jpg)
![公众号消息](https://s1.ax1x.com/2020/05/20/Y7dXDA.jpg)
![文章编辑](https://s1.ax1x.com/2020/04/10/GTqrzd.png)
![系统菜单管理](https://s1.ax1x.com/2020/04/18/JnKk7V.jpg)
![管理员列表](https://s1.ax1x.com/2020/04/18/JnKimq.jpg)

## [项目开发进度](https://www.yuque.com/nifury/wx/kens6d)
## [代码贡献指南](https://www.yuque.com/nifury/wx/ykqswi)

## 开发交流
QQ群:1023785886(已满)、993128490 技术交流群严禁广告,发广告立即踢出+拉黑+举报,加群密码:wx

+ 390
- 0
db/mysql-0.7.0.sql View File

@@ -0,0 +1,390 @@
/*
V0.7.0 完整脚本,由于本次升级大量重构,不提供升级脚本,请备份重要数据后升级
Author: Nifury
Date: 19/06/2020
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for cms_article
-- ----------------------------
DROP TABLE IF EXISTS `article`;
DROP TABLE IF EXISTS `cms_article`;
CREATE TABLE `cms_article` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type` tinyint(1) NULL DEFAULT 1 COMMENT '文章类型[1:普通文章,5:帮助中心]',
`title` varchar(1024) CHARACTER SET utf8 NOT NULL COMMENT '标题',
`summary` varchar(1024) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '文章摘要',
`tags` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '文章标签',
`content` longtext CHARACTER SET utf8 NULL COMMENT '内容',
`category` varchar(25) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '分类',
`sub_category` varchar(25) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '二级目录',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
`open_count` int(11) NULL DEFAULT 0 COMMENT '点击次数',
`start_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '生效时间',
`end_time` datetime(0) NULL DEFAULT NULL COMMENT '失效时间',
`target_link` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '指向外链',
`image` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '文章首图',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_title`(`title`) USING BTREE COMMENT '标题不得重复'
) ENGINE = InnoDB AUTO_INCREMENT = 337 CHARACTER SET = utf8 COMMENT = 'CMS文章中心' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_captcha
-- ----------------------------
DROP TABLE IF EXISTS `sys_captcha`;
CREATE TABLE `sys_captcha` (
`uuid` char(36) CHARACTER SET utf8mb4 NOT NULL COMMENT 'uuid',
`code` varchar(6) CHARACTER SET utf8mb4 NOT NULL COMMENT '验证码',
`expire_time` datetime(0) NULL DEFAULT NULL COMMENT '过期时间',
PRIMARY KEY (`uuid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统验证码' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_config
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`param_key` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT 'key',
`param_value` varchar(2000) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT 'value',
`status` tinyint(4) NULL DEFAULT 1 COMMENT '状态 0:隐藏 1:显示',
`remark` varchar(500) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `param_key`(`param_key`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COMMENT = '系统配置信息表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_config
-- ----------------------------
INSERT INTO `sys_config` VALUES (1, 'CLOUD_STORAGE_CONFIG_KEY', '{\"type\":3,\"qiniuDomain\":\"\",\"qiniuPrefix\":\"\",\"qiniuAccessKey\":\"\",\"qiniuSecretKey\":\"\",\"qiniuBucketName\":\"\",\"aliyunDomain\":\"\",\"aliyunPrefix\":\"\",\"aliyunEndPoint\":\"\",\"aliyunAccessKeyId\":\"\",\"aliyunAccessKeySecret\":\"\",\"aliyunBucketName\":\"\",\"qcloudDomain\":\"\",\"qcloudPrefix\":\"\",\"qcloudAppId\":\"\",\"qcloudSecretId\":\"\",\"qcloudSecretKey\":\"\",\"qcloudBucketName\":\"\",\"qcloudRegion\":\"ap-guangzhou\"}', 0, '云存储配置信息');

-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '用户名',
`operation` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '用户操作',
`method` varchar(200) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '请求方法',
`params` varchar(5000) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '请求参数',
`time` bigint(20) NULL DEFAULT NULL COMMENT '执行时长(毫秒)',
`ip` varchar(64) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT 'IP地址',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 324 CHARACTER SET = utf8mb4 COMMENT = '系统日志' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`type` int(11) NULL DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '菜单图标',
`order_num` int(11) NULL DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 123 CHARACTER SET = utf8mb4 COMMENT = '菜单管理' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 0, '系统管理', NULL, NULL, 0, 'el-icon-s-tools', 0);
INSERT INTO `sys_menu` VALUES (2, 1, '管理员列表', 'sys/user', NULL, 1, 'admin', 1);
INSERT INTO `sys_menu` VALUES (3, 1, '角色管理', 'sys/role', NULL, 1, 'role', 2);
INSERT INTO `sys_menu` VALUES (4, 1, '菜单管理', 'sys/menu', NULL, 1, 'menu', 3);
INSERT INTO `sys_menu` VALUES (6, 0, '微信管理', NULL, NULL, 0, 'el-icon-s-promotion', 1);
INSERT INTO `sys_menu` VALUES (7, 0, '内容管理', '', '', 0, 'el-icon-document-copy', 2);
INSERT INTO `sys_menu` VALUES (9, 0, '日志报表', '', '', 0, 'el-icon-s-order', 4);
INSERT INTO `sys_menu` VALUES (15, 2, '查看', NULL, 'sys:user:list,sys:user:info', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (16, 2, '新增', NULL, 'sys:user:save,sys:role:select', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (17, 2, '修改', NULL, 'sys:user:update,sys:role:select', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (18, 2, '删除', NULL, 'sys:user:delete', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (19, 3, '查看', NULL, 'sys:role:list,sys:role:info', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (20, 3, '新增', NULL, 'sys:role:save,sys:menu:list', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (21, 3, '修改', NULL, 'sys:role:update,sys:menu:list', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (22, 3, '删除', NULL, 'sys:role:delete', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (23, 4, '查看', NULL, 'sys:menu:list,sys:menu:info', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (24, 4, '新增', NULL, 'sys:menu:save,sys:menu:select', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (25, 4, '修改', NULL, 'sys:menu:update,sys:menu:select', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (26, 4, '删除', NULL, 'sys:menu:delete', 2, NULL, 0);
INSERT INTO `sys_menu` VALUES (27, 1, '参数管理', 'sys/config', 'sys:config:list,sys:config:info,sys:config:save,sys:config:update,sys:config:delete', 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (29, 9, '系统日志', 'sys/log', 'sys:log:list', 1, 'log', 7);
INSERT INTO `sys_menu` VALUES (30, 1, '文件上传', 'oss/oss', 'sys:oss:all', 1, 'oss', 6);
INSERT INTO `sys_menu` VALUES (32, 6, '公众号菜单', 'wx/wx-menu', '', 1, 'log', 0);
INSERT INTO `sys_menu` VALUES (33, 6, '素材管理', 'wx/wx-assets', '', 1, '', 0);
INSERT INTO `sys_menu` VALUES (41, 7, '文章管理', 'wx/article', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (42, 41, '查看', NULL, 'wx:article:list,wx:article:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (43, 41, '新增', NULL, 'wx:article:save', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (44, 41, '修改', NULL, 'wx:article:update', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (45, 41, '删除', NULL, 'wx:article:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (66, 6, '自动回复规则', 'wx/msg-reply-rule', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (67, 66, '查看', NULL, 'wx:msgreplyrule:list,wx:msgreplyrule:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (68, 66, '新增', NULL, 'wx:msgreplyrule:save', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (69, 66, '修改', NULL, 'wx:msgreplyrule:update', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (70, 66, '删除', NULL, 'wx:msgreplyrule:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (71, 6, '模板消息', 'wx/msg-template', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (72, 71, '查看', NULL, 'wx:msgtemplate:list,wx:msgtemplate:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (73, 71, '新增', NULL, 'wx:msgtemplate:save', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (74, 71, '修改', NULL, 'wx:msgtemplate:update', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (75, 71, '删除', NULL, 'wx:msgtemplate:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (81, 9, '模版消息发送记录', 'wx/template-msg-log', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (84, 81, '列表', NULL, 'wx:templatemsglog:list', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (85, 81, '删除', NULL, 'wx:templatemsglog:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (99, 32, '更新公众号菜单', '', 'wx:menu:save', 2, '', 0);
INSERT INTO `sys_menu` VALUES (100, 33, '查看', '', 'wx:wxassets:list', 2, '', 0);
INSERT INTO `sys_menu` VALUES (101, 33, '新增修改', '', 'wx:wxassets:save', 2, '', 0);
INSERT INTO `sys_menu` VALUES (103, 6, '带参二维码', 'wx/wx-qrcode', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (104, 103, '查看', NULL, 'wx:wxqrcode:list,wx:wxqrcode:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (105, 103, '新增', NULL, 'wx:wxqrcode:save', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (107, 103, '删除', NULL, 'wx:wxqrcode:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (108, 6, '粉丝管理', 'wx/wx-user', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (109, 108, '查看', NULL, 'wx:wxuser:list,wx:wxuser:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (110, 108, '删除', NULL, 'wx:wxuser:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (111, 108, '同步', '', 'wx:wxuser:save', 2, '', 6);
INSERT INTO `sys_menu` VALUES (112, 33, '删除', '', 'wx:wxassets:delete', 2, '', 0);
INSERT INTO `sys_menu` VALUES (113, 6, '公众号消息', 'wx/wx-msg', NULL, 1, '', 6);
INSERT INTO `sys_menu` VALUES (114, 113, '查看', NULL, 'wx:wxmsg:list,wx:wxmsg:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (115, 113, '新增', NULL, 'wx:wxmsg:save', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (117, 113, '删除', NULL, 'wx:wxmsg:delete', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (118, 6, '公众号账号', 'wx/wx-account', NULL, 1, 'config', 6);
INSERT INTO `sys_menu` VALUES (119, 118, '查看', NULL, 'wx:wxaccount:list,wx:wxaccount:info', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (120, 118, '新增', NULL, 'wx:wxaccount:save', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (121, 118, '修改', NULL, 'wx:wxaccount:update', 2, NULL, 6);
INSERT INTO `sys_menu` VALUES (122, 118, '删除', NULL, 'wx:wxaccount:delete', 2, NULL, 6);

-- ----------------------------
-- Table structure for sys_oss
-- ----------------------------
DROP TABLE IF EXISTS `sys_oss`;
CREATE TABLE `sys_oss` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`url` varchar(200) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT 'URL地址',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COMMENT = '文件上传' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(100) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '角色名称',
`remark` varchar(100) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '备注',
`create_user_id` bigint(20) NULL DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COMMENT = '角色' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
`menu_id` bigint(20) NULL DEFAULT NULL COMMENT '菜单ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COMMENT = '角色与菜单对应关系' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 NOT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '密码',
`salt` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '盐',
`email` varchar(100) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(100) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '手机号',
`status` tinyint(4) NULL DEFAULT NULL COMMENT '状态 0:禁用 1:正常',
`create_user_id` bigint(20) NULL DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`user_id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COMMENT = '系统用户' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', 'cdac762d0ba79875489f6a8b430fa8b5dfe0cdd81da38b80f02f33328af7fd4a', 'YzcmCZNvbXocrsz9dm8e', 'niefy@qq.com', '16666666666', 1, 1, '2016-11-11 11:11:11');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COMMENT = '用户与角色对应关系' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for sys_user_token
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_token`;
CREATE TABLE `sys_user_token` (
`user_id` bigint(20) NOT NULL,
`token` varchar(100) CHARACTER SET utf8mb4 NOT NULL COMMENT 'token',
`expire_time` datetime(0) NULL DEFAULT NULL COMMENT '过期时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`user_id`) USING BTREE,
UNIQUE INDEX `token`(`token`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统用户Token' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for wx_account
-- ----------------------------
DROP TABLE IF EXISTS `wx_account`;
CREATE TABLE `wx_account` (
`appid` char(20) CHARACTER SET utf8 NOT NULL COMMENT 'appid',
`name` varchar(50) CHARACTER SET utf8 NOT NULL COMMENT '公众号名称',
`type` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '账号类型',
`verified` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '认证状态',
`secret` char(32) CHARACTER SET utf8 NOT NULL COMMENT 'appsecret',
`token` varchar(32) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT 'token',
`aes_key` varchar(43) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT 'aesKey',
PRIMARY KEY (`appid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '公众号账号' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for wx_msg
-- ----------------------------
DROP TABLE IF EXISTS `wx_msg`;
CREATE TABLE `wx_msg` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`appid` char(20) CHARACTER SET utf8 NOT NULL COMMENT 'appid',
`openid` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '微信用户ID',
`in_out` tinyint(1) UNSIGNED NULL DEFAULT NULL COMMENT '消息方向',
`msg_type` char(25) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '消息类型',
`detail` json NULL COMMENT '消息详情',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_appid`(`appid`) USING BTREE COMMENT 'appid'
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COMMENT = '微信消息' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for wx_msg_reply_rule
-- ----------------------------
DROP TABLE IF EXISTS `msg_reply_rule`;
DROP TABLE IF EXISTS `wx_msg_reply_rule`;
CREATE TABLE `wx_msg_reply_rule` (
`rule_id` int(11) NOT NULL AUTO_INCREMENT,
`appid` char(20) CHARACTER SET utf8 NULL DEFAULT '' COMMENT 'appid',
`rule_name` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '规则名称',
`match_value` varchar(200) CHARACTER SET utf8 NOT NULL COMMENT '匹配的关键词、事件等',
`exact_match` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否精确匹配',
`reply_type` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '1' COMMENT '回复消息类型',
`reply_content` varchar(1024) CHARACTER SET utf8 NOT NULL COMMENT '回复消息内容',
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '规则是否有效',
`desc` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '备注说明',
`effect_time_start` time(0) NULL DEFAULT '00:00:00' COMMENT '生效起始时间',
`effect_time_end` time(0) NULL DEFAULT '23:59:59' COMMENT '生效结束时间',
`priority` int(3) UNSIGNED NULL DEFAULT 0 COMMENT '规则优先级',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
PRIMARY KEY (`rule_id`) USING BTREE,
INDEX `idx_appid`(`appid`) USING BTREE COMMENT 'appid'
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8 COMMENT = '自动回复规则' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of wx_msg_reply_rule
-- ----------------------------
INSERT INTO `wx_msg_reply_rule` VALUES (1, '', '关注公众号', 'subscribe', 0, 'text', '你好,欢迎关注!\n<a href=\"https://github.com/niefy\">点击链接查看我的主页</a>', 1, '关注回复', '00:00:00', '23:59:59', 0, '2020-05-20 15:15:00');

-- ----------------------------
-- Table structure for wx_msg_template
-- ----------------------------
DROP TABLE IF EXISTS `msg_template`;
DROP TABLE IF EXISTS `wx_msg_template`;
CREATE TABLE `wx_msg_template` (
`id` bigint(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',
`appid` char(20) CHARACTER SET utf8 NOT NULL COMMENT 'appid',
`template_id` varchar(100) CHARACTER SET utf8 NOT NULL COMMENT '公众号模板ID',
`name` varchar(50) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '模版名称',
`title` varchar(20) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '标题',
`content` text CHARACTER SET utf8 NULL COMMENT '模板内容',
`data` json NULL COMMENT '消息内容',
`url` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '链接',
`miniprogram` json NULL COMMENT '小程序信息',
`status` tinyint(1) UNSIGNED NOT NULL COMMENT '是否有效',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_name`(`name`) USING BTREE COMMENT '模板名称',
INDEX `idx_status`(`status`) USING BTREE COMMENT '模板状态',
INDEX `idx_appid`(`appid`) USING BTREE COMMENT 'appid'
) ENGINE = InnoDB AUTO_INCREMENT = 62 CHARACTER SET = utf8 COMMENT = '消息模板' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Table structure for wx_qr_code
-- ----------------------------
DROP TABLE IF EXISTS `wx_qr_code`;
CREATE TABLE `wx_qr_code` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`appid` char(20) CHARACTER SET utf8 NOT NULL COMMENT 'appid',
`is_temp` tinyint(1) NULL DEFAULT NULL COMMENT '是否为临时二维码',
`scene_str` varchar(64) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '场景值ID',
`ticket` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '二维码ticket',
`url` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '二维码图片解析后的地址',
`expire_time` datetime(0) NULL DEFAULT NULL COMMENT '该二维码失效时间',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '该二维码创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_appid`(`appid`) USING BTREE COMMENT 'appid'
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COMMENT = '公众号带参二维码' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for wx_template_msg_log
-- ----------------------------
DROP TABLE IF EXISTS `template_msg_log`;
DROP TABLE IF EXISTS `wx_template_msg_log`;
CREATE TABLE `wx_template_msg_log` (
`log_id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`appid` char(20) CHARACTER SET utf8 NOT NULL COMMENT 'appid',
`touser` varchar(50) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '用户openid',
`template_id` varchar(50) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT 'templateid',
`data` json NULL COMMENT '消息数据',
`url` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '消息链接',
`miniprogram` json NULL COMMENT '小程序信息',
`send_time` datetime(0) NULL DEFAULT NULL COMMENT '发送时间',
`send_result` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '发送结果',
PRIMARY KEY (`log_id`) USING BTREE,
INDEX `idx_appid`(`appid`) USING BTREE COMMENT 'appid'
) ENGINE = InnoDB AUTO_INCREMENT = 116250 CHARACTER SET = utf8 COMMENT = '微信模版消息发送记录' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for wx_user
-- ----------------------------
DROP TABLE IF EXISTS `wx_user`;
CREATE TABLE `wx_user` (
`openid` varchar(50) CHARACTER SET utf8mb4 NOT NULL COMMENT '微信openid',
`appid` char(20) CHARACTER SET utf8 NOT NULL COMMENT 'appid',
`phone` char(11) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '手机号',
`nickname` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '昵称',
`sex` tinyint(4) NULL DEFAULT NULL COMMENT '性别(0-未知、1-男、2-女)',
`city` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '城市',
`province` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '省份',
`headimgurl` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '头像',
`subscribe_time` datetime(0) NULL DEFAULT NULL COMMENT '订阅时间',
`subscribe` tinyint(3) UNSIGNED NULL DEFAULT 1 COMMENT '是否关注',
`unionid` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT 'unionid',
`remark` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '备注',
`tagid_list` json NULL COMMENT '标签ID列表',
`subscribe_scene` varchar(50) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '关注场景',
`qr_scene_str` varchar(64) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '扫码场景值',
PRIMARY KEY (`openid`) USING BTREE,
INDEX `idx_unionid`(`unionid`) USING BTREE COMMENT 'unionid',
INDEX `idx_appid`(`appid`) USING BTREE COMMENT 'appid'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

+ 195
- 0
pom.xml View File

@@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.niefy</groupId>
<artifactId>wx-api</artifactId>
<version>0.8.2</version>
<packaging>jar</packaging>
<description>wx-api</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mybatisplus.version>3.5.1</mybatisplus.version>
<mysql.version>8.0.28</mysql.version>
<shiro.version>1.8.0</shiro.version>
<jwt.version>0.9.1</jwt.version>
<kaptcha.version>0.0.9</kaptcha.version>
<qiniu.version>7.9.3</qiniu.version>
<aliyun.oss.version>3.14.0</aliyun.oss.version>
<qcloud.cos.version>5.6.69</qcloud.cos.version>
<swagger.version>3.0.0</swagger.version>
<fastjson.version>1.2.79</fastjson.version>
<lombok.version>1.18.22</lombok.version>
<weixin-java.version>4.2.0</weixin-java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${qcloud.cos.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--阿里云短信包-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.1.0</version>
</dependency>

</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources/</directory>
</resource>
</resources>
</build>

<repositories>
<repository>
<id>aliyunmaven</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

</project>

+ 18
- 0
src/main/java/com/github/niefy/BootApplication.java View File

@@ -0,0 +1,18 @@
package com.github.niefy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;


@SpringBootApplication
@EnableAsync
@EnableCaching
public class BootApplication {

public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}

}

+ 19
- 0
src/main/java/com/github/niefy/common/annotation/SysLog.java View File

@@ -0,0 +1,19 @@
package com.github.niefy.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 系统日志注解
* @author Mark sunlightcs@gmail.com
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

String value() default "";
}

+ 95
- 0
src/main/java/com/github/niefy/common/aspect/SysLogAspect.java View File

@@ -0,0 +1,95 @@
package com.github.niefy.common.aspect;

import com.alibaba.fastjson.JSON;
import com.github.niefy.common.annotation.SysLog;
import com.github.niefy.common.utils.IPUtils;
import com.github.niefy.modules.sys.entity.SysLogEntity;
import com.github.niefy.modules.sys.entity.SysUserEntity;
import com.github.niefy.modules.sys.service.SysLogService;
import com.github.niefy.common.utils.HttpContextUtils;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;


/**
* 系统日志,切面处理类
* @author Mark sunlightcs@gmail.com
*/
@Aspect
@Component
public class SysLogAspect {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SysLogService sysLogService;

@Pointcut("@annotation(com.github.niefy.common.annotation.SysLog)")
public void logPointCut() {

}

@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;

//保存日志
saveSysLog(point, time);

return result;
}

private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();

SysLogEntity sysLog = new SysLogEntity();
SysLog syslog = method.getAnnotation(SysLog.class);
if (syslog != null) {
//注解上的描述
sysLog.setOperation(syslog.value());
}

//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");

//请求的参数
Object[] args = joinPoint.getArgs();
try {
String params = JSON.toJSONString(args);
sysLog.setParams(params);
} catch (Exception e) {
logger.error("saveSysLogError", e);
}

//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));

//用户名
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
sysLog.setUsername(username);

sysLog.setTime(time);
sysLog.setCreateDate(new Date());
//保存系统日志
sysLogService.save(sysLog);
}
}

+ 52
- 0
src/main/java/com/github/niefy/common/exception/RRException.java View File

@@ -0,0 +1,52 @@
package com.github.niefy.common.exception;

/**
* 自定义异常
* @author Mark sunlightcs@gmail.com
*/
public class RRException extends RuntimeException {
private static final long serialVersionUID = 1L;

private String msg;
private int code = 500;

public RRException(String msg) {
super(msg);
this.msg = msg;
}

public RRException(String msg, Throwable e) {
super(msg, e);
this.msg = msg;
}

public RRException(String msg, int code) {
super(msg);
this.msg = msg;
this.code = code;
}

public RRException(String msg, int code, Throwable e) {
super(msg, e);
this.msg = msg;
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}


}

+ 62
- 0
src/main/java/com/github/niefy/common/exception/RRExceptionHandler.java View File

@@ -0,0 +1,62 @@
package com.github.niefy.common.exception;

import com.github.niefy.common.utils.R;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
* 异常处理器
* @author Mark sunlightcs@gmail.com
*/
@RestControllerAdvice
public class RRExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());

/**
* 处理自定义异常
*/
@ExceptionHandler(RRException.class)
public R handleRrException(RRException e) {
R r = new R();
r.put("code", e.getCode());
r.put("msg", e.getMessage());

return r;
}

@ExceptionHandler(NoHandlerFoundException.class)
public R handlerNoFoundException(Exception e) {
logger.error(e.getMessage(), e);
return R.error(404, "路径不存在,请检查路径是否正确");
}

@ExceptionHandler(DuplicateKeyException.class)
public R handleDuplicateKeyException(DuplicateKeyException e) {
logger.error(e.getMessage(), e);
return R.error("数据库中已存在该记录");
}

@ExceptionHandler(AuthorizationException.class)
public R handleAuthorizationException(AuthorizationException e) {
logger.error(e.getMessage(), e);
return R.error("没有权限,请联系管理员授权");
}

@ExceptionHandler({WxErrorException.class})
public R handleWxErrorException(WxErrorException e) {
logger.error(e.getMessage(), e);
return R.error("微信公众平台接口错误:" + e.getError().getErrorMsg());
}

@ExceptionHandler(Exception.class)
public R handleException(Exception e) {
logger.error(e.getMessage(), e);
return R.error();
}
}

+ 34
- 0
src/main/java/com/github/niefy/common/handler/JSONArrayTypeHandler.java View File

@@ -0,0 +1,34 @@
package com.github.niefy.common.handler;

import com.alibaba.fastjson.JSONArray;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@MappedJdbcTypes(JdbcType.VARCHAR)
public class JSONArrayTypeHandler extends BaseTypeHandler<JSONArray> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, JSONArray array, JdbcType jdbcType) throws SQLException {
ps.setString(i,array.toJSONString());
}

@Override
public JSONArray getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
return JSONArray.parseArray(resultSet.getString(columnName));
}

@Override
public JSONArray getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
return JSONArray.parseArray(resultSet.getString(columnIndex));
}

@Override
public JSONArray getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
return JSONArray.parseArray(callableStatement.getString(columnIndex));
}
}

+ 34
- 0
src/main/java/com/github/niefy/common/handler/JSONObjectTypeHandler.java View File

@@ -0,0 +1,34 @@
package com.github.niefy.common.handler;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@MappedJdbcTypes(JdbcType.VARCHAR)
public class JSONObjectTypeHandler extends BaseTypeHandler<JSONObject> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, JSONObject array, JdbcType jdbcType) throws SQLException {
ps.setString(i,array.toJSONString());
}

@Override
public JSONObject getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
return JSONObject.parseObject(resultSet.getString(columnName));
}

@Override
public JSONObject getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
return JSONObject.parseObject(resultSet.getString(columnIndex));
}

@Override
public JSONObject getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
return JSONObject.parseObject(callableStatement.getString(columnIndex));
}
}

+ 12
- 0
src/main/java/com/github/niefy/common/utils/ConfigConstant.java View File

@@ -0,0 +1,12 @@
package com.github.niefy.common.utils;

/**
* 系统参数相关Key
* @author Mark sunlightcs@gmail.com
*/
public class ConfigConstant {
/**
* 云存储配置KEY
*/
public final static String CLOUD_STORAGE_CONFIG_KEY = "CLOUD_STORAGE_CONFIG_KEY";
}

+ 122
- 0
src/main/java/com/github/niefy/common/utils/Constant.java View File

@@ -0,0 +1,122 @@
package com.github.niefy.common.utils;

/**
* 常量
* @author Mark sunlightcs@gmail.com
*/
public class Constant {
/** 超级管理员ID */
public static final int SUPER_ADMIN = 1;
/**
* 当前页码
*/
public static final String PAGE = "page";
/**
* 每页显示记录数
*/
public static final String LIMIT = "limit";
/**
* 排序字段
*/
public static final String ORDER_FIELD = "sidx";
/**
* 排序方式
*/
public static final String ORDER = "order";
/**
* 升序
*/
public static final String ASC = "asc";

/**
* 请求header中的微信用户端源链接参数
*/
public static final String WX_CLIENT_HREF_HEADER = "wx-client-href";

/**
* 菜单类型
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2016年11月15日 下午1:24:29
*/
public enum MenuType {
/**
* 目录
*/
CATALOG(0),
/**
* 菜单
*/
MENU(1),
/**
* 按钮
*/
BUTTON(2);

private int value;

MenuType(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}

/**
* 定时任务状态
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2016年12月3日 上午12:07:22
*/
public enum ScheduleStatus {
/**
* 正常
*/
NORMAL(0),
/**
* 暂停
*/
PAUSE(1);

private int value;

ScheduleStatus(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}

/**
* 云服务商
*/
public enum CloudService {
/**
* 七牛云
*/
QINIU(1),
/**
* 阿里云
*/
ALIYUN(2),
/**
* 腾讯云
*/
QCLOUD(3);

private int value;

CloudService(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}

}

+ 64
- 0
src/main/java/com/github/niefy/common/utils/CookieUtil.java View File

@@ -0,0 +1,64 @@
package com.github.niefy.common.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* Cookie工具类
*/
public class CookieUtil {

/**
* 设置cookie
*
* @param maxAge 秒
*/
public static void setCookie(HttpServletResponse response, String cookieName, String cookieValue,
int maxAge) {
Cookie cookie = new Cookie(cookieName, cookieValue);
maxAge = Math.max(maxAge, 0);
cookie.setMaxAge(maxAge);
cookie.setPath("/");
response.addCookie(cookie);
}

public static void clearCookie(HttpServletResponse response, String cookieName, String domain) {
Cookie cookie = new Cookie(cookieName, "");
cookie.setMaxAge(0);
cookie.setDomain(domain);
cookie.setPath("/");
response.addCookie(cookie);
}

public static void refreshCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String domain, int maxAge) {
Cookie cookie = getCookie(request, cookieName);
if (cookie != null) {
cookie.setMaxAge(maxAge);
cookie.setDomain(domain);
cookie.setPath("/");
response.addCookie(cookie);
}
}

public static String getCookieValue(HttpServletRequest request, String cookieName) {
Cookie cookie = getCookie(request, cookieName);
if (cookie != null) {
return cookie.getValue();
}
return null;
}

private static Cookie getCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookieName.equals(cookies[i].getName())) {
return cookies[i];
}
}
}
return null;
}
}

+ 88
- 0
src/main/java/com/github/niefy/common/utils/DateUtils.java View File

@@ -0,0 +1,88 @@
package com.github.niefy.common.utils;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 日期处理
* @author Mark sunlightcs@gmail.com
*/
public class DateUtils {
/** 时间格式(yyyy-MM-dd) */
public final static String DATE_PATTERN = "yyyy-MM-dd";
/** 时间格式(yyyy-MM-dd HH:mm:ss) */
public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

/**
* 日期格式化 日期格式为:yyyy-MM-dd
* @param date 日期
* @return 返回yyyy-MM-dd格式日期
*/
public static String format(Date date) {
return format(date, DATE_PATTERN);
}

/**
* 日期格式化 日期格式为:yyyy-MM-dd
* @param date 日期
* @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN
* @return 返回yyyy-MM-dd格式日期
*/
public static String format(Date date, String pattern) {
if (date != null) {
SimpleDateFormat df = new SimpleDateFormat(pattern);
return df.format(date);
}
return null;
}

/**
* 对日期的【秒】进行加/减
* @param date 日期
* @param seconds 秒数,负数为减
* @return 加/减几秒后的日期
*/
public static Date addDateSeconds(Date date, int seconds) {
return new Date(date.getTime()+seconds*1000);
}

/**
* 对日期的【分钟】进行加/减
* @param date 日期
* @param minutes 分钟数,负数为减
* @return 加/减几分钟后的日期
*/
public static Date addDateMinutes(Date date, int minutes) {
return new Date(date.getTime()+minutes*60*1000);
}

/**
* 对日期的【小时】进行加/减
* @param date 日期
* @param hours 小时数,负数为减
* @return 加/减几小时后的日期
*/
public static Date addDateHours(Date date, int hours) {
return new Date(date.getTime()+hours*60*60*1000);
}

/**
* 对日期的【天】进行加/减
* @param date 日期
* @param days 天数,负数为减
* @return 加/减几天后的日期
*/
public static Date addDateDays(Date date, int days) {
return new Date(date.getTime()+days*24*60*60*1000);
}

/**
* 对日期的【周】进行加/减
* @param date 日期
* @param weeks 周数,负数为减
* @return 加/减几周后的日期
*/
public static Date addDateWeeks(Date date, int weeks) {
return new Date(date.getTime()+weeks*7*24*60*60*1000);
}
}

+ 25
- 0
src/main/java/com/github/niefy/common/utils/HttpContextUtils.java View File

@@ -0,0 +1,25 @@
package com.github.niefy.common.utils;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

public class HttpContextUtils {

public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}

public static String getDomain() {
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}

public static String getOrigin() {
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
}

+ 48
- 0
src/main/java/com/github/niefy/common/utils/IPUtils.java View File

@@ -0,0 +1,48 @@
package com.github.niefy.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
* IP地址
* @author Mark sunlightcs@gmail.com
*/
public class IPUtils {
private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
private static final String UNKNOWN = "unknown";

/**
* 获取IP地址
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
logger.error("IPUtils ERROR ", e);
}

return ip;
}

}

+ 20
- 0
src/main/java/com/github/niefy/common/utils/Json.java View File

@@ -0,0 +1,20 @@
package com.github.niefy.common.utils;

import com.alibaba.fastjson.JSON;

public class Json {

/**
* 对象序列化为JSON字符串
*
* @param object
* @return
*/
public static String toJsonString(Object object) {
return JSON.toJSONString(object);
}

public static <T> T fromJson(String jsonStr, Class<T> clazz) {
return JSON.parseObject(jsonStr, clazz);
}
}

+ 75
- 0
src/main/java/com/github/niefy/common/utils/MD5Util.java View File

@@ -0,0 +1,75 @@
package com.github.niefy.common.utils;

import java.security.MessageDigest;
import java.util.Objects;

/**
* MD5加密工具类
*/
public class MD5Util {
private static final String DEFAULT_MD_5_SALT = "fjdsl321312kf349832&*^*903294[JNLIUIK]%fsdjfkl";//加盐md5盐值

/**
* 获得字符串的md5值
*
* @return md5加密后的字符串
*/
public static String getMd5(String s) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] btInput = s.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char[] str = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 校验字符串的md5值
*
* @param str 目标字符串
* @param md5 基准md5
* @return 校验结果
*/
public static boolean checkMd5(String str, String md5) {
return Objects.requireNonNull(getMd5(str)).equalsIgnoreCase(md5);
}

/**
* 获得加盐md5,算法过程是原字符串md5后连接加盐字符串后再进行md5
*
* @param str 待加密的字符串
* @param salt 盐
* @return 加盐md5
*/
public static String getMd5AndSalt(String str, String salt) {
return getMd5(Objects.requireNonNull(getMd5(str)).concat(salt));
}

/**
* 获得加盐md5,算法过程是原字符串md5后连接加盐字符串后再进行md5
* 使用默认盐值
*
* @param str 待加密的字符串
* @return 加盐md5
*/
public static String getMd5AndSalt(String str) {
return getMd5(Objects.requireNonNull(getMd5(str)).concat(DEFAULT_MD_5_SALT));
}
}

+ 20
- 0
src/main/java/com/github/niefy/common/utils/MapUtils.java View File

@@ -0,0 +1,20 @@
package com.github.niefy.common.utils;

import java.util.HashMap;


/**
* Map工具类
* @author Mark sunlightcs@gmail.com
*/
public class MapUtils extends HashMap<String, Object> {


private static final long serialVersionUID = 1L;

@Override
public MapUtils put(String key, Object value) {
super.put(key, value);
return this;
}
}

+ 101
- 0
src/main/java/com/github/niefy/common/utils/PageUtils.java View File

@@ -0,0 +1,101 @@
package com.github.niefy.common.utils;

import com.baomidou.mybatisplus.core.metadata.IPage;

import java.io.Serializable;
import java.util.List;

/**
* 分页工具类
* @author Mark sunlightcs@gmail.com
*/
public class PageUtils implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private int totalCount;
/**
* 每页记录数
*/
private int pageSize;
/**
* 总页数
*/
private int totalPage;
/**
* 当前页数
*/
private int currPage;
/**
* 列表数据
*/
private List<?> list;

/**
* 分页
* @param list 列表数据
* @param totalCount 总记录数
* @param pageSize 每页记录数
* @param currPage 当前页数
*/
public PageUtils(List<?> list, int totalCount, int pageSize, int currPage) {
this.list = list;
this.totalCount = totalCount;
this.pageSize = pageSize;
this.currPage = currPage;
this.totalPage = (int) Math.ceil((double) totalCount / pageSize);
}

/**
* 分页
*/
public PageUtils(IPage<?> page) {
this.list = page.getRecords();
this.totalCount = (int) page.getTotal();
this.pageSize = (int) page.getSize();
this.currPage = (int) page.getCurrent();
this.totalPage = (int) page.getPages();
}

public int getTotalCount() {
return totalCount;
}

public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}

public int getPageSize() {
return pageSize;
}

public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}

public int getTotalPage() {
return totalPage;
}

public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}

public int getCurrPage() {
return currPage;
}

public void setCurrPage(int currPage) {
this.currPage = currPage;
}

public List<?> getList() {
return list;
}

public void setList(List<?> list) {
this.list = list;
}

}

+ 68
- 0
src/main/java/com/github/niefy/common/utils/Query.java View File

@@ -0,0 +1,68 @@
package com.github.niefy.common.utils;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.niefy.common.xss.SQLFilter;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

/**
* 查询参数
* @author Mark sunlightcs@gmail.com
*/
public class Query<T> {

public IPage<T> getPage(Map<String, Object> params) {
return this.getPage(params, null, false);
}

public IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
//分页参数
long curPage = 1;
long limit = 10;

if (params.get(Constant.PAGE) != null) {
curPage = Long.parseLong((String) params.get(Constant.PAGE));
}
if (params.get(Constant.LIMIT) != null) {
limit = Long.parseLong((String) params.get(Constant.LIMIT));
}

//分页对象
Page<T> page = new Page<>(curPage, limit);

//分页参数
params.put(Constant.PAGE, page);

//排序字段
//防止SQL注入(因为sidx、order是通过拼接SQL实现排序的,会有SQL注入风险)
String orderField = SQLFilter.sqlInject((String) params.get(Constant.ORDER_FIELD));
String order = (String) params.get(Constant.ORDER);


//前端字段排序
if (StringUtils.isNotEmpty(orderField) && StringUtils.isNotEmpty(order)) {
if (Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField));
} else {
return page.addOrder(OrderItem.desc(orderField));
}
}

//没有排序字段,则不排序
if (StringUtils.isBlank(defaultOrderField)) {
return page;
}

//默认排序
if (isAsc) {
page.addOrder(OrderItem.asc(defaultOrderField));
} else {
page.addOrder(OrderItem.desc(defaultOrderField));
}

return page;
}
}

+ 61
- 0
src/main/java/com/github/niefy/common/utils/R.java View File

@@ -0,0 +1,61 @@
package com.github.niefy.common.utils;

import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

/**
* 返回数据
* @author Mark sunlightcs@gmail.com
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;

public R() {
put("code", 200);
put("msg", "success");
}

public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}

public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}

public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}

public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}

public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}

public static R ok() {
return new R();
}

@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}

public R put(Object value) {
super.put("data", value);
return this;
}
}

+ 35
- 0
src/main/java/com/github/niefy/common/utils/SHA1Util.java View File

@@ -0,0 +1,35 @@
package com.github.niefy.common.utils;

import java.security.MessageDigest;

/**
* SHA1加密工具类
*/
public class SHA1Util {

public static String sha1(String s) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] btInput = s.getBytes();
// 获得SHA1摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("sha-1");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char[] str = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

+ 57
- 0
src/main/java/com/github/niefy/common/utils/SmsUtils.java View File

@@ -0,0 +1,57 @@
package com.github.niefy.common.utils;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SmsUtils {
@Value("${sms.accessKeyId}")
public static String accessKeyId;
@Value("${sms.secret}")
public static String secret;
@Value("${sms.signName}")
public static String signName; // 短信签名
@Value("${sms.templateCode}")
public static String templateCode; //短信模板
@Value("${sms.regionId}")
public static String regionId; // 短信服务器区域
//平台信息
public static String sendNoteMessgae(String phoneNumbers, String code) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, secret);
DefaultAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();

request.setSysMethod(MethodType.POST);
//下面两个不能动
request.setSysProduct("Dysmsapi");
request.setSysDomain("dysmsapi.aliyuncs.com");

request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
//自定义参数(手机号,验证码,签名,模板)
request.putQueryParameter("RegoinId", regionId);
request.putQueryParameter("PhoneNumbers", phoneNumbers);
request.putQueryParameter("SignName", signName); //填自己申请的名称
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
try {
CommonResponse response = client.getCommonResponse(request);
String returnStr = response.getData();
System.out.println(returnStr);
JSONObject jsonObject = JSONObject.parseObject(returnStr);
return jsonObject.getString("Message");
} catch (ServerException e) {
return e.getErrMsg();
} catch (ClientException e) {
return e.getErrMsg();
}
}
}

+ 42
- 0
src/main/java/com/github/niefy/common/utils/SpringContextUtils.java View File

@@ -0,0 +1,42 @@
package com.github.niefy.common.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
* Spring Context 工具类
* @author Mark sunlightcs@gmail.com
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {
public static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}

public static Object getBean(String name) {
return applicationContext.getBean(name);
}

public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}

public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}

public static boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}

public static Class<?> getType(String name) {
return applicationContext.getType(name);
}

}

+ 456
- 0
src/main/java/com/github/niefy/common/utils/StringUtils.java View File

@@ -0,0 +1,456 @@
package com.github.niefy.common.utils;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* 字符串工具类
*
* @author nsgk
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils
{
/** 空字符串 */
private static final String NULLSTR = "";

/** 下划线 */
private static final char SEPARATOR = '_';

/**
* 获取参数不为空值
*
* @param value defaultValue 要判断的value
* @return value 返回值
*/
public static <T> T nvl(T value, T defaultValue)
{
return value != null ? value : defaultValue;
}
/**
* 获取参数为null时返回空字符串
*
* @param value 要判断的value
* @return value 返回值
*/
public static String nvlNull(String value)
{
return (value != null && !"null".equals(value)) ? value : "";
}

/**
* * 判断一个Collection是否为空, 包含List,Set,Queue
*
* @param coll 要判断的Collection
* @return true:为空 false:非空
*/
public static boolean isEmpty(Collection<?> coll)
{
return isNull(coll) || coll.isEmpty();
}

/**
* * 判断一个Collection是否非空,包含List,Set,Queue
*
* @param coll 要判断的Collection
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Collection<?> coll)
{
return !isEmpty(coll);
}

/**
* * 判断一个对象数组是否为空
*
* @param objects 要判断的对象数组
** @return true:为空 false:非空
*/
public static boolean isEmpty(Object[] objects)
{
return isNull(objects) || (objects.length == 0);
}

/**
* * 判断一个对象数组是否非空
*
* @param objects 要判断的对象数组
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Object[] objects)
{
return !isEmpty(objects);
}

/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true:为空 false:非空
*/
public static boolean isEmpty(Map<?, ?> map)
{
return isNull(map) || map.isEmpty();
}

/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Map<?, ?> map)
{
return !isEmpty(map);
}

/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str)
{
return isNull(str) || NULLSTR.equals(str.trim());
}

/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true:非空串 false:空串
*/
public static boolean isNotEmpty(String str)
{
return !isEmpty(str);
}

/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true:为空 false:非空
*/
public static boolean isNull(Object object)
{
return object == null;
}

/**
* * 判断一个对象是否非空
*
* @param object Object
* @return true:非空 false:空
*/
public static boolean isNotNull(Object object)
{
return !isNull(object);
}

/**
* * 判断一个对象是否是数组类型(Java基本型别的数组)
*
* @param object 对象
* @return true:是数组 false:不是数组
*/
public static boolean isArray(Object object)
{
return isNotNull(object) && object.getClass().isArray();
}

/**
* 去空格
*/
public static String trim(String str)
{
return (str == null ? "" : str.trim());
}

/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @return 结果
*/
public static String substring(final String str, int start)
{
if (str == null)
{
return NULLSTR;
}

if (start < 0)
{
start = str.length() + start;
}

if (start < 0)
{
start = 0;
}
if (start > str.length())
{
return NULLSTR;
}

return str.substring(start);
}

/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @param end 结束
* @return 结果
*/
public static String substring(final String str, int start, int end)
{
if (str == null)
{
return NULLSTR;
}

if (end < 0)
{
end = str.length() + end;
}
if (start < 0)
{
start = str.length() + start;
}

if (end > str.length())
{
end = str.length();
}

if (start > end)
{
return NULLSTR;
}

if (start < 0)
{
start = 0;
}
if (end < 0)
{
end = 0;
}

return str.substring(start, end);
}

/**
* 字符串转set
*
* @param str 字符串
* @param sep 分隔符
* @return set集合
*/
public static final Set<String> str2Set(String str, String sep)
{
return new HashSet<String>(str2List(str, sep, true, false));
}

/**
* 字符串转list
*
* @param str 字符串
* @param sep 分隔符
* @param filterBlank 过滤纯空白
* @param trim 去掉首尾空白
* @return list集合
*/
public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim)
{
List<String> list = new ArrayList<String>();
if (StringUtils.isEmpty(str))
{
return list;
}

// 过滤空白字符串
if (filterBlank && StringUtils.isBlank(str))
{
return list;
}
String[] split = str.split(sep);
for (String string : split)
{
if (filterBlank && StringUtils.isBlank(string))
{
continue;
}
if (trim)
{
string = string.trim();
}
list.add(string);
}

return list;
}

/**
* 下划线转驼峰命名
*/
public static String toUnderScoreCase(String str)
{
if (str == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
// 前置字符是否大写
boolean preCharIsUpperCase = true;
// 当前字符是否大写
boolean curreCharIsUpperCase = true;
// 下一字符是否大写
boolean nexteCharIsUpperCase = true;
for (int i = 0; i < str.length(); i++)
{
char c = str.charAt(i);
if (i > 0)
{
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
}
else
{
preCharIsUpperCase = false;
}

curreCharIsUpperCase = Character.isUpperCase(c);

if (i < (str.length() - 1))
{
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
}

if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase)
{
sb.append(SEPARATOR);
}
else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase)
{
sb.append(SEPARATOR);
}
sb.append(Character.toLowerCase(c));
}

return sb.toString();
}

/**
* 是否包含字符串
*
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inStringIgnoreCase(String str, String... strs)
{
if (str != null && strs != null)
{
for (String s : strs)
{
if (str.equalsIgnoreCase(trim(s)))
{
return true;
}
}
}
return false;
}

/**
* 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String convertToCamelCase(String name)
{
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty())
{
// 没必要转换
return "";
}
else if (!name.contains("_"))
{
// 不含下划线,仅将首字母大写
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
// 用下划线将原始字符串分割
String[] camels = name.split("_");
for (String camel : camels)
{
// 跳过原始字符串中开头、结尾的下换线或双重下划线
if (camel.isEmpty())
{
continue;
}
// 首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
return result.toString();
}

/**
* 驼峰式命名法 例如:user_name->userName
*/
public static String toCamelCase(String s)
{
if (s == null)
{
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++)
{
char c = s.charAt(i);

if (c == SEPARATOR)
{
upperCase = true;
}
else if (upperCase)
{
sb.append(Character.toUpperCase(c));
upperCase = false;
}
else
{
sb.append(c);
}
}
return sb.toString();
}
/**
* 字符串去除特殊字符
*/
public static String replaceBlank(String str) {
String dest = "";
if (str!=null) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
dest = m.replaceAll("");
}
return dest;
}


@SuppressWarnings("unchecked")
public static <T> T cast(Object obj)
{
return (T) obj;
}
}

+ 39
- 0
src/main/java/com/github/niefy/common/validator/ValidatorUtils.java View File

@@ -0,0 +1,39 @@
package com.github.niefy.common.validator;

import com.github.niefy.common.exception.RRException;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

/**
* hibernate-validator校验工具类
* 参考文档:http://docs.jboss.org/hibernate/validator/5.4/reference/en-US/html_single/
* @author Mark sunlightcs@gmail.com
*/
public class ValidatorUtils {
private static Validator validator;

static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}

/**
* 校验对象
* @param object 待校验对象
* @param groups 待校验的组
* @throws RRException 校验不通过,则报RRException异常
*/
public static void validateEntity(Object object, Class<?>... groups)
throws RRException {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
StringBuilder msg = new StringBuilder();
for (ConstraintViolation<Object> constraint : constraintViolations) {
msg.append(constraint.getMessage()).append("<br>");
}
throw new RRException(msg.toString());
}
}
}

+ 8
- 0
src/main/java/com/github/niefy/common/validator/group/AddGroup.java View File

@@ -0,0 +1,8 @@
package com.github.niefy.common.validator.group;

/**
* 新增数据 Group
* @author Mark sunlightcs@gmail.com
*/
public interface AddGroup {
}

+ 8
- 0
src/main/java/com/github/niefy/common/validator/group/AliyunGroup.java View File

@@ -0,0 +1,8 @@
package com.github.niefy.common.validator.group;

/**
* 阿里云
* @author Mark sunlightcs@gmail.com
*/
public interface AliyunGroup {
}

+ 12
- 0
src/main/java/com/github/niefy/common/validator/group/Group.java View File

@@ -0,0 +1,12 @@
package com.github.niefy.common.validator.group;

import javax.validation.GroupSequence;

/**
* 定义校验顺序,如果AddGroup组失败,则UpdateGroup组不会再校验
* @author Mark sunlightcs@gmail.com
*/
@GroupSequence({AddGroup.class, UpdateGroup.class})
public interface Group {

}

+ 8
- 0
src/main/java/com/github/niefy/common/validator/group/QcloudGroup.java View File

@@ -0,0 +1,8 @@
package com.github.niefy.common.validator.group;

/**
* 腾讯云
* @author Mark sunlightcs@gmail.com
*/
public interface QcloudGroup {
}

+ 8
- 0
src/main/java/com/github/niefy/common/validator/group/QiniuGroup.java View File

@@ -0,0 +1,8 @@
package com.github.niefy.common.validator.group;

/**
* 七牛
* @author Mark sunlightcs@gmail.com
*/
public interface QiniuGroup {
}

+ 11
- 0
src/main/java/com/github/niefy/common/validator/group/UpdateGroup.java View File

@@ -0,0 +1,11 @@
package com.github.niefy.common.validator.group;

/**
* 更新数据 Group
*
* @author Mark sunlightcs@gmail.com
*/

public interface UpdateGroup {

}

+ 542
- 0
src/main/java/com/github/niefy/common/xss/HTMLFilter.java View File

@@ -0,0 +1,542 @@
package com.github.niefy.common.xss;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* 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 HTMLFilter().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 Joseph O'Connell
* @author Cal Hendersen
* @author Michael Semb Wever
*/
public final class HTMLFilter {

/**
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
private static final Pattern P_END_ARROW = Pattern.compile("^>");
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_AMP = Pattern.compile("&");
private static final Pattern P_QUOTE = Pattern.compile("<");
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");

// @xxx could grow large... maybe use sesat's ReferenceMap
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();

/**
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<>();

/**
* html elements which must always be self-closing (e.g. "<img />")
**/
private final String[] vSelfClosingTags;
/**
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
**/
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. "<b></b>" or "<b />")
**/
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. "<b text </b>" becomes "<b> text </b>"). If set to false,
* unbalanced angle brackets will be html escaped.
*/
private final boolean alwaysMakeTags;

/**
* Default constructor.
*/
public HTMLFilter() {
vAllowed = new HashMap<>();

final ArrayList<String> aAtts = new ArrayList<>();
aAtts.add("href");
aAtts.add("target");
vAllowed.put("a", aAtts);

final ArrayList<String> imgAtts = new ArrayList<>();
imgAtts.add("src");
imgAtts.add("width");
imgAtts.add("height");
imgAtts.add("alt");
vAllowed.put("img", imgAtts);

final ArrayList<String> noAtts = new ArrayList<>();
vAllowed.put("b", noAtts);
vAllowed.put("strong", noAtts);
vAllowed.put("i", noAtts);
vAllowed.put("em", noAtts);

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 HTMLFilter(final boolean debug) {
this();
vDebug = debug;

}

/**
* Map-parameter configurable constructor.
*
* @param conf map containing configuration. keys match field names.
*/
public HTMLFilter(final Map<String, Object> 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<String, List<String>>) 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) {
Logger.getAnonymousLogger().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, "&amp;", result);
result = regexReplace(P_QUOTE, "&quot;", result);
result = regexReplace(P_LEFT_ARROW, "&lt;", result);
result = regexReplace(P_RIGHT_ARROW, "&gt;", 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("<!--" + htmlSpecialChars(match) + "-->"));
}
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, "&lt;$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);

//
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}

return s;
}

private String checkTags(String s) {
Matcher m = P_TAGS.matcher(s);

final StringBuffer buf = new StringBuffer();
while (m.find()) {
String replaceStr = m.group(1);
replaceStr = processTag(replaceStr);
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
}
m.appendTail(buf);

s = buf.toString();

// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
for (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 (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 regexPattern, final String replacement, final String s) {
Matcher m = regexPattern.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<String> paramNames = new ArrayList<String>();
final List<String> paramValues = new ArrayList<String>();
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);

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);
if (s.startsWith("#//")) {
s = "#" + s.substring(3);
}
}
}

return s;
}

private String decodeEntities(String s) {
StringBuffer buf = new StringBuffer();

Matcher m = P_ENTITY.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.decode(match);
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);
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);
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();

s = validateEntities(s);
return s;
}

private String validateEntities(final String s) {
StringBuffer buf = new StringBuffer();

// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find()) {
final String one = m.group(1); //([^&;]*)
final String two = m.group(2); //(?=(;|&|$))
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
}
m.appendTail(buf);

return encodeQuotes(buf.toString());
}

private String encodeQuotes(final String s) {
if (encodeQuotes) {
StringBuffer buf = new StringBuffer();
Matcher m = P_VALID_QUOTES.matcher(s);
while (m.find()) {
final String one = m.group(1); //(>|^)
final String two = m.group(2); //([^<]+?)
final String three = m.group(3); //(<|$)
m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, "&quot;", 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
: "&amp;" + preamble;
}

private boolean isValidEntity(final String entity) {
return inArray(entity, vAllowedEntities);
}

private static boolean inArray(final String s, final String[] array) {
for (String item : array) {
if (item != null && item.equals(s)) {
return true;
}
}
return false;
}

private boolean allowed(final String name) {
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
}

private boolean allowedAttribute(final String name, final String paramName) {
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
}
}

+ 41
- 0
src/main/java/com/github/niefy/common/xss/SQLFilter.java View File

@@ -0,0 +1,41 @@
package com.github.niefy.common.xss;

import com.github.niefy.common.exception.RRException;
import org.apache.commons.lang3.StringUtils;

/**
* SQL过滤
* @author Mark sunlightcs@gmail.com
*/
public class SQLFilter {

/**
* SQL注入过滤
* @param str 待验证的字符串
*/
public static String sqlInject(String str) {
if (StringUtils.isBlank(str)) {
return null;
}
//去掉'|"|;|\字符
str = StringUtils.replace(str, "'", "");
str = StringUtils.replace(str, "\"", "");
str = StringUtils.replace(str, ";", "");
str = StringUtils.replace(str, "\\", "");

//转换成小写
str = str.toLowerCase();

//非法字符
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"};

//判断是否包含非法字符
for (String keyword : keywords) {
if (str.indexOf(keyword) != -1) {
throw new RRException("包含非法字符");
}
}

return str;
}
}

+ 29
- 0
src/main/java/com/github/niefy/common/xss/XssFilter.java View File

@@ -0,0 +1,29 @@
package com.github.niefy.common.xss;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
* XSS过滤
* @author Mark sunlightcs@gmail.com
*/
public class XssFilter implements Filter {

@Override
public void init(FilterConfig config) {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}

@Override
public void destroy() {
}

}

+ 139
- 0
src/main/java/com/github/niefy/common/xss/XssHttpServletRequestWrapper.java View File

@@ -0,0 +1,139 @@
package com.github.niefy.common.xss;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* XSS过滤处理
* @author Mark sunlightcs@gmail.com
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
//没被包装过的HttpServletRequest(特殊场景,需要自己过滤)
HttpServletRequest orgRequest;
//html过滤
private final static HTMLFilter HTML_FILTER = new HTMLFilter();

public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}

@Override
public ServletInputStream getInputStream() throws IOException {
//非json类型,直接返回
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
return super.getInputStream();
}

//为空,直接返回
String json = IOUtils.toString(super.getInputStream(), StandardCharsets.UTF_8);
if (StringUtils.isBlank(json)) {
return super.getInputStream();
}

//xss过滤
json = xssEncode(json);
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.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) {
String value = super.getParameter(xssEncode(name));
if (StringUtils.isNotBlank(value)) {
value = xssEncode(value);
}
return value;
}

@Override
public String[] getParameterValues(String name) {
String[] parameters = super.getParameterValues(name);
if (parameters == null || parameters.length == 0) {
return null;
}

for (int i = 0; i < parameters.length; i++) {
parameters[i] = xssEncode(parameters[i]);
}
return parameters;
}

@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = new LinkedHashMap<>();
Map<String, String[]> parameters = super.getParameterMap();
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
values[i] = xssEncode(values[i]);
}
map.put(key, values);
}
return map;
}

@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (StringUtils.isNotBlank(value)) {
value = xssEncode(value);
}
return value;
}

private String xssEncode(String input) {
return HTML_FILTER.filter(input);
}

/**
* 获取最原始的request
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}

/**
* 获取最原始的request
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
if (request instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
}

return request;
}

}

+ 17
- 0
src/main/java/com/github/niefy/config/CorsConfig.java View File

@@ -0,0 +1,17 @@
package com.github.niefy.config;

import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//@Configuration
public class CorsConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}

+ 41
- 0
src/main/java/com/github/niefy/config/FilterConfig.java View File

@@ -0,0 +1,41 @@
package com.github.niefy.config;

import com.github.niefy.common.xss.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;

/**
* Filter配置
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean shiroFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
registration.addInitParameter("targetFilterLifecycle", "true");
registration.setEnabled(true);
registration.setOrder(Integer.MAX_VALUE - 1);
registration.addUrlPatterns("/sys/*");
registration.addUrlPatterns("/manage/*");
return registration;
}

@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
}

+ 30
- 0
src/main/java/com/github/niefy/config/KaptchaConfig.java View File

@@ -0,0 +1,30 @@
package com.github.niefy.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;


/**
* 生成验证码配置
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class KaptchaConfig {

@Bean
public DefaultKaptcha producer() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "5");
properties.put("kaptcha.textproducer.font.names", "Arial,Courier,cmr10,宋体,楷体,微软雅黑");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

+ 26
- 0
src/main/java/com/github/niefy/config/MybatisPlusConfig.java View File

@@ -0,0 +1,26 @@
package com.github.niefy.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* mybatis-plus配置
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class MybatisPlusConfig {

/**
* 分页插件,一缓和二缓遵循mybatis的规则
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

}

+ 68
- 0
src/main/java/com/github/niefy/config/ShiroConfig.java View File

@@ -0,0 +1,68 @@
package com.github.niefy.config;

import com.github.niefy.modules.sys.oauth2.OAuth2Filter;
import com.github.niefy.modules.sys.oauth2.OAuth2Realm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Shiro配置
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class ShiroConfig {

@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setRememberMeManager(null);
return securityManager;
}

@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);

//oauth过滤
Map<String, Filter> filters = new HashMap<>(4);
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);

Map<String, String> filterMap = new LinkedHashMap<>(8);
filterMap.put("/sys/login", "anon");
filterMap.put("/sys/smsManage", "anon");
filterMap.put("/manage/wxUser/findByPhone/**", "anon");
filterMap.put("/sys/**", "oauth2");
filterMap.put("/manage/**", "oauth2");
filterMap.put("/wx/**", "anon");
filterMap.put("/**", "anon");
shiroFilter.setFilterChainDefinitionMap(filterMap);

return shiroFilter;
}

@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}

}

+ 38
- 0
src/main/java/com/github/niefy/config/SwaggerConfig.java View File

@@ -0,0 +1,38 @@
package com.github.niefy.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
@EnableOpenApi
public class SwaggerConfig implements WebMvcConfigurer {

@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
//加了ApiOperation注解的类,才生成接口文档
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("微信公众号对接后台")
.description("wx-api文档,管理后台相关接口需先登录后获取token使用,token获取步骤:1.获取验证码接口获取验证码图片 2.登录接口填写账号密码和刚刚的验证码信息")
.version("0.8.0")
.build();
}

}

+ 68
- 0
src/main/java/com/github/niefy/config/TaskExcutor.java View File

@@ -0,0 +1,68 @@
package com.github.niefy.config;

import org.springframework.beans.factory.annotation.Value;

import java.util.concurrent.*;

/**
* 系统线程池,系统各种任务使用同一个线程池,以防止创建过多线程池
*/
public class TaskExcutor {
@Value("${task.corePoolSize}")
static int corePoolSize;
@Value("${task.maximumPoolSize}")
static int maximumPoolSize;
@Value("${task.keepAliveTime}")
static long keepAliveTime;
private TaskExcutor(){}

/**
* 使用静态内部类实现单例懒加载
*/
private static class ExcutorHolder{
/**
* 线程池
* corePoolSize=5 核心线程数
* maximumPoolSize=30 最大线程数
* keepAliveTime=10,unit=TimeUnit.SECOND 线程最大空闲时间为10秒60L
* workQueue=new SynchronousQueue<Runnable>() 链表队列
* handler=new ThreadPoolExecutor.CallerRunsPolicy()
*/
private static final ExecutorService EXCUTOR = new ThreadPoolExecutor(
corePoolSize,maximumPoolSize,keepAliveTime, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadPoolExecutor.CallerRunsPolicy());
}
/**
* 使用静态内部类实现单例懒加载
*/
private static class SchedulerHolder{
private static final ScheduledExecutorService SCHEDULER =
Executors.newScheduledThreadPool(5);
}

/**
* 将任务提交到系统线程池中执行
* 1.如果线程数未达核心线程,创建核心线程
* 2.已达核心线程数,添加到任务队列
* 3.核心线程已满、队列已满,创建新空闲线程
* 4.核心线程已满、队列已满、无法创建新空闲线程,执行拒绝策略
* 本工具类拒绝策略使用内置ThreadPoolExecutor.CallerRunsPolicy,即让添加任务的主线程来执行任务,这样主线程被占用无法继续添加任务,相当于线程池全满后添加任务的线程会被阻塞
* @param task
* @return
*/
public static Future<?> submit(Runnable task){
return ExcutorHolder.EXCUTOR.submit(task);
}

/**
* 将定时任务添加到系统线程池
* @param task
* @param delay
* @param unit
* @return
*/
public static ScheduledFuture<?> schedule(Runnable task,long delay, TimeUnit unit){
return SchedulerHolder.SCHEDULER.schedule(task,delay,unit);
}
}

+ 69
- 0
src/main/java/com/github/niefy/modules/oss/cloud/AbstractCloudStorageService.java View File

@@ -0,0 +1,69 @@
package com.github.niefy.modules.oss.cloud;

import com.github.niefy.common.utils.DateUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.InputStream;
import java.util.Date;
import java.util.UUID;

/**
* 云存储(支持七牛、阿里云、腾讯云、又拍云)
* @author Mark sunlightcs@gmail.com
*/
public abstract class AbstractCloudStorageService {
/** 云存储配置信息 */
CloudStorageConfig config;

/**
* 文件路径
* @param prefix 前缀
* @param suffix 后缀
* @return 返回上传路径
*/
public String getPath(String prefix, String suffix) {
//生成uuid
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//文件路径
String path = DateUtils.format(new Date(), "yyyyMMdd") + "/" + uuid;

if (StringUtils.isNotBlank(prefix)) {
path = prefix + "/" + path;
}

return path + suffix;
}

/**
* 文件上传
* @param data 文件字节数组
* @param path 文件路径,包含文件名
* @return 返回http地址
*/
public abstract String upload(byte[] data, String path);

/**
* 文件上传
* @param data 文件字节数组
* @param suffix 后缀
* @return 返回http地址
*/
public abstract String uploadSuffix(byte[] data, String suffix);

/**
* 文件上传
* @param inputStream 字节流
* @param path 文件路径,包含文件名
* @return 返回http地址
*/
public abstract String upload(InputStream inputStream, String path);

/**
* 文件上传
* @param inputStream 字节流
* @param suffix 后缀
* @return 返回http地址
*/
public abstract String uploadSuffix(InputStream inputStream, String suffix);

}

+ 54
- 0
src/main/java/com/github/niefy/modules/oss/cloud/AliyunAbstractCloudStorageService.java View File

@@ -0,0 +1,54 @@
package com.github.niefy.modules.oss.cloud;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.github.niefy.common.exception.RRException;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
* 阿里云存储
* @author Mark sunlightcs@gmail.com
*/
public class AliyunAbstractCloudStorageService extends AbstractCloudStorageService {
private OSS client;

public AliyunAbstractCloudStorageService(CloudStorageConfig config) {
this.config = config;

//初始化
init();
}

private void init() {
client = new OSSClientBuilder().build(config.getAliyunEndPoint(), config.getAliyunAccessKeyId(),
config.getAliyunAccessKeySecret());
}

@Override
public String upload(byte[] data, String path) {
return upload(new ByteArrayInputStream(data), path);
}

@Override
public String upload(InputStream inputStream, String path) {
try {
client.putObject(config.getAliyunBucketName(), path, inputStream);
} catch (Exception e) {
throw new RRException("上传文件失败,请检查配置信息", e);
}

return config.getAliyunDomain() + "/" + path;
}

@Override
public String uploadSuffix(byte[] data, String suffix) {
return upload(data, getPath(config.getAliyunPrefix(), suffix));
}

@Override
public String uploadSuffix(InputStream inputStream, String suffix) {
return upload(inputStream, getPath(config.getAliyunPrefix(), suffix));
}
}

+ 85
- 0
src/main/java/com/github/niefy/modules/oss/cloud/CloudStorageConfig.java View File

@@ -0,0 +1,85 @@
package com.github.niefy.modules.oss.cloud;


import com.github.niefy.common.validator.group.AliyunGroup;
import com.github.niefy.common.validator.group.QcloudGroup;
import com.github.niefy.common.validator.group.QiniuGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

/**
* 云存储配置信息
* @author Mark sunlightcs@gmail.com
*/
@Data
public class CloudStorageConfig implements Serializable {
private static final long serialVersionUID = 1L;

//类型 1:七牛 2:阿里云 3:腾讯云
@Range(min = 1, max = 3, message = "类型错误")
private Integer type;

//七牛绑定的域名
@NotBlank(message = "七牛绑定的域名不能为空", groups = QiniuGroup.class)
@URL(message = "七牛绑定的域名格式不正确", groups = QiniuGroup.class)
private String qiniuDomain;
//七牛路径前缀
private String qiniuPrefix;
//七牛ACCESS_KEY
@NotBlank(message = "七牛AccessKey不能为空", groups = QiniuGroup.class)
private String qiniuAccessKey;
//七牛SECRET_KEY
@NotBlank(message = "七牛SecretKey不能为空", groups = QiniuGroup.class)
private String qiniuSecretKey;
//七牛存储空间名
@NotBlank(message = "七牛空间名不能为空", groups = QiniuGroup.class)
private String qiniuBucketName;

//阿里云绑定的域名
@NotBlank(message = "阿里云绑定的域名不能为空", groups = AliyunGroup.class)
@URL(message = "阿里云绑定的域名格式不正确", groups = AliyunGroup.class)
private String aliyunDomain;
//阿里云路径前缀
private String aliyunPrefix;
//阿里云EndPoint
@NotBlank(message = "阿里云EndPoint不能为空", groups = AliyunGroup.class)
private String aliyunEndPoint;
//阿里云AccessKeyId
@NotBlank(message = "阿里云AccessKeyId不能为空", groups = AliyunGroup.class)
private String aliyunAccessKeyId;
//阿里云AccessKeySecret
@NotBlank(message = "阿里云AccessKeySecret不能为空", groups = AliyunGroup.class)
private String aliyunAccessKeySecret;
//阿里云BucketName
@NotBlank(message = "阿里云BucketName不能为空", groups = AliyunGroup.class)
private String aliyunBucketName;

//腾讯云绑定的域名
@NotBlank(message = "腾讯云绑定的域名不能为空", groups = QcloudGroup.class)
@URL(message = "腾讯云绑定的域名格式不正确", groups = QcloudGroup.class)
private String qcloudDomain;
//腾讯云路径前缀
private String qcloudPrefix;
//腾讯云AppId
@NotNull(message = "腾讯云AppId不能为空", groups = QcloudGroup.class)
private Integer qcloudAppId;
//腾讯云SecretId
@NotBlank(message = "腾讯云SecretId不能为空", groups = QcloudGroup.class)
private String qcloudSecretId;
//腾讯云SecretKey
@NotBlank(message = "腾讯云SecretKey不能为空", groups = QcloudGroup.class)
private String qcloudSecretKey;
//腾讯云BucketName
@NotBlank(message = "腾讯云BucketName不能为空", groups = QcloudGroup.class)
private String qcloudBucketName;
//腾讯云COS所属地区
@NotBlank(message = "所属地区不能为空", groups = QcloudGroup.class)
private String qcloudRegion;


}

+ 35
- 0
src/main/java/com/github/niefy/modules/oss/cloud/OSSFactory.java View File

@@ -0,0 +1,35 @@
package com.github.niefy.modules.oss.cloud;


import com.github.niefy.common.utils.Constant;
import com.github.niefy.modules.sys.service.SysConfigService;
import com.github.niefy.common.utils.ConfigConstant;
import com.github.niefy.common.utils.SpringContextUtils;

/**
* 文件上传Factory
* @author Mark sunlightcs@gmail.com
*/
public final class OSSFactory {
private static SysConfigService sysConfigService;

static {
OSSFactory.sysConfigService = (SysConfigService) SpringContextUtils.getBean("sysConfigService");
}

public static AbstractCloudStorageService build() {
//获取云存储配置信息
CloudStorageConfig config = sysConfigService.getConfigObject(ConfigConstant.CLOUD_STORAGE_CONFIG_KEY, CloudStorageConfig.class);

if (config.getType() == Constant.CloudService.QINIU.getValue()) {
return new QiniuAbstractCloudStorageService(config);
} else if (config.getType() == Constant.CloudService.ALIYUN.getValue()) {
return new AliyunAbstractCloudStorageService(config);
} else if (config.getType() == Constant.CloudService.QCLOUD.getValue()) {
return new QcloudAbstractCloudStorageService(config);
}

return null;
}

}

+ 81
- 0
src/main/java/com/github/niefy/modules/oss/cloud/QcloudAbstractCloudStorageService.java View File

@@ -0,0 +1,81 @@
package com.github.niefy.modules.oss.cloud;


import com.github.niefy.common.exception.RRException;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.region.Region;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* 腾讯云存储
* @author Mark sunlightcs@gmail.com
*/
@Slf4j
public class QcloudAbstractCloudStorageService extends AbstractCloudStorageService {
private COSClient client;
private static final String SEPARTOR="/";

public QcloudAbstractCloudStorageService(CloudStorageConfig config) {
this.config = config;

//初始化
init();
}

private void init() {
COSCredentials credentials = new BasicCOSCredentials(config.getQcloudSecretId(),config.getQcloudSecretKey());

//设置bucket所在的区域,华南:gz 华北:tj 华东:sh
Region region = new Region(config.getQcloudRegion());
//初始化客户端配置
ClientConfig clientConfig = new ClientConfig(region);

client = new COSClient(credentials,clientConfig);
}

@Override
public String upload(byte[] data, String path) {
//腾讯云必需要以"/"开头
if (!path.startsWith(SEPARTOR)) {
path = SEPARTOR + path;
}
ObjectMetadata objectMetadata = new ObjectMetadata();
// 设置输入流长度为500
objectMetadata.setContentLength(data.length);
//上传到腾讯云
PutObjectRequest request = new PutObjectRequest(config.getQcloudBucketName(), path, new ByteArrayInputStream(data), objectMetadata);
client.putObject(request);

return config.getQcloudDomain() + path;
}

@Override
public String upload(InputStream inputStream, String path) {
try {
byte[] data = IOUtils.toByteArray(inputStream);
return this.upload(data, path);
} catch (IOException e) {
throw new RRException("上传文件失败", e);
}
}

@Override
public String uploadSuffix(byte[] data, String suffix) {
return upload(data, getPath(config.getQcloudPrefix(), suffix));
}

@Override
public String uploadSuffix(InputStream inputStream, String suffix) {
return upload(inputStream, getPath(config.getQcloudPrefix(), suffix));
}
}

+ 68
- 0
src/main/java/com/github/niefy/modules/oss/cloud/QiniuAbstractCloudStorageService.java View File

@@ -0,0 +1,68 @@
package com.github.niefy.modules.oss.cloud;

import com.github.niefy.common.exception.RRException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;

/**
* 七牛云存储
* @author Mark sunlightcs@gmail.com
*/
public class QiniuAbstractCloudStorageService extends AbstractCloudStorageService {
private UploadManager uploadManager;
private String token;

public QiniuAbstractCloudStorageService(CloudStorageConfig config) {
this.config = config;

//初始化
init();
}

private void init() {
uploadManager = new UploadManager(new Configuration(Region.autoRegion()));
token = Auth.create(config.getQiniuAccessKey(), config.getQiniuSecretKey()).
uploadToken(config.getQiniuBucketName());
}

@Override
public String upload(byte[] data, String path) {
try {
Response res = uploadManager.put(data, path, token);
if (!res.isOK()) {
throw new RuntimeException("上传七牛出错:" + res.toString());
}
} catch (Exception e) {
throw new RRException("上传文件失败,请核对七牛配置信息", e);
}

return config.getQiniuDomain() + "/" + path;
}

@Override
public String upload(InputStream inputStream, String path) {
try {
byte[] data = IOUtils.toByteArray(inputStream);
return this.upload(data, path);
} catch (IOException e) {
throw new RRException("上传文件失败", e);
}
}

@Override
public String uploadSuffix(byte[] data, String suffix) {
return upload(data, getPath(config.getQiniuPrefix(), suffix));
}

@Override
public String uploadSuffix(InputStream inputStream, String suffix) {
return upload(inputStream, getPath(config.getQiniuPrefix(), suffix));
}
}

+ 135
- 0
src/main/java/com/github/niefy/modules/oss/controller/SysOssController.java View File

@@ -0,0 +1,135 @@
package com.github.niefy.modules.oss.controller;

import com.alibaba.fastjson.JSON;
import com.github.niefy.common.exception.RRException;
import com.github.niefy.common.utils.ConfigConstant;
import com.github.niefy.common.utils.Constant;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.common.utils.R;
import com.github.niefy.common.validator.ValidatorUtils;
import com.github.niefy.common.validator.group.AliyunGroup;
import com.github.niefy.common.validator.group.QcloudGroup;
import com.github.niefy.common.validator.group.QiniuGroup;
import com.github.niefy.modules.oss.cloud.CloudStorageConfig;
import com.github.niefy.modules.oss.cloud.OSSFactory;
import com.github.niefy.modules.oss.entity.SysOssEntity;
import com.github.niefy.modules.oss.service.SysOssService;
import com.github.niefy.modules.sys.service.SysConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
* 文件上传
* @author Mark sunlightcs@gmail.com
*/
@RestController
@RequestMapping("sys/oss")
@Api(tags = {"对象存储/文件上传"})
public class SysOssController {
@Autowired
private SysOssService sysOssService;
@Autowired
private SysConfigService sysConfigService;

private final static String KEY = ConfigConstant.CLOUD_STORAGE_CONFIG_KEY;

/**
* 列表
*/
@ApiOperation(value = "文件列表",notes = "对象存储管理的文件")
@GetMapping("/list")
@RequiresPermissions("sys:oss:all")
public R list(@RequestParam Map<String, Object> params) {
PageUtils page = sysOssService.queryPage(params);

return R.ok().put("page", page);
}


/**
* 云存储配置信息
*/
@GetMapping("/config")
@RequiresPermissions("sys:oss:all")
@ApiOperation(value = "云存储配置信息",notes = "首次使用前先管理后台新增配置")
public R config() {
CloudStorageConfig config = sysConfigService.getConfigObject(KEY, CloudStorageConfig.class);

return R.ok().put("config", config);
}


/**
* 保存云存储配置信息
*/
@PostMapping("/saveConfig")
@RequiresPermissions("sys:oss:all")
@ApiOperation(value = "保存云存储配置信息")
public R saveConfig(@RequestBody CloudStorageConfig config) {
//校验类型
ValidatorUtils.validateEntity(config);

if (config.getType() == Constant.CloudService.QINIU.getValue()) {
//校验七牛数据
ValidatorUtils.validateEntity(config, QiniuGroup.class);
} else if (config.getType() == Constant.CloudService.ALIYUN.getValue()) {
//校验阿里云数据
ValidatorUtils.validateEntity(config, AliyunGroup.class);
} else if (config.getType() == Constant.CloudService.QCLOUD.getValue()) {
//校验腾讯云数据
ValidatorUtils.validateEntity(config, QcloudGroup.class);
}

sysConfigService.updateValueByKey(KEY, JSON.toJSONString(config));

return R.ok();
}


/**
* 上传文件
*/
@PostMapping("/upload")
@RequiresPermissions("sys:oss:all")
@ApiOperation(value = "上传文件到OSS")
public R upload(@RequestParam("file") MultipartFile file) throws Exception {
if (file.isEmpty()) {
throw new RRException("上传文件不能为空");
}

//上传文件
String suffix = Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().lastIndexOf("."));
String url = Objects.requireNonNull(OSSFactory.build()).uploadSuffix(file.getBytes(), suffix);

//保存文件信息
SysOssEntity ossEntity = new SysOssEntity();
ossEntity.setUrl(url);
ossEntity.setCreateDate(new Date());
sysOssService.save(ossEntity);

return R.ok().put("url", url);
}


/**
* 删除
*/
@PostMapping("/delete")
@RequiresPermissions("sys:oss:all")
@ApiOperation(value = "删除文件",notes = "只删除记录,云端文件不会删除")
public R delete(@RequestBody Long[] ids) {
sysOssService.removeByIds(Arrays.asList(ids));

return R.ok();
}

}

+ 14
- 0
src/main/java/com/github/niefy/modules/oss/dao/SysOssDao.java View File

@@ -0,0 +1,14 @@
package com.github.niefy.modules.oss.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.oss.entity.SysOssEntity;
import org.apache.ibatis.annotations.Mapper;

/**
* 文件上传
* @author Mark sunlightcs@gmail.com
*/
@Mapper
public interface SysOssDao extends BaseMapper<SysOssEntity> {

}

+ 27
- 0
src/main/java/com/github/niefy/modules/oss/entity/SysOssEntity.java View File

@@ -0,0 +1,27 @@
package com.github.niefy.modules.oss.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;


/**
* 文件上传
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_oss")
public class SysOssEntity implements Serializable {
private static final long serialVersionUID = 1L;

@TableId
private Long id;
//URL地址
private String url;
//创建时间
private Date createDate;

}

+ 20
- 0
src/main/java/com/github/niefy/modules/oss/service/SysOssService.java View File

@@ -0,0 +1,20 @@
package com.github.niefy.modules.oss.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.modules.oss.entity.SysOssEntity;

import java.util.Map;

/**
* 文件上传
* @author Mark sunlightcs@gmail.com
*/
public interface SysOssService extends IService<SysOssEntity> {
/**
* 分页查询用户数据
* @param params 查询参数
* @return PageUtils 分页结果
*/
PageUtils queryPage(Map<String, Object> params);
}

+ 27
- 0
src/main/java/com/github/niefy/modules/oss/service/impl/SysOssServiceImpl.java View File

@@ -0,0 +1,27 @@
package com.github.niefy.modules.oss.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.niefy.modules.oss.dao.SysOssDao;
import com.github.niefy.modules.oss.entity.SysOssEntity;
import com.github.niefy.modules.oss.service.SysOssService;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.common.utils.Query;
import org.springframework.stereotype.Service;

import java.util.Map;


@Service("sysOssService")
public class SysOssServiceImpl extends ServiceImpl<SysOssDao, SysOssEntity> implements SysOssService {

@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<SysOssEntity> page = this.page(
new Query<SysOssEntity>().getPage(params)
);

return new PageUtils(page);
}

}

+ 22
- 0
src/main/java/com/github/niefy/modules/sys/controller/AbstractController.java View File

@@ -0,0 +1,22 @@
package com.github.niefy.modules.sys.controller;

import com.github.niefy.modules.sys.entity.SysUserEntity;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Controller公共组件
* @author Mark sunlightcs@gmail.com
*/
public abstract class AbstractController {
protected Logger logger = LoggerFactory.getLogger(getClass());

protected SysUserEntity getUser() {
return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
}

protected Long getUserId() {
return getUser().getUserId();
}
}

+ 97
- 0
src/main/java/com/github/niefy/modules/sys/controller/SysConfigController.java View File

@@ -0,0 +1,97 @@
package com.github.niefy.modules.sys.controller;


import com.github.niefy.common.annotation.SysLog;
import com.github.niefy.modules.sys.entity.SysConfigEntity;
import com.github.niefy.modules.sys.service.SysConfigService;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.common.utils.R;
import com.github.niefy.common.validator.ValidatorUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
* 系统配置信息
* @author Mark sunlightcs@gmail.com
*/
@RestController
@RequestMapping("/sys/config")
@Api(tags = {"系统配置信息"})
public class SysConfigController extends AbstractController {
@Autowired
private SysConfigService sysConfigService;

/**
* 所有配置列表
*/
@GetMapping("/list")
@RequiresPermissions("sys:config:list")
@ApiOperation(value = "配置项列表",notes = "配置项需专业人员修改")
public R list(@RequestParam Map<String, Object> params) {
PageUtils page = sysConfigService.queryPage(params);

return R.ok().put("page", page);
}


/**
* 配置信息
*/
@GetMapping("/info/{id}")
@RequiresPermissions("sys:config:info")
@ApiOperation(value = "配置详情",notes = "")
public R info(@PathVariable("id") Long id) {
SysConfigEntity config = sysConfigService.getById(id);

return R.ok().put("config", config);
}

/**
* 保存配置
*/
@SysLog("保存配置")
@PostMapping("/save")
@RequiresPermissions("sys:config:save")
@ApiOperation(value = "保存配置",notes = "")
public R save(@RequestBody SysConfigEntity config) {
ValidatorUtils.validateEntity(config);

sysConfigService.saveConfig(config);

return R.ok();
}

/**
* 修改配置
*/
@SysLog("修改配置")
@PostMapping("/update")
@RequiresPermissions("sys:config:update")
@ApiOperation(value = "修改配置",notes = "")
public R update(@RequestBody SysConfigEntity config) {
ValidatorUtils.validateEntity(config);

sysConfigService.update(config);

return R.ok();
}

/**
* 删除配置
*/
@SysLog("删除配置")
@PostMapping("/delete")
@RequiresPermissions("sys:config:delete")
@ApiOperation(value = "删除配置",notes = "")
public R delete(@RequestBody Long[] ids) {
sysConfigService.deleteBatch(ids);

return R.ok();
}

}

+ 43
- 0
src/main/java/com/github/niefy/modules/sys/controller/SysLogController.java View File

@@ -0,0 +1,43 @@
package com.github.niefy.modules.sys.controller;

import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.common.utils.R;
import com.github.niefy.modules.sys.service.SysLogService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Map;


/**
* 系统日志
* @author Mark sunlightcs@gmail.com
*/
@Controller
@RequestMapping("/sys/log")
@Api(tags = {"系统日志-管理后台"})
public class SysLogController {
@Autowired
private SysLogService sysLogService;

/**
* 列表
*/
@ResponseBody
@GetMapping("/list")
@RequiresPermissions("sys:log:list")
@ApiOperation(value = "日志列表",notes = "")
public R list(@RequestParam Map<String, Object> params) {
PageUtils page = sysLogService.queryPage(params);

return R.ok().put("page", page);
}

}

+ 98
- 0
src/main/java/com/github/niefy/modules/sys/controller/SysLoginController.java View File

@@ -0,0 +1,98 @@
package com.github.niefy.modules.sys.controller;

import com.github.niefy.common.utils.R;
import com.github.niefy.modules.sys.entity.SysUserEntity;
import com.github.niefy.modules.sys.form.SysLoginForm;
import com.github.niefy.modules.sys.service.SysCaptchaService;
import com.github.niefy.modules.sys.service.SysUserService;
import com.github.niefy.modules.sys.service.SysUserTokenService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;

/**
* 登录相关
* @author Mark sunlightcs@gmail.com
*/
@RestController
@Api(tags = {"系统登录-管理后台"})
public class SysLoginController extends AbstractController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserTokenService sysUserTokenService;
@Autowired
private SysCaptchaService sysCaptchaService;

/**
* 验证码
*/
@GetMapping("captcha")
@ApiOperation(value = "获取验证码",notes = "返回验证码图片")
public void captcha(HttpServletResponse response, @ApiParam(value = "随意填,但每次不得重复", required = true)String uuid) throws IOException {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");

//获取图片验证码
BufferedImage image = sysCaptchaService.getCaptcha(uuid);

try(//try-with-resources 语法,自动关闭资源
ServletOutputStream out = response.getOutputStream()
){
ImageIO.write(image, "jpg", out);
}
}

/**
* 登录
*/
@PostMapping("/sys/login")
@ApiOperation(value = "登录",notes = "需先获取验证码")
public Map<String, Object> login(@RequestBody SysLoginForm form) {
boolean captcha = sysCaptchaService.validate(form.getUuid(), form.getCaptcha());
if (!captcha) {
return R.error("验证码不正确");
}

//用户信息
SysUserEntity user = sysUserService.queryByUserName(form.getUsername());

//账号不存在、密码错误
if (user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
return R.error("账号或密码不正确");
}

//账号锁定
if (user.getStatus() == 0) {
return R.error("账号已被锁定,请联系管理员");
}

//生成token,并保存到数据库
return sysUserTokenService.createToken(user.getUserId());
}


/**
* 退出
*/
@PostMapping("/sys/logout")
@ApiOperation(value = "退出登录",notes = "")
public R logout() {
sysUserTokenService.logout(getUserId());
return R.ok();
}

}

+ 198
- 0
src/main/java/com/github/niefy/modules/sys/controller/SysMenuController.java View File

@@ -0,0 +1,198 @@
package com.github.niefy.modules.sys.controller;

import com.github.niefy.common.annotation.SysLog;
import com.github.niefy.common.exception.RRException;
import com.github.niefy.common.utils.Constant;
import com.github.niefy.common.utils.R;
import com.github.niefy.modules.sys.entity.SysMenuEntity;
import com.github.niefy.modules.sys.service.ShiroService;
import com.github.niefy.modules.sys.service.SysMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
* 系统菜单
* @author Mark sunlightcs@gmail.com
*/
@RestController
@RequestMapping("/sys/menu")
@Api(tags = {"管理后台菜单"})
public class SysMenuController extends AbstractController {
@Autowired
private SysMenuService sysMenuService;
@Autowired
private ShiroService shiroService;

/**
* 导航菜单
*/
@GetMapping("/nav")
@ApiOperation(value = "管理后台导航菜单",notes = "不同登录用户结果不同")
public R nav() {
List<SysMenuEntity> menuList = sysMenuService.getUserMenuList(getUserId());
Set<String> permissions = shiroService.getUserPermissions(getUserId());
return Objects.requireNonNull(R.ok().put("menuList", menuList)).put("permissions", permissions);
}

/**
* 所有菜单列表
*/
@GetMapping("/list")
@RequiresPermissions("sys:menu:list")
@ApiOperation(value = "管理后台菜单列表",notes = "超级管理员可看到全部列表")
public List<SysMenuEntity> list() {
List<SysMenuEntity> menuList;
// 超级管理员
if (getUserId() == Constant.SUPER_ADMIN) {
menuList = sysMenuService.list();
} else {
menuList = sysMenuService.queryUserAllMenu(getUserId());
}
for (SysMenuEntity sysMenuEntity : menuList) {
SysMenuEntity parentMenuEntity = sysMenuService.getById(sysMenuEntity.getParentId());
if (parentMenuEntity != null) {
sysMenuEntity.setParentName(parentMenuEntity.getName());
}
}

return menuList;
}

/**
* 选择菜单(添加、修改菜单)
*/
@GetMapping("/select")
@RequiresPermissions("sys:menu:select")
@ApiOperation(value = "选择菜单",notes = "")
public R select() {
//查询列表数据
List<SysMenuEntity> menuList = sysMenuService.queryNotButtonList();

//添加顶级菜单
SysMenuEntity root = new SysMenuEntity();
root.setMenuId(0L);
root.setName("一级菜单");
root.setParentId(-1L);
root.setOpen(true);
menuList.add(root);

return R.ok().put("menuList", menuList);
}

/**
* 菜单信息
*/
@GetMapping("/info/{menuId}")
@RequiresPermissions("sys:menu:info")
@ApiOperation(value = "菜单详情",notes = "")
public R info(@PathVariable("menuId") Long menuId) {
SysMenuEntity menu = sysMenuService.getById(menuId);
return R.ok().put("menu", menu);
}

/**
* 保存
*/
@SysLog("保存菜单")
@PostMapping("/save")
@RequiresPermissions("sys:menu:save")
@ApiOperation(value = "保存菜单",notes = "")
public R save(@RequestBody SysMenuEntity menu) {
//数据校验
verifyForm(menu);

sysMenuService.save(menu);

return R.ok();
}

/**
* 修改
*/
@SysLog("修改菜单")
@PostMapping("/update")
@RequiresPermissions("sys:menu:update")
@ApiOperation(value = "修改菜单",notes = "")
public R update(@RequestBody SysMenuEntity menu) {
//数据校验
verifyForm(menu);

sysMenuService.updateById(menu);

return R.ok();
}

/**
* 删除
*/
@SysLog("删除菜单")
@PostMapping("/delete/{menuId}")
@RequiresPermissions("sys:menu:delete")
@ApiOperation(value = "删除菜单",notes = "")
public R delete(@PathVariable("menuId") long menuId) {
if (menuId <= 31) {
return R.error("系统菜单,不能删除");
}

//判断是否有子菜单或按钮
List<SysMenuEntity> menuList = sysMenuService.queryListParentId(menuId);
if (menuList.size() > 0) {
return R.error("请先删除子菜单或按钮");
}

sysMenuService.delete(menuId);

return R.ok();
}

/**
* 验证参数是否正确
*/
private void verifyForm(SysMenuEntity menu) {
if (StringUtils.isBlank(menu.getName())) {
throw new RRException("菜单名称不能为空");
}

if (menu.getParentId() == null) {
throw new RRException("上级菜单不能为空");
}

//菜单
if (menu.getType() == Constant.MenuType.MENU.getValue()) {
if (StringUtils.isBlank(menu.getUrl())) {
throw new RRException("菜单URL不能为空");
}
}

//上级菜单类型
int parentType = Constant.MenuType.CATALOG.getValue();
if (menu.getParentId() != 0) {
SysMenuEntity parentMenu = sysMenuService.getById(menu.getParentId());
parentType = parentMenu.getType();
}

//目录、菜单
if (menu.getType() == Constant.MenuType.CATALOG.getValue() ||
menu.getType() == Constant.MenuType.MENU.getValue()) {
if (parentType != Constant.MenuType.CATALOG.getValue()) {
throw new RRException("上级菜单只能为目录类型");
}
return;
}

//按钮
if (menu.getType() == Constant.MenuType.BUTTON.getValue()) {
if (parentType != Constant.MenuType.MENU.getValue()) {
throw new RRException("上级菜单只能为菜单类型");
}
}
}
}

+ 129
- 0
src/main/java/com/github/niefy/modules/sys/controller/SysRoleController.java View File

@@ -0,0 +1,129 @@
package com.github.niefy.modules.sys.controller;

import com.github.niefy.common.annotation.SysLog;
import com.github.niefy.common.utils.Constant;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.common.utils.R;
import com.github.niefy.common.validator.ValidatorUtils;
import com.github.niefy.modules.sys.entity.SysRoleEntity;
import com.github.niefy.modules.sys.service.SysRoleMenuService;
import com.github.niefy.modules.sys.service.SysRoleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 角色管理
* @author Mark sunlightcs@gmail.com
*/
@RestController
@RequestMapping("/sys/role")
@Api(tags = {"管理后台角色"})
public class SysRoleController extends AbstractController {
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysRoleMenuService sysRoleMenuService;

/**
* 角色列表
*/
@GetMapping("/list")
@RequiresPermissions("sys:role:list")
@ApiOperation(value = "角色列表",notes = "")
public R list(@RequestParam Map<String, Object> params) {
//如果不是超级管理员,则只查询自己创建的角色列表
if (getUserId() != Constant.SUPER_ADMIN) {
params.put("createUserId", getUserId());
}

PageUtils page = sysRoleService.queryPage(params);

return R.ok().put("page", page);
}

/**
* 角色列表
*/
@GetMapping("/select")
@RequiresPermissions("sys:role:select")
@ApiOperation(value = "拥有的角色列表",notes = "")
public R select() {
Map<String, Object> map = new HashMap<>(4);

//如果不是超级管理员,则只查询自己所拥有的角色列表
if (getUserId() != Constant.SUPER_ADMIN) {
map.put("create_user_id", getUserId());
}
List<SysRoleEntity> list = sysRoleService.listByMap(map);

return R.ok().put("list", list);
}

/**
* 角色信息
*/
@GetMapping("/info/{roleId}")
@RequiresPermissions("sys:role:info")
@ApiOperation(value = "角色详情",notes = "")
public R info(@PathVariable("roleId") Long roleId) {
SysRoleEntity role = sysRoleService.getById(roleId);

//查询角色对应的菜单
List<Long> menuIdList = sysRoleMenuService.queryMenuIdList(roleId);
role.setMenuIdList(menuIdList);

return R.ok().put("role", role);
}

/**
* 保存角色
*/
@SysLog("保存角色")
@PostMapping("/save")
@RequiresPermissions("sys:role:save")
@ApiOperation(value = "保存角色",notes = "")
public R save(@RequestBody SysRoleEntity role) {
ValidatorUtils.validateEntity(role);

role.setCreateUserId(getUserId());
sysRoleService.saveRole(role);

return R.ok();
}

/**
* 修改角色
*/
@SysLog("修改角色")
@PostMapping("/update")
@RequiresPermissions("sys:role:update")
@ApiOperation(value = "修改角色",notes = "")
public R update(@RequestBody SysRoleEntity role) {
ValidatorUtils.validateEntity(role);

role.setCreateUserId(getUserId());
sysRoleService.update(role);

return R.ok();
}

/**
* 删除角色
*/
@SysLog("删除角色")
@PostMapping("/delete")
@RequiresPermissions("sys:role:delete")
@ApiOperation(value = "删除角色",notes = "")
public R delete(@RequestBody Long[] roleIds) {
sysRoleService.deleteBatch(roleIds);

return R.ok();
}
}

+ 155
- 0
src/main/java/com/github/niefy/modules/sys/controller/SysUserController.java View File

@@ -0,0 +1,155 @@
package com.github.niefy.modules.sys.controller;

import com.github.niefy.common.annotation.SysLog;
import com.github.niefy.common.utils.Constant;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.common.utils.R;
import com.github.niefy.common.validator.ValidatorUtils;
import com.github.niefy.common.validator.group.AddGroup;
import com.github.niefy.common.validator.group.UpdateGroup;
import com.github.niefy.modules.sys.entity.SysUserEntity;
import com.github.niefy.modules.sys.form.PasswordForm;
import com.github.niefy.modules.sys.service.SysUserRoleService;
import com.github.niefy.modules.sys.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
* 系统用户
* @author Mark sunlightcs@gmail.com
*/
@RestController
@RequestMapping("/sys/user")
@Api(tags = {"管理后台用户-管理后台"})
public class SysUserController extends AbstractController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserRoleService sysUserRoleService;


/**
* 所有用户列表
*/
@GetMapping("/list")
@RequiresPermissions("sys:user:list")
@ApiOperation(value = "用户列表",notes = "")
public R list(@RequestParam Map<String, Object> params) {
//只有超级管理员,才能查看所有管理员列表
if (getUserId() != Constant.SUPER_ADMIN) {
params.put("createUserId", getUserId());
}
PageUtils page = sysUserService.queryPage(params);

return R.ok().put("page", page);
}

/**
* 获取登录的用户信息
*/
@GetMapping("/info")
@ApiOperation(value = "登录用户信息",notes = "")
public R info() {
return R.ok().put("user", getUser());
}

/**
* 修改登录用户密码
*/
@SysLog("修改密码")
@PostMapping("/password")
@ApiOperation(value = "修改密码",notes = "")
public R password(@RequestBody PasswordForm form) {
Assert.hasText(form.getNewPassword(), "新密码不为能空");

//sha256加密
String password = new Sha256Hash(form.getPassword(), getUser().getSalt()).toHex();
//sha256加密
String newPassword = new Sha256Hash(form.getNewPassword(), getUser().getSalt()).toHex();

//更新密码
boolean flag = sysUserService.updatePassword(getUserId(), password, newPassword);
if (!flag) {
return R.error("原密码不正确");
}

return R.ok();
}

/**
* 用户信息
*/
@GetMapping("/info/{userId}")
@RequiresPermissions("sys:user:info")
@ApiOperation(value = "用户信息",notes = "")
public R info(@PathVariable("userId") Long userId) {
SysUserEntity user = sysUserService.getById(userId);

//获取用户所属的角色列表
List<Long> roleIdList = sysUserRoleService.queryRoleIdList(userId);
user.setRoleIdList(roleIdList);

return R.ok().put("user", user);
}

/**
* 保存用户
*/
@SysLog("保存用户")
@PostMapping("/save")
@RequiresPermissions("sys:user:save")
@ApiOperation(value = "保存用户",notes = "")
public R save(@RequestBody SysUserEntity user) {
ValidatorUtils.validateEntity(user, AddGroup.class);

user.setCreateUserId(getUserId());
sysUserService.saveUser(user);

return R.ok();
}

/**
* 修改用户
*/
@SysLog("修改用户")
@PostMapping("/update")
@RequiresPermissions("sys:user:update")
@ApiOperation(value = "删除用户",notes = "")
public R update(@RequestBody SysUserEntity user) {
ValidatorUtils.validateEntity(user, UpdateGroup.class);

user.setCreateUserId(getUserId());
sysUserService.update(user);

return R.ok();
}

/**
* 删除用户
*/
@SysLog("删除用户")
@PostMapping("/delete")
@RequiresPermissions("sys:user:delete")
@ApiOperation(value = "删除用户",notes = "")
public R delete(@RequestBody Long[] userIds) {
if (Arrays.stream(userIds).anyMatch(id->id.intValue()==Constant.SUPER_ADMIN)) {
return R.error("系统管理员不能删除");
}
if (Arrays.stream(userIds).anyMatch(id->getUserId().equals(id))) {
return R.error("当前用户不能删除");
}

sysUserService.deleteBatch(userIds);

return R.ok();
}
}

+ 14
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysCaptchaDao.java View File

@@ -0,0 +1,14 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysCaptchaEntity;
import org.apache.ibatis.annotations.Mapper;

/**
* 验证码
* @author Mark sunlightcs@gmail.com
*/
@Mapper
public interface SysCaptchaDao extends BaseMapper<SysCaptchaEntity> {

}

+ 28
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysConfigDao.java View File

@@ -0,0 +1,28 @@
package com.github.niefy.modules.sys.dao;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysConfigEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
* 系统配置信息
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysConfigDao extends BaseMapper<SysConfigEntity> {

/**
* 根据key,查询value
*/
SysConfigEntity queryByKey(String paramKey);

/**
* 根据key,更新value
*/
int updateValueByKey(@Param("paramKey") String paramKey, @Param("paramValue") String paramValue);

}

+ 15
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysLogDao.java View File

@@ -0,0 +1,15 @@
package com.github.niefy.modules.sys.dao;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysLogEntity;
import org.apache.ibatis.annotations.Mapper;

/**
* 系统日志
* @author Mark sunlightcs@gmail.com
*/
@Mapper
public interface SysLogDao extends BaseMapper<SysLogEntity> {

}

+ 35
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysMenuDao.java View File

@@ -0,0 +1,35 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysMenuEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
* 菜单管理
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysMenuDao extends BaseMapper<SysMenuEntity> {

/**
* 根据父菜单,查询子菜单
* @param parentId 父菜单ID
*/
List<SysMenuEntity> queryListParentId(Long parentId);

/**
* 获取不包含按钮的菜单列表
*/
List<SysMenuEntity> queryNotButtonList();

/**
* 获取用户所有的菜单
* @param userId 用户id
* @return
*/
List<SysMenuEntity> queryUserAllMenu(Long userId);
}

+ 22
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysRoleDao.java View File

@@ -0,0 +1,22 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysRoleEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
* 角色管理
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysRoleDao extends BaseMapper<SysRoleEntity> {

/**
* 查询用户创建的角色ID列表
*/
List<Long> queryRoleIdList(Long createUserId);
}

+ 32
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysRoleMenuDao.java View File

@@ -0,0 +1,32 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysRoleMenuEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
* 角色与菜单对应关系
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysRoleMenuDao extends BaseMapper<SysRoleMenuEntity> {

/**
* 根据角色ID,获取菜单ID列表
*/
List<Long> queryMenuIdList(Long roleId);

/**
* 根据角色ID数组,批量删除
*/
int deleteBatch(Long[] roleIds);

/**
* 查询用户的所有菜单ID
*/
List<Long> queryAllMenuId(Long userId);
}

+ 29
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysUserDao.java View File

@@ -0,0 +1,29 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysUserEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
* 系统用户
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysUserDao extends BaseMapper<SysUserEntity> {

/**
* 查询用户的所有权限
* @param userId 用户ID
*/
List<String> queryAllPerms(Long userId);

/**
* 根据用户名,查询系统用户
*/
SysUserEntity queryByUserName(String username);

}

+ 28
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysUserRoleDao.java View File

@@ -0,0 +1,28 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysUserRoleEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
* 用户与角色对应关系
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysUserRoleDao extends BaseMapper<SysUserRoleEntity> {

/**
* 根据用户ID,获取角色ID列表
*/
List<Long> queryRoleIdList(Long userId);


/**
* 根据角色ID数组,批量删除
*/
int deleteBatch(Long[] roleIds);
}

+ 18
- 0
src/main/java/com/github/niefy/modules/sys/dao/SysUserTokenDao.java View File

@@ -0,0 +1,18 @@
package com.github.niefy.modules.sys.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.niefy.modules.sys.entity.SysUserTokenEntity;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;

/**
* 系统用户Token
* @author Mark sunlightcs@gmail.com
*/
@Mapper
@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期
public interface SysUserTokenDao extends BaseMapper<SysUserTokenEntity> {

SysUserTokenEntity queryByToken(String token);

}

+ 28
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysCaptchaEntity.java View File

@@ -0,0 +1,28 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.Date;

/**
* 系统验证码
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_captcha")
public class SysCaptchaEntity {
@TableId(type = IdType.INPUT)
private String uuid;
/**
* 验证码
*/
private String code;
/**
* 过期时间
*/
private Date expireTime;

}

+ 27
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysConfigEntity.java View File

@@ -0,0 +1,27 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

import javax.validation.constraints.NotBlank;

/**
* 系统配置信息
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_config")
public class SysConfigEntity implements Serializable{
private static final long serialVersionUID = 1L;
@TableId
private Long id;
@NotBlank(message = "参数名不能为空")
private String paramKey;
@NotBlank(message = "参数值不能为空")
private String paramValue;
private String remark;

}

+ 46
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysLogEntity.java View File

@@ -0,0 +1,46 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;


/**
* 系统日志
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_log")
public class SysLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long id;
//用户名
private String username;
//用户操作
private String operation;
//请求方法
private String method;
//请求参数
private String params;
//执行时长(毫秒)
private Long time;
//IP地址
private String ip;
//创建时间
private Date createDate;

public SysLogEntity() {
}

public SysLogEntity(String openid, String method, String params, String ip) {
this.username = openid;
this.method = method;
this.params = params;
this.ip = ip;
this.createDate = new Date();
}
}

+ 76
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysMenuEntity.java View File

@@ -0,0 +1,76 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
* 菜单管理
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_menu")
public class SysMenuEntity implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 菜单ID
*/
@TableId
private Long menuId;

/**
* 父菜单ID,一级菜单为0
*/
private Long parentId;

/**
* 父菜单名称
*/
@TableField(exist = false)
private String parentName;

/**
* 菜单名称
*/
private String name;

/**
* 菜单URL
*/
private String url;

/**
* 授权(多个用逗号分隔,如:user:list,user:create)
*/
private String perms;

/**
* 类型 0:目录 1:菜单 2:按钮
*/
private Integer type;

/**
* 菜单图标
*/
private String icon;

/**
* 排序
*/
private Integer orderNum;

/**
* ztree属性
*/
@TableField(exist = false)
private Boolean open;

@TableField(exist = false)
private List<?> list;

}

+ 53
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysRoleEntity.java View File

@@ -0,0 +1,53 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
* 角色
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_role")
public class SysRoleEntity implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 角色ID
*/
@TableId
private Long roleId;

/**
* 角色名称
*/
@NotBlank(message = "角色名称不能为空")
private String roleName;

/**
* 备注
*/
private String remark;

/**
* 创建者ID
*/
private Long createUserId;

@TableField(exist = false)
private List<Long> menuIdList;

/**
* 创建时间
*/
private Date createTime;


}

+ 31
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysRoleMenuEntity.java View File

@@ -0,0 +1,31 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
* 角色与菜单对应关系
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_role_menu")
public class SysRoleMenuEntity implements Serializable {
private static final long serialVersionUID = 1L;

@TableId
private Long id;

/**
* 角色ID
*/
private Long roleId;

/**
* 菜单ID
*/
private Long menuId;

}

+ 81
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysUserEntity.java View File

@@ -0,0 +1,81 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.github.niefy.common.validator.group.AddGroup;
import com.github.niefy.common.validator.group.UpdateGroup;
import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
* 系统用户
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_user")
public class SysUserEntity implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 用户ID
*/
@TableId
private Long userId;

/**
* 用户名
*/
@NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String username;

/**
* 密码
*/
@NotBlank(message = "密码不能为空", groups = AddGroup.class)
private String password;

/**
* 盐
*/
private String salt;

/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空", groups = {AddGroup.class, UpdateGroup.class})
@Email(message = "邮箱格式不正确", groups = {AddGroup.class, UpdateGroup.class})
private String email;

/**
* 手机号
*/
private String mobile;

/**
* 状态 0:禁用 1:正常
*/
private Integer status;

/**
* 角色ID列表
*/
@TableField(exist = false)
private List<Long> roleIdList;

/**
* 创建者ID
*/
private Long createUserId;

/**
* 创建时间
*/
private Date createTime;

}

+ 31
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysUserRoleEntity.java View File

@@ -0,0 +1,31 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
* 用户与角色对应关系
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_user_role")
public class SysUserRoleEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long id;

/**
* 用户ID
*/
private Long userId;

/**
* 角色ID
*/
private Long roleId;


}

+ 31
- 0
src/main/java/com/github/niefy/modules/sys/entity/SysUserTokenEntity.java View File

@@ -0,0 +1,31 @@
package com.github.niefy.modules.sys.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;


/**
* 系统用户Token
* @author Mark sunlightcs@gmail.com
*/
@Data
@TableName("sys_user_token")
public class SysUserTokenEntity implements Serializable {
private static final long serialVersionUID = 1L;

//用户ID
@TableId(type = IdType.INPUT)
private Long userId;
//token
private String token;
//过期时间
private Date expireTime;
//更新时间
private Date updateTime;

}

+ 20
- 0
src/main/java/com/github/niefy/modules/sys/form/PasswordForm.java View File

@@ -0,0 +1,20 @@
package com.github.niefy.modules.sys.form;

import lombok.Data;

/**
* 密码表单
* @author Mark sunlightcs@gmail.com
*/
@Data
public class PasswordForm {
/**
* 原密码
*/
private String password;
/**
* 新密码
*/
private String newPassword;

}

+ 22
- 0
src/main/java/com/github/niefy/modules/sys/form/SysLoginForm.java View File

@@ -0,0 +1,22 @@
package com.github.niefy.modules.sys.form;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
* 登录表单
* @author Mark sunlightcs@gmail.com
*/
@Data
public class SysLoginForm {
@ApiModelProperty(value = "登录用户名",required = true)
private String username;
@ApiModelProperty(value = "登录密码",required = true)
private String password;
@ApiModelProperty(value = "验证码图片",notes = "可使用验证码接口获取",required = true)
private String captcha;
@ApiModelProperty(value = "验证码图片对应UUID",notes = "获取验证码时填写的那个",required = true)
private String uuid;


}

+ 100
- 0
src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Filter.java View File

@@ -0,0 +1,100 @@
package com.github.niefy.modules.sys.oauth2;

import com.alibaba.fastjson.JSON;
import com.github.niefy.common.utils.R;
import com.github.niefy.common.utils.HttpContextUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* oauth2过滤器
* @author Mark sunlightcs@gmail.com
*/
public class OAuth2Filter extends AuthenticatingFilter {
Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);

if (StringUtils.isBlank(token)) {
return null;
}

return new OAuth2Token(token);
}

@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
}

@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());

String json = JSON.toJSONString(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));

httpResponse.getWriter().print(json);

return false;
}

return executeLogin(request, response);
}

@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());

String json = JSON.toJSONString(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
logger.error("onLoginFailure", e1);
}

return false;
}

/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest) {
//从header中获取token
String token = httpRequest.getHeader("token");

//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter("token");
}

return token;
}


}

+ 69
- 0
src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Realm.java View File

@@ -0,0 +1,69 @@
package com.github.niefy.modules.sys.oauth2;

import com.github.niefy.modules.sys.entity.SysUserEntity;
import com.github.niefy.modules.sys.service.ShiroService;
import com.github.niefy.modules.sys.entity.SysUserTokenEntity;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
* 认证
* @author Mark sunlightcs@gmail.com
*/
@Component
public class OAuth2Realm extends AuthorizingRealm {
@Autowired
private ShiroService shiroService;

@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}

/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal();
Long userId = user.getUserId();

//用户权限列表
Set<String> permsSet = shiroService.getUserPermissions(userId);

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}

/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();

//根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken);
//token失效
if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException("token失效,请重新登录");
}

//查询用户信息
SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId());
//账号锁定
if (user.getStatus() == 0) {
throw new LockedAccountException("账号已被锁定,请联系管理员");
}

return new SimpleAuthenticationInfo(user, accessToken, getName());
}
}

+ 27
- 0
src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Token.java View File

@@ -0,0 +1,27 @@
package com.github.niefy.modules.sys.oauth2;


import org.apache.shiro.authc.AuthenticationToken;

/**
* token
* @author Mark sunlightcs@gmail.com
*/
public class OAuth2Token implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private String token;

public OAuth2Token(String token) {
this.token = token;
}

@Override
public String getPrincipal() {
return token;
}

@Override
public Object getCredentials() {
return token;
}
}

+ 43
- 0
src/main/java/com/github/niefy/modules/sys/oauth2/TokenGenerator.java View File

@@ -0,0 +1,43 @@
package com.github.niefy.modules.sys.oauth2;

import com.github.niefy.common.exception.RRException;

import java.security.MessageDigest;
import java.util.UUID;

/**
* 生成token
* @author Mark sunlightcs@gmail.com
*/
public class TokenGenerator {

public static String generateValue() {
return generateValue(UUID.randomUUID().toString());
}

private static final char[] HEX_CODE = "0123456789abcdef".toCharArray();

public static String toHexString(byte[] data) {
if (data == null) {
return null;
}
StringBuilder r = new StringBuilder(data.length * 2);
for (byte b : data) {
r.append(HEX_CODE[(b >> 4) & 0xF]);
r.append(HEX_CODE[(b & 0xF)]);
}
return r.toString();
}

public static String generateValue(String param) {
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] messageDigest = algorithm.digest();
return toHexString(messageDigest);
} catch (Exception e) {
throw new RRException("生成Token失败", e);
}
}
}

+ 26
- 0
src/main/java/com/github/niefy/modules/sys/service/ShiroService.java View File

@@ -0,0 +1,26 @@
package com.github.niefy.modules.sys.service;

import com.github.niefy.modules.sys.entity.SysUserEntity;
import com.github.niefy.modules.sys.entity.SysUserTokenEntity;

import java.util.Set;

/**
* shiro相关接口
* @author Mark sunlightcs@gmail.com
*/
public interface ShiroService {
/**
* 获取用户权限列表
*/
Set<String> getUserPermissions(long userId);

SysUserTokenEntity queryByToken(String token);

/**
* 根据用户ID,查询用户
* @param userId 用户ID
* @return SysUserEntity 管理用户
*/
SysUserEntity queryUser(Long userId);
}

+ 26
- 0
src/main/java/com/github/niefy/modules/sys/service/SysCaptchaService.java View File

@@ -0,0 +1,26 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.modules.sys.entity.SysCaptchaEntity;

import java.awt.image.BufferedImage;

/**
* 验证码
* @author Mark sunlightcs@gmail.com
*/
public interface SysCaptchaService extends IService<SysCaptchaEntity> {

/**
* 获取图片验证码
*/
BufferedImage getCaptcha(String uuid);

/**
* 验证码效验
* @param uuid uuid
* @param code 验证码
* @return true:成功 false:失败
*/
boolean validate(String uuid, String code);
}

+ 61
- 0
src/main/java/com/github/niefy/modules/sys/service/SysConfigService.java View File

@@ -0,0 +1,61 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.modules.sys.entity.SysConfigEntity;
import com.github.niefy.common.utils.PageUtils;

import java.util.Map;

/**
* 系统配置信息
* @author Mark sunlightcs@gmail.com
*/
public interface SysConfigService extends IService<SysConfigEntity> {
/**
* 分页查询用户数据
* @param params 查询参数
* @return PageUtils 分页结果
*/
PageUtils queryPage(Map<String, Object> params);

/**
* 保存配置信息
*/
void saveConfig(SysConfigEntity config);

/**
* 更新配置信息
*/
void update(SysConfigEntity config);

/**
* 根据key,更新value
*/
void updateValueByKey(String key, String value);

/**
* 删除配置信息
*/
void deleteBatch(Long[] ids);

/**
* 根据key,获取配置的value值
* @param key key
*/
String getValue(String key);
/**
* 根据key,获取配置的SysConfig信息
*
* @param key key
*/
SysConfigEntity getSysConfig(String key);

/**
* 根据key,获取value的Object对象
* @param key key
* @param clazz Object对象
*/
<T> T getConfigObject(String key, Class<T> clazz);

}

+ 24
- 0
src/main/java/com/github/niefy/modules/sys/service/SysLogService.java View File

@@ -0,0 +1,24 @@
package com.github.niefy.modules.sys.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.modules.sys.entity.SysLogEntity;

import java.util.Map;


/**
* 系统日志
* @author Mark sunlightcs@gmail.com
*/
public interface SysLogService extends IService<SysLogEntity> {
/**
* 分页查询用户数据
* @param params 查询参数
* @return PageUtils 分页结果
*/
PageUtils queryPage(Map<String, Object> params);


}

+ 50
- 0
src/main/java/com/github/niefy/modules/sys/service/SysMenuService.java View File

@@ -0,0 +1,50 @@
package com.github.niefy.modules.sys.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.modules.sys.entity.SysMenuEntity;

import java.util.List;


/**
* 菜单管理
* @author Mark sunlightcs@gmail.com
*/
public interface SysMenuService extends IService<SysMenuEntity> {

/**
* 根据父菜单,查询子菜单
* @param parentId 父菜单ID
* @param menuIdList 用户菜单ID
*/
List<SysMenuEntity> queryListParentId(Long parentId, List<Long> menuIdList);

/**
* 根据父菜单,查询子菜单
* @param parentId 父菜单ID
*/
List<SysMenuEntity> queryListParentId(Long parentId);

/**
* 获取不包含按钮的菜单列表
*/
List<SysMenuEntity> queryNotButtonList();

/**
* 获取用户菜单列表
*/
List<SysMenuEntity> getUserMenuList(Long userId);

/**
* 删除
*/
void delete(Long menuId);

/**
* 获取用户所有的菜单
* @param userId 用户id
* @return
*/
List<SysMenuEntity> queryUserAllMenu(Long userId);
}

+ 32
- 0
src/main/java/com/github/niefy/modules/sys/service/SysRoleMenuService.java View File

@@ -0,0 +1,32 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.modules.sys.entity.SysRoleMenuEntity;

import java.util.List;


/**
* 角色与菜单对应关系
* @author Mark sunlightcs@gmail.com
*/
public interface SysRoleMenuService extends IService<SysRoleMenuEntity> {

void saveOrUpdate(Long roleId, List<Long> menuIdList);

/**
* 根据角色ID,获取菜单ID列表
*/
List<Long> queryMenuIdList(Long roleId);

/**
* 根据角色ID数组,批量删除
*/
int deleteBatch(Long[] roleIds);

/**
* 查询用户的所有菜单ID
*/
List<Long> queryAllMenuId(Long userId);

}

+ 34
- 0
src/main/java/com/github/niefy/modules/sys/service/SysRoleService.java View File

@@ -0,0 +1,34 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.modules.sys.entity.SysRoleEntity;
import com.github.niefy.common.utils.PageUtils;

import java.util.List;
import java.util.Map;


/**
* 角色
* @author Mark sunlightcs@gmail.com
*/
public interface SysRoleService extends IService<SysRoleEntity> {
/**
* 分页查询用户数据
* @param params 查询参数
* @return PageUtils 分页结果
*/
PageUtils queryPage(Map<String, Object> params);

void saveRole(SysRoleEntity role);

void update(SysRoleEntity role);

void deleteBatch(Long[] roleIds);


/**
* 查询用户创建的角色ID列表
*/
List<Long> queryRoleIdList(Long createUserId);
}

+ 26
- 0
src/main/java/com/github/niefy/modules/sys/service/SysUserRoleService.java View File

@@ -0,0 +1,26 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.modules.sys.entity.SysUserRoleEntity;

import java.util.List;


/**
* 用户与角色对应关系
* @author Mark sunlightcs@gmail.com
*/
public interface SysUserRoleService extends IService<SysUserRoleEntity> {

void saveOrUpdate(Long userId, List<Long> roleIdList);

/**
* 根据用户ID,获取角色ID列表
*/
List<Long> queryRoleIdList(Long userId);

/**
* 根据角色ID数组,批量删除
*/
int deleteBatch(Long[] roleIds);
}

+ 56
- 0
src/main/java/com/github/niefy/modules/sys/service/SysUserService.java View File

@@ -0,0 +1,56 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.common.utils.PageUtils;
import com.github.niefy.modules.sys.entity.SysUserEntity;

import java.util.List;
import java.util.Map;


/**
* 系统用户
* @author Mark sunlightcs@gmail.com
*/
public interface SysUserService extends IService<SysUserEntity> {
/**
* 分页查询用户数据
* @param params 查询参数
* @return PageUtils 分页结果
*/
PageUtils queryPage(Map<String, Object> params);

/**
* 查询用户的所有权限
* @param userId 用户ID
*/
List<String> queryAllPerms(Long userId);

/**
* 根据用户名,查询系统用户
*/
SysUserEntity queryByUserName(String username);

/**
* 保存用户
*/
void saveUser(SysUserEntity user);

/**
* 修改用户
*/
void update(SysUserEntity user);

/**
* 删除用户
*/
void deleteBatch(Long[] userIds);

/**
* 修改密码
* @param userId 用户ID
* @param password 原密码
* @param newPassword 新密码
*/
boolean updatePassword(Long userId, String password, String newPassword);
}

+ 25
- 0
src/main/java/com/github/niefy/modules/sys/service/SysUserTokenService.java View File

@@ -0,0 +1,25 @@
package com.github.niefy.modules.sys.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.github.niefy.common.utils.R;
import com.github.niefy.modules.sys.entity.SysUserTokenEntity;

/**
* 用户Token
* @author Mark sunlightcs@gmail.com
*/
public interface SysUserTokenService extends IService<SysUserTokenEntity> {

/**
* 生成token
* @param userId 用户ID
*/
R createToken(long userId);

/**
* 退出,修改token值
* @param userId 用户ID
*/
void logout(long userId);

}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save