@@ -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 |
@@ -0,0 +1,13 @@ | |||
language: java | |||
jdk: | |||
- openjdk8 | |||
script: "mvn clean package -Dmaven.test.skip=true" | |||
branches: | |||
only: | |||
- master | |||
notifications: | |||
email: | |||
- niefy@qq.com |
@@ -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"] |
@@ -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. |
@@ -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://www.yuque.com/nifury/wx/kens6d) | |||
## [代码贡献指南](https://www.yuque.com/nifury/wx/ykqswi) | |||
## 开发交流 | |||
QQ群:1023785886(已满)、993128490 技术交流群严禁广告,发广告立即踢出+拉黑+举报,加群密码:wx |
@@ -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; |
@@ -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> |
@@ -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); | |||
} | |||
} |
@@ -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 ""; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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"; | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.github.niefy.common.validator.group; | |||
/** | |||
* 新增数据 Group | |||
* @author Mark sunlightcs@gmail.com | |||
*/ | |||
public interface AddGroup { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.github.niefy.common.validator.group; | |||
/** | |||
* 阿里云 | |||
* @author Mark sunlightcs@gmail.com | |||
*/ | |||
public interface AliyunGroup { | |||
} |
@@ -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 { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.github.niefy.common.validator.group; | |||
/** | |||
* 腾讯云 | |||
* @author Mark sunlightcs@gmail.com | |||
*/ | |||
public interface QcloudGroup { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.github.niefy.common.validator.group; | |||
/** | |||
* 七牛 | |||
* @author Mark sunlightcs@gmail.com | |||
*/ | |||
public interface QiniuGroup { | |||
} |
@@ -0,0 +1,11 @@ | |||
package com.github.niefy.common.validator.group; | |||
/** | |||
* 更新数据 Group | |||
* | |||
* @author Mark sunlightcs@gmail.com | |||
*/ | |||
public interface UpdateGroup { | |||
} |
@@ -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, "&", result); | |||
result = regexReplace(P_QUOTE, """, result); | |||
result = regexReplace(P_LEFT_ARROW, "<", result); | |||
result = regexReplace(P_RIGHT_ARROW, ">", result); | |||
return result; | |||
} | |||
//--------------------------------------------------------------- | |||
/** | |||
* given a user submitted input String, filter out any invalid or restricted | |||
* html. | |||
* | |||
* @param input text (i.e. submitted by a user) than may contain html | |||
* @return "clean" version of input, with only valid, whitelisted html elements allowed | |||
*/ | |||
public String filter(final String input) { | |||
reset(); | |||
String s = input; | |||
debug("************************************************"); | |||
debug(" INPUT: " + input); | |||
s = escapeComments(s); | |||
debug(" escapeComments: " + s); | |||
s = balanceHtml(s); | |||
debug(" balanceHTML: " + s); | |||
s = checkTags(s); | |||
debug(" checkTags: " + s); | |||
s = processRemoveBlanks(s); | |||
debug("processRemoveBlanks: " + s); | |||
s = validateEntities(s); | |||
debug(" validateEntites: " + s); | |||
debug("************************************************\n\n"); | |||
return s; | |||
} | |||
public boolean isAlwaysMakeTags() { | |||
return alwaysMakeTags; | |||
} | |||
public boolean isStripComments() { | |||
return stripComment; | |||
} | |||
private String escapeComments(final String s) { | |||
final Matcher m = P_COMMENTS.matcher(s); | |||
final StringBuffer buf = new StringBuffer(); | |||
if (m.find()) { | |||
final String match = m.group(1); //(.*?) | |||
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + 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, "<$1", s); | |||
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); | |||
// | |||
// the last regexp causes '<>' entities to appear | |||
// (we need to do a lookahead assertion so that the last bracket can | |||
// be used in the next pass of the regexp) | |||
// | |||
s = regexReplace(P_BOTH_ARROWS, "", s); | |||
} | |||
return s; | |||
} | |||
private String checkTags(String s) { | |||
Matcher m = P_TAGS.matcher(s); | |||
final StringBuffer buf = new StringBuffer(); | |||
while (m.find()) { | |||
String replaceStr = m.group(1); | |||
replaceStr = processTag(replaceStr); | |||
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); | |||
} | |||
m.appendTail(buf); | |||
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, """, two) + three)); | |||
} | |||
m.appendTail(buf); | |||
return buf.toString(); | |||
} else { | |||
return s; | |||
} | |||
} | |||
private String checkEntity(final String preamble, final String term) { | |||
return ";".equals(term) && isValidEntity(preamble) | |||
? '&' + preamble | |||
: "&" + preamble; | |||
} | |||
private boolean isValidEntity(final String entity) { | |||
return inArray(entity, vAllowedEntities); | |||
} | |||
private static boolean inArray(final String s, final String[] array) { | |||
for (String item : array) { | |||
if (item != null && item.equals(s)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
private boolean allowed(final String name) { | |||
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); | |||
} | |||
private boolean allowedAttribute(final String name, final String paramName) { | |||
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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() { | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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> { | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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("上级菜单只能为菜单类型"); | |||
} | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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> { | |||
} |
@@ -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); | |||
} |
@@ -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> { | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |