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