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