@@ -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); | |||||
} |