From 850f6da0a431d870f5d173b8e1b8df2df555d916 Mon Sep 17 00:00:00 2001 From: yangfuda <945208611@qq.com> Date: Wed, 21 Sep 2022 17:17:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7=E6=96=B0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 32 ++ .travis.yml | 13 + Dockerfile | 14 + LICENSE | 201 +++++++ README.md | 43 ++ db/mysql-0.7.0.sql | 390 +++++++++++++ pom.xml | 195 +++++++ .../com/github/niefy/BootApplication.java | 18 + .../niefy/common/annotation/SysLog.java | 19 + .../niefy/common/aspect/SysLogAspect.java | 95 +++ .../niefy/common/exception/RRException.java | 52 ++ .../common/exception/RRExceptionHandler.java | 62 ++ .../common/handler/JSONArrayTypeHandler.java | 34 ++ .../common/handler/JSONObjectTypeHandler.java | 34 ++ .../niefy/common/utils/ConfigConstant.java | 12 + .../github/niefy/common/utils/Constant.java | 122 ++++ .../github/niefy/common/utils/CookieUtil.java | 64 +++ .../github/niefy/common/utils/DateUtils.java | 88 +++ .../niefy/common/utils/HttpContextUtils.java | 25 + .../github/niefy/common/utils/IPUtils.java | 48 ++ .../com/github/niefy/common/utils/Json.java | 20 + .../github/niefy/common/utils/MD5Util.java | 75 +++ .../github/niefy/common/utils/MapUtils.java | 20 + .../github/niefy/common/utils/PageUtils.java | 101 ++++ .../com/github/niefy/common/utils/Query.java | 68 +++ .../java/com/github/niefy/common/utils/R.java | 61 ++ .../github/niefy/common/utils/SHA1Util.java | 35 ++ .../github/niefy/common/utils/SmsUtils.java | 57 ++ .../common/utils/SpringContextUtils.java | 42 ++ .../niefy/common/utils/StringUtils.java | 456 +++++++++++++++ .../common/validator/ValidatorUtils.java | 39 ++ .../common/validator/group/AddGroup.java | 8 + .../common/validator/group/AliyunGroup.java | 8 + .../niefy/common/validator/group/Group.java | 12 + .../common/validator/group/QcloudGroup.java | 8 + .../common/validator/group/QiniuGroup.java | 8 + .../common/validator/group/UpdateGroup.java | 11 + .../github/niefy/common/xss/HTMLFilter.java | 542 ++++++++++++++++++ .../github/niefy/common/xss/SQLFilter.java | 41 ++ .../github/niefy/common/xss/XssFilter.java | 29 + .../xss/XssHttpServletRequestWrapper.java | 139 +++++ .../com/github/niefy/config/CorsConfig.java | 17 + .../com/github/niefy/config/FilterConfig.java | 41 ++ .../github/niefy/config/KaptchaConfig.java | 30 + .../niefy/config/MybatisPlusConfig.java | 26 + .../com/github/niefy/config/ShiroConfig.java | 68 +++ .../github/niefy/config/SwaggerConfig.java | 38 ++ .../com/github/niefy/config/TaskExcutor.java | 68 +++ .../cloud/AbstractCloudStorageService.java | 69 +++ .../AliyunAbstractCloudStorageService.java | 54 ++ .../modules/oss/cloud/CloudStorageConfig.java | 85 +++ .../niefy/modules/oss/cloud/OSSFactory.java | 35 ++ .../QcloudAbstractCloudStorageService.java | 81 +++ .../QiniuAbstractCloudStorageService.java | 68 +++ .../oss/controller/SysOssController.java | 135 +++++ .../niefy/modules/oss/dao/SysOssDao.java | 14 + .../modules/oss/entity/SysOssEntity.java | 27 + .../modules/oss/service/SysOssService.java | 20 + .../oss/service/impl/SysOssServiceImpl.java | 27 + .../sys/controller/AbstractController.java | 22 + .../sys/controller/SysConfigController.java | 97 ++++ .../sys/controller/SysLogController.java | 43 ++ .../sys/controller/SysLoginController.java | 98 ++++ .../sys/controller/SysMenuController.java | 198 +++++++ .../sys/controller/SysRoleController.java | 129 +++++ .../sys/controller/SysUserController.java | 155 +++++ .../niefy/modules/sys/dao/SysCaptchaDao.java | 14 + .../niefy/modules/sys/dao/SysConfigDao.java | 28 + .../niefy/modules/sys/dao/SysLogDao.java | 15 + .../niefy/modules/sys/dao/SysMenuDao.java | 35 ++ .../niefy/modules/sys/dao/SysRoleDao.java | 22 + .../niefy/modules/sys/dao/SysRoleMenuDao.java | 32 ++ .../niefy/modules/sys/dao/SysUserDao.java | 29 + .../niefy/modules/sys/dao/SysUserRoleDao.java | 28 + .../modules/sys/dao/SysUserTokenDao.java | 18 + .../modules/sys/entity/SysCaptchaEntity.java | 28 + .../modules/sys/entity/SysConfigEntity.java | 27 + .../modules/sys/entity/SysLogEntity.java | 46 ++ .../modules/sys/entity/SysMenuEntity.java | 76 +++ .../modules/sys/entity/SysRoleEntity.java | 53 ++ .../modules/sys/entity/SysRoleMenuEntity.java | 31 + .../modules/sys/entity/SysUserEntity.java | 81 +++ .../modules/sys/entity/SysUserRoleEntity.java | 31 + .../sys/entity/SysUserTokenEntity.java | 31 + .../niefy/modules/sys/form/PasswordForm.java | 20 + .../niefy/modules/sys/form/SysLoginForm.java | 22 + .../modules/sys/oauth2/OAuth2Filter.java | 100 ++++ .../niefy/modules/sys/oauth2/OAuth2Realm.java | 69 +++ .../niefy/modules/sys/oauth2/OAuth2Token.java | 27 + .../modules/sys/oauth2/TokenGenerator.java | 43 ++ .../modules/sys/service/ShiroService.java | 26 + .../sys/service/SysCaptchaService.java | 26 + .../modules/sys/service/SysConfigService.java | 61 ++ .../modules/sys/service/SysLogService.java | 24 + .../modules/sys/service/SysMenuService.java | 50 ++ .../sys/service/SysRoleMenuService.java | 32 ++ .../modules/sys/service/SysRoleService.java | 34 ++ .../sys/service/SysUserRoleService.java | 26 + .../modules/sys/service/SysUserService.java | 56 ++ .../sys/service/SysUserTokenService.java | 25 + .../sys/service/impl/ShiroServiceImpl.java | 60 ++ .../service/impl/SysCaptchaServiceImpl.java | 58 ++ .../service/impl/SysConfigServiceImpl.java | 85 +++ .../sys/service/impl/SysLogServiceImpl.java | 31 + .../sys/service/impl/SysMenuServiceImpl.java | 102 ++++ .../service/impl/SysRoleMenuServiceImpl.java | 55 ++ .../sys/service/impl/SysRoleServiceImpl.java | 112 ++++ .../service/impl/SysUserRoleServiceImpl.java | 48 ++ .../sys/service/impl/SysUserServiceImpl.java | 131 +++++ .../service/impl/SysUserTokenServiceImpl.java | 65 +++ .../WxMpMessageRouterConfiguration.java | 52 ++ .../wx/config/WxMpServiceConfiguration.java | 19 + .../wx/controller/ArticleController.java | 82 +++ .../wx/controller/WxAuthController.java | 134 +++++ .../wx/controller/WxMpPortalController.java | 106 ++++ .../wx/controller/WxUserController.java | 38 ++ .../wx/controller/WxUserTagsController.java | 72 +++ .../niefy/modules/wx/dao/ArticleMapper.java | 14 + .../modules/wx/dao/MsgReplyRuleMapper.java | 11 + .../modules/wx/dao/MsgTemplateMapper.java | 11 + .../modules/wx/dao/TemplateMsgLogMapper.java | 9 + .../niefy/modules/wx/dao/WxAccountMapper.java | 18 + .../niefy/modules/wx/dao/WxMsgMapper.java | 18 + .../niefy/modules/wx/dao/WxQrCodeMapper.java | 19 + .../niefy/modules/wx/dao/WxUserMapper.java | 14 + .../modules/wx/dto/PageSizeConstant.java | 9 + .../niefy/modules/wx/dto/RegexConstant.java | 44 ++ .../niefy/modules/wx/entity/Article.java | 39 ++ .../niefy/modules/wx/entity/MsgReplyRule.java | 48 ++ .../niefy/modules/wx/entity/MsgTemplate.java | 56 ++ .../github/niefy/modules/wx/entity/SMS.java | 22 + .../modules/wx/entity/TemplateMsgLog.java | 56 ++ .../niefy/modules/wx/entity/WxAccount.java | 65 +++ .../github/niefy/modules/wx/entity/WxMsg.java | 103 ++++ .../niefy/modules/wx/entity/WxQrCode.java | 64 +++ .../niefy/modules/wx/entity/WxUser.java | 85 +++ .../modules/wx/enums/ArticleTypeEnum.java | 35 ++ .../modules/wx/form/AccountBindForm.java | 15 + .../wx/form/MaterialFileDeleteForm.java | 14 + .../modules/wx/form/TemplateMsgBatchForm.java | 33 ++ .../modules/wx/form/TemplateMsgForm.java | 24 + .../modules/wx/form/WxH5OuthrizeForm.java | 16 + .../niefy/modules/wx/form/WxMsgReplyForm.java | 15 + .../niefy/modules/wx/form/WxQrCodeForm.java | 17 + .../wx/form/WxUserBatchTaggingForm.java | 14 + .../niefy/modules/wx/form/WxUserTagForm.java | 14 + .../modules/wx/form/WxUserTaggingForm.java | 11 + .../modules/wx/handler/AbstractHandler.java | 14 + .../modules/wx/handler/KfSessionHandler.java | 26 + .../modules/wx/handler/LocationHandler.java | 40 ++ .../niefy/modules/wx/handler/LogHandler.java | 39 ++ .../niefy/modules/wx/handler/MenuHandler.java | 38 ++ .../niefy/modules/wx/handler/MsgHandler.java | 53 ++ .../niefy/modules/wx/handler/NullHandler.java | 25 + .../niefy/modules/wx/handler/ScanHandler.java | 32 ++ .../wx/handler/StoreCheckNotifyHandler.java | 28 + .../modules/wx/handler/SubscribeHandler.java | 59 ++ .../wx/handler/UnsubscribeHandler.java | 32 ++ .../wx/manage/ArticleManageController.java | 81 +++ .../manage/MsgReplyRuleManageController.java | 97 ++++ .../manage/MsgTemplateManageController.java | 141 +++++ .../modules/wx/manage/SmsController.java | 78 +++ .../TemplateMsgLogManageController.java | 97 ++++ .../manage/TemplateMsgManageController.java | 85 +++ .../wx/manage/WxAccountManageController.java | 79 +++ .../wx/manage/WxAssetsManageController.java | 165 ++++++ .../wx/manage/WxMenuManageController.java | 54 ++ .../wx/manage/WxMsgManageController.java | 84 +++ .../wx/manage/WxQrCodeManageController.java | 86 +++ .../wx/manage/WxUserManageController.java | 94 +++ .../wx/manage/WxUserTagsManageController.java | 92 +++ .../modules/wx/service/ArticleService.java | 62 ++ .../wx/service/MsgReplyRuleService.java | 51 ++ .../modules/wx/service/MsgReplyService.java | 107 ++++ .../wx/service/MsgTemplateService.java | 41 ++ .../wx/service/TemplateMsgLogService.java | 23 + .../wx/service/TemplateMsgService.java | 18 + .../modules/wx/service/WxAccountService.java | 30 + .../modules/wx/service/WxAssetsService.java | 87 +++ .../modules/wx/service/WxMsgService.java | 29 + .../modules/wx/service/WxQrCodeService.java | 37 ++ .../modules/wx/service/WxUserService.java | 63 ++ .../modules/wx/service/WxUserTagsService.java | 76 +++ .../wx/service/impl/ArticleServiceImpl.java | 153 +++++ .../service/impl/MsgReplyRuleServiceImpl.java | 127 ++++ .../wx/service/impl/MsgReplyServiceImpl.java | 188 ++++++ .../service/impl/MsgTemplateServiceImpl.java | 63 ++ .../impl/TemplateMsgLogServiceImpl.java | 42 ++ .../service/impl/TemplateMsgServiceImpl.java | 94 +++ .../wx/service/impl/WxAccountServiceImpl.java | 121 ++++ .../wx/service/impl/WxAssetsServiceImpl.java | 115 ++++ .../modules/wx/service/impl/WxMpTemplate.java | 0 .../wx/service/impl/WxMsgServiceImpl.java | 50 ++ .../wx/service/impl/WxQrCodeServiceImpl.java | 69 +++ .../wx/service/impl/WxUserServiceImpl.java | 194 +++++++ .../service/impl/WxUserTagsServiceImpl.java | 85 +++ src/main/resources/application-dev.yml | 11 + src/main/resources/application-prod.yml | 11 + src/main/resources/application.yml | 74 +++ src/main/resources/logback-spring.xml | 45 ++ src/main/resources/mapper/oss/SysOssDao.xml | 7 + .../resources/mapper/sys/SysConfigDao.xml | 15 + src/main/resources/mapper/sys/SysLogDao.xml | 6 + src/main/resources/mapper/sys/SysMenuDao.xml | 21 + src/main/resources/mapper/sys/SysRoleDao.xml | 10 + .../resources/mapper/sys/SysRoleMenuDao.xml | 24 + src/main/resources/mapper/sys/SysUserDao.xml | 18 + .../resources/mapper/sys/SysUserRoleDao.xml | 16 + .../resources/mapper/sys/SysUserTokenDao.xml | 9 + .../resources/mapper/wx/ArticleMapper.xml | 10 + .../mapper/wx/MsgReplyRuleMapper.xml | 6 + .../resources/mapper/wx/MsgTemplateMapper.xml | 5 + .../mapper/wx/TemplateMsgLogMapper.xml | 6 + .../resources/mapper/wx/WxAccountMapper.xml | 8 + src/main/resources/mapper/wx/WxMsgMapper.xml | 6 + .../resources/mapper/wx/WxQrCodeMapper.xml | 7 + src/main/resources/mapper/wx/WxUserMapper.xml | 12 + .../wx/service/TemplateMsgServiceTest.java | 42 ++ 218 files changed, 12328 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 db/mysql-0.7.0.sql create mode 100644 pom.xml create mode 100644 src/main/java/com/github/niefy/BootApplication.java create mode 100644 src/main/java/com/github/niefy/common/annotation/SysLog.java create mode 100644 src/main/java/com/github/niefy/common/aspect/SysLogAspect.java create mode 100644 src/main/java/com/github/niefy/common/exception/RRException.java create mode 100644 src/main/java/com/github/niefy/common/exception/RRExceptionHandler.java create mode 100644 src/main/java/com/github/niefy/common/handler/JSONArrayTypeHandler.java create mode 100644 src/main/java/com/github/niefy/common/handler/JSONObjectTypeHandler.java create mode 100644 src/main/java/com/github/niefy/common/utils/ConfigConstant.java create mode 100644 src/main/java/com/github/niefy/common/utils/Constant.java create mode 100644 src/main/java/com/github/niefy/common/utils/CookieUtil.java create mode 100644 src/main/java/com/github/niefy/common/utils/DateUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/HttpContextUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/IPUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/Json.java create mode 100644 src/main/java/com/github/niefy/common/utils/MD5Util.java create mode 100644 src/main/java/com/github/niefy/common/utils/MapUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/PageUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/Query.java create mode 100644 src/main/java/com/github/niefy/common/utils/R.java create mode 100644 src/main/java/com/github/niefy/common/utils/SHA1Util.java create mode 100644 src/main/java/com/github/niefy/common/utils/SmsUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/SpringContextUtils.java create mode 100644 src/main/java/com/github/niefy/common/utils/StringUtils.java create mode 100644 src/main/java/com/github/niefy/common/validator/ValidatorUtils.java create mode 100644 src/main/java/com/github/niefy/common/validator/group/AddGroup.java create mode 100644 src/main/java/com/github/niefy/common/validator/group/AliyunGroup.java create mode 100644 src/main/java/com/github/niefy/common/validator/group/Group.java create mode 100644 src/main/java/com/github/niefy/common/validator/group/QcloudGroup.java create mode 100644 src/main/java/com/github/niefy/common/validator/group/QiniuGroup.java create mode 100644 src/main/java/com/github/niefy/common/validator/group/UpdateGroup.java create mode 100644 src/main/java/com/github/niefy/common/xss/HTMLFilter.java create mode 100644 src/main/java/com/github/niefy/common/xss/SQLFilter.java create mode 100644 src/main/java/com/github/niefy/common/xss/XssFilter.java create mode 100644 src/main/java/com/github/niefy/common/xss/XssHttpServletRequestWrapper.java create mode 100644 src/main/java/com/github/niefy/config/CorsConfig.java create mode 100644 src/main/java/com/github/niefy/config/FilterConfig.java create mode 100644 src/main/java/com/github/niefy/config/KaptchaConfig.java create mode 100644 src/main/java/com/github/niefy/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/github/niefy/config/ShiroConfig.java create mode 100644 src/main/java/com/github/niefy/config/SwaggerConfig.java create mode 100644 src/main/java/com/github/niefy/config/TaskExcutor.java create mode 100644 src/main/java/com/github/niefy/modules/oss/cloud/AbstractCloudStorageService.java create mode 100644 src/main/java/com/github/niefy/modules/oss/cloud/AliyunAbstractCloudStorageService.java create mode 100644 src/main/java/com/github/niefy/modules/oss/cloud/CloudStorageConfig.java create mode 100644 src/main/java/com/github/niefy/modules/oss/cloud/OSSFactory.java create mode 100644 src/main/java/com/github/niefy/modules/oss/cloud/QcloudAbstractCloudStorageService.java create mode 100644 src/main/java/com/github/niefy/modules/oss/cloud/QiniuAbstractCloudStorageService.java create mode 100644 src/main/java/com/github/niefy/modules/oss/controller/SysOssController.java create mode 100644 src/main/java/com/github/niefy/modules/oss/dao/SysOssDao.java create mode 100644 src/main/java/com/github/niefy/modules/oss/entity/SysOssEntity.java create mode 100644 src/main/java/com/github/niefy/modules/oss/service/SysOssService.java create mode 100644 src/main/java/com/github/niefy/modules/oss/service/impl/SysOssServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/AbstractController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/SysConfigController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/SysLogController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/SysLoginController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/SysMenuController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/SysRoleController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/controller/SysUserController.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysCaptchaDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysConfigDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysLogDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysMenuDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysRoleDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysRoleMenuDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysUserDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysUserRoleDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/dao/SysUserTokenDao.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysCaptchaEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysConfigEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysLogEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysMenuEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysRoleEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysRoleMenuEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysUserEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysUserRoleEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/entity/SysUserTokenEntity.java create mode 100644 src/main/java/com/github/niefy/modules/sys/form/PasswordForm.java create mode 100644 src/main/java/com/github/niefy/modules/sys/form/SysLoginForm.java create mode 100644 src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Filter.java create mode 100644 src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Realm.java create mode 100644 src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Token.java create mode 100644 src/main/java/com/github/niefy/modules/sys/oauth2/TokenGenerator.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/ShiroService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysCaptchaService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysConfigService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysLogService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysMenuService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysRoleMenuService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysRoleService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysUserRoleService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysUserService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/SysUserTokenService.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/ShiroServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysCaptchaServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysConfigServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysLogServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysMenuServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleMenuServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysUserRoleServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysUserServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/sys/service/impl/SysUserTokenServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/config/WxMpMessageRouterConfiguration.java create mode 100644 src/main/java/com/github/niefy/modules/wx/config/WxMpServiceConfiguration.java create mode 100644 src/main/java/com/github/niefy/modules/wx/controller/ArticleController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/controller/WxAuthController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/controller/WxMpPortalController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/controller/WxUserController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/controller/WxUserTagsController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/ArticleMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/MsgReplyRuleMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/MsgTemplateMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/TemplateMsgLogMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/WxAccountMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/WxMsgMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/WxQrCodeMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dao/WxUserMapper.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dto/PageSizeConstant.java create mode 100644 src/main/java/com/github/niefy/modules/wx/dto/RegexConstant.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/Article.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/MsgReplyRule.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/MsgTemplate.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/SMS.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/TemplateMsgLog.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/WxAccount.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/WxMsg.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/WxQrCode.java create mode 100644 src/main/java/com/github/niefy/modules/wx/entity/WxUser.java create mode 100644 src/main/java/com/github/niefy/modules/wx/enums/ArticleTypeEnum.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/AccountBindForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/MaterialFileDeleteForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/TemplateMsgBatchForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/TemplateMsgForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/WxH5OuthrizeForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/WxMsgReplyForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/WxQrCodeForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/WxUserBatchTaggingForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/WxUserTagForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/form/WxUserTaggingForm.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/AbstractHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/KfSessionHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/LocationHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/LogHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/MenuHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/MsgHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/NullHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/ScanHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/StoreCheckNotifyHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/SubscribeHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/handler/UnsubscribeHandler.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/ArticleManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/MsgReplyRuleManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/MsgTemplateManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/SmsController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgLogManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxAccountManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxAssetsManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxMenuManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxMsgManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxQrCodeManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxUserManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/manage/WxUserTagsManageController.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/ArticleService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/MsgReplyRuleService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/MsgReplyService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/MsgTemplateService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/TemplateMsgLogService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/TemplateMsgService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/WxAccountService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/WxAssetsService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/WxMsgService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/WxQrCodeService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/WxUserService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/WxUserTagsService.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/ArticleServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyRuleServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/MsgTemplateServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgLogServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxAccountServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxAssetsServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxMpTemplate.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxMsgServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxQrCodeServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxUserServiceImpl.java create mode 100644 src/main/java/com/github/niefy/modules/wx/service/impl/WxUserTagsServiceImpl.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/main/resources/mapper/oss/SysOssDao.xml create mode 100644 src/main/resources/mapper/sys/SysConfigDao.xml create mode 100644 src/main/resources/mapper/sys/SysLogDao.xml create mode 100644 src/main/resources/mapper/sys/SysMenuDao.xml create mode 100644 src/main/resources/mapper/sys/SysRoleDao.xml create mode 100644 src/main/resources/mapper/sys/SysRoleMenuDao.xml create mode 100644 src/main/resources/mapper/sys/SysUserDao.xml create mode 100644 src/main/resources/mapper/sys/SysUserRoleDao.xml create mode 100644 src/main/resources/mapper/sys/SysUserTokenDao.xml create mode 100644 src/main/resources/mapper/wx/ArticleMapper.xml create mode 100644 src/main/resources/mapper/wx/MsgReplyRuleMapper.xml create mode 100644 src/main/resources/mapper/wx/MsgTemplateMapper.xml create mode 100644 src/main/resources/mapper/wx/TemplateMsgLogMapper.xml create mode 100644 src/main/resources/mapper/wx/WxAccountMapper.xml create mode 100644 src/main/resources/mapper/wx/WxMsgMapper.xml create mode 100644 src/main/resources/mapper/wx/WxQrCodeMapper.xml create mode 100644 src/main/resources/mapper/wx/WxUserMapper.xml create mode 100644 src/test/java/com/github/niefy/modules/wx/service/TemplateMsgServiceTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f8db70 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e0a5710 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: java +jdk: + - openjdk8 + +script: "mvn clean package -Dmaven.test.skip=true" + +branches: + only: + - master + +notifications: + email: + - niefy@qq.com \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0784cc2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +#设置镜像使用的基础镜像 +FROM openjdk:8u322-jre-buster +# 作者 +MAINTAINER niefy +#设置镜像暴露的端口 这里要与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"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5419f80 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# wx-api +微信公众号管理系统,支持多公众号接入。提供公众号菜单、自动回复、公众号素材、模板消息、CMS等管理功能 + +### [📖使用文档](https://www.yuque.com/nifury/wx) | [Github仓库](https://github.com/niefy/wx-api) | [码云仓库](https://gitee.com/niefy/wx-api) + +## 项目说明 +- wx-api是一个轻量级的公众号开发种子项目,可快速接入微信公众号管理功能 +- 管理后台前端项目wx-manage:https://github.com/niefy/wx-manage +- 移动端示例wx-client: https://github.com/niefy/wx-client +- swagger文档(启动wx-api后查看):http://localhost:8088/wx/swagger-ui/index.html + +## [docker方式启动文档](https://www.yuque.com/nifury/wx/nf1rvm) +## [开发环境启动文档](https://www.yuque.com/nifury/wx/guobb7) +## [生产环境部署步骤](https://www.yuque.com/nifury/wx/ofehhv) + +## 技术选型 +- 核心框架:Spring Boot +- 安全框架:Apache Shiro +- 持久层框架:[MyBatis-Plus](https://baomidou.oschina.io/mybatis-plus-doc/#/quick-start) +- 公众号开发框架:[WxJava](https://github.com/Wechat-Group/WxJava) +- 项目脚手架:[renren-fast](https://gitee.com/renrenio/renren-fast) +- 页面交互:Vue2.x、ElementUI、TinyMce Editor、Vuex + + +## 截图 +![公众号账号](https://s1.ax1x.com/2020/06/23/NUTQAg.png) +![公众号菜单](https://s1.ax1x.com/2020/06/23/NUTlNQ.png) +![自动回复](https://s1.ax1x.com/2020/04/10/GTqyQA.png) +![模板消息配置](https://s1.ax1x.com/2020/04/18/JnKZhF.jpg) +![模板消息发送](https://s1.ax1x.com/2020/04/18/JnKEkT.jpg) +![粉丝管理](https://s1.ax1x.com/2020/04/18/JnKVtU.jpg) +![带参二维码](https://s1.ax1x.com/2020/04/18/JnKF00.jpg) +![素材管理](https://s1.ax1x.com/2020/05/20/Y7djHI.jpg) +![公众号消息](https://s1.ax1x.com/2020/05/20/Y7dXDA.jpg) +![文章编辑](https://s1.ax1x.com/2020/04/10/GTqrzd.png) +![系统菜单管理](https://s1.ax1x.com/2020/04/18/JnKk7V.jpg) +![管理员列表](https://s1.ax1x.com/2020/04/18/JnKimq.jpg) + +## [项目开发进度](https://www.yuque.com/nifury/wx/kens6d) +## [代码贡献指南](https://www.yuque.com/nifury/wx/ykqswi) + +## 开发交流 +QQ群:1023785886(已满)、993128490 技术交流群严禁广告,发广告立即踢出+拉黑+举报,加群密码:wx diff --git a/db/mysql-0.7.0.sql b/db/mysql-0.7.0.sql new file mode 100644 index 0000000..a8885cd --- /dev/null +++ b/db/mysql-0.7.0.sql @@ -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点击链接查看我的主页', 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; diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..23e3b27 --- /dev/null +++ b/pom.xml @@ -0,0 +1,195 @@ + + + 4.0.0 + com.github.niefy + wx-api + 0.8.2 + jar + wx-api + + + org.springframework.boot + spring-boot-starter-parent + 2.6.4 + + + + UTF-8 + UTF-8 + 1.8 + 3.5.1 + 8.0.28 + 1.8.0 + 0.9.1 + 0.0.9 + 7.9.3 + 3.14.0 + 5.6.69 + 3.0.0 + 1.2.79 + 1.18.22 + 4.2.0 + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-validation + + + com.github.binarywang + weixin-java-mp + ${weixin-java.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatisplus.version} + + + com.baomidou + mybatis-plus-generator + + + + + mysql + mysql-connector-java + ${mysql.version} + + + org.apache.shiro + shiro-core + ${shiro.version} + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + com.github.axet + kaptcha + ${kaptcha.version} + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + com.qiniu + qiniu-java-sdk + ${qiniu.version} + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun.oss.version} + + + com.qcloud + cos_api + ${qcloud.cos.version} + + + org.slf4j + slf4j-log4j12 + + + + + com.alibaba + fastjson + ${fastjson.version} + + + org.projectlombok + lombok + ${lombok.version} + + + + com.aliyun + aliyun-java-sdk-core + 4.5.3 + + + com.aliyun + aliyun-java-sdk-dysmsapi + 1.1.0 + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + src/main/resources/ + + + + + + + aliyunmaven + 阿里云公共仓库 + https://maven.aliyun.com/repository/public + + true + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + diff --git a/src/main/java/com/github/niefy/BootApplication.java b/src/main/java/com/github/niefy/BootApplication.java new file mode 100644 index 0000000..0fbc30d --- /dev/null +++ b/src/main/java/com/github/niefy/BootApplication.java @@ -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); + } + +} diff --git a/src/main/java/com/github/niefy/common/annotation/SysLog.java b/src/main/java/com/github/niefy/common/annotation/SysLog.java new file mode 100644 index 0000000..c2261cb --- /dev/null +++ b/src/main/java/com/github/niefy/common/annotation/SysLog.java @@ -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 ""; +} diff --git a/src/main/java/com/github/niefy/common/aspect/SysLogAspect.java b/src/main/java/com/github/niefy/common/aspect/SysLogAspect.java new file mode 100644 index 0000000..009a658 --- /dev/null +++ b/src/main/java/com/github/niefy/common/aspect/SysLogAspect.java @@ -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); + } +} diff --git a/src/main/java/com/github/niefy/common/exception/RRException.java b/src/main/java/com/github/niefy/common/exception/RRException.java new file mode 100644 index 0000000..e72087e --- /dev/null +++ b/src/main/java/com/github/niefy/common/exception/RRException.java @@ -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; + } + + +} diff --git a/src/main/java/com/github/niefy/common/exception/RRExceptionHandler.java b/src/main/java/com/github/niefy/common/exception/RRExceptionHandler.java new file mode 100644 index 0000000..cea136a --- /dev/null +++ b/src/main/java/com/github/niefy/common/exception/RRExceptionHandler.java @@ -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(); + } +} diff --git a/src/main/java/com/github/niefy/common/handler/JSONArrayTypeHandler.java b/src/main/java/com/github/niefy/common/handler/JSONArrayTypeHandler.java new file mode 100644 index 0000000..0cff35b --- /dev/null +++ b/src/main/java/com/github/niefy/common/handler/JSONArrayTypeHandler.java @@ -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 { + @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)); + } +} diff --git a/src/main/java/com/github/niefy/common/handler/JSONObjectTypeHandler.java b/src/main/java/com/github/niefy/common/handler/JSONObjectTypeHandler.java new file mode 100644 index 0000000..96524a2 --- /dev/null +++ b/src/main/java/com/github/niefy/common/handler/JSONObjectTypeHandler.java @@ -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 { + @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)); + } +} diff --git a/src/main/java/com/github/niefy/common/utils/ConfigConstant.java b/src/main/java/com/github/niefy/common/utils/ConfigConstant.java new file mode 100644 index 0000000..3b9fb47 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/ConfigConstant.java @@ -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"; +} diff --git a/src/main/java/com/github/niefy/common/utils/Constant.java b/src/main/java/com/github/niefy/common/utils/Constant.java new file mode 100644 index 0000000..2c9aafb --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/Constant.java @@ -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; + } + } + +} diff --git a/src/main/java/com/github/niefy/common/utils/CookieUtil.java b/src/main/java/com/github/niefy/common/utils/CookieUtil.java new file mode 100644 index 0000000..4205636 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/CookieUtil.java @@ -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; + } +} diff --git a/src/main/java/com/github/niefy/common/utils/DateUtils.java b/src/main/java/com/github/niefy/common/utils/DateUtils.java new file mode 100644 index 0000000..8414dec --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/DateUtils.java @@ -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); + } +} diff --git a/src/main/java/com/github/niefy/common/utils/HttpContextUtils.java b/src/main/java/com/github/niefy/common/utils/HttpContextUtils.java new file mode 100644 index 0000000..95151d4 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/HttpContextUtils.java @@ -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"); + } +} diff --git a/src/main/java/com/github/niefy/common/utils/IPUtils.java b/src/main/java/com/github/niefy/common/utils/IPUtils.java new file mode 100644 index 0000000..327da17 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/IPUtils.java @@ -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; + } + +} diff --git a/src/main/java/com/github/niefy/common/utils/Json.java b/src/main/java/com/github/niefy/common/utils/Json.java new file mode 100644 index 0000000..634e1a4 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/Json.java @@ -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 fromJson(String jsonStr, Class clazz) { + return JSON.parseObject(jsonStr, clazz); + } +} diff --git a/src/main/java/com/github/niefy/common/utils/MD5Util.java b/src/main/java/com/github/niefy/common/utils/MD5Util.java new file mode 100644 index 0000000..f778b8f --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/MD5Util.java @@ -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)); + } +} diff --git a/src/main/java/com/github/niefy/common/utils/MapUtils.java b/src/main/java/com/github/niefy/common/utils/MapUtils.java new file mode 100644 index 0000000..12f090f --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/MapUtils.java @@ -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 { + + + private static final long serialVersionUID = 1L; + + @Override + public MapUtils put(String key, Object value) { + super.put(key, value); + return this; + } +} diff --git a/src/main/java/com/github/niefy/common/utils/PageUtils.java b/src/main/java/com/github/niefy/common/utils/PageUtils.java new file mode 100644 index 0000000..ff1047a --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/PageUtils.java @@ -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; + } + +} diff --git a/src/main/java/com/github/niefy/common/utils/Query.java b/src/main/java/com/github/niefy/common/utils/Query.java new file mode 100644 index 0000000..0a21531 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/Query.java @@ -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 { + + public IPage getPage(Map params) { + return this.getPage(params, null, false); + } + + public IPage getPage(Map 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 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; + } +} diff --git a/src/main/java/com/github/niefy/common/utils/R.java b/src/main/java/com/github/niefy/common/utils/R.java new file mode 100644 index 0000000..46ea5a6 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/R.java @@ -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 { + 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 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; + } +} diff --git a/src/main/java/com/github/niefy/common/utils/SHA1Util.java b/src/main/java/com/github/niefy/common/utils/SHA1Util.java new file mode 100644 index 0000000..17f70e7 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/SHA1Util.java @@ -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; + } + } +} diff --git a/src/main/java/com/github/niefy/common/utils/SmsUtils.java b/src/main/java/com/github/niefy/common/utils/SmsUtils.java new file mode 100644 index 0000000..72d82e6 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/SmsUtils.java @@ -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(); + } + } +} diff --git a/src/main/java/com/github/niefy/common/utils/SpringContextUtils.java b/src/main/java/com/github/niefy/common/utils/SpringContextUtils.java new file mode 100644 index 0000000..e667417 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/SpringContextUtils.java @@ -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 getBean(String name, Class 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); + } + +} diff --git a/src/main/java/com/github/niefy/common/utils/StringUtils.java b/src/main/java/com/github/niefy/common/utils/StringUtils.java new file mode 100644 index 0000000..c875557 --- /dev/null +++ b/src/main/java/com/github/niefy/common/utils/StringUtils.java @@ -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 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 str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + 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 cast(Object obj) + { + return (T) obj; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/niefy/common/validator/ValidatorUtils.java b/src/main/java/com/github/niefy/common/validator/ValidatorUtils.java new file mode 100644 index 0000000..cdd97b9 --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/ValidatorUtils.java @@ -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> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) { + StringBuilder msg = new StringBuilder(); + for (ConstraintViolation constraint : constraintViolations) { + msg.append(constraint.getMessage()).append("
"); + } + throw new RRException(msg.toString()); + } + } +} diff --git a/src/main/java/com/github/niefy/common/validator/group/AddGroup.java b/src/main/java/com/github/niefy/common/validator/group/AddGroup.java new file mode 100644 index 0000000..c2b3095 --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/group/AddGroup.java @@ -0,0 +1,8 @@ +package com.github.niefy.common.validator.group; + +/** + * 新增数据 Group + * @author Mark sunlightcs@gmail.com + */ +public interface AddGroup { +} diff --git a/src/main/java/com/github/niefy/common/validator/group/AliyunGroup.java b/src/main/java/com/github/niefy/common/validator/group/AliyunGroup.java new file mode 100644 index 0000000..fe2d123 --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/group/AliyunGroup.java @@ -0,0 +1,8 @@ +package com.github.niefy.common.validator.group; + +/** + * 阿里云 + * @author Mark sunlightcs@gmail.com + */ +public interface AliyunGroup { +} diff --git a/src/main/java/com/github/niefy/common/validator/group/Group.java b/src/main/java/com/github/niefy/common/validator/group/Group.java new file mode 100644 index 0000000..c5575ee --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/group/Group.java @@ -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 { + +} diff --git a/src/main/java/com/github/niefy/common/validator/group/QcloudGroup.java b/src/main/java/com/github/niefy/common/validator/group/QcloudGroup.java new file mode 100644 index 0000000..15ccc45 --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/group/QcloudGroup.java @@ -0,0 +1,8 @@ +package com.github.niefy.common.validator.group; + +/** + * 腾讯云 + * @author Mark sunlightcs@gmail.com + */ +public interface QcloudGroup { +} diff --git a/src/main/java/com/github/niefy/common/validator/group/QiniuGroup.java b/src/main/java/com/github/niefy/common/validator/group/QiniuGroup.java new file mode 100644 index 0000000..9203397 --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/group/QiniuGroup.java @@ -0,0 +1,8 @@ +package com.github.niefy.common.validator.group; + +/** + * 七牛 + * @author Mark sunlightcs@gmail.com + */ +public interface QiniuGroup { +} diff --git a/src/main/java/com/github/niefy/common/validator/group/UpdateGroup.java b/src/main/java/com/github/niefy/common/validator/group/UpdateGroup.java new file mode 100644 index 0000000..cf57efa --- /dev/null +++ b/src/main/java/com/github/niefy/common/validator/group/UpdateGroup.java @@ -0,0 +1,11 @@ +package com.github.niefy.common.validator.group; + +/** + * 更新数据 Group + * + * @author Mark sunlightcs@gmail.com + */ + +public interface UpdateGroup { + +} diff --git a/src/main/java/com/github/niefy/common/xss/HTMLFilter.java b/src/main/java/com/github/niefy/common/xss/HTMLFilter.java new file mode 100644 index 0000000..3608df0 --- /dev/null +++ b/src/main/java/com/github/niefy/common/xss/HTMLFilter.java @@ -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 P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + 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. "" or "") + **/ + 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. "" becomes " text "). If set to false, + * unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() { + vAllowed = new HashMap<>(); + + final ArrayList aAtts = new ArrayList<>(); + aAtts.add("href"); + aAtts.add("target"); + vAllowed.put("a", aAtts); + + final ArrayList imgAtts = new ArrayList<>(); + imgAtts.add("src"); + imgAtts.add("width"); + imgAtts.add("height"); + imgAtts.add("alt"); + vAllowed.put("img", imgAtts); + + final ArrayList 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 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>) 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("")); + } + 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 += ""; + } + } + + 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[^>]*)?>")); + } + 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 ""; + } + } + } + } + + // 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 paramNames = new ArrayList(); + final List paramValues = new ArrayList(); + 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)); + } +} diff --git a/src/main/java/com/github/niefy/common/xss/SQLFilter.java b/src/main/java/com/github/niefy/common/xss/SQLFilter.java new file mode 100644 index 0000000..7b2515a --- /dev/null +++ b/src/main/java/com/github/niefy/common/xss/SQLFilter.java @@ -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; + } +} diff --git a/src/main/java/com/github/niefy/common/xss/XssFilter.java b/src/main/java/com/github/niefy/common/xss/XssFilter.java new file mode 100644 index 0000000..e768279 --- /dev/null +++ b/src/main/java/com/github/niefy/common/xss/XssFilter.java @@ -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() { + } + +} diff --git a/src/main/java/com/github/niefy/common/xss/XssHttpServletRequestWrapper.java b/src/main/java/com/github/niefy/common/xss/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..5f18384 --- /dev/null +++ b/src/main/java/com/github/niefy/common/xss/XssHttpServletRequestWrapper.java @@ -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 getParameterMap() { + Map map = new LinkedHashMap<>(); + Map 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; + } + +} diff --git a/src/main/java/com/github/niefy/config/CorsConfig.java b/src/main/java/com/github/niefy/config/CorsConfig.java new file mode 100644 index 0000000..cfd17eb --- /dev/null +++ b/src/main/java/com/github/niefy/config/CorsConfig.java @@ -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); + } +} diff --git a/src/main/java/com/github/niefy/config/FilterConfig.java b/src/main/java/com/github/niefy/config/FilterConfig.java new file mode 100644 index 0000000..b43f87e --- /dev/null +++ b/src/main/java/com/github/niefy/config/FilterConfig.java @@ -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; + } +} diff --git a/src/main/java/com/github/niefy/config/KaptchaConfig.java b/src/main/java/com/github/niefy/config/KaptchaConfig.java new file mode 100644 index 0000000..d8dc683 --- /dev/null +++ b/src/main/java/com/github/niefy/config/KaptchaConfig.java @@ -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; + } +} diff --git a/src/main/java/com/github/niefy/config/MybatisPlusConfig.java b/src/main/java/com/github/niefy/config/MybatisPlusConfig.java new file mode 100644 index 0000000..280689f --- /dev/null +++ b/src/main/java/com/github/niefy/config/MybatisPlusConfig.java @@ -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; + } + +} diff --git a/src/main/java/com/github/niefy/config/ShiroConfig.java b/src/main/java/com/github/niefy/config/ShiroConfig.java new file mode 100644 index 0000000..04f56ce --- /dev/null +++ b/src/main/java/com/github/niefy/config/ShiroConfig.java @@ -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 filters = new HashMap<>(4); + filters.put("oauth2", new OAuth2Filter()); + shiroFilter.setFilters(filters); + + Map 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; + } + +} diff --git a/src/main/java/com/github/niefy/config/SwaggerConfig.java b/src/main/java/com/github/niefy/config/SwaggerConfig.java new file mode 100644 index 0000000..c91ba2a --- /dev/null +++ b/src/main/java/com/github/niefy/config/SwaggerConfig.java @@ -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(); + } + +} diff --git a/src/main/java/com/github/niefy/config/TaskExcutor.java b/src/main/java/com/github/niefy/config/TaskExcutor.java new file mode 100644 index 0000000..b97f5a8 --- /dev/null +++ b/src/main/java/com/github/niefy/config/TaskExcutor.java @@ -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() 链表队列 + * handler=new ThreadPoolExecutor.CallerRunsPolicy() + */ + private static final ExecutorService EXCUTOR = new ThreadPoolExecutor( + corePoolSize,maximumPoolSize,keepAliveTime, TimeUnit.SECONDS, + new SynchronousQueue(), + 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); + } +} diff --git a/src/main/java/com/github/niefy/modules/oss/cloud/AbstractCloudStorageService.java b/src/main/java/com/github/niefy/modules/oss/cloud/AbstractCloudStorageService.java new file mode 100644 index 0000000..8b51e4f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/cloud/AbstractCloudStorageService.java @@ -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); + +} diff --git a/src/main/java/com/github/niefy/modules/oss/cloud/AliyunAbstractCloudStorageService.java b/src/main/java/com/github/niefy/modules/oss/cloud/AliyunAbstractCloudStorageService.java new file mode 100644 index 0000000..122841e --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/cloud/AliyunAbstractCloudStorageService.java @@ -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)); + } +} diff --git a/src/main/java/com/github/niefy/modules/oss/cloud/CloudStorageConfig.java b/src/main/java/com/github/niefy/modules/oss/cloud/CloudStorageConfig.java new file mode 100644 index 0000000..900118c --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/cloud/CloudStorageConfig.java @@ -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; + + +} diff --git a/src/main/java/com/github/niefy/modules/oss/cloud/OSSFactory.java b/src/main/java/com/github/niefy/modules/oss/cloud/OSSFactory.java new file mode 100644 index 0000000..1babcf6 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/cloud/OSSFactory.java @@ -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; + } + +} diff --git a/src/main/java/com/github/niefy/modules/oss/cloud/QcloudAbstractCloudStorageService.java b/src/main/java/com/github/niefy/modules/oss/cloud/QcloudAbstractCloudStorageService.java new file mode 100644 index 0000000..be7efdd --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/cloud/QcloudAbstractCloudStorageService.java @@ -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)); + } +} diff --git a/src/main/java/com/github/niefy/modules/oss/cloud/QiniuAbstractCloudStorageService.java b/src/main/java/com/github/niefy/modules/oss/cloud/QiniuAbstractCloudStorageService.java new file mode 100644 index 0000000..b81f71c --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/cloud/QiniuAbstractCloudStorageService.java @@ -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)); + } +} diff --git a/src/main/java/com/github/niefy/modules/oss/controller/SysOssController.java b/src/main/java/com/github/niefy/modules/oss/controller/SysOssController.java new file mode 100644 index 0000000..7c6bd9f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/controller/SysOssController.java @@ -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 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(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/oss/dao/SysOssDao.java b/src/main/java/com/github/niefy/modules/oss/dao/SysOssDao.java new file mode 100644 index 0000000..067f164 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/dao/SysOssDao.java @@ -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 { + +} diff --git a/src/main/java/com/github/niefy/modules/oss/entity/SysOssEntity.java b/src/main/java/com/github/niefy/modules/oss/entity/SysOssEntity.java new file mode 100644 index 0000000..cf8cac4 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/entity/SysOssEntity.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/oss/service/SysOssService.java b/src/main/java/com/github/niefy/modules/oss/service/SysOssService.java new file mode 100644 index 0000000..052b822 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/service/SysOssService.java @@ -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 { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); +} diff --git a/src/main/java/com/github/niefy/modules/oss/service/impl/SysOssServiceImpl.java b/src/main/java/com/github/niefy/modules/oss/service/impl/SysOssServiceImpl.java new file mode 100644 index 0000000..2305a0c --- /dev/null +++ b/src/main/java/com/github/niefy/modules/oss/service/impl/SysOssServiceImpl.java @@ -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 implements SysOssService { + + @Override + public PageUtils queryPage(Map params) { + IPage page = this.page( + new Query().getPage(params) + ); + + return new PageUtils(page); + } + +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/AbstractController.java b/src/main/java/com/github/niefy/modules/sys/controller/AbstractController.java new file mode 100644 index 0000000..51ff5fc --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/AbstractController.java @@ -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(); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/SysConfigController.java b/src/main/java/com/github/niefy/modules/sys/controller/SysConfigController.java new file mode 100644 index 0000000..b614e26 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/SysConfigController.java @@ -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 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(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/SysLogController.java b/src/main/java/com/github/niefy/modules/sys/controller/SysLogController.java new file mode 100644 index 0000000..1cc9989 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/SysLogController.java @@ -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 params) { + PageUtils page = sysLogService.queryPage(params); + + return R.ok().put("page", page); + } + +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/SysLoginController.java b/src/main/java/com/github/niefy/modules/sys/controller/SysLoginController.java new file mode 100644 index 0000000..a1e1d0d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/SysLoginController.java @@ -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 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(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/SysMenuController.java b/src/main/java/com/github/niefy/modules/sys/controller/SysMenuController.java new file mode 100644 index 0000000..f387eeb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/SysMenuController.java @@ -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 menuList = sysMenuService.getUserMenuList(getUserId()); + Set 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 list() { + List 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 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 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("上级菜单只能为菜单类型"); + } + } + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/SysRoleController.java b/src/main/java/com/github/niefy/modules/sys/controller/SysRoleController.java new file mode 100644 index 0000000..63b98f5 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/SysRoleController.java @@ -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 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 map = new HashMap<>(4); + + //如果不是超级管理员,则只查询自己所拥有的角色列表 + if (getUserId() != Constant.SUPER_ADMIN) { + map.put("create_user_id", getUserId()); + } + List 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 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(); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/controller/SysUserController.java b/src/main/java/com/github/niefy/modules/sys/controller/SysUserController.java new file mode 100644 index 0000000..e5e1c3a --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/controller/SysUserController.java @@ -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 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 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(); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysCaptchaDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysCaptchaDao.java new file mode 100644 index 0000000..712b221 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysCaptchaDao.java @@ -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 { + +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysConfigDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysConfigDao.java new file mode 100644 index 0000000..17e5800 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysConfigDao.java @@ -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 { + + /** + * 根据key,查询value + */ + SysConfigEntity queryByKey(String paramKey); + + /** + * 根据key,更新value + */ + int updateValueByKey(@Param("paramKey") String paramKey, @Param("paramValue") String paramValue); + +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysLogDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysLogDao.java new file mode 100644 index 0000000..617c106 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysLogDao.java @@ -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 { + +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysMenuDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysMenuDao.java new file mode 100644 index 0000000..26ab025 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysMenuDao.java @@ -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 { + + /** + * 根据父菜单,查询子菜单 + * @param parentId 父菜单ID + */ + List queryListParentId(Long parentId); + + /** + * 获取不包含按钮的菜单列表 + */ + List queryNotButtonList(); + + /** + * 获取用户所有的菜单 + * @param userId 用户id + * @return + */ + List queryUserAllMenu(Long userId); +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysRoleDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysRoleDao.java new file mode 100644 index 0000000..9699fed --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysRoleDao.java @@ -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 { + + /** + * 查询用户创建的角色ID列表 + */ + List queryRoleIdList(Long createUserId); +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysRoleMenuDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysRoleMenuDao.java new file mode 100644 index 0000000..cc9f0fd --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysRoleMenuDao.java @@ -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 { + + /** + * 根据角色ID,获取菜单ID列表 + */ + List queryMenuIdList(Long roleId); + + /** + * 根据角色ID数组,批量删除 + */ + int deleteBatch(Long[] roleIds); + + /** + * 查询用户的所有菜单ID + */ + List queryAllMenuId(Long userId); +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysUserDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysUserDao.java new file mode 100644 index 0000000..5de55f5 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysUserDao.java @@ -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 { + + /** + * 查询用户的所有权限 + * @param userId 用户ID + */ + List queryAllPerms(Long userId); + + /** + * 根据用户名,查询系统用户 + */ + SysUserEntity queryByUserName(String username); + +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysUserRoleDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysUserRoleDao.java new file mode 100644 index 0000000..f7c5350 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysUserRoleDao.java @@ -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 { + + /** + * 根据用户ID,获取角色ID列表 + */ + List queryRoleIdList(Long userId); + + + /** + * 根据角色ID数组,批量删除 + */ + int deleteBatch(Long[] roleIds); +} diff --git a/src/main/java/com/github/niefy/modules/sys/dao/SysUserTokenDao.java b/src/main/java/com/github/niefy/modules/sys/dao/SysUserTokenDao.java new file mode 100644 index 0000000..ea8d82f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/dao/SysUserTokenDao.java @@ -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 queryByToken(String token); + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysCaptchaEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysCaptchaEntity.java new file mode 100644 index 0000000..8b4b567 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysCaptchaEntity.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysConfigEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysConfigEntity.java new file mode 100644 index 0000000..55007f2 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysConfigEntity.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysLogEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysLogEntity.java new file mode 100644 index 0000000..3bfda32 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysLogEntity.java @@ -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(); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysMenuEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysMenuEntity.java new file mode 100644 index 0000000..7ff0c67 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysMenuEntity.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysRoleEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysRoleEntity.java new file mode 100644 index 0000000..30898e4 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysRoleEntity.java @@ -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 menuIdList; + + /** + * 创建时间 + */ + private Date createTime; + + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysRoleMenuEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysRoleMenuEntity.java new file mode 100644 index 0000000..4493d33 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysRoleMenuEntity.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysUserEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysUserEntity.java new file mode 100644 index 0000000..5f98be9 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysUserEntity.java @@ -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 roleIdList; + + /** + * 创建者ID + */ + private Long createUserId; + + /** + * 创建时间 + */ + private Date createTime; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysUserRoleEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysUserRoleEntity.java new file mode 100644 index 0000000..3559bfa --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysUserRoleEntity.java @@ -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; + + +} diff --git a/src/main/java/com/github/niefy/modules/sys/entity/SysUserTokenEntity.java b/src/main/java/com/github/niefy/modules/sys/entity/SysUserTokenEntity.java new file mode 100644 index 0000000..f6dfaab --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/entity/SysUserTokenEntity.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/form/PasswordForm.java b/src/main/java/com/github/niefy/modules/sys/form/PasswordForm.java new file mode 100644 index 0000000..4e6abb5 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/form/PasswordForm.java @@ -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; + +} diff --git a/src/main/java/com/github/niefy/modules/sys/form/SysLoginForm.java b/src/main/java/com/github/niefy/modules/sys/form/SysLoginForm.java new file mode 100644 index 0000000..f2a35c7 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/form/SysLoginForm.java @@ -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; + + +} diff --git a/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Filter.java b/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Filter.java new file mode 100644 index 0000000..618cc40 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Filter.java @@ -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; + } + + +} diff --git a/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Realm.java b/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Realm.java new file mode 100644 index 0000000..c35c54d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Realm.java @@ -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 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()); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Token.java b/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Token.java new file mode 100644 index 0000000..7e7f02b --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/oauth2/OAuth2Token.java @@ -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; + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/oauth2/TokenGenerator.java b/src/main/java/com/github/niefy/modules/sys/oauth2/TokenGenerator.java new file mode 100644 index 0000000..c5e83be --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/oauth2/TokenGenerator.java @@ -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); + } + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/ShiroService.java b/src/main/java/com/github/niefy/modules/sys/service/ShiroService.java new file mode 100644 index 0000000..09ba89a --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/ShiroService.java @@ -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 getUserPermissions(long userId); + + SysUserTokenEntity queryByToken(String token); + + /** + * 根据用户ID,查询用户 + * @param userId 用户ID + * @return SysUserEntity 管理用户 + */ + SysUserEntity queryUser(Long userId); +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysCaptchaService.java b/src/main/java/com/github/niefy/modules/sys/service/SysCaptchaService.java new file mode 100644 index 0000000..b118720 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysCaptchaService.java @@ -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 { + + /** + * 获取图片验证码 + */ + BufferedImage getCaptcha(String uuid); + + /** + * 验证码效验 + * @param uuid uuid + * @param code 验证码 + * @return true:成功 false:失败 + */ + boolean validate(String uuid, String code); +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysConfigService.java b/src/main/java/com/github/niefy/modules/sys/service/SysConfigService.java new file mode 100644 index 0000000..d917cf6 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysConfigService.java @@ -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 { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map 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 getConfigObject(String key, Class clazz); + +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysLogService.java b/src/main/java/com/github/niefy/modules/sys/service/SysLogService.java new file mode 100644 index 0000000..adcd11f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysLogService.java @@ -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 { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysMenuService.java b/src/main/java/com/github/niefy/modules/sys/service/SysMenuService.java new file mode 100644 index 0000000..780e8b0 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysMenuService.java @@ -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 { + + /** + * 根据父菜单,查询子菜单 + * @param parentId 父菜单ID + * @param menuIdList 用户菜单ID + */ + List queryListParentId(Long parentId, List menuIdList); + + /** + * 根据父菜单,查询子菜单 + * @param parentId 父菜单ID + */ + List queryListParentId(Long parentId); + + /** + * 获取不包含按钮的菜单列表 + */ + List queryNotButtonList(); + + /** + * 获取用户菜单列表 + */ + List getUserMenuList(Long userId); + + /** + * 删除 + */ + void delete(Long menuId); + + /** + * 获取用户所有的菜单 + * @param userId 用户id + * @return + */ + List queryUserAllMenu(Long userId); +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysRoleMenuService.java b/src/main/java/com/github/niefy/modules/sys/service/SysRoleMenuService.java new file mode 100644 index 0000000..bfecdbf --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysRoleMenuService.java @@ -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 { + + void saveOrUpdate(Long roleId, List menuIdList); + + /** + * 根据角色ID,获取菜单ID列表 + */ + List queryMenuIdList(Long roleId); + + /** + * 根据角色ID数组,批量删除 + */ + int deleteBatch(Long[] roleIds); + + /** + * 查询用户的所有菜单ID + */ + List queryAllMenuId(Long userId); + +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysRoleService.java b/src/main/java/com/github/niefy/modules/sys/service/SysRoleService.java new file mode 100644 index 0000000..d6bfdaa --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysRoleService.java @@ -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 { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + void saveRole(SysRoleEntity role); + + void update(SysRoleEntity role); + + void deleteBatch(Long[] roleIds); + + + /** + * 查询用户创建的角色ID列表 + */ + List queryRoleIdList(Long createUserId); +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysUserRoleService.java b/src/main/java/com/github/niefy/modules/sys/service/SysUserRoleService.java new file mode 100644 index 0000000..1c974ec --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysUserRoleService.java @@ -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 { + + void saveOrUpdate(Long userId, List roleIdList); + + /** + * 根据用户ID,获取角色ID列表 + */ + List queryRoleIdList(Long userId); + + /** + * 根据角色ID数组,批量删除 + */ + int deleteBatch(Long[] roleIds); +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysUserService.java b/src/main/java/com/github/niefy/modules/sys/service/SysUserService.java new file mode 100644 index 0000000..53e8f7f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysUserService.java @@ -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 { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 查询用户的所有权限 + * @param userId 用户ID + */ + List 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); +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/SysUserTokenService.java b/src/main/java/com/github/niefy/modules/sys/service/SysUserTokenService.java new file mode 100644 index 0000000..4d1f1f7 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/SysUserTokenService.java @@ -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 { + + /** + * 生成token + * @param userId 用户ID + */ + R createToken(long userId); + + /** + * 退出,修改token值 + * @param userId 用户ID + */ + void logout(long userId); + +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/ShiroServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/ShiroServiceImpl.java new file mode 100644 index 0000000..c5da370 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/ShiroServiceImpl.java @@ -0,0 +1,60 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.github.niefy.modules.sys.dao.SysUserDao; +import com.github.niefy.common.utils.Constant; +import com.github.niefy.modules.sys.dao.SysMenuDao; +import com.github.niefy.modules.sys.dao.SysUserTokenDao; +import com.github.niefy.modules.sys.entity.SysMenuEntity; +import com.github.niefy.modules.sys.entity.SysUserEntity; +import com.github.niefy.modules.sys.entity.SysUserTokenEntity; +import com.github.niefy.modules.sys.service.ShiroService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +public class ShiroServiceImpl implements ShiroService { + @Autowired + private SysMenuDao sysMenuDao; + @Autowired + private SysUserDao sysUserDao; + @Autowired + private SysUserTokenDao sysUserTokenDao; + + @Override + public Set getUserPermissions(long userId) { + List permsList; + + //系统管理员,拥有最高权限 + if (userId == Constant.SUPER_ADMIN) { + List menuList = sysMenuDao.selectList(null); + permsList = new ArrayList<>(menuList.size()); + for (SysMenuEntity menu : menuList) { + permsList.add(menu.getPerms()); + } + } else { + permsList = sysUserDao.queryAllPerms(userId); + } + //用户权限列表 + Set permsSet = new HashSet<>(); + for (String perms : permsList) { + if (StringUtils.isBlank(perms)) { + continue; + } + permsSet.addAll(Arrays.asList(perms.trim().split(","))); + } + return permsSet; + } + + @Override + public SysUserTokenEntity queryByToken(String token) { + return sysUserTokenDao.queryByToken(token); + } + + @Override + public SysUserEntity queryUser(Long userId) { + return sysUserDao.selectById(userId); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysCaptchaServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysCaptchaServiceImpl.java new file mode 100644 index 0000000..23011cb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysCaptchaServiceImpl.java @@ -0,0 +1,58 @@ +package com.github.niefy.modules.sys.service.impl; + + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.DateUtils; +import com.github.niefy.modules.sys.service.SysCaptchaService; +import com.google.code.kaptcha.Producer; +import com.github.niefy.common.exception.RRException; +import com.github.niefy.modules.sys.dao.SysCaptchaDao; +import com.github.niefy.modules.sys.entity.SysCaptchaEntity; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.awt.image.BufferedImage; +import java.util.Date; + +/** + * 验证码 + * @author Mark sunlightcs@gmail.com + */ +@Service("sysCaptchaService") +public class SysCaptchaServiceImpl extends ServiceImpl implements SysCaptchaService { + @Autowired + private Producer producer; + + @Override + public BufferedImage getCaptcha(String uuid) { + if (StringUtils.isBlank(uuid)) { + throw new RRException("uuid不能为空"); + } + //生成文字验证码 + String code = producer.createText(); + + SysCaptchaEntity captchaEntity = new SysCaptchaEntity(); + captchaEntity.setUuid(uuid); + captchaEntity.setCode(code); + //5分钟后过期 + captchaEntity.setExpireTime(DateUtils.addDateMinutes(new Date(), 5)); + this.save(captchaEntity); + + return producer.createImage(code); + } + + @Override + public boolean validate(String uuid, String code) { + SysCaptchaEntity captchaEntity = this.getOne(new QueryWrapper().eq("uuid", uuid)); + if (captchaEntity == null) { + return false; + } + + //删除验证码 + this.removeById(uuid); + + return captchaEntity.getCode().equalsIgnoreCase(code) && captchaEntity.getExpireTime().getTime() >= System.currentTimeMillis(); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysConfigServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..dc9d160 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,85 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.modules.sys.entity.SysConfigEntity; +import com.github.niefy.modules.sys.service.SysConfigService; +import com.github.niefy.common.exception.RRException; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.sys.dao.SysConfigDao; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.Map; + +@Service("sysConfigService") +public class SysConfigServiceImpl extends ServiceImpl implements SysConfigService { + + @Override + public PageUtils queryPage(Map params) { + String paramKey = (String) params.get("paramKey"); + + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .like(StringUtils.isNotBlank(paramKey), "param_key", paramKey) + .eq("status", 1) + ); + + return new PageUtils(page); + } + + @Override + public void saveConfig(SysConfigEntity config) { + this.save(config); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(SysConfigEntity config) { + this.updateById(config); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateValueByKey(String key, String value) { + baseMapper.updateValueByKey(key, value); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteBatch(Long[] ids) { + this.removeByIds(Arrays.asList(ids)); + } + + @Override + public String getValue(String key) { + SysConfigEntity config = baseMapper.queryByKey(key); + + return config == null ? null : config.getParamValue(); + } + + @Override + public SysConfigEntity getSysConfig(String key) { + return baseMapper.queryByKey(key); + } + + @Override + public T getConfigObject(String key, Class clazz) { + String value = getValue(key); + if (StringUtils.isNotBlank(value)) { + return JSON.parseObject(value, clazz); + } + + try { + return clazz.newInstance(); + } catch (Exception e) { + throw new RRException("获取参数失败"); + } + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysLogServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysLogServiceImpl.java new file mode 100644 index 0000000..93652d0 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysLogServiceImpl.java @@ -0,0 +1,31 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.sys.dao.SysLogDao; +import com.github.niefy.modules.sys.entity.SysLogEntity; +import com.github.niefy.modules.sys.service.SysLogService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.Map; + + +@Service("sysLogService") +public class SysLogServiceImpl extends ServiceImpl implements SysLogService { + + @Override + public PageUtils queryPage(Map params) { + String key = (String) params.get("key"); + + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper().like(StringUtils.isNotBlank(key), "username", key) + ); + + return new PageUtils(page); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysMenuServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..80e0f8b --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,102 @@ +package com.github.niefy.modules.sys.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.Constant; +import com.github.niefy.common.utils.MapUtils; +import com.github.niefy.modules.sys.dao.SysMenuDao; +import com.github.niefy.modules.sys.entity.SysMenuEntity; +import com.github.niefy.modules.sys.service.SysMenuService; +import com.github.niefy.modules.sys.service.SysRoleMenuService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + + +@Service("sysMenuService") +public class SysMenuServiceImpl extends ServiceImpl implements SysMenuService { + @Autowired + private SysRoleMenuService sysRoleMenuService; + + @Override + public List queryListParentId(Long parentId, List menuIdList) { + List menuList = queryListParentId(parentId); + if (menuIdList == null) { + return menuList; + } + + List userMenuList = new ArrayList<>(); + for (SysMenuEntity menu : menuList) { + if (menuIdList.contains(menu.getMenuId())) { + userMenuList.add(menu); + } + } + return userMenuList; + } + + @Override + public List queryListParentId(Long parentId) { + return baseMapper.queryListParentId(parentId); + } + + @Override + public List queryNotButtonList() { + return baseMapper.queryNotButtonList(); + } + + @Override + public List getUserMenuList(Long userId) { + //系统管理员,拥有最高权限 + if (userId == Constant.SUPER_ADMIN) { + return getAllMenuList(null); + } + + //用户菜单列表 + List menuIdList = sysRoleMenuService.queryAllMenuId(userId); + return getAllMenuList(menuIdList); + } + + @Override + public void delete(Long menuId) { + //删除菜单 + this.removeById(menuId); + //删除菜单与角色关联 + sysRoleMenuService.removeByMap(new MapUtils().put("menu_id", menuId)); + } + + @Override + public List queryUserAllMenu(Long userId) { + return baseMapper.queryUserAllMenu(userId); + } + + /** + * 获取所有菜单列表 + */ + private List getAllMenuList(List menuIdList) { + //查询根菜单列表 + List menuList = queryListParentId(0L, menuIdList); + //递归获取子菜单 + getMenuTreeList(menuList, menuIdList); + + return menuList; + } + + /** + * 递归 + */ + private List getMenuTreeList(List menuList, List menuIdList) { + List subMenuList = new ArrayList<>(); + + for (SysMenuEntity entity : menuList) { + //目录 + if (entity.getType() == Constant.MenuType.CATALOG.getValue()) { + entity.setList(getMenuTreeList(queryListParentId(entity.getMenuId(), menuIdList), menuIdList)); + } + subMenuList.add(entity); + } + + return subMenuList; + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleMenuServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..c9ec9e4 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleMenuServiceImpl.java @@ -0,0 +1,55 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.modules.sys.dao.SysRoleMenuDao; +import com.github.niefy.modules.sys.entity.SysRoleMenuEntity; +import com.github.niefy.modules.sys.service.SysRoleMenuService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + + +/** + * 角色与菜单对应关系 + * @author Mark sunlightcs@gmail.com + */ +@Service("sysRoleMenuService") +public class SysRoleMenuServiceImpl extends ServiceImpl implements SysRoleMenuService { + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveOrUpdate(Long roleId, List menuIdList) { + //先删除角色与菜单关系 + deleteBatch(new Long[]{roleId}); + + if (menuIdList.size() == 0) { + return; + } + + //保存角色与菜单关系 + for (Long menuId : menuIdList) { + SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity(); + sysRoleMenuEntity.setMenuId(menuId); + sysRoleMenuEntity.setRoleId(roleId); + + this.save(sysRoleMenuEntity); + } + } + + @Override + public List queryMenuIdList(Long roleId) { + return baseMapper.queryMenuIdList(roleId); + } + + @Override + public int deleteBatch(Long[] roleIds) { + return baseMapper.deleteBatch(roleIds); + } + + @Override + public List queryAllMenuId(Long userId) { + return baseMapper.queryAllMenuId(userId); + } + +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..0484b03 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,112 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.exception.RRException; +import com.github.niefy.common.utils.Constant; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.sys.dao.SysRoleDao; +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 com.github.niefy.modules.sys.service.SysUserRoleService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 角色 + * @author Mark sunlightcs@gmail.com + */ +@Service("sysRoleService") +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { + @Autowired + private SysRoleMenuService sysRoleMenuService; + @Autowired + private SysUserRoleService sysUserRoleService; + + @Override + public PageUtils queryPage(Map params) { + String roleName = (String) params.get("roleName"); + Long createUserId = (Long) params.get("createUserId"); + + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .like(StringUtils.isNotBlank(roleName), "role_name", roleName) + .eq(createUserId != null, "create_user_id", createUserId) + ); + + return new PageUtils(page); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveRole(SysRoleEntity role) { + role.setCreateTime(new Date()); + this.save(role); + + //检查权限是否越权 + checkPrems(role); + + //保存角色与菜单关系 + sysRoleMenuService.saveOrUpdate(role.getRoleId(), role.getMenuIdList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(SysRoleEntity role) { + this.updateById(role); + + //检查权限是否越权 + checkPrems(role); + + //更新角色与菜单关系 + sysRoleMenuService.saveOrUpdate(role.getRoleId(), role.getMenuIdList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteBatch(Long[] roleIds) { + //删除角色 + this.removeByIds(Arrays.asList(roleIds)); + + //删除角色与菜单关联 + sysRoleMenuService.deleteBatch(roleIds); + + //删除角色与用户关联 + sysUserRoleService.deleteBatch(roleIds); + } + + + @Override + public List queryRoleIdList(Long createUserId) { + return baseMapper.queryRoleIdList(createUserId); + } + + /** + * 检查权限是否越权 + */ + private void checkPrems(SysRoleEntity role) { + //如果不是超级管理员,则需要判断角色的权限是否超过自己的权限 + if (role.getCreateUserId() == Constant.SUPER_ADMIN) { + return; + } + + //查询用户所拥有的菜单列表 + List menuIdList = sysRoleMenuService.queryAllMenuId(role.getCreateUserId()); + + //判断是否越权 + if (!menuIdList.containsAll(role.getMenuIdList())) { + throw new RRException("新增角色的权限,已超出你的权限范围"); + } + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserRoleServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..de82137 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,48 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.MapUtils; +import com.github.niefy.modules.sys.dao.SysUserRoleDao; +import com.github.niefy.modules.sys.entity.SysUserRoleEntity; +import com.github.niefy.modules.sys.service.SysUserRoleService; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * 用户与角色对应关系 + * @author Mark sunlightcs@gmail.com + */ +@Service("sysUserRoleService") +public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { + + @Override + public void saveOrUpdate(Long userId, List roleIdList) { + //先删除用户与角色关系 + this.removeByMap(new MapUtils().put("user_id", userId)); + + if (roleIdList == null || roleIdList.size() == 0) { + return; + } + + //保存用户与角色关系 + for (Long roleId : roleIdList) { + SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity(); + sysUserRoleEntity.setUserId(userId); + sysUserRoleEntity.setRoleId(roleId); + + this.save(sysUserRoleEntity); + } + } + + @Override + public List queryRoleIdList(Long userId) { + return baseMapper.queryRoleIdList(userId); + } + + @Override + public int deleteBatch(Long[] roleIds) { + return baseMapper.deleteBatch(roleIds); + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..f1fddbb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserServiceImpl.java @@ -0,0 +1,131 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.Constant; +import com.github.niefy.modules.sys.dao.SysUserDao; +import com.github.niefy.modules.sys.entity.SysUserEntity; +import com.github.niefy.modules.sys.service.SysRoleService; +import com.github.niefy.modules.sys.service.SysUserRoleService; +import com.github.niefy.modules.sys.service.SysUserService; +import com.github.niefy.common.exception.RRException; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.crypto.hash.Sha256Hash; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + + +/** + * 系统用户 + * @author Mark sunlightcs@gmail.com + */ +@Service("sysUserService") +public class SysUserServiceImpl extends ServiceImpl implements SysUserService { + @Autowired + private SysUserRoleService sysUserRoleService; + @Autowired + private SysRoleService sysRoleService; + + @Override + public PageUtils queryPage(Map params) { + String username = (String) params.get("username"); + Long createUserId = (Long) params.get("createUserId"); + + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .like(StringUtils.isNotBlank(username), "username", username) + .eq(createUserId != null, "create_user_id", createUserId) + ); + + return new PageUtils(page); + } + + @Override + public List queryAllPerms(Long userId) { + return baseMapper.queryAllPerms(userId); + } + + @Override + public SysUserEntity queryByUserName(String username) { + return baseMapper.queryByUserName(username); + } + + @Override + @Transactional + public void saveUser(SysUserEntity user) { + user.setCreateTime(new Date()); + //sha256加密 + String salt = RandomStringUtils.randomAlphanumeric(20); + user.setPassword(new Sha256Hash(user.getPassword(), salt).toHex()); + user.setSalt(salt); + this.save(user); + + //检查角色是否越权 + checkRole(user); + + //保存用户与角色关系 + sysUserRoleService.saveOrUpdate(user.getUserId(), user.getRoleIdList()); + } + + @Override + @Transactional + public void update(SysUserEntity user) { + if (StringUtils.isBlank(user.getPassword())) { + user.setPassword(null); + } else { + user.setPassword(new Sha256Hash(user.getPassword(), user.getSalt()).toHex()); + } + this.updateById(user); + + //检查角色是否越权 + checkRole(user); + + //保存用户与角色关系 + sysUserRoleService.saveOrUpdate(user.getUserId(), user.getRoleIdList()); + } + + @Override + public void deleteBatch(Long[] userId) { + this.removeByIds(Arrays.asList(userId)); + } + + @Override + public boolean updatePassword(Long userId, String password, String newPassword) { + SysUserEntity userEntity = new SysUserEntity(); + userEntity.setPassword(newPassword); + return this.update(userEntity, + new QueryWrapper().eq("user_id", userId).eq("password", password)); + } + + /** + * 检查角色是否越权 + */ + private void checkRole(SysUserEntity user) { + if (user.getRoleIdList() == null || user.getRoleIdList().size() == 0) { + return; + } + //如果不是超级管理员,则需要判断用户的角色是否自己创建 + if (user.getCreateUserId() == Constant.SUPER_ADMIN) { + return; + } + + //查询用户创建的角色列表 + List roleIdList = sysRoleService.queryRoleIdList(user.getCreateUserId()); + + //判断是否越权 + if (!roleIdList.containsAll(user.getRoleIdList())) { + throw new RRException("新增用户所选角色,不是本人创建"); + } + } +} diff --git a/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserTokenServiceImpl.java b/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserTokenServiceImpl.java new file mode 100644 index 0000000..0a5d1be --- /dev/null +++ b/src/main/java/com/github/niefy/modules/sys/service/impl/SysUserTokenServiceImpl.java @@ -0,0 +1,65 @@ +package com.github.niefy.modules.sys.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.modules.sys.oauth2.TokenGenerator; +import com.github.niefy.modules.sys.service.SysUserTokenService; +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.sys.dao.SysUserTokenDao; +import com.github.niefy.modules.sys.entity.SysUserTokenEntity; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.Objects; + + +@Service("sysUserTokenService") +public class SysUserTokenServiceImpl extends ServiceImpl implements SysUserTokenService { + //12小时后过期 + private final static int EXPIRE = 3600 * 12; + + + @Override + public R createToken(long userId) { + //生成一个token + String token = TokenGenerator.generateValue(); + + //当前时间 + Date now = new Date(); + //过期时间 + Date expireTime = new Date(now.getTime() + EXPIRE * 1000); + + //判断是否生成过token + SysUserTokenEntity tokenEntity = this.getById(userId); + if (tokenEntity == null) { + tokenEntity = new SysUserTokenEntity(); + tokenEntity.setUserId(userId); + tokenEntity.setToken(token); + tokenEntity.setUpdateTime(now); + tokenEntity.setExpireTime(expireTime); + + //保存token + this.save(tokenEntity); + } else { + tokenEntity.setToken(token); + tokenEntity.setUpdateTime(now); + tokenEntity.setExpireTime(expireTime); + + //更新token + this.updateById(tokenEntity); + } + + return Objects.requireNonNull(R.ok().put("token", token)).put("expire", EXPIRE); + } + + @Override + public void logout(long userId) { + //生成一个token + String token = TokenGenerator.generateValue(); + + //修改token + SysUserTokenEntity tokenEntity = new SysUserTokenEntity(); + tokenEntity.setUserId(userId); + tokenEntity.setToken(token); + this.updateById(tokenEntity); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/config/WxMpMessageRouterConfiguration.java b/src/main/java/com/github/niefy/modules/wx/config/WxMpMessageRouterConfiguration.java new file mode 100644 index 0000000..6088406 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/config/WxMpMessageRouterConfiguration.java @@ -0,0 +1,52 @@ +package com.github.niefy.modules.wx.config; + +import com.github.niefy.modules.wx.handler.*; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static me.chanjar.weixin.common.api.WxConsts.EventType; +import static me.chanjar.weixin.common.api.WxConsts.EventType.*; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.KF_CREATE_SESSION; + +@RequiredArgsConstructor +@Configuration +public class WxMpMessageRouterConfiguration { + private final LogHandler logHandler; + private final NullHandler nullHandler; + private final KfSessionHandler kfSessionHandler; + private final MenuHandler menuHandler; + private final MsgHandler msgHandler; + private final ScanHandler scanHandler; + private final UnsubscribeHandler unsubscribeHandler; + private final SubscribeHandler subscribeHandler; + + @Bean + public WxMpMessageRouter messageRouter(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + + // 记录所有事件的日志 + newRouter.rule().async(false).handler(this.logHandler).next(); + + // 接收客服会话管理事件 + newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION).handler(this.kfSessionHandler).end(); + // 自定义菜单事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.CLICK).handler(this.menuHandler).end(); + // 关注事件 + newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); + // 取消关注事件 + newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); + //扫描带参二维码事件 + newRouter.rule().async(false).msgType(EVENT).event(SCAN).handler(this.scanHandler).end(); + //其他事件 + newRouter.rule().async(false).msgType(EVENT).handler(this.nullHandler).end(); + + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); + + return newRouter; + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/config/WxMpServiceConfiguration.java b/src/main/java/com/github/niefy/modules/wx/config/WxMpServiceConfiguration.java new file mode 100644 index 0000000..4116b9d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/config/WxMpServiceConfiguration.java @@ -0,0 +1,19 @@ +package com.github.niefy.modules.wx.config; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@RequiredArgsConstructor +@Configuration +public class WxMpServiceConfiguration { + + @Bean + public WxMpService wxMpService() { + WxMpService wxMpService = new WxMpServiceImpl(); + wxMpService.setMaxRetryTimes(3); + return wxMpService; + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/controller/ArticleController.java b/src/main/java/com/github/niefy/modules/wx/controller/ArticleController.java new file mode 100644 index 0000000..d2bb500 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/controller/ArticleController.java @@ -0,0 +1,82 @@ +package com.github.niefy.modules.wx.controller; + +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.Article; +import com.github.niefy.modules.wx.enums.ArticleTypeEnum; +import com.github.niefy.modules.wx.service.ArticleService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +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.RestController; + +import java.util.List; + +/** + * cms文章 + */ +@RestController +@RequestMapping("/article") +@Api(tags = {"CMS文章"}) +public class ArticleController { + @Autowired + ArticleService articleService; + + /** + * 查看文章详情 + * + * @param articleId + * @return + */ + @GetMapping("/detail") + @ApiOperation(value = "文章详情",notes = "") + public R getArticle(int articleId) { + Article article = articleService.findById(articleId); + return R.ok().put(article); + } + + /** + * 查看目录 + * + * @param category + * @return + */ + @GetMapping("/category") + @ApiOperation(value = "目录信息",notes = "") + public R getQuestions(String type, String category) { + ArticleTypeEnum articleType = ArticleTypeEnum.of(type); + if (articleType == null) { + return R.error("文章类型有误"); + } + List
articles = articleService.selectCategory(articleType, category); + return R.ok().put(articles); + } + + /** + * 文章搜索 + * + * @param category + * @param keywords + * @return + */ + @GetMapping("/search") + @ApiOperation(value = "文章搜索",notes = "") + public R getQuestions(String type, + @RequestParam(required = false) String category, + @RequestParam(required = false) String keywords) { + ArticleTypeEnum articleType = ArticleTypeEnum.of(type); + if (articleType == null) { + return R.error("文章类型有误"); + } + if (!StringUtils.hasText(keywords)) { + return R.error("关键词不得为空"); + } + List
articles = articleService.search(articleType, category, keywords); + return R.ok().put(articles); + } + + +} diff --git a/src/main/java/com/github/niefy/modules/wx/controller/WxAuthController.java b/src/main/java/com/github/niefy/modules/wx/controller/WxAuthController.java new file mode 100644 index 0000000..f597c77 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/controller/WxAuthController.java @@ -0,0 +1,134 @@ +package com.github.niefy.modules.wx.controller; + +import com.github.niefy.common.utils.*; +import com.github.niefy.modules.sys.service.SysLogService; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.form.WxH5OuthrizeForm; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; + +/** + * 微信网页授权相关 + */ +@RestController +@RequestMapping("/wxAuth") +@Api(tags = {"微信网页授权"}) +@RequiredArgsConstructor +public class WxAuthController { + Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + SysLogService sysLogService; + private final WxMpService wxMpService; + + /** + * 使用微信授权code换取openid + * + * @param request + * @param response + * @param form + * @return + */ + @PostMapping("/codeToOpenid") + @CrossOrigin + @ApiOperation(value = "网页登录-code换取openid",notes = "scope为snsapi_base") + public R codeToOpenid(HttpServletRequest request, HttpServletResponse response, + @CookieValue String appid, @RequestBody WxH5OuthrizeForm form) { + try { + this.wxMpService.switchoverTo(appid); + WxOAuth2AccessToken token = wxMpService.getOAuth2Service().getAccessToken(form.getCode()); + String openid = token.getOpenId(); + CookieUtil.setCookie(response, "openid", openid, 365 * 24 * 60 * 60); + String openidToken = MD5Util.getMd5AndSalt(openid); + CookieUtil.setCookie(response, "openidToken", openidToken, 365 * 24 * 60 * 60); + return R.ok().put(openid); + } catch (WxErrorException e) { + logger.error("code换取openid失败", e); + return R.error(e.getError().getErrorMsg()); + } + } + + /** + * 使用微信授权code换取用户信息(需scope为 snsapi_userinfo) + * + * @param request + * @param response + * @param form + * @return + */ + @PostMapping("/codeToUserInfo") + @CrossOrigin + @ApiOperation(value = "网页登录-code换取用户信息",notes = "需scope为 snsapi_userinfo") + public R codeToUserInfo(HttpServletRequest request, HttpServletResponse response, + @CookieValue String appid, @RequestBody WxH5OuthrizeForm form) { + try { + this.wxMpService.switchoverTo(appid); + WxOAuth2AccessToken token = wxMpService.getOAuth2Service().getAccessToken(form.getCode()); + WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(token,"zh_CN"); + String openid = userInfo.getOpenid(); + CookieUtil.setCookie(response, "openid", openid, 365 * 24 * 60 * 60); + String openidToken = MD5Util.getMd5AndSalt(openid); + CookieUtil.setCookie(response, "openidToken", openidToken, 365 * 24 * 60 * 60); + return R.ok().put(new WxUser(userInfo,appid)); + } catch (WxErrorException e) { + logger.error("code换取用户信息失败", e); + return R.error(e.getError().getErrorMsg()); + } + } + + /** + * 获取微信分享的签名配置 + * 允许跨域(只有微信公众号添加了js安全域名的网站才能加载微信分享,故这里不对域名进行校验) + * + * @param request + * @param response + * @return + */ + @GetMapping("/getShareSignature") + @ApiOperation(value = "获取微信分享的签名配置",notes = "微信公众号添加了js安全域名的网站才能加载微信分享") + public R getShareSignature(HttpServletRequest request, HttpServletResponse response,@CookieValue String appid) throws WxErrorException { + this.wxMpService.switchoverTo(appid); + // 1.拼接url(当前网页的URL,不包含#及其后面部分) + String wxShareUrl = request.getHeader(Constant.WX_CLIENT_HREF_HEADER); + if (!StringUtils.hasText(wxShareUrl)) { + return R.error("header中缺少"+Constant.WX_CLIENT_HREF_HEADER+"参数,微信分享加载失败"); + } + wxShareUrl = wxShareUrl.split("#")[0]; + Map wxMap = new TreeMap<>(); + String wxNoncestr = UUID.randomUUID().toString(); + String wxTimestamp = (System.currentTimeMillis() / 1000) + ""; + wxMap.put("noncestr", wxNoncestr); + wxMap.put("timestamp", wxTimestamp); + wxMap.put("jsapi_ticket", wxMpService.getJsapiTicket()); + wxMap.put("url", wxShareUrl); + + // 加密获取signature + StringBuilder wxBaseString = new StringBuilder(); + wxMap.forEach((key, value) -> wxBaseString.append(key).append("=").append(value).append("&")); + String wxSignString = wxBaseString.substring(0, wxBaseString.length() - 1); + // signature + String wxSignature = SHA1Util.sha1(wxSignString); + Map resMap = new TreeMap<>(); + resMap.put("appId", appid); + resMap.put("wxTimestamp", wxTimestamp); + resMap.put("wxNoncestr", wxNoncestr); + resMap.put("wxSignature", wxSignature); + return R.ok().put(resMap); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/controller/WxMpPortalController.java b/src/main/java/com/github/niefy/modules/wx/controller/WxMpPortalController.java new file mode 100644 index 0000000..df0c0f2 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/controller/WxMpPortalController.java @@ -0,0 +1,106 @@ +package com.github.niefy.modules.wx.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +/** + * 微信消息 + * @author Binary Wang + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/wx/msg/{appid}") +@Api(tags = {"微信消息 - 腾讯会调用"}) +public class WxMpPortalController { + private final WxMpService wxService; + private final WxMpMessageRouter messageRouter; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @GetMapping(produces = "text/plain;charset=utf-8") + @ApiOperation(value = "微信服务器的认证消息",notes = "公众号接入开发模式时腾讯调用此接口") + public String authGet(@PathVariable String appid, + @RequestParam(name = "signature", required = false) String signature, + @RequestParam(name = "timestamp", required = false) String timestamp, + @RequestParam(name = "nonce", required = false) String nonce, + @RequestParam(name = "echostr", required = false) String echostr) { + + logger.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, + timestamp, nonce, echostr); + if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { + throw new IllegalArgumentException("请求参数非法,请核实!"); + } + this.wxService.switchoverTo(appid); + + if (wxService.checkSignature(timestamp, nonce, signature)) { + return echostr; + } + + return "非法请求"; + } + + @PostMapping(produces = "application/xml; charset=UTF-8") + @ApiOperation(value = "微信各类消息",notes = "公众号接入开发模式后才有效") + public String post(@PathVariable String appid, + @RequestBody String requestBody, + @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("openid") String openid, + @RequestParam(name = "encrypt_type", required = false) String encType, + @RequestParam(name = "msg_signature", required = false) String msgSignature) { +// logger.debug("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," +// + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", +// openid, signature, encType, msgSignature, timestamp, nonce, requestBody); + this.wxService.switchoverTo(appid); + if (!wxService.checkSignature(timestamp, nonce, signature)) { + throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); + } + + String out = null; + if (encType == null) { + // 明文传输的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); + WxMpXmlOutMessage outMessage = this.route(appid,inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toXml(); + } else if ("aes".equalsIgnoreCase(encType)) { + // aes加密的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), + timestamp, nonce, msgSignature); + logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + WxMpXmlOutMessage outMessage = this.route(appid,inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); + } + + logger.debug("\n组装回复信息:{}", out); + return out; + } + + private WxMpXmlOutMessage route(String appid,WxMpXmlMessage message) { + try { + return this.messageRouter.route(appid,message); + } catch (Exception e) { + logger.error("路由消息时出现异常!", e); + } + + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/controller/WxUserController.java b/src/main/java/com/github/niefy/modules/wx/controller/WxUserController.java new file mode 100644 index 0000000..cc91fac --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/controller/WxUserController.java @@ -0,0 +1,38 @@ +package com.github.niefy.modules.wx.controller; + +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.service.WxUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 微信用户(粉丝) + */ +@RestController +@RequestMapping("/wxUser") +@RequiredArgsConstructor +@Api(tags = {"微信粉丝"}) +public class WxUserController { + Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + WxUserService wxUserService; + private final WxMpService wxMpService; + + @GetMapping("/getUserInfo") + @ApiOperation(value = "获取粉丝信息") + public R getUserInfo(@CookieValue String appid,@CookieValue String openid){ + this.wxMpService.switchoverTo(appid); + WxUser wxUser = wxUserService.getById(openid); + return R.ok().put(wxUser); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/controller/WxUserTagsController.java b/src/main/java/com/github/niefy/modules/wx/controller/WxUserTagsController.java new file mode 100644 index 0000000..02046cb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/controller/WxUserTagsController.java @@ -0,0 +1,72 @@ +package com.github.niefy.modules.wx.controller; + +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.form.WxUserTaggingForm; +import com.github.niefy.modules.wx.service.WxUserService; +import com.github.niefy.modules.wx.service.WxUserTagsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 粉丝标签 + */ +@RestController +@RequestMapping("/wxUserTags") +@RequiredArgsConstructor +@Api(tags = {"粉丝标签"}) +public class WxUserTagsController { + @Autowired + WxUserTagsService wxUserTagsService; + @Autowired + WxUserService wxUserService; + private final WxMpService wxMpService; + + @GetMapping("/userTags") + @ApiOperation(value = "当前用户的标签") + public R userTags(@CookieValue String appid,@CookieValue String openid){ + if(openid==null){ + return R.error("none_openid"); + } + this.wxMpService.switchoverTo(appid); + WxUser wxUser = wxUserService.getById(openid); + if(wxUser==null){ + wxUser=wxUserService.refreshUserInfo(openid,appid); + if(wxUser==null) { + return R.error("not_subscribed"); + } + } + return R.ok().put(wxUser.getTagidList()); + } + + @PostMapping("/tagging") + @ApiOperation(value = "给用户绑定标签") + public R tagging(@CookieValue String appid,@CookieValue String openid , @RequestBody WxUserTaggingForm form) { + this.wxMpService.switchoverTo(appid); + try { + wxUserTagsService.tagging(form.getTagid(),openid); + }catch (WxErrorException e){ + WxError error = e.getError(); + if(50005==error.getErrorCode()){//未关注公众号 + return R.error("not_subscribed"); + }else { + return R.error(error.getErrorMsg()); + } + } + return R.ok(); + } + + @PostMapping("/untagging") + @ApiOperation(value = "解绑标签") + public R untagging(@CookieValue String appid,@CookieValue String openid , @RequestBody WxUserTaggingForm form) throws WxErrorException { + this.wxMpService.switchoverTo(appid); + wxUserTagsService.untagging(form.getTagid(),openid); + return R.ok(); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/ArticleMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/ArticleMapper.java new file mode 100644 index 0000000..62e3241 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/ArticleMapper.java @@ -0,0 +1,14 @@ +package com.github.niefy.modules.wx.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.github.niefy.modules.wx.entity.Article; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.scheduling.annotation.Async; + +@Mapper +@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期 +public interface ArticleMapper extends BaseMapper
{ + @Async + void addOpenCount(int id); +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/MsgReplyRuleMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/MsgReplyRuleMapper.java new file mode 100644 index 0000000..d775c4d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/MsgReplyRuleMapper.java @@ -0,0 +1,11 @@ +package com.github.niefy.modules.wx.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.github.niefy.modules.wx.entity.MsgReplyRule; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期 +public interface MsgReplyRuleMapper extends BaseMapper { +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/MsgTemplateMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/MsgTemplateMapper.java new file mode 100644 index 0000000..320a326 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/MsgTemplateMapper.java @@ -0,0 +1,11 @@ +package com.github.niefy.modules.wx.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.github.niefy.modules.wx.entity.MsgTemplate; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期 +public interface MsgTemplateMapper extends BaseMapper { +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/TemplateMsgLogMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/TemplateMsgLogMapper.java new file mode 100644 index 0000000..f5aceb6 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/TemplateMsgLogMapper.java @@ -0,0 +1,9 @@ +package com.github.niefy.modules.wx.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.github.niefy.modules.wx.entity.TemplateMsgLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface TemplateMsgLogMapper extends BaseMapper { +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/WxAccountMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/WxAccountMapper.java new file mode 100644 index 0000000..38cfb10 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/WxAccountMapper.java @@ -0,0 +1,18 @@ +package com.github.niefy.modules.wx.dao; + +import com.github.niefy.modules.wx.entity.WxAccount; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; + +/** + * 公众号账号 + * + * @author niefy + * @date 2020-06-17 13:56:51 + */ +@Mapper +@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期 +public interface WxAccountMapper extends BaseMapper { + +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/WxMsgMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/WxMsgMapper.java new file mode 100644 index 0000000..6fcfcb6 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/WxMsgMapper.java @@ -0,0 +1,18 @@ +package com.github.niefy.modules.wx.dao; + +import com.github.niefy.modules.wx.entity.WxMsg; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; + +/** + * 微信消息 + * + * @author niefy + * @date 2020-05-14 17:28:34 + */ +@Mapper +@CacheNamespace(flushInterval = 10*1000L)//缓存过期时间(毫秒) +public interface WxMsgMapper extends BaseMapper { + +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/WxQrCodeMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/WxQrCodeMapper.java new file mode 100644 index 0000000..6e724eb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/WxQrCodeMapper.java @@ -0,0 +1,19 @@ +package com.github.niefy.modules.wx.dao; + +import com.github.niefy.modules.wx.entity.WxQrCode; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; + +/** + * 公众号带参二维码 + * + * @author niefy + * @email niefy@qq.com + * @date 2020-01-02 11:11:55 + */ +@Mapper +@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期 +public interface WxQrCodeMapper extends BaseMapper { + +} diff --git a/src/main/java/com/github/niefy/modules/wx/dao/WxUserMapper.java b/src/main/java/com/github/niefy/modules/wx/dao/WxUserMapper.java new file mode 100644 index 0000000..651ace7 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dao/WxUserMapper.java @@ -0,0 +1,14 @@ +package com.github.niefy.modules.wx.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.github.niefy.modules.wx.entity.WxUser; +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +@CacheNamespace(flushInterval = 300000L)//缓存五分钟过期 +public interface WxUserMapper extends BaseMapper { + + void unsubscribe(String openid); + WxUser findByPhone(String phone); +} diff --git a/src/main/java/com/github/niefy/modules/wx/dto/PageSizeConstant.java b/src/main/java/com/github/niefy/modules/wx/dto/PageSizeConstant.java new file mode 100644 index 0000000..edf207c --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dto/PageSizeConstant.java @@ -0,0 +1,9 @@ +package com.github.niefy.modules.wx.dto; + +public class PageSizeConstant { + /** + * 默认分页大小 + */ + public static final int PAGE_SIZE_SMALL = 20; + public static final int PAGE_SIZE_MEDIUM = 50; +} diff --git a/src/main/java/com/github/niefy/modules/wx/dto/RegexConstant.java b/src/main/java/com/github/niefy/modules/wx/dto/RegexConstant.java new file mode 100644 index 0000000..4aef92d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/dto/RegexConstant.java @@ -0,0 +1,44 @@ +package com.github.niefy.modules.wx.dto; + +/** + * 常用正则表达式 + */ +public class RegexConstant { + /** + * 手机号码 + */ + public static final String PHONE_NUM = "1\\d{10}"; + /** + * 六位数字 + */ + public static final String SIX_NUMBER = "\\d{6}"; + /** + * 六位字符 + */ + public static final String SIX_CHAR = ".{6}"; + /** + * 图片文件名 + */ + public static final String IMAGE_FILE_NAME = ".*\\.(jpg|JPG|jpeg|JPEG|gif|GIF|png|PNG)$"; + /** + * SQL注入常用字符 + */ + public static final String SQL_INJECTION_WORDS = ".*([';]+|(--)+).*"; + /** + * 逗号分割的数字列表 + */ + public static final String NUMBER_ARRAY = "^\\d+(,\\d+)*$"; + /** + * 时间戳,毫秒标识的时间格式 + */ + public static final String TIME_MILLIS = "^[0-9]{10,}$"; + /** + * 日期字符串格式,如 2018-01-01 + */ + public static final String DATE_STRING = "^\\d{2}-\\d{2}-\\d{2}$"; + /** + * 时间字符串格式,如 12:00:00 + */ + public static final String TIME_STRING = "^\\d{2}:\\d{2}:\\d{2}$"; + +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/Article.java b/src/main/java/com/github/niefy/modules/wx/entity/Article.java new file mode 100644 index 0000000..250a919 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/Article.java @@ -0,0 +1,39 @@ +package com.github.niefy.modules.wx.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.github.niefy.common.utils.Json; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.io.Serializable; +import java.util.Date; + +/** + * cms文章 + */ +@Data +@TableName("cms_article") +public class Article implements Serializable { + private static final long serialVersionUID = 1L; + @TableId(type = IdType.AUTO) + private Long id; + private int type; + @TableField(insertStrategy = FieldStrategy.IGNORED)//title重复则不插入 + @NotEmpty(message = "标题不得为空") + private String title; + private String tags; + private String summary; + private String content; + private String category; + private String subCategory; + private Date createTime; + private Date updateTime; + private int openCount; + private String targetLink; + private String image; + + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/MsgReplyRule.java b/src/main/java/com/github/niefy/modules/wx/entity/MsgReplyRule.java new file mode 100644 index 0000000..7acd820 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/MsgReplyRule.java @@ -0,0 +1,48 @@ +package com.github.niefy.modules.wx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.github.niefy.common.utils.Json; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.io.Serializable; +import java.sql.Time; +import java.util.Date; + +/** + * 自动回复规则 + * @author Nifury + * @date 2017-11-1 + */ +@Data +@TableName("wx_msg_reply_rule") +public class MsgReplyRule implements Serializable { + private static final long serialVersionUID = 1L; + @TableId(type = IdType.AUTO) + private Long ruleId; + private String appid; + @NotEmpty(message = "规则名称不得为空") + private String ruleName; + @NotEmpty(message = "匹配关键词不得为空") + private String matchValue; + private boolean exactMatch; + private String replyType; + @NotEmpty(message = "回复内容不得为空") + private String replyContent; + @TableField(value = "`status`") + private boolean status; + @TableField(value = "`desc`") + private String desc; + private Time effectTimeStart; + private Time effectTimeEnd; + private int priority; + private Date updateTime; + + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/MsgTemplate.java b/src/main/java/com/github/niefy/modules/wx/entity/MsgTemplate.java new file mode 100644 index 0000000..f99d1b3 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/MsgTemplate.java @@ -0,0 +1,56 @@ +package com.github.niefy.modules.wx.entity; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.github.niefy.common.utils.Json; +import lombok.Data; +import me.chanjar.weixin.mp.bean.template.WxMpTemplate; + +import java.io.Serializable; +import java.util.Date; + +/** + * 模板消息模板 + * @author Nifury + * @date 2017-9-27 + */ +@Data +@TableName("wx_msg_template") +public class MsgTemplate implements Serializable { + private static final long serialVersionUID = 1L; + @TableId(type = IdType.AUTO) + private Long id; + private String appid; + private String templateId; + @TableField(value = "`name`") + private String name; + private String title; + private String content; + private JSONArray data; + private String url; + private JSONObject miniprogram; + @TableField(value = "`status`") + private boolean status; + private Date updateTime; + public MsgTemplate() { + + } + public MsgTemplate(WxMpTemplate mpTemplate,String appid) { + this.appid = appid; + this.templateId=mpTemplate.getTemplateId(); + this.title=mpTemplate.getTitle(); + this.name=mpTemplate.getTemplateId(); + this.content = mpTemplate.getContent(); + this.status=true; + } + + @Override + public String toString() { + return Json.toJsonString(this); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/SMS.java b/src/main/java/com/github/niefy/modules/wx/entity/SMS.java new file mode 100644 index 0000000..a6e651d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/SMS.java @@ -0,0 +1,22 @@ +package com.github.niefy.modules.wx.entity; +import lombok.Data; + +@Data +public class SMS{ + /** + * openid + */ + private String openId; + /** + * 手机号码 + */ + private String mobile; + /** + * 短信验证码 + */ + private String code; + /** + * 短信验证码发送时间 + */ + private String codeTime; +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/TemplateMsgLog.java b/src/main/java/com/github/niefy/modules/wx/entity/TemplateMsgLog.java new file mode 100644 index 0000000..4865d68 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/TemplateMsgLog.java @@ -0,0 +1,56 @@ +package com.github.niefy.modules.wx.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.IdType; +import com.github.niefy.common.utils.Json; +import lombok.Data; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; + +import java.io.Serializable; +import java.util.Date; + +/** + * 模板消息日志 + * @author Nifury + * @date 2017-9-27 + */ +@Data +@TableName("wx_template_msg_log") +public class TemplateMsgLog implements Serializable { + + private static final long serialVersionUID = 1L; + @TableId(type = IdType.AUTO) + private Long logId; + private String appid; + private String touser; + private String templateId; + private JSONArray data; + private String url; + private JSONObject miniprogram; + private Date sendTime; + private String sendResult; + + public TemplateMsgLog() { + } + + public TemplateMsgLog(WxMpTemplateMessage msg,String appid, String sendResult) { + this.appid = appid; + this.touser = msg.getToUser(); + this.templateId = msg.getTemplateId(); + this.url = msg.getUrl(); + this.miniprogram = JSONObject.parseObject(JSON.toJSONString(msg.getMiniProgram())); + this.data = JSONArray.parseArray(JSON.toJSONString(msg.getData())); + this.sendTime = new Date(); + this.sendResult = sendResult; + } + + @Override + public String toString() { + return Json.toJsonString(this); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/WxAccount.java b/src/main/java/com/github/niefy/modules/wx/entity/WxAccount.java new file mode 100644 index 0000000..c627ebf --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/WxAccount.java @@ -0,0 +1,65 @@ +package com.github.niefy.modules.wx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; + +import javax.validation.constraints.NotEmpty; +import java.io.Serializable; + +/** + * 公众号账号 + * + * @author niefy + * @date 2020-06-17 13:56:51 + */ +@Data +@TableName("wx_account") +public class WxAccount implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(type = IdType.INPUT) + @NotEmpty(message = "appid不得为空") + private String appid; + /** + * 公众号名称 + */ + @NotEmpty(message = "名称不得为空") + private String name; + /** + * 账号类型 + */ + private int type; + /** + * 认证状态 + */ + private boolean verified; + /** + * appsecret + */ + @NotEmpty(message = "appSecret不得为空") + private String secret; + /** + * token + */ + private String token; + /** + * aesKey + */ + private String aesKey; + + public WxMpDefaultConfigImpl toWxMpConfigStorage(){ + WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl(); + configStorage.setAppId(appid); + configStorage.setSecret(secret); + configStorage.setToken(token); + configStorage.setAesKey(aesKey); + return configStorage; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/WxMsg.java b/src/main/java/com/github/niefy/modules/wx/entity/WxMsg.java new file mode 100644 index 0000000..dd9f3ac --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/WxMsg.java @@ -0,0 +1,103 @@ +package com.github.niefy.modules.wx.entity; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; + +import java.io.Serializable; +import java.util.Date; + +/** + * 微信消息 + * + * @author niefy + * @date 2020-05-14 17:28:34 + */ +@Data +@TableName("wx_msg") +public class WxMsg implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId + private Long id; + private String appid; + /** + * 微信用户ID + */ + private String openid; + /** + * 消息方向 + */ + private byte inOut; + /** + * 消息类型 + */ + private String msgType; + /** + * 消息详情 + */ + private JSONObject detail; + /** + * 创建时间 + */ + private Date createTime; + + public static class WxMsgInOut{ + static final byte IN=0; + static final byte OUT=1; + } + + public WxMsg() { + } + public WxMsg(WxMpXmlMessage wxMessage) { + this.openid=wxMessage.getFromUser(); + this.appid= WxMpConfigStorageHolder.get(); + this.inOut = WxMsgInOut.IN; + this.msgType = wxMessage.getMsgType(); + this.detail = new JSONObject(); + Long createTime = wxMessage.getCreateTime(); + this.createTime = createTime==null?new Date():new Date(createTime*1000); + if(WxConsts.XmlMsgType.TEXT.equals(this.msgType)){ + this.detail.put("content",wxMessage.getContent()); + }else if(WxConsts.XmlMsgType.IMAGE.equals(this.msgType)){ + this.detail.put("picUrl",wxMessage.getPicUrl()); + this.detail.put("mediaId",wxMessage.getMediaId()); + }else if(WxConsts.XmlMsgType.VOICE.equals(this.msgType)){ + this.detail.put("format",wxMessage.getFormat()); + this.detail.put("mediaId",wxMessage.getMediaId()); + }else if(WxConsts.XmlMsgType.VIDEO.equals(this.msgType) || + WxConsts.XmlMsgType.SHORTVIDEO.equals(this.msgType)){ + this.detail.put("thumbMediaId",wxMessage.getThumbMediaId()); + this.detail.put("mediaId",wxMessage.getMediaId()); + }else if(WxConsts.XmlMsgType.LOCATION.equals(this.msgType)){ + this.detail.put("locationX",wxMessage.getLocationX()); + this.detail.put("locationY",wxMessage.getLocationY()); + this.detail.put("scale",wxMessage.getScale()); + this.detail.put("label",wxMessage.getLabel()); + }else if(WxConsts.XmlMsgType.LINK.equals(this.msgType)){ + this.detail.put("title",wxMessage.getTitle()); + this.detail.put("description",wxMessage.getDescription()); + this.detail.put("url",wxMessage.getUrl()); + }else if(WxConsts.XmlMsgType.EVENT.equals(this.msgType)){ + this.detail.put("event",wxMessage.getEvent()); + this.detail.put("eventKey",wxMessage.getEventKey()); + } + } + public static WxMsg buildOutMsg(String msgType,String openid,JSONObject detail){ + WxMsg wxMsg = new WxMsg(); + wxMsg.appid= WxMpConfigStorageHolder.get(); + wxMsg.msgType = msgType; + wxMsg.openid = openid; + wxMsg.detail = detail; + wxMsg.createTime=new Date(); + wxMsg.inOut = WxMsgInOut.OUT; + return wxMsg; + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/WxQrCode.java b/src/main/java/com/github/niefy/modules/wx/entity/WxQrCode.java new file mode 100644 index 0000000..04f140c --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/WxQrCode.java @@ -0,0 +1,64 @@ +package com.github.niefy.modules.wx.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.io.Serializable; +import java.util.Date; + +import com.github.niefy.modules.wx.form.WxQrCodeForm; +import lombok.Data; + +/** + * 公众号带参二维码 + * + * @author niefy + * @email niefy@qq.com + * @date 2020-01-02 11:11:55 + */ +@Data +@TableName("wx_qr_code") +public class WxQrCode implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + private String appid; + /** + * 二维码类型 + */ + private Boolean isTemp; + /** + * 场景值ID + */ + private String sceneStr; + /** + * 二维码ticket + */ + private String ticket; + /** + * 二维码图片解析后的地址 + */ + private String url; + /** + * 该二维码失效时间 + */ + private Date expireTime; + /** + * 该二维码创建时间 + */ + private Date createTime; + + public WxQrCode() { + } + + public WxQrCode(WxQrCodeForm form,String appid) { + this.appid = appid; + this.isTemp = form.getIsTemp(); + this.sceneStr = form.getSceneStr(); + this.createTime = new Date(); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/entity/WxUser.java b/src/main/java/com/github/niefy/modules/wx/entity/WxUser.java new file mode 100644 index 0000000..33e426a --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/entity/WxUser.java @@ -0,0 +1,85 @@ +package com.github.niefy.modules.wx.entity; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.github.niefy.common.utils.Json; +import lombok.Data; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.Date; + +/** + * 微信粉丝 + * @author Nifury + * @date 2017-9-27 + */ +@Data +@TableName("wx_user") +public class WxUser implements Serializable { + + private static final long serialVersionUID = 1L; + @TableId(type = IdType.INPUT) + private String openid; + private String appid; + private String phone; + private String nickname; + private int sex; + private String city; + private String province; + private String headimgurl; + @JSONField(name = "subscribe_time") + private Date subscribeTime; + private boolean subscribe; + private String unionid; + private String remark; + private JSONArray tagidList; + private String subscribeScene; + private String qrSceneStr; + + public WxUser() { + } + + public WxUser(String openid) { + this.openid = openid; + } + + public WxUser(WxMpUser wxMpUser,String appid) { + this.openid = wxMpUser.getOpenId(); + this.appid = appid; + this.subscribe=wxMpUser.getSubscribe(); + if(wxMpUser.getSubscribe()){ + this.nickname = wxMpUser.getNickname(); + this.headimgurl = wxMpUser.getHeadImgUrl(); + this.subscribeTime = new Date(wxMpUser.getSubscribeTime()*1000); + this.unionid=wxMpUser.getUnionId(); + this.remark=wxMpUser.getRemark(); + this.tagidList=JSONArray.parseArray(JSONObject.toJSONString(wxMpUser.getTagIds())); + this.subscribeScene=wxMpUser.getSubscribeScene(); + String qrScene = wxMpUser.getQrScene(); + this.qrSceneStr= !StringUtils.hasText(qrScene) ? wxMpUser.getQrSceneStr() : qrScene; + } + } + + public WxUser(WxOAuth2UserInfo wxMpUser, String appid) { + this.openid = wxMpUser.getOpenid(); + this.appid = appid; + this.subscribe=wxMpUser.getNickname()!=null; + if(this.subscribe){ + this.nickname = wxMpUser.getNickname(); + this.headimgurl = wxMpUser.getHeadImgUrl(); + this.unionid=wxMpUser.getUnionId(); + } + } + + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/enums/ArticleTypeEnum.java b/src/main/java/com/github/niefy/modules/wx/enums/ArticleTypeEnum.java new file mode 100644 index 0000000..941e2f9 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/enums/ArticleTypeEnum.java @@ -0,0 +1,35 @@ +package com.github.niefy.modules.wx.enums; + +/** + * 定义文章类型 + */ +public enum ArticleTypeEnum { + /** + * 普通文章 + */ + COMMON(1), + /** + * 帮助中心文章 + */ + QUESTION(5); + /** + * 数据库属性值 + */ + private int value; + + ArticleTypeEnum(int type) { + this.value = type; + } + + public int getValue() { + return this.value; + } + + public static ArticleTypeEnum of(String name) { + try { + return ArticleTypeEnum.valueOf(name); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/AccountBindForm.java b/src/main/java/com/github/niefy/modules/wx/form/AccountBindForm.java new file mode 100644 index 0000000..18e0b6d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/AccountBindForm.java @@ -0,0 +1,15 @@ +package com.github.niefy.modules.wx.form; + +import com.github.niefy.common.utils.Json; +import lombok.Data; + +@Data +public class AccountBindForm { + String phoneNum; + String idCodeSuffix; + + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/MaterialFileDeleteForm.java b/src/main/java/com/github/niefy/modules/wx/form/MaterialFileDeleteForm.java new file mode 100644 index 0000000..1570652 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/MaterialFileDeleteForm.java @@ -0,0 +1,14 @@ +package com.github.niefy.modules.wx.form; + +import com.github.niefy.common.utils.Json; +import lombok.Data; + +@Data +public class MaterialFileDeleteForm { + String mediaId; + + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/TemplateMsgBatchForm.java b/src/main/java/com/github/niefy/modules/wx/form/TemplateMsgBatchForm.java new file mode 100644 index 0000000..7cb71ca --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/TemplateMsgBatchForm.java @@ -0,0 +1,33 @@ +package com.github.niefy.modules.wx.form; + +import com.github.niefy.common.utils.Json; +import lombok.Data; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +/** + * 批量发送模板消息表单 + * 通过用户筛选条件(一般使用标签筛选),将消息发送给数据库中所有符合筛选条件的用户 + * 若所有筛选条件都为空,则表示发送给所有用户 + * + */ +@Data +public class TemplateMsgBatchForm { + @NotNull(message = "需用户筛选条件参数") + Map wxUserFilterParams; + @NotEmpty(message = "模板ID不得为空") + private String templateId; + private String url; + private WxMpTemplateMessage.MiniProgram miniprogram; + @NotEmpty(message = "消息模板数据不得为空") + private List data; + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/TemplateMsgForm.java b/src/main/java/com/github/niefy/modules/wx/form/TemplateMsgForm.java new file mode 100644 index 0000000..1d7b9ec --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/TemplateMsgForm.java @@ -0,0 +1,24 @@ +package com.github.niefy.modules.wx.form; + +import com.github.niefy.common.exception.RRException; +import com.github.niefy.common.utils.Json; +import lombok.Data; + +@Data +public class TemplateMsgForm { + private String openid; + private String msg; + private String template; + + @Override + public String toString() { + return Json.toJsonString(this); + } + + public boolean isValid() { + if (openid == null || openid.isEmpty() || msg == null || msg.isEmpty() || template == null || template.isEmpty()) { + throw new RRException("缺少必要参数"); + } + return true; + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/WxH5OuthrizeForm.java b/src/main/java/com/github/niefy/modules/wx/form/WxH5OuthrizeForm.java new file mode 100644 index 0000000..73d3bcc --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/WxH5OuthrizeForm.java @@ -0,0 +1,16 @@ +package com.github.niefy.modules.wx.form; + +import com.github.niefy.common.utils.Json; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +@Data +public class WxH5OuthrizeForm { + @NotEmpty(message = "code不得为空") + private String code; + + @Override + public String toString() { + return Json.toJsonString(this); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/WxMsgReplyForm.java b/src/main/java/com/github/niefy/modules/wx/form/WxMsgReplyForm.java new file mode 100644 index 0000000..6461b72 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/WxMsgReplyForm.java @@ -0,0 +1,15 @@ +package com.github.niefy.modules.wx.form; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +@Data +public class WxMsgReplyForm { + @NotEmpty(message = "用户信息不得为空") + private String openid; + @NotEmpty(message = "回复类型不得为空") + private String replyType; + @NotEmpty(message = "回复内容不得为空") + private String replyContent; +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/WxQrCodeForm.java b/src/main/java/com/github/niefy/modules/wx/form/WxQrCodeForm.java new file mode 100644 index 0000000..ca418d0 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/WxQrCodeForm.java @@ -0,0 +1,17 @@ +package com.github.niefy.modules.wx.form; + +import lombok.Data; + +import javax.validation.constraints.Max; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; + +@Data +public class WxQrCodeForm { + @NotEmpty(message = "场景值ID不得为空") + @Size(min = 1, max = 64, message = "场景值长度限制为1到64") + private String sceneStr; + @Max(value = 2592000, message = "过期时间不得超过30天") + private Integer expireSeconds; + private Boolean isTemp = true; +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/WxUserBatchTaggingForm.java b/src/main/java/com/github/niefy/modules/wx/form/WxUserBatchTaggingForm.java new file mode 100644 index 0000000..3630c43 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/WxUserBatchTaggingForm.java @@ -0,0 +1,14 @@ +package com.github.niefy.modules.wx.form; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; +@Data +public class WxUserBatchTaggingForm { + @NotNull(message = "标签ID不得为空") + private Long tagid; + @NotNull(message = "openid列表不得为空") + @Length(min = 1,max = 50,message = "每次处理数量1-50个") + private String[] openidList; +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/WxUserTagForm.java b/src/main/java/com/github/niefy/modules/wx/form/WxUserTagForm.java new file mode 100644 index 0000000..44ae6cb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/WxUserTagForm.java @@ -0,0 +1,14 @@ +package com.github.niefy.modules.wx.form; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; + +@Data +public class WxUserTagForm { + private Long id; + @NotEmpty(message = "标签名称不得为空") + @Size(min = 1,max = 30,message = "标签名称长度必须为1-30字符") + private String name; +} diff --git a/src/main/java/com/github/niefy/modules/wx/form/WxUserTaggingForm.java b/src/main/java/com/github/niefy/modules/wx/form/WxUserTaggingForm.java new file mode 100644 index 0000000..73366cf --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/form/WxUserTaggingForm.java @@ -0,0 +1,11 @@ +package com.github.niefy.modules.wx.form; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Data +public class WxUserTaggingForm { + @NotNull(message = "标签ID不得为空") + private Long tagid; +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/AbstractHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/AbstractHandler.java new file mode 100644 index 0000000..c179bc0 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/AbstractHandler.java @@ -0,0 +1,14 @@ +package com.github.niefy.modules.wx.handler; + +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Binary Wang + */ +public abstract class AbstractHandler implements WxMpMessageHandler { + + protected Logger logger = LoggerFactory.getLogger(getClass()); + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/KfSessionHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/KfSessionHandler.java new file mode 100644 index 0000000..57dded4 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/KfSessionHandler.java @@ -0,0 +1,26 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +public class KfSessionHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/LocationHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/LocationHandler.java new file mode 100644 index 0000000..d04f423 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/LocationHandler.java @@ -0,0 +1,40 @@ +package com.github.niefy.modules.wx.handler; + + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +/** + * @author Binary Wang + */ +@Component +public class LocationHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) { + //TODO 接收处理用户发送的地理位置消息 + + } + + //上报地理位置事件 + this.logger.info("\n上报地理位置 。。。 "); + this.logger.info("\n纬度 : " + wxMessage.getLatitude()); + this.logger.info("\n经度 : " + wxMessage.getLongitude()); + this.logger.info("\n精度 : " + wxMessage.getPrecision()); + + //TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 + + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/LogHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/LogHandler.java new file mode 100644 index 0000000..39a2f3f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/LogHandler.java @@ -0,0 +1,39 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import com.github.niefy.common.utils.Json; + +import com.github.niefy.modules.wx.entity.WxMsg; +import com.github.niefy.modules.wx.service.WxMsgService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +public class LogHandler extends AbstractHandler { + @Autowired + WxMsgService wxMsgService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + try { + this.logger.debug("\n接收到请求消息,内容:{}", Json.toJsonString(wxMessage)); + wxMsgService.addWxMsg(new WxMsg(wxMessage)); + } catch (Exception e) { + this.logger.error("记录消息异常",e); + } + + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/MenuHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/MenuHandler.java new file mode 100644 index 0000000..a31cead --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/MenuHandler.java @@ -0,0 +1,38 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import com.github.niefy.modules.wx.service.MsgReplyService; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +public class MenuHandler extends AbstractHandler { + @Autowired + MsgReplyService msgReplyService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) { + if (WxConsts.EventType.VIEW.equals(wxMessage.getEvent())) { + return null; + } + String appid = WxMpConfigStorageHolder.get(); + logger.info("菜单事件:" + wxMessage.getEventKey()); + msgReplyService.tryAutoReply(appid, true, wxMessage.getFromUser(), wxMessage.getEventKey()); + return null; + } + + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/MsgHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/MsgHandler.java new file mode 100644 index 0000000..e0bc06a --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/MsgHandler.java @@ -0,0 +1,53 @@ +package com.github.niefy.modules.wx.handler; + + +import java.util.Map; + +import com.github.niefy.modules.wx.entity.WxMsg; +import com.github.niefy.modules.wx.service.MsgReplyService; +import com.github.niefy.modules.wx.service.WxMsgService; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +public class MsgHandler extends AbstractHandler { + Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + MsgReplyService msgReplyService; + @Autowired + WxMsgService wxMsgService; + private static final String TRANSFER_CUSTOMER_SERVICE_KEY = "人工"; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + + String textContent = wxMessage.getContent(); + String fromUser = wxMessage.getFromUser(); + String appid = WxMpConfigStorageHolder.get(); + boolean autoReplyed = msgReplyService.tryAutoReply(appid,false, fromUser, textContent); + //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 + if (TRANSFER_CUSTOMER_SERVICE_KEY.equals(textContent) || !autoReplyed) { + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.TRANSFER_CUSTOMER_SERVICE,fromUser,null)); + return WxMpXmlOutMessage + .TRANSFER_CUSTOMER_SERVICE().fromUser(wxMessage.getToUser()) + .toUser(fromUser).build(); + } + return null; + + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/NullHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/NullHandler.java new file mode 100644 index 0000000..91a8ffd --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/NullHandler.java @@ -0,0 +1,25 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +public class NullHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/ScanHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/ScanHandler.java new file mode 100644 index 0000000..e4cfa57 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/ScanHandler.java @@ -0,0 +1,32 @@ +package com.github.niefy.modules.wx.handler; + +import com.github.niefy.modules.wx.service.MsgReplyService; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang + */ +@Component +public class ScanHandler extends AbstractHandler { + @Autowired + MsgReplyService msgReplyService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) { + //扫码事件处理 + this.logger.info("用户扫描带参二维码 OPENID: " + wxMpXmlMessage.getFromUser()); + String appid = WxMpConfigStorageHolder.get(); + msgReplyService.tryAutoReply(appid, true, wxMpXmlMessage.getFromUser(), wxMpXmlMessage.getEventKey()); + + return null; + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/StoreCheckNotifyHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/StoreCheckNotifyHandler.java new file mode 100644 index 0000000..deecee4 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/StoreCheckNotifyHandler.java @@ -0,0 +1,28 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * 门店审核事件处理 + * + * @author 王彬 (Binary Wang) + */ +@Component +public class StoreCheckNotifyHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + // TODO 处理门店审核事件 + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/SubscribeHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/SubscribeHandler.java new file mode 100644 index 0000000..3a06efd --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/SubscribeHandler.java @@ -0,0 +1,59 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.github.niefy.modules.wx.service.WxUserService; +import com.github.niefy.modules.wx.service.MsgReplyService; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.util.StringUtils; + +/** + * @author Binary Wang + */ +@Component +public class SubscribeHandler extends AbstractHandler { + @Autowired + MsgReplyService msgReplyService; + @Autowired + WxUserService userService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + + this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser() + ",事件:" + wxMessage.getEventKey()); + String appid = WxMpConfigStorageHolder.get(); + this.logger.info("appid:{}",appid); + userService.refreshUserInfo(wxMessage.getFromUser(),appid); + + msgReplyService.tryAutoReply(appid, true, wxMessage.getFromUser(), wxMessage.getEvent()); + + if (StringUtils.hasText(wxMessage.getEventKey())) {// 处理特殊事件,如用户扫描带参二维码关注 + msgReplyService.tryAutoReply(appid, true, wxMessage.getFromUser(), wxMessage.getEventKey()); + } + return null; + } + + /** + * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 + */ + protected WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage) { + this.logger.info("特殊请求-新关注用户 OPENID: " + wxMessage.getFromUser()); + //对关注事件和扫码事件分别处理 + String appid = WxMpConfigStorageHolder.get(); + userService.refreshUserInfo(wxMessage.getFromUser(),appid); + msgReplyService.tryAutoReply(appid, true, wxMessage.getFromUser(), wxMessage.getEvent()); + if (StringUtils.hasText(wxMessage.getEventKey())) { + msgReplyService.tryAutoReply(appid, true, wxMessage.getFromUser(), wxMessage.getEventKey()); + } + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/handler/UnsubscribeHandler.java b/src/main/java/com/github/niefy/modules/wx/handler/UnsubscribeHandler.java new file mode 100644 index 0000000..021e812 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/handler/UnsubscribeHandler.java @@ -0,0 +1,32 @@ +package com.github.niefy.modules.wx.handler; + +import java.util.Map; + +import com.github.niefy.modules.wx.service.WxUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +@Component +public class UnsubscribeHandler extends AbstractHandler { + @Autowired + WxUserService userService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + String openid = wxMessage.getFromUser(); + this.logger.info("取消关注用户 OPENID: " + openid); + userService.unsubscribe(openid); + return null; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/ArticleManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/ArticleManageController.java new file mode 100644 index 0000000..ada5ceb --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/ArticleManageController.java @@ -0,0 +1,81 @@ +package com.github.niefy.modules.wx.manage; + +import java.util.Arrays; +import java.util.Map; + +import com.github.niefy.modules.wx.entity.Article; +import com.github.niefy.modules.wx.service.ArticleService; +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 com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; + + +/** + * 文章 + * + * @author niefy + * @email niefy@qq.com + * @date 2019-11-12 18:30:16 + */ +@RestController +@RequestMapping("/manage/article") +@Api(tags = {"文章管理-管理后台"}) +public class ArticleManageController { + @Autowired + private ArticleService articleService; + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:article:list") + @ApiOperation(value = "列表") + public R list(@RequestParam Map params) { + PageUtils page = articleService.queryPage(params); + + return R.ok().put("page", page); + } + + + /** + * 信息 + */ + @GetMapping("/info/{id}") + @RequiresPermissions("wx:article:info") + @ApiOperation(value = "详情") + public R info(@PathVariable("id") Integer id) { + Article article = articleService.getById(id); + + return R.ok().put("article", article); + } + + /** + * 保存 + */ + @PostMapping("/save") + @RequiresPermissions("wx:article:save") + @ApiOperation(value = "保存") + public R save(@RequestBody Article article) { + articleService.saveArticle(article); + + return R.ok(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:article:delete") + @ApiOperation(value = "删除") + public R delete(@RequestBody Integer[] ids) { + articleService.removeByIds(Arrays.asList(ids)); + + return R.ok(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/MsgReplyRuleManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/MsgReplyRuleManageController.java new file mode 100644 index 0000000..773e3b2 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/MsgReplyRuleManageController.java @@ -0,0 +1,97 @@ +package com.github.niefy.modules.wx.manage; + +import java.util.Arrays; +import java.util.Map; + +import com.github.niefy.modules.wx.service.MsgReplyRuleService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.mp.api.WxMpService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import com.github.niefy.modules.wx.entity.MsgReplyRule; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; + + +/** + * 自动回复规则 + * + * @author niefy + * @email niefy@qq.com + * @date 2019-11-12 18:30:15 + */ +@RestController +@RequestMapping("/manage/msgReplyRule") +@Api(tags = {"自动回复规则-管理后台"}) +public class MsgReplyRuleManageController { + @Autowired + private MsgReplyRuleService msgReplyRuleService; + @Autowired + private WxMpService wxMpService; + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:msgreplyrule:list") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid,@RequestParam Map params) { + params.put("appid",appid); + PageUtils page = msgReplyRuleService.queryPage(params); + + return R.ok().put("page", page); + } + + + /** + * 信息 + */ + @GetMapping("/info/{ruleId}") + @RequiresPermissions("wx:msgreplyrule:info") + @ApiOperation(value = "详情") + public R info(@PathVariable("ruleId") Integer ruleId) { + MsgReplyRule msgReplyRule = msgReplyRuleService.getById(ruleId); + + return R.ok().put("msgReplyRule", msgReplyRule); + } + + /** + * 保存 + */ + @PostMapping("/save") + @RequiresPermissions("wx:msgreplyrule:save") + @ApiOperation(value = "保存") + public R save(@RequestBody MsgReplyRule msgReplyRule) { + msgReplyRuleService.save(msgReplyRule); + + return R.ok(); + } + + /** + * 修改 + */ + @PostMapping("/update") + @RequiresPermissions("wx:msgreplyrule:update") + @ApiOperation(value = "修改") + public R update(@RequestBody MsgReplyRule msgReplyRule) { + msgReplyRuleService.updateById(msgReplyRule); + + return R.ok(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:msgreplyrule:delete") + @ApiOperation(value = "删除") + public R delete(@RequestBody Integer[] ruleIds) { + msgReplyRuleService.removeByIds(Arrays.asList(ruleIds)); + + return R.ok(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/MsgTemplateManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/MsgTemplateManageController.java new file mode 100644 index 0000000..6530448 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/MsgTemplateManageController.java @@ -0,0 +1,141 @@ +package com.github.niefy.modules.wx.manage; + +import java.util.Arrays; +import java.util.Map; + +import com.github.niefy.modules.wx.entity.MsgTemplate; +import com.github.niefy.modules.wx.form.TemplateMsgBatchForm; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.mp.api.WxMpService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import me.chanjar.weixin.common.error.WxErrorException; + +import com.github.niefy.modules.wx.service.MsgTemplateService; +import com.github.niefy.modules.wx.service.TemplateMsgService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; + + +/** + * 消息模板 + * + * @author niefy + * @email niefy@qq.com + * @date 2019-11-12 18:30:15 + */ +@RestController +@RequestMapping("/manage/msgTemplate") +@Api(tags = {"消息模板-管理后台","模板消息的模板"}) +public class MsgTemplateManageController { + @Autowired + private MsgTemplateService msgTemplateService; + @Autowired + private TemplateMsgService templateMsgService; + @Autowired + private WxMpService wxMpService; + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:msgtemplate:list") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid,@RequestParam Map params) { + params.put("appid",appid); + PageUtils page = msgTemplateService.queryPage(params); + + return R.ok().put("page", page); + } + + + /** + * 信息 + */ + @GetMapping("/info/{id}") + @RequiresPermissions("wx:msgtemplate:info") + @ApiOperation(value = "详情-通过ID") + public R info(@PathVariable("id") Long id) { + MsgTemplate msgTemplate = msgTemplateService.getById(id); + + return R.ok().put("msgTemplate", msgTemplate); + } + /** + * 信息 + */ + @GetMapping("/getByName") + @RequiresPermissions("wx:msgtemplate:info") + @ApiOperation(value = "详情-通过名称") + public R getByName( String name){ + MsgTemplate msgTemplate = msgTemplateService.selectByName(name); + + return R.ok().put("msgTemplate", msgTemplate); + } + + /** + * 保存 + */ + @PostMapping("/save") + @RequiresPermissions("wx:msgtemplate:save") + @ApiOperation(value = "保存") + public R save(@RequestBody MsgTemplate msgTemplate) { + msgTemplateService.save(msgTemplate); + + return R.ok(); + } + + /** + * 修改 + */ + @PostMapping("/update") + @RequiresPermissions("wx:msgtemplate:update") + @ApiOperation(value = "修改") + public R update(@RequestBody MsgTemplate msgTemplate) { + msgTemplateService.updateById(msgTemplate); + + return R.ok(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:msgtemplate:delete") + @ApiOperation(value = "删除") + public R delete(@RequestBody String[] ids) { + msgTemplateService.removeByIds(Arrays.asList(ids)); + + return R.ok(); + } + + /** + * 同步公众号模板 + */ + @PostMapping("/syncWxTemplate") + @RequiresPermissions("wx:msgtemplate:save") + @ApiOperation(value = "同步公众号模板") + public R syncWxTemplate(@CookieValue String appid) throws WxErrorException { + this.wxMpService.switchoverTo(appid); + msgTemplateService.syncWxTemplate(appid); + return R.ok(); + } + + /** + * 批量向用户发送模板消息 + * 通过用户筛选条件(一般使用标签筛选),将消息发送给数据库中所有符合筛选条件的用户 + */ + @PostMapping("/sendMsgBatch") + @RequiresPermissions("wx:msgtemplate:save") + @ApiOperation(value = "批量向用户发送模板消息",notes = "将消息发送给数据库中所有符合筛选条件的用户") + public R sendMsgBatch(@CookieValue String appid,@RequestBody TemplateMsgBatchForm form) { + this.wxMpService.switchoverTo(appid); + templateMsgService.sendMsgBatch(form, appid); + return R.ok(); + } + + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/SmsController.java b/src/main/java/com/github/niefy/modules/wx/manage/SmsController.java new file mode 100644 index 0000000..dc24a4b --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/SmsController.java @@ -0,0 +1,78 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.StringUtils; +import com.github.niefy.common.utils.SmsUtils; +import com.github.niefy.modules.wx.entity.SMS; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.service.WxUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import java.util.Date; +import java.util.Random; +import com.github.niefy.common.utils.R; + +//参考https://www.jianshu.com/p/a0caa7ff2336 +@RestController +@RequestMapping("/manage/sms") +@Api(tags = {"短信-绑定手机号"}) +public class SmsController { + @Autowired + private WxUserService userService; + @ApiOperation(value = "获取短信验证码", notes = "获取短信验证码") +// @ApiImplicitParam(name = "mobile", value = "手机号码", paramType = "query", required = true, dataType = "String") + @PostMapping(value = "/code") + public R getMobileCode(@RequestBody SMS sms) { + String mobile = sms.getMobile(); + if (StringUtils.isEmpty(mobile)) { + return R.error("手机号不能为空"); + } + Random random = new Random(); + StringBuffer smscode = new StringBuffer(); + for (int i = 0; i < 6; i++) { + smscode.append(random.nextInt(10)); + } + //发送短信 + String code = SmsUtils.sendNoteMessgae(mobile, String.valueOf(smscode)); + if("OK".equals(code)){ + //返回验证码和发送时间 + return R.ok().put("smsCode",String.valueOf(smscode)+new Date().getTime()); + }else { + return R.error("短信发送失败"); + } + } + + @ApiOperation(value = "验证短信验证码", notes = "验证短信验证码") +// @ApiImplicitParams({@ApiImplicitParam(name = "mobile", value = "手机号码", paramType = "query", required = true, dataType = "String"), @ApiImplicitParam(name = "code", value = "验证码", paramType = "query", required = true, dataType = "String")}) + @PostMapping(value = "/verifyCode") + public R smsLogin(@RequestBody SMS sms) { + String openId = sms.getOpenId(); + String mobile = sms.getMobile(); + String smsCode = sms.getCode(); + String code_codeTime = sms.getCodeTime(); + String code = code_codeTime.substring(0,6); + long codeTime = Long.parseLong(code_codeTime.substring(6)); + if (new Date().getTime()>(codeTime+(5*60*1000))) { + return R.error("验证码已过期"); + } + if (!code.equals(smsCode)) { + return R.error("验证码错误"); + } + if (StringUtils.isEmpty(mobile)) { + return R.error("手机号不能为空"); + } + //根据手机号码查找是否存在 + WxUser wxUser = userService.findByPhone(mobile); + if(wxUser != null){ + return R.error("手机号已绑定"); + }else { + WxUser wxUsernew = new WxUser(); + wxUsernew.setOpenid(openId); + wxUsernew.setPhone(mobile); + userService.saveOrUpdate(wxUsernew); + return R.ok(); + } + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgLogManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgLogManageController.java new file mode 100644 index 0000000..4ba81b0 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgLogManageController.java @@ -0,0 +1,97 @@ +package com.github.niefy.modules.wx.manage; + +import java.util.Arrays; +import java.util.Map; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.mp.api.WxMpService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import com.github.niefy.modules.wx.entity.TemplateMsgLog; +import com.github.niefy.modules.wx.service.TemplateMsgLogService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; + + +/** + * 模版消息发送记录 + * + * @author niefy + * @email niefy@qq.com + * @date 2019-11-12 18:30:15 + */ +@RestController +@RequestMapping("/manage/templateMsgLog") +@Api(tags = {"模板消息发送记录-管理后台"}) +public class TemplateMsgLogManageController { + @Autowired + private TemplateMsgLogService templateMsgLogService; + @Autowired + private WxMpService wxMpService; + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:templatemsglog:list") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid,@RequestParam Map params) { + params.put("appid",appid); + PageUtils page = templateMsgLogService.queryPage(params); + + return R.ok().put("page", page); + } + + + /** + * 信息 + */ + @GetMapping("/info/{logId}") + @RequiresPermissions("wx:templatemsglog:info") + @ApiOperation(value = "详情") + public R info(@CookieValue String appid,@PathVariable("logId") Integer logId) { + TemplateMsgLog templateMsgLog = templateMsgLogService.getById(logId); + + return R.ok().put("templateMsgLog", templateMsgLog); + } + + /** + * 保存 + */ + @PostMapping("/save") + @RequiresPermissions("wx:templatemsglog:save") + @ApiOperation(value = "保存") + public R save(@CookieValue String appid,@RequestBody TemplateMsgLog templateMsgLog) { + templateMsgLogService.save(templateMsgLog); + + return R.ok(); + } + + /** + * 修改 + */ + @PostMapping("/update") + @RequiresPermissions("wx:templatemsglog:update") + @ApiOperation(value = "修改") + public R update(@CookieValue String appid,@RequestBody TemplateMsgLog templateMsgLog) { + templateMsgLogService.updateById(templateMsgLog); + + return R.ok(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:templatemsglog:delete") + @ApiOperation(value = "删除") + public R delete(@CookieValue String appid,@RequestBody Integer[] logIds) { + templateMsgLogService.removeByIds(Arrays.asList(logIds)); + + return R.ok(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgManageController.java new file mode 100644 index 0000000..dd48ad8 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/TemplateMsgManageController.java @@ -0,0 +1,85 @@ +package com.github.niefy.modules.wx.manage; + +import com.alibaba.fastjson.JSONObject; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; +import com.github.niefy.common.utils.StringUtils; +import com.github.niefy.config.TaskExcutor; +import com.github.niefy.modules.wx.entity.TemplateMsgLog; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.service.TemplateMsgLogService; +import com.github.niefy.modules.wx.service.TemplateMsgService; +import com.github.niefy.modules.wx.service.WxUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; +import me.chanjar.weixin.mp.enums.WxMpApiUrl; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + + +/** + * 模版消息发送记录 + * + * @author niefy + * @email niefy@qq.com + * @date 2019-11-12 18:30:15 + */ +@RestController +@RequestMapping("/manage/templateMsg") +@Api(tags = {"模板消息发送-管理后台"}) +public class TemplateMsgManageController { + @Autowired + TemplateMsgService templateMsgService; + @Autowired + private WxUserService userService; + + /** + * 发送模板消息给用户 + * 添加消息模板指引:https://kf.qq.com/faq/170209E3InyI170209nIF7RJ.html + * 示例消息模板为:{{first.DATA}} ↵商品名称:{{keyword1.DATA}} ↵购买时间:{{keyword2.DATA}} ↵{{remark.DATA}} + * 参数:json字符串,需进行解析 + * eg:{"phones":"18811408841,18811408841","datas":"dept-威海市乳山市|dept-威海市乳山市","appid":"公众号appid","templateId":"模板IDtemplateId","url":"url"} + * 使用固定线程的线程池 + */ + @GetMapping("/sendTemplateMsg/{templateMsg}") + @ApiOperation(value = "外部接口-发送模板信息") + public void sendTemplateMsg(@PathVariable("templateMsg") String templateMsg) { + TaskExcutor.submit(() -> { + JSONObject templateMsgObject = JSONObject.parseObject(templateMsg); + String phones = templateMsgObject.getString("phones");//所有手机号 + String phoness[] = phones.split(","); + for (String phone:phoness) { + WxUser wxUser = userService.findByPhone(phone); + if(StringUtils.isNotNull(wxUser)){ + String datas = templateMsgObject.getString("datas");//所有模板内容 + String appid = templateMsgObject.getString("appid"); + String templateId = templateMsgObject.getString("templateId"); + String url = templateMsgObject.getString("url"); + String datass[] = datas.split("\\|"); + List data = new ArrayList<>(); + for (String data_z:datass) { + String data_zz[] = data_z.split("-"); + data.add(new WxMpTemplateData(data_zz[0], data_zz[1])); + } + WxMpTemplateMessage wxMpTemplateMessage = WxMpTemplateMessage.builder() + .templateId(templateId) + .url(url) + .toUser(wxUser.getOpenid()) + .data(data) + .build(); + templateMsgService.sendTemplateMsg(wxMpTemplateMessage,appid); + } + } + }); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxAccountManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxAccountManageController.java new file mode 100644 index 0000000..0a823da --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxAccountManageController.java @@ -0,0 +1,79 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.WxAccount; +import com.github.niefy.modules.wx.service.WxAccountService; +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.Arrays; +import java.util.List; + + + +/** + * 公众号账号 + * + * @author niefy + * @date 2020-06-17 13:56:51 + */ +@RestController +@RequestMapping("/manage/wxAccount") +@Api(tags = {"公众号账号-管理后台"}) +public class WxAccountManageController { + @Autowired + private WxAccountService wxAccountService; + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:wxaccount:list") + @ApiOperation(value = "列表") + public R list(){ + List list = wxAccountService.list(); + + return R.ok().put("list", list); + } + + + /** + * 信息 + */ + @GetMapping("/info/{appid}") + @RequiresPermissions("wx:wxaccount:info") + @ApiOperation(value = "详情") + public R info(@PathVariable("id") String appid){ + WxAccount wxAccount = wxAccountService.getById(appid); + + return R.ok().put("wxAccount", wxAccount); + } + + /** + * 保存 + */ + @PostMapping("/save") + @RequiresPermissions("wx:wxaccount:save") + @ApiOperation(value = "保存") + public R save(@RequestBody WxAccount wxAccount){ + wxAccountService.save(wxAccount); + + return R.ok(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:wxaccount:delete") + @ApiOperation(value = "删除") + public R delete(@RequestBody String[] appids){ + wxAccountService.removeByIds(Arrays.asList(appids)); + + return R.ok(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxAssetsManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxAssetsManageController.java new file mode 100644 index 0000000..0bf0c12 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxAssetsManageController.java @@ -0,0 +1,165 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.form.MaterialFileDeleteForm; +import com.github.niefy.modules.wx.service.WxAssetsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.material.*; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +/** + * 微信公众号素材管理 + * 参考官方文档:https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html + * 参考WxJava开发文档:https://github.com/Wechat-Group/WxJava/wiki/MP_永久素材管理 + */ +@RestController +@RequestMapping("/manage/wxAssets") +@Api(tags = {"公众号素材-管理后台"}) +public class WxAssetsManageController { + Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + WxAssetsService wxAssetsService; + + /** + * 获取素材总数 + * + * @return + * @throws WxErrorException + */ + @GetMapping("/materialCount") + @ApiOperation(value = "文件素材总数") + public R materialCount(@CookieValue String appid) throws WxErrorException { + WxMpMaterialCountResult res = wxAssetsService.materialCount(appid); + return R.ok().put(res); + } + + /** + * 获取素材总数 + * + * @return + * @throws WxErrorException + */ + @GetMapping("/materialNewsInfo") + @ApiOperation(value = "图文素材总数") + public R materialNewsInfo(@CookieValue String appid,String mediaId) throws WxErrorException { + WxMpMaterialNews res = wxAssetsService.materialNewsInfo(appid,mediaId); + return R.ok().put(res); + } + + + /** + * 根据类别分页获取非图文素材列表 + * + * @param type + * @param page + * @return + * @throws WxErrorException + */ + @GetMapping("/materialFileBatchGet") + @RequiresPermissions("wx:wxassets:list") + @ApiOperation(value = "根据类别分页获取非图文素材列表") + public R materialFileBatchGet(@CookieValue String appid,@RequestParam(defaultValue = "image") String type, + @RequestParam(defaultValue = "1") int page) throws WxErrorException { + WxMpMaterialFileBatchGetResult res = wxAssetsService.materialFileBatchGet(appid,type,page); + return R.ok().put(res); + } + + /** + * 分页获取图文素材列表 + * + * @param page + * @return + * @throws WxErrorException + */ + @GetMapping("/materialNewsBatchGet") + @RequiresPermissions("wx:wxassets:list") + @ApiOperation(value = "分页获取图文素材列表") + public R materialNewsBatchGet(@CookieValue String appid,@RequestParam(defaultValue = "1") int page) throws WxErrorException { + WxMpMaterialNewsBatchGetResult res = wxAssetsService.materialNewsBatchGet(appid,page); + return R.ok().put(res); + } + + /** + * 添加图文永久素材 + * + * @param articles + * @return + * @throws WxErrorException + */ + @PostMapping("/materialNewsUpload") + @RequiresPermissions("wx:wxassets:save") + @ApiOperation(value = "添加图文永久素材") + public R materialNewsUpload(@CookieValue String appid,@RequestBody List articles) throws WxErrorException { + if(articles.isEmpty()) { + return R.error("图文列表不得为空"); + } + WxMpMaterialUploadResult res = wxAssetsService.materialNewsUpload(appid,articles); + return R.ok().put(res); + } + + /** + * 修改图文素材文章 + * + * @param form + * @return + * @throws WxErrorException + */ + @PostMapping("/materialArticleUpdate") + @RequiresPermissions("wx:wxassets:save") + @ApiOperation(value = "修改图文素材文章") + public R materialArticleUpdate(@CookieValue String appid,@RequestBody WxMpMaterialArticleUpdate form) throws WxErrorException { + if(form.getArticles()==null) { + return R.error("文章不得为空"); + } + wxAssetsService.materialArticleUpdate(appid,form); + return R.ok(); + } + + /** + * 添加多媒体永久素材 + * + * @param file + * @param fileName + * @param mediaType + * @return + * @throws WxErrorException + * @throws IOException + */ + @PostMapping("/materialFileUpload") + @RequiresPermissions("wx:wxassets:save") + @ApiOperation(value = "添加多媒体永久素材") + public R materialFileUpload(@CookieValue String appid,MultipartFile file, String fileName, String mediaType) throws WxErrorException, IOException { + if (file == null) { + return R.error("文件不得为空"); + } + + WxMpMaterialUploadResult res = wxAssetsService.materialFileUpload(appid,mediaType,fileName,file); + return R.ok().put(res); + } + + /** + * 删除素材 + * + * @param form + * @return + * @throws WxErrorException + */ + @PostMapping("/materialDelete") + @RequiresPermissions("wx:wxassets:delete") + @ApiOperation(value = "删除素材") + public R materialDelete(@CookieValue String appid,@RequestBody MaterialFileDeleteForm form) throws WxErrorException { + boolean res = wxAssetsService.materialDelete(appid,form.getMediaId()); + return R.ok().put(res); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxMenuManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxMenuManageController.java new file mode 100644 index 0000000..5a06f2b --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxMenuManageController.java @@ -0,0 +1,54 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.R; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.bean.menu.WxMenu; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.menu.WxMpMenu; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 微信公众号菜单管理 + * 官方文档:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html + * WxJava开发文档:https://github.com/Wechat-Group/WxJava/wiki/MP_自定义菜单管理 + */ +@RestController +@RequestMapping("/manage/wxMenu") +@RequiredArgsConstructor +@Api(tags = {"公众号菜单-管理后台"}) +public class WxMenuManageController { + Logger logger = LoggerFactory.getLogger(this.getClass()); + private final WxMpService wxService; + @Autowired + private WxMpService wxMpService; + + /** + * 获取公众号菜单 + */ + @GetMapping("/getMenu") + @ApiOperation(value = "获取公众号菜单") + public R getMenu(@CookieValue String appid) throws WxErrorException { + wxMpService.switchoverTo(appid); + WxMpMenu wxMpMenu = wxService.getMenuService().menuGet(); + return R.ok().put(wxMpMenu); + } + + /** + * 创建、更新菜单 + */ + @PostMapping("/updateMenu") + @RequiresPermissions("wx:menu:save") + @ApiOperation(value = "创建、更新菜单") + public R updateMenu(@CookieValue String appid,@RequestBody WxMenu wxMenu) throws WxErrorException { + wxMpService.switchoverTo(appid); + wxService.getMenuService().menuCreate(wxMenu); + return R.ok(); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxMsgManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxMsgManageController.java new file mode 100644 index 0000000..b459bcf --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxMsgManageController.java @@ -0,0 +1,84 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.WxMsg; +import com.github.niefy.modules.wx.form.WxMsgReplyForm; +import com.github.niefy.modules.wx.service.MsgReplyService; +import com.github.niefy.modules.wx.service.WxMsgService; +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.Arrays; +import java.util.Map; + + +/** + * 微信消息 + * + * @author niefy + * @date 2020-05-14 17:28:34 + */ +@RestController +@RequestMapping("/manage/wxMsg") +@Api(tags = {"公众号消息记录-管理后台"}) +public class WxMsgManageController { + @Autowired + private WxMsgService wxMsgService; + @Autowired + private MsgReplyService msgReplyService; + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:wxmsg:list") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid,@RequestParam Map params){ + params.put("appid",appid); + PageUtils page = wxMsgService.queryPage(params); + + return R.ok().put("page", page); + } + + + /** + * 信息 + */ + @GetMapping("/info/{id}") + @RequiresPermissions("wx:wxmsg:info") + @ApiOperation(value = "详情") + public R info(@CookieValue String appid,@PathVariable("id") Long id){ + WxMsg wxMsg = wxMsgService.getById(id); + + return R.ok().put("wxMsg", wxMsg); + } + + /** + * 回复 + */ + @PostMapping("/reply") + @RequiresPermissions("wx:wxmsg:save") + @ApiOperation(value = "回复") + public R reply(@CookieValue String appid,@RequestBody WxMsgReplyForm form){ + + msgReplyService.reply(form.getOpenid(),form.getReplyType(),form.getReplyContent()); + return R.ok(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:wxmsg:delete") + @ApiOperation(value = "删除") + public R delete(@CookieValue String appid,@RequestBody Long[] ids){ + wxMsgService.removeByIds(Arrays.asList(ids)); + + return R.ok(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxQrCodeManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxQrCodeManageController.java new file mode 100644 index 0000000..4875491 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxQrCodeManageController.java @@ -0,0 +1,86 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.modules.wx.form.WxQrCodeForm; +import com.github.niefy.modules.wx.service.WxQrCodeService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.WxQrCode; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.Map; + +/** + * 公众号带参二维码管理 + * https://github.com/Wechat-Group/WxJava/wiki/MP_二维码管理 + */ +@RestController +@RequestMapping("/manage/wxQrCode") +@Api(tags = {"公众号带参二维码-管理后台"}) +public class WxQrCodeManageController { + Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private WxQrCodeService wxQrCodeService; + @Autowired + private WxMpService wxMpService; + + /** + * 创建带参二维码ticket + */ + @PostMapping("/createTicket") + @RequiresPermissions("wx:wxqrcode:save") + @ApiOperation(value = "创建带参二维码ticket",notes = "ticket可以换取二维码图片") + public R createTicket(@CookieValue String appid,@RequestBody WxQrCodeForm form) throws WxErrorException { + wxMpService.switchoverTo(appid); + WxMpQrCodeTicket ticket = wxQrCodeService.createQrCode(appid,form); + return R.ok().put(ticket); + } + + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:wxqrcode:list") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid,@RequestParam Map params) { + params.put("appid",appid); + PageUtils page = wxQrCodeService.queryPage(params); + + return R.ok().put("page", page); + } + + + /** + * 信息 + */ + @GetMapping("/info/{id}") + @RequiresPermissions("wx:wxqrcode:info") + @ApiOperation(value = "详情") + public R info(@CookieValue String appid,@PathVariable("id") Long id) { + WxQrCode wxQrCode = wxQrCodeService.getById(id); + + return R.ok().put("wxQrCode", wxQrCode); + } + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:wxqrcode:delete") + @ApiOperation(value = "删除") + public R delete(@CookieValue String appid,@RequestBody Long[] ids) { + wxQrCodeService.removeByIds(Arrays.asList(ids)); + + return R.ok(); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxUserManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxUserManageController.java new file mode 100644 index 0000000..afb34c2 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxUserManageController.java @@ -0,0 +1,94 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.service.WxUserService; +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.Arrays; +import java.util.List; +import java.util.Map; + + +/** + * 用户表 + * + * @author niefy + * @email niefy@qq.com + * @date 2020-03-07 13:55:23 + */ +@RestController +@RequestMapping("/manage/wxUser") +@Api(tags = {"公众号粉丝-管理后台"}) +public class WxUserManageController { + @Autowired + private WxUserService userService; + /** + * 列表 + */ + @GetMapping("/list") + @RequiresPermissions("wx:wxuser:list") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid,@RequestParam Map params) { + params.put("appid",appid); + PageUtils page = new PageUtils(userService.queryPage(params)); + + return R.ok().put("page", page); + } + + /** + * 列表 + */ + @PostMapping("/listByIds") + @RequiresPermissions("wx:wxuser:list") + @ApiOperation(value = "列表-ID查询") + public R listByIds(@CookieValue String appid,@RequestBody String[] openids){ + List users = userService.listByIds(Arrays.asList(openids)); + return R.ok().put(users); + } + + + /** + * 信息 + */ + @GetMapping("/info/{openid}") + @RequiresPermissions("wx:wxuser:info") + @ApiOperation(value = "详情") + public R info(@CookieValue String appid,@PathVariable("openid") String openid) { + WxUser wxUser = userService.getById(openid); + + return R.ok().put("wxUser", wxUser); + } + + /** + * 同步用户列表 + */ + @PostMapping("/syncWxUsers") + @RequiresPermissions("wx:wxuser:save") + @ApiOperation(value = "同步用户列表到数据库") + public R syncWxUsers(@CookieValue String appid) { + userService.syncWxUsers(appid); + + return R.ok("任务已建立"); + } + + + + /** + * 删除 + */ + @PostMapping("/delete") + @RequiresPermissions("wx:wxuser:delete") + @ApiOperation(value = "删除") + public R delete(@CookieValue String appid,@RequestBody String[] ids) { + userService.removeByIds(Arrays.asList(ids)); + + return R.ok(); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/manage/WxUserTagsManageController.java b/src/main/java/com/github/niefy/modules/wx/manage/WxUserTagsManageController.java new file mode 100644 index 0000000..dafe1f3 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/manage/WxUserTagsManageController.java @@ -0,0 +1,92 @@ +package com.github.niefy.modules.wx.manage; + +import com.github.niefy.common.utils.R; +import com.github.niefy.modules.wx.form.WxUserBatchTaggingForm; +import com.github.niefy.modules.wx.form.WxUserTagForm; +import com.github.niefy.modules.wx.service.WxUserService; +import com.github.niefy.modules.wx.service.WxUserTagsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 公众号用户标签 + */ +@RestController +@RequestMapping("/manage/wxUserTags") +@Api(tags = {"公众号用户标签-管理后台"}) +public class WxUserTagsManageController { + @Autowired + private WxUserService wxUserService; + @Autowired + private WxUserTagsService wxUserTagsService; + + /** + * 查询用户标签 + */ + @GetMapping("/list") + @RequiresPermissions("wx:wxuser:info") + @ApiOperation(value = "列表") + public R list(@CookieValue String appid) throws WxErrorException { + List wxUserTags = wxUserTagsService.getWxTags(appid); + return R.ok().put("list",wxUserTags); + } + + /** + * 修改或新增标签 + */ + @PostMapping("/save") + @RequiresPermissions("wx:wxuser:save") + @ApiOperation(value = "保存") + public R save(@CookieValue String appid,@RequestBody WxUserTagForm form) throws WxErrorException{ + Long tagid = form.getId(); + if(tagid==null || tagid<=0){ + wxUserTagsService.creatTag(appid,form.getName()); + }else { + wxUserTagsService.updateTag(appid,tagid,form.getName()); + } + return R.ok(); + } + + /** + * 删除标签 + */ + @PostMapping("/delete/{tagid}") + @RequiresPermissions("wx:wxuser:save") + @ApiOperation(value = "删除标签") + public R delete(@CookieValue String appid,@PathVariable("tagid") Long tagid) throws WxErrorException{ + if(tagid==null || tagid<=0){ + return R.error("标签ID不得为空"); + } + wxUserTagsService.deleteTag(appid,tagid); + return R.ok(); + } + + /** + * 批量给用户打标签 + */ + @PostMapping("/batchTagging") + @RequiresPermissions("wx:wxuser:save") + @ApiOperation(value = "批量给用户打标签") + public R batchTagging(@CookieValue String appid,@RequestBody WxUserBatchTaggingForm form) throws WxErrorException{ + + wxUserTagsService.batchTagging(appid,form.getTagid(),form.getOpenidList()); + return R.ok(); + } + /** + * 批量移除用户标签 + */ + @PostMapping("/batchUnTagging") + @RequiresPermissions("wx:wxuser:save") + @ApiOperation(value = "批量移除用户标签") + public R batchUnTagging(@CookieValue String appid,@RequestBody WxUserBatchTaggingForm form) throws WxErrorException{ + wxUserTagsService.batchUnTagging(appid,form.getTagid(),form.getOpenidList()); + return R.ok(); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/ArticleService.java b/src/main/java/com/github/niefy/modules/wx/service/ArticleService.java new file mode 100644 index 0000000..e47df42 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/ArticleService.java @@ -0,0 +1,62 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.modules.wx.entity.Article; +import com.github.niefy.modules.wx.enums.ArticleTypeEnum; + +import java.util.List; +import java.util.Map; + +public interface ArticleService extends IService
{ + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 查询文章详情,每次查询后增加点击次数 + * + * @param id + * @return + */ + Article findById(int id); + + /** + * 添加或编辑文章,同名文章不可重复添加 + * + * @param article + */ + boolean saveArticle(Article article); + + /** + * 按条件分页查询 + * + * @param title + * @param page + * @return + */ + IPage
getArticles(String title, int page); + + /** + * 查看目录,不返回文章详情字段 + * + * @param articleType + * @param category + * @return + */ + List
selectCategory(ArticleTypeEnum articleType, String category); + + /** + * 文章查找,不返回文章详情字段 + * + * @param articleType + * @param category + * @param keywords + * @return + */ + List
search(ArticleTypeEnum articleType, String category, String keywords); +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/MsgReplyRuleService.java b/src/main/java/com/github/niefy/modules/wx/service/MsgReplyRuleService.java new file mode 100644 index 0000000..9c53296 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/MsgReplyRuleService.java @@ -0,0 +1,51 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.modules.wx.entity.MsgReplyRule; + +import java.util.List; +import java.util.Map; + +public interface MsgReplyRuleService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 保存自动回复规则 + * + * @param msgReplyRule + */ + + @Override + boolean save(MsgReplyRule msgReplyRule); + + /** + * 获取所有的回复规则 + * + * @return + */ + List getRules(); + + /** + * 获取当前时段内所有有效的回复规则 + * + * @return 有效的规则列表 + */ + List getValidRules(); + + /** + * 筛选符合条件的回复规则 + * + * + * @param appid + * @param exactMatch 是否精确匹配 + * @param keywords 关键词 + * @return 规则列表 + */ + List getMatchedRules(String appid, boolean exactMatch, String keywords); +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/MsgReplyService.java b/src/main/java/com/github/niefy/modules/wx/service/MsgReplyService.java new file mode 100644 index 0000000..33aaec1 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/MsgReplyService.java @@ -0,0 +1,107 @@ +package com.github.niefy.modules.wx.service; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 公众号消息处理 + * 官方文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html#7 + * WxJava客服消息文档:https://github.com/Wechat-Group/WxJava/wiki/MP_主动发送消息(客服消息) + */ +public interface MsgReplyService { + Logger logger = LoggerFactory.getLogger(MsgReplyService.class); + + /** + * 根据规则配置通过微信客服消息接口自动回复消息 + * + * + * @param appid + * @param exactMatch 是否精确匹配 + * @param toUser 用户openid + * @param keywords 匹配关键词 + * @return 是否已自动回复,无匹配规则则不自动回复 + */ + boolean tryAutoReply(String appid, boolean exactMatch, String toUser, String keywords); + + default void reply(String toUser,String replyType, String replyContent){ + try { + if (WxConsts.KefuMsgType.TEXT.equals(replyType)) { + this.replyText(toUser, replyContent); + } else if (WxConsts.KefuMsgType.IMAGE.equals(replyType)) { + this.replyImage(toUser, replyContent); + } else if (WxConsts.KefuMsgType.VOICE.equals(replyType)) { + this.replyVoice(toUser, replyContent); + } else if (WxConsts.KefuMsgType.VIDEO.equals(replyType)) { + this.replyVideo(toUser, replyContent); + } else if (WxConsts.KefuMsgType.MUSIC.equals(replyType)) { + this.replyMusic(toUser, replyContent); + } else if (WxConsts.KefuMsgType.NEWS.equals(replyType)) { + this.replyNews(toUser, replyContent); + } else if (WxConsts.KefuMsgType.MPNEWS.equals(replyType)) { + this.replyMpNews(toUser, replyContent); + } else if (WxConsts.KefuMsgType.WXCARD.equals(replyType)) { + this.replyWxCard(toUser, replyContent); + } else if (WxConsts.KefuMsgType.MINIPROGRAMPAGE.equals(replyType)) { + this.replyMiniProgram(toUser, replyContent); + } else if (WxConsts.KefuMsgType.MSGMENU.equals(replyType)) { + this.replyMsgMenu(toUser, replyContent); + } + } catch (Exception e) { + logger.error("自动回复出错:", e); + } + } + + /** + * 回复文字消息 + */ + void replyText(String toUser, String replyContent) throws WxErrorException; + + /** + * 回复图片消息 + */ + void replyImage(String toUser, String mediaId) throws WxErrorException; + + /** + * 回复录音消息 + */ + void replyVoice(String toUser, String mediaId) throws WxErrorException; + + /** + * 回复视频消息 + */ + void replyVideo(String toUser, String mediaId) throws WxErrorException; + + /** + * 回复音乐消息 + */ + void replyMusic(String toUser, String mediaId) throws WxErrorException; + + /** + * 回复图文消息(点击跳转到外链) + * 图文消息条数限制在1条以内 + */ + void replyNews(String toUser, String newsInfoJson) throws WxErrorException; + + /** + * 回复公众号文章消息(点击跳转到图文消息页面) + * 图文消息条数限制在1条以内 + */ + void replyMpNews(String toUser, String mediaId) throws WxErrorException; + + /** + * 回复卡券消息 + */ + void replyWxCard(String toUser, String cardId) throws WxErrorException; + + /** + * 回复小程序消息 + */ + void replyMiniProgram(String toUser, String miniProgramInfoJson) throws WxErrorException; + + /** + * 回复菜单消息 + */ + void replyMsgMenu(String toUser, String msgMenusJson) throws WxErrorException; +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/MsgTemplateService.java b/src/main/java/com/github/niefy/modules/wx/service/MsgTemplateService.java new file mode 100644 index 0000000..3a4f8f6 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/MsgTemplateService.java @@ -0,0 +1,41 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.modules.wx.entity.MsgTemplate; + +import me.chanjar.weixin.common.error.WxErrorException; + +import com.github.niefy.common.utils.PageUtils; + +import java.util.Map; + +/** + * 消息模板 + * + * @author niefy + * @email niefy@qq.com + * @date 2019-11-12 18:30:15 + */ +public interface MsgTemplateService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 通过模板名称查询 + * + * @param name + * @return + */ + MsgTemplate selectByName(String name); + + /** + * 同步公众号已添加的消息模板 + * @throws WxErrorException + */ + void syncWxTemplate(String appid) throws WxErrorException; +} + diff --git a/src/main/java/com/github/niefy/modules/wx/service/TemplateMsgLogService.java b/src/main/java/com/github/niefy/modules/wx/service/TemplateMsgLogService.java new file mode 100644 index 0000000..cc3dc82 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/TemplateMsgLogService.java @@ -0,0 +1,23 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.modules.wx.entity.TemplateMsgLog; + +import java.util.Map; + +public interface TemplateMsgLogService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 记录log,异步入库 + * + * @param log + */ + void addLog(TemplateMsgLog log); +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/TemplateMsgService.java b/src/main/java/com/github/niefy/modules/wx/service/TemplateMsgService.java new file mode 100644 index 0000000..0121748 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/TemplateMsgService.java @@ -0,0 +1,18 @@ +package com.github.niefy.modules.wx.service; + +import com.github.niefy.modules.wx.form.TemplateMsgBatchForm; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; + +public interface TemplateMsgService { + /** + * 发送微信模版消息 + */ + void sendTemplateMsg(WxMpTemplateMessage msg,String appid); + + /** + * 批量消息发送 + * @param form + * @param appid + */ + void sendMsgBatch(TemplateMsgBatchForm form,String appid); +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/WxAccountService.java b/src/main/java/com/github/niefy/modules/wx/service/WxAccountService.java new file mode 100644 index 0000000..786f7c7 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/WxAccountService.java @@ -0,0 +1,30 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.modules.wx.entity.WxAccount; + +import java.util.Collection; +import java.util.Map; + +/** + * 公众号账号 + * + * @author niefy + * @date 2020-06-17 13:56:51 + */ +public interface WxAccountService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + @Override + boolean save(WxAccount entity); + + @Override + boolean removeByIds(Collection idList); +} + diff --git a/src/main/java/com/github/niefy/modules/wx/service/WxAssetsService.java b/src/main/java/com/github/niefy/modules/wx/service/WxAssetsService.java new file mode 100644 index 0000000..7212246 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/WxAssetsService.java @@ -0,0 +1,87 @@ +package com.github.niefy.modules.wx.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.material.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +public interface WxAssetsService { + /** + * 获取素材总数 + * @return + * @throws WxErrorException + * @param appid + */ + WxMpMaterialCountResult materialCount(String appid) throws WxErrorException; + + /** + * 获取图文素材详情 + * + * @param appid + * @param mediaId + * @return + * @throws WxErrorException + */ + WxMpMaterialNews materialNewsInfo(String appid, String mediaId) throws WxErrorException; + /** + * 根据类别分页获取非图文素材列表 + * + * @param appid + * @param type + * @param page + * @return + * @throws WxErrorException + */ + WxMpMaterialFileBatchGetResult materialFileBatchGet(String appid, String type, int page) throws WxErrorException; + + /** + * 分页获取图文素材列表 + * + * @param appid + * @param page + * @return + * @throws WxErrorException + */ + WxMpMaterialNewsBatchGetResult materialNewsBatchGet(String appid, int page) throws WxErrorException; + + /** + * 添加图文永久素材 + * + * @param appid + * @param articles + * @return + * @throws WxErrorException + */ + WxMpMaterialUploadResult materialNewsUpload(String appid, List articles)throws WxErrorException; + + /** + * 更新图文素材中的某篇文章 + * @param appid + * @param form + */ + void materialArticleUpdate(String appid, WxMpMaterialArticleUpdate form) throws WxErrorException; + + /** + * 添加多媒体永久素材 + * + * @param appid + * @param mediaType + * @param fileName + * @param file + * @return + * @throws WxErrorException + */ + WxMpMaterialUploadResult materialFileUpload(String appid, String mediaType, String fileName, MultipartFile file) throws WxErrorException, IOException; + + /** + * 删除素材 + * + * @param appid + * @param mediaId + * @return + * @throws WxErrorException + */ + boolean materialDelete(String appid, String mediaId)throws WxErrorException; +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/WxMsgService.java b/src/main/java/com/github/niefy/modules/wx/service/WxMsgService.java new file mode 100644 index 0000000..ad20978 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/WxMsgService.java @@ -0,0 +1,29 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.modules.wx.entity.WxMsg; + +import java.util.Map; + +/** + * 微信消息 + * + * @author niefy + * @date 2020-05-14 17:28:34 + */ +public interface WxMsgService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 记录msg,异步入库 + * @param msg + */ + void addWxMsg(WxMsg msg); +} + diff --git a/src/main/java/com/github/niefy/modules/wx/service/WxQrCodeService.java b/src/main/java/com/github/niefy/modules/wx/service/WxQrCodeService.java new file mode 100644 index 0000000..787563d --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/WxQrCodeService.java @@ -0,0 +1,37 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.modules.wx.entity.WxQrCode; +import com.github.niefy.modules.wx.form.WxQrCodeForm; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; + +import java.util.Map; + +/** + * 公众号带参二维码 + * + * @author niefy + * @email niefy@qq.com + * @date 2020-01-02 11:11:55 + */ +public interface WxQrCodeService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + PageUtils queryPage(Map params); + + /** + * 创建公众号带参二维码 + * + * + * @param appid + * @param form + * @return + */ + WxMpQrCodeTicket createQrCode(String appid, WxQrCodeForm form) throws WxErrorException; +} + diff --git a/src/main/java/com/github/niefy/modules/wx/service/WxUserService.java b/src/main/java/com/github/niefy/modules/wx/service/WxUserService.java new file mode 100644 index 0000000..072a334 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/WxUserService.java @@ -0,0 +1,63 @@ +package com.github.niefy.modules.wx.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.niefy.modules.wx.entity.WxUser; + +import java.util.List; +import java.util.Map; + +public interface WxUserService extends IService { + /** + * 分页查询用户数据 + * @param params 查询参数 + * @return PageUtils 分页结果 + */ + IPage queryPage(Map params); + + /** + * 根据openid更新用户信息 + * + * @param openid + * @return + */ + WxUser refreshUserInfo(String openid,String appid); + + /** + * 异步批量更新用户信息 + * @param openidList + */ + void refreshUserInfoAsync(String[] openidList,String appid); + + /** + * 数据存在时更新,否则新增 + * + * @param user + */ + void updateOrInsert(WxUser user); + + /** + * 取消关注,更新关注状态 + * + * @param openid + */ + void unsubscribe(String openid); + + /** + * 根据手机号查询关注用户 + * + * @param phone + */ + WxUser findByPhone(String phone); + /** + * 同步用户列表 + */ + void syncWxUsers(String appid); + + /** + * 通过传入的openid列表,同步用户列表 + * @param openids + */ + void syncWxUsers(List openids,String appid); + +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/WxUserTagsService.java b/src/main/java/com/github/niefy/modules/wx/service/WxUserTagsService.java new file mode 100644 index 0000000..09de0d3 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/WxUserTagsService.java @@ -0,0 +1,76 @@ +package com.github.niefy.modules.wx.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; + +import java.util.List; + +public interface WxUserTagsService { + /** + * 获取公众号用户标签 + * @return + * @throws WxErrorException + * @param appid + */ + List getWxTags(String appid) throws WxErrorException; + + /** + * 创建标签 + * @param appid + * @param name 标签名称 + */ + void creatTag(String appid, String name) throws WxErrorException; + + /** + * 修改标签 + * @param appid + * @param tagid 标签ID + * @param name 标签名称 + */ + void updateTag(String appid, Long tagid, String name) throws WxErrorException; + + /** + * 删除标签 + * + * @param appid + * @param tagid 标签ID + * @throws WxErrorException + */ + void deleteTag(String appid, Long tagid) throws WxErrorException; + + /** + * 批量给用户打标签 + * + * @param appid + * @param tagid 标签ID + * @param openidList 用户openid列表 + * @throws WxErrorException + */ + void batchTagging(String appid, Long tagid, String[] openidList) throws WxErrorException; + + /** + * 批量取消用户标签 + * + * @param appid + * @param tagid 标签ID + * @param openidList 用户openid列表 + * @throws WxErrorException + */ + void batchUnTagging(String appid, Long tagid, String[] openidList) throws WxErrorException; + + /** + * 为用户绑定标签 + * @param tagid + * @param openid + * @throws WxErrorException + */ + void tagging(Long tagid, String openid) throws WxErrorException; + + /** + * 取消用户所绑定的某一个标签 + * @param tagid + * @param openid + * @throws WxErrorException + */ + void untagging(Long tagid, String openid) throws WxErrorException; +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/ArticleServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/ArticleServiceImpl.java new file mode 100644 index 0000000..82385d7 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/ArticleServiceImpl.java @@ -0,0 +1,153 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.modules.wx.dto.PageSizeConstant; +import com.github.niefy.modules.wx.entity.Article; +import com.github.niefy.modules.wx.enums.ArticleTypeEnum; +import com.github.niefy.modules.wx.service.ArticleService; +import com.github.niefy.common.exception.RRException; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.wx.dao.ArticleMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @author Nifury + * @date 2017-10-27 + */ +@Service +public class ArticleServiceImpl extends ServiceImpl implements ArticleService { + private static final String ID_PLACEHOLDER = "${articleId}"; + /** + * 查询文章列表时返回的字段(过滤掉详情字段以加快速度) + */ + private static final String LIST_FILEDS = "id,summary,image,sub_category,update_time,title,type,tags,create_time,target_link,open_count,category"; + @Autowired + ArticleMapper articleMapper; + + @Override + public PageUtils queryPage(Map params) { + String title = (String) params.get("title"); + String type = (String) params.get("type"); + String category = (String) params.get("category"); + String subCategory = (String) params.get("subCategory"); + IPage
page = this.page( + new Query
().getPage(params), + new QueryWrapper
() + .select(LIST_FILEDS) + .eq(StringUtils.hasText(type), "type", type) + .like(StringUtils.hasText(category), "category", category) + .like(StringUtils.hasText(subCategory), "sub_category", subCategory) + .like(StringUtils.hasText(title), "title", title) + ); + + return new PageUtils(page); + } + + /** + * 查询文章详情,每次查询后增加点击次数 + * + * @param id + * @return + */ + @Override + public Article findById(int id) { + if (id <= 0) { + return null; + } + Article article = articleMapper.selectById(id); + if (article != null) { + articleMapper.addOpenCount(id); + } + return article; + } + + + /** + * 添加或编辑文章,同名文章不可重复添加 + * + * @param article + */ + + @Override + public boolean saveArticle(Article article) { + article.setUpdateTime(new Date()); + if (null != article.getId() && article.getId() > 0) { + articleMapper.updateById(article); + } else { + String title = article.getTitle(); + long count = articleMapper.selectCount( + new QueryWrapper
().eq("title", title) + .eq("category", article.getCategory()) + .eq("sub_category", article.getSubCategory()) + ); + if (count > 0) { + throw new RRException("同目录下文章[" + title + "]已存在,不可重复添加"); + } + article.setCreateTime(new Date()); + articleMapper.insert(article); + } + String targetLink = article.getTargetLink(); + if (targetLink.indexOf(ID_PLACEHOLDER) > -1) { + article.setTargetLink(targetLink.replace(ID_PLACEHOLDER, article.getId() + "")); + articleMapper.updateById(article); + } + return true; + } + + /** + * 按条件分页查询 + * + * @param title + * @param page + * @return + */ + @Override + public IPage
getArticles(String title, int page) { + return this.page(new Page
(page, PageSizeConstant.PAGE_SIZE_SMALL), + new QueryWrapper
().like(StringUtils.hasText("title"), "title", title) + .select(LIST_FILEDS) + .orderBy(true, false, "update_time")); + } + + /** + * 查看目录,不返回文章详情字段 + * + * @param articleType + * @param category + * @return + */ + @Override + public List
selectCategory(ArticleTypeEnum articleType, String category) { + return this.list(new QueryWrapper
() + .select(LIST_FILEDS) + .eq("type", articleType.getValue()) + .eq("category", category)); + } + + /** + * 文章查找,不返回文章详情字段 + * + * @param articleType + * @param category + * @param keywords + * @return + */ + @Override + public List
search(ArticleTypeEnum articleType, String category, String keywords) { + return this.list(new QueryWrapper
() + .select(LIST_FILEDS) + .eq("type", articleType.getValue()) + .eq(StringUtils.hasText(category), "category", category) + .and(i -> i.like("summary", keywords).or().like("title", keywords))); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyRuleServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyRuleServiceImpl.java new file mode 100644 index 0000000..085f088 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyRuleServiceImpl.java @@ -0,0 +1,127 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.modules.wx.service.MsgReplyRuleService; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.wx.entity.MsgReplyRule; +import com.github.niefy.modules.wx.dao.MsgReplyRuleMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.LocalTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class MsgReplyRuleServiceImpl extends ServiceImpl implements MsgReplyRuleService { + @Autowired + MsgReplyRuleMapper msgReplyRuleMapper; + + @Override + public PageUtils queryPage(Map params) { + String matchValue = (String) params.get("matchValue"); + String appid = (String) params.get("appid"); + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .eq(StringUtils.hasText(appid), "appid", appid) + .or() + .apply("appid is null or appid = ''") + .like(StringUtils.hasText(matchValue), "match_value", matchValue) + .orderByDesc("update_time") + ); + + return new PageUtils(page); + } + + /** + * 保存自动回复规则 + * + * @param msgReplyRule + */ + + @Override + public boolean save(MsgReplyRule msgReplyRule) { + if (msgReplyRule.getRuleId() > 0) { + msgReplyRuleMapper.updateById(msgReplyRule); + } else { + msgReplyRuleMapper.insert(msgReplyRule); + } + return false; + } + + /** + * 获取所有的回复规则 + * + * @return + */ + @Override + public List getRules() { + return msgReplyRuleMapper.selectList(new QueryWrapper().orderByDesc("rule_id")); + } + + /** + * 获取当前时段内所有有效的回复规则 + * + * @return + */ + @Override + public List getValidRules() { + return msgReplyRuleMapper.selectList( + new QueryWrapper() + .eq("status", 1) + .isNotNull("match_value") + .ne("match_value", "") + .orderByDesc("priority")); + } + + /** + * 筛选符合条件的回复规则 + * + * + * @param appid 公众号appid + * @param exactMatch 是否精确匹配 + * @param keywords 关键词 + * @return 规则列表 + */ + @Override + public List getMatchedRules(String appid, boolean exactMatch, String keywords) { + LocalTime now = LocalTime.now(); + return this.getValidRules().stream() + .filter(rule->!StringUtils.hasText(rule.getAppid()) || appid.equals(rule.getAppid())) // 检测是否是对应公众号的规则,如果appid为空则为通用规则 + .filter(rule->null == rule.getEffectTimeStart() || rule.getEffectTimeStart().toLocalTime().isBefore(now))// 检测是否在有效时段,effectTimeStart为null则一直有效 + .filter(rule->null == rule.getEffectTimeEnd() || rule.getEffectTimeEnd().toLocalTime().isAfter(now)) // 检测是否在有效时段,effectTimeEnd为null则一直有效 + .filter(rule->isMatch(exactMatch || rule.isExactMatch(),rule.getMatchValue().split(","),keywords)) //检测是否符合匹配规则 + .collect(Collectors.toList()); + } + + /** + * 检测文字是否匹配规则 + * 精确匹配时,需关键词与规则词语一致 + * 非精确匹配时,检测文字需包含任意一个规则词语 + * + * @param exactMatch 是否精确匹配 + * @param ruleWords 规则列表 + * @param checkWords 需检测的文字 + * @return + */ + public static boolean isMatch(boolean exactMatch, String[] ruleWords, String checkWords) { + if (!StringUtils.hasText(checkWords)) { + return false; + } + for (String words : ruleWords) { + if (exactMatch && words.equals(checkWords)) { + return true;//精确匹配,需关键词与规则词语一致 + } + if (!exactMatch && checkWords.contains(words)) { + return true;//模糊匹配 + } + } + return false; + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyServiceImpl.java new file mode 100644 index 0000000..eda0479 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/MsgReplyServiceImpl.java @@ -0,0 +1,188 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.github.niefy.config.TaskExcutor; +import com.github.niefy.modules.wx.entity.MsgReplyRule; +import com.github.niefy.modules.wx.entity.WxMsg; +import com.github.niefy.modules.wx.service.MsgReplyRuleService; +import com.github.niefy.modules.wx.service.MsgReplyService; +import com.github.niefy.modules.wx.service.WxMsgService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 微信公众号消息处理 + * 官方文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Ma nagement/Service_Center_messages.html#7 + * 参考WxJava客服消息文档:https://github.com/Wechat-Group/WxJava/wiki/MP_主动发送消息(客服消息) + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class MsgReplyServiceImpl implements MsgReplyService { + @Autowired + MsgReplyRuleService msgReplyRuleService; + @Autowired + WxMpService wxMpService; + @Value("${wx.mp.autoReplyInterval:1000}") + Long autoReplyInterval; + @Autowired + WxMsgService wxMsgService; + + /** + * 根据规则配置通过微信客服消息接口自动回复消息 + * + * + * @param appid 公众号appid + * @param exactMatch 是否精确匹配 + * @param toUser 用户openid + * @param keywords 匹配关键词 + * @return 是否已自动回复,无匹配规则则不自动回复 + */ + @Override + public boolean tryAutoReply(String appid, boolean exactMatch, String toUser, String keywords) { + try { + List rules = msgReplyRuleService.getMatchedRules(appid,exactMatch, keywords); + if (rules.isEmpty()) { + return false; + } + long delay = 0; + for (MsgReplyRule rule : rules) { + TaskExcutor.schedule(() -> { + wxMpService.switchover(appid); + this.reply(toUser,rule.getReplyType(),rule.getReplyContent()); + }, delay, TimeUnit.MILLISECONDS); + delay += autoReplyInterval; + } + return true; + } catch (Exception e) { + log.error("自动回复出错:", e); + } + return false; + } + + @Override + public void replyText(String toUser, String content) throws WxErrorException { + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.TEXT().toUser(toUser).content(content).build()); + + JSONObject json = new JSONObject().fluentPut("content",content); + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.TEXT,toUser,json)); + } + + @Override + public void replyImage(String toUser, String mediaId) throws WxErrorException { + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.IMAGE().toUser(toUser).mediaId(mediaId).build()); + + JSONObject json = new JSONObject().fluentPut("mediaId",mediaId); + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.IMAGE,toUser,json)); + } + + @Override + public void replyVoice(String toUser, String mediaId) throws WxErrorException { + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.VOICE().toUser(toUser).mediaId(mediaId).build()); + + JSONObject json = new JSONObject().fluentPut("mediaId",mediaId); + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.VOICE,toUser,json)); + } + + @Override + public void replyVideo(String toUser, String mediaId) throws WxErrorException { + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.VIDEO().toUser(toUser).mediaId(mediaId).build()); + + JSONObject json = new JSONObject().fluentPut("mediaId",mediaId); + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.VIDEO,toUser,json)); + } + + @Override + public void replyMusic(String toUser, String musicInfoJson) throws WxErrorException { + JSONObject json = JSON.parseObject(musicInfoJson); + wxMpService.getKefuService().sendKefuMessage( + WxMpKefuMessage.MUSIC().toUser(toUser) + .musicUrl(json.getString("musicurl")) + .hqMusicUrl(json.getString("hqmusicurl")) + .title(json.getString("title")) + .description(json.getString("description")) + .thumbMediaId(json.getString("thumb_media_id")) + .build()); + + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.IMAGE,toUser,json)); + } + + /** + * 发送图文消息(点击跳转到外链) 图文消息条数限制在1条以内 + * @param toUser + * @param newsInfoJson + * @throws WxErrorException + */ + @Override + public void replyNews(String toUser, String newsInfoJson) throws WxErrorException { + WxMpKefuMessage.WxArticle wxArticle = JSON.parseObject(newsInfoJson, WxMpKefuMessage.WxArticle.class); + List newsList = new ArrayList(){{add(wxArticle);}}; + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.NEWS().toUser(toUser).articles(newsList).build()); + + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.NEWS,toUser,JSON.parseObject(newsInfoJson))); + } + + /** + * 发送图文消息(点击跳转到图文消息页面) 图文消息条数限制在1条以内 + * @param toUser + * @param mediaId + * @throws WxErrorException + */ + @Override + public void replyMpNews(String toUser, String mediaId) throws WxErrorException { + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.MPNEWS().toUser(toUser).mediaId(mediaId).build()); + + JSONObject json = new JSONObject().fluentPut("mediaId",mediaId); + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.MPNEWS,toUser,json)); + } + + @Override + public void replyWxCard(String toUser, String cardId) throws WxErrorException { + wxMpService.getKefuService().sendKefuMessage(WxMpKefuMessage.WXCARD().toUser(toUser).cardId(cardId).build()); + + JSONObject json = new JSONObject().fluentPut("cardId",cardId); + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.WXCARD,toUser,json)); + } + + @Override + public void replyMiniProgram(String toUser, String miniProgramInfoJson) throws WxErrorException { + JSONObject json = JSON.parseObject(miniProgramInfoJson); + wxMpService.getKefuService().sendKefuMessage( + WxMpKefuMessage.MINIPROGRAMPAGE() + .toUser(toUser) + .title(json.getString("title")) + .appId(json.getString("appid")) + .pagePath(json.getString("pagepath")) + .thumbMediaId(json.getString("thumb_media_id")) + .build()); + + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.IMAGE,toUser,json)); + } + + @Override + public void replyMsgMenu(String toUser, String msgMenusJson) throws WxErrorException { + JSONObject json = JSON.parseObject(msgMenusJson); + List msgMenus = JSON.parseArray(json.getString("list"),WxMpKefuMessage.MsgMenu.class); + wxMpService.getKefuService().sendKefuMessage( + WxMpKefuMessage.MSGMENU() + .toUser(toUser) + .headContent(json.getString("head_content")) + .tailContent(json.getString("tail_content")) + .msgMenus(msgMenus).build()); + + wxMsgService.addWxMsg(WxMsg.buildOutMsg(WxConsts.KefuMsgType.IMAGE,toUser,json)); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/MsgTemplateServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/MsgTemplateServiceImpl.java new file mode 100644 index 0000000..45c5a7f --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/MsgTemplateServiceImpl.java @@ -0,0 +1,63 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.modules.wx.dao.MsgTemplateMapper; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.wx.entity.MsgTemplate; +import com.github.niefy.modules.wx.service.MsgTemplateService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.template.WxMpTemplate; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +@Service("msgTemplateService") +public class MsgTemplateServiceImpl extends ServiceImpl implements MsgTemplateService { + @Autowired + private WxMpService wxService; + + @Override + public PageUtils queryPage(Map params) { + String title = (String) params.get("title"); + String name = (String) params.get("name"); + String appid = (String) params.get("appid"); + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .eq(StringUtils.hasText(appid), "appid", appid) + .like(StringUtils.hasText(title), "title", title) + .like(StringUtils.hasText(name), "name", name) + ); + + return new PageUtils(page); + } + + @Override + public MsgTemplate selectByName(String name) { + Assert.hasText(name, "模板名称不得为空"); + return this.getOne(new QueryWrapper() + .eq("name", name) + .eq("status", 1) + .last("LIMIT 1")); + } + + @Override + public void syncWxTemplate(String appid) throws WxErrorException { + List wxMpTemplateList= wxService.getTemplateMsgService().getAllPrivateTemplate(); + List templates = wxMpTemplateList.stream().map(item->new MsgTemplate(item,appid)).collect(Collectors.toList()); + this.saveBatch(templates); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgLogServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgLogServiceImpl.java new file mode 100644 index 0000000..c258fd5 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgLogServiceImpl.java @@ -0,0 +1,42 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.wx.dao.TemplateMsgLogMapper; +import com.github.niefy.modules.wx.entity.TemplateMsgLog; +import com.github.niefy.modules.wx.service.TemplateMsgLogService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Map; + +@Service +public class TemplateMsgLogServiceImpl extends ServiceImpl implements TemplateMsgLogService { + + @Override + public PageUtils queryPage(Map params) { + String appid = (String) params.get("appid"); + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .eq(StringUtils.hasText(appid), "appid", appid) + ); + + return new PageUtils(page); + } + + /** + * 记录log,异步入库 + * + * @param log + */ + @Override + @Async + public void addLog(TemplateMsgLog log) { + this.baseMapper.insert(log); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgServiceImpl.java new file mode 100644 index 0000000..b3c8e5c --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgServiceImpl.java @@ -0,0 +1,94 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.niefy.config.TaskExcutor; +import com.github.niefy.modules.wx.entity.TemplateMsgLog; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.form.TemplateMsgBatchForm; +import com.github.niefy.modules.wx.service.MsgTemplateService; +import com.github.niefy.modules.wx.service.TemplateMsgLogService; +import com.github.niefy.modules.wx.service.TemplateMsgService; +import com.github.niefy.modules.wx.service.WxUserService; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Nifury + * @date 2017-9-27 + */ +@Service +@RequiredArgsConstructor +public class TemplateMsgServiceImpl implements TemplateMsgService { + Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + private TemplateMsgLogService templateMsgLogService; + private final WxMpService wxService; + @Autowired + MsgTemplateService msgTemplateService; + @Autowired + WxUserService wxUserService; + + /** + * 发送微信模版消息,使用固定线程的线程池 + */ + @Override + @Async + public void sendTemplateMsg(WxMpTemplateMessage msg,String appid) { + TaskExcutor.submit(() -> { + String result; + try { + wxService.switchover(appid); + result = wxService.getTemplateMsgService().sendTemplateMsg(msg); + } catch (WxErrorException e) { + result = e.getMessage(); + } + + //保存发送日志 + TemplateMsgLog log = new TemplateMsgLog(msg,appid, result); + templateMsgLogService.addLog(log); + }); + } + + @Override + @Async + public void sendMsgBatch(TemplateMsgBatchForm form, String appid) { + logger.info("批量发送模板消息任务开始,参数:{}",form.toString()); + wxService.switchover(appid); + WxMpTemplateMessage.WxMpTemplateMessageBuilder builder = WxMpTemplateMessage.builder() + .templateId(form.getTemplateId()) + .url(form.getUrl()) + .miniProgram(form.getMiniprogram()) + .data(form.getData()); + Map filterParams = form.getWxUserFilterParams(); + if(filterParams==null) { + filterParams=new HashMap<>(8); + } + long currentPage=1L,totalPages=Long.MAX_VALUE; + filterParams.put("appid",appid); + filterParams.put("limit","500"); + while (currentPage<=totalPages){ + filterParams.put("page",String.valueOf(currentPage)); + //按条件查询用户 + IPage wxUsers = wxUserService.queryPage(filterParams); + logger.info("批量发送模板消息任务,使用查询条件,处理第{}页,总用户数:{}",currentPage,wxUsers.getTotal()); + wxUsers.getRecords().forEach(user->{ + WxMpTemplateMessage msg = builder.toUser(user.getOpenid()).build(); + this.sendTemplateMsg(msg,appid); + }); + currentPage=wxUsers.getCurrent()+1L; + totalPages=wxUsers.getPages(); + } + logger.info("批量发送模板消息任务结束"); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxAccountServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxAccountServiceImpl.java new file mode 100644 index 0000000..f8a5892 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/WxAccountServiceImpl.java @@ -0,0 +1,121 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.wx.dao.WxAccountMapper; +import com.github.niefy.modules.wx.entity.WxAccount; +import com.github.niefy.modules.wx.service.WxAccountService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +@Service("wxAccountService") +public class WxAccountServiceImpl extends ServiceImpl implements WxAccountService { + Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + WxMpService wxMpService; + + @Override + public PageUtils queryPage(Map params) { + String name = (String) params.get("name"); + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .like(StringUtils.hasText(name), "name", name) + ); + + return new PageUtils(page); + } + + @PostConstruct + public void loadWxMpConfigStorages(){ + logger.info("加载公众号配置..."); + List accountList = this.list(); + if (accountList == null || accountList.isEmpty()) { + logger.info("未读取到公众号配置,请在管理后台添加"); + return; + } + logger.info("加载到{}条公众号配置",accountList.size()); + accountList.forEach(this::addAccountToRuntime); + logger.info("公众号配置加载完成"); + } + + @Override + public boolean save(WxAccount entity) { + Assert.notNull(entity,"WxAccount不得为空"); + String appid = entity.getAppid(); + if(this.isAccountInRuntime(appid)){ //已有此appid信息,更新 + logger.info("更新公众号配置"); + wxMpService.removeConfigStorage(appid); + this.addAccountToRuntime(entity); + + return SqlHelper.retBool(this.baseMapper.updateById(entity)); + }else {//已有此appid信息,新增 + logger.info("新增公众号配置"); + this.addAccountToRuntime(entity); + + return SqlHelper.retBool(this.baseMapper.insert(entity)); + } + + } + + @Override + public boolean removeByIds(Collection idList) { + Assert.notEmpty(idList,"WxAccount不得为空"); + + // 更新wxMpService配置 + logger.info("同步移除公众号配置"); + idList.forEach(id-> wxMpService.removeConfigStorage((String) id)); + + return SqlHelper.retBool(this.baseMapper.deleteBatchIds(idList)); + } + + /** + * 判断当前账号是存在 + * @param appid + * @return + */ + private boolean isAccountInRuntime(String appid){ + try { + return wxMpService.switchover(appid); + }catch (NullPointerException e){// sdk bug,未添加任何账号时configStorageMap为null会出错 + return false; + } + } + /** + * 添加账号到当前程序,如首次添加需初始化configStorageMap + * @param entity + */ + private synchronized void addAccountToRuntime(WxAccount entity){ + String appid = entity.getAppid(); + WxMpDefaultConfigImpl config = entity.toWxMpConfigStorage(); + try { + wxMpService.addConfigStorage(appid,config); + }catch (NullPointerException e){ + logger.info("需初始化configStorageMap..."); + Map configStorages = new HashMap<>(4); + configStorages.put(appid,config); + wxMpService.setMultiConfigStorages(configStorages,appid); + } + } + + +} \ No newline at end of file diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxAssetsServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxAssetsServiceImpl.java new file mode 100644 index 0000000..243ce14 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/WxAssetsServiceImpl.java @@ -0,0 +1,115 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.github.niefy.modules.wx.dto.PageSizeConstant; +import com.github.niefy.modules.wx.service.WxAssetsService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.material.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +@Service +@CacheConfig(cacheNames = {"wxAssetsServiceCache"}) +@Slf4j +public class WxAssetsServiceImpl implements WxAssetsService { + @Autowired + WxMpService wxMpService; + + @Override + @Cacheable(key="methodName+ #appid") + public WxMpMaterialCountResult materialCount(String appid) throws WxErrorException { + log.info("从API获取素材总量"); + wxMpService.switchoverTo(appid); + return wxMpService.getMaterialService().materialCount(); + } + + @Override + @Cacheable(key="methodName + #appid + #mediaId") + public WxMpMaterialNews materialNewsInfo(String appid, String mediaId) throws WxErrorException { + log.info("从API获取图文素材详情,mediaId={}",mediaId); + wxMpService.switchoverTo(appid); + return wxMpService.getMaterialService().materialNewsInfo(mediaId); + } + + @Override + @Cacheable(key="methodName + #appid + #type + #page") + public WxMpMaterialFileBatchGetResult materialFileBatchGet(String appid, String type, int page) throws WxErrorException { + log.info("从API获取媒体素材列表,type={},page={}",type,page); + wxMpService.switchoverTo(appid); + final int pageSize = PageSizeConstant.PAGE_SIZE_SMALL; + int offset=(page-1)* pageSize; + return wxMpService.getMaterialService().materialFileBatchGet(type,offset, pageSize); + } + + @Cacheable(key="methodName + #appid + #page") + @Override + public WxMpMaterialNewsBatchGetResult materialNewsBatchGet(String appid, int page) throws WxErrorException { + log.info("从API获取媒体素材列表,page={}",page); + wxMpService.switchoverTo(appid); + final int pageSize = PageSizeConstant.PAGE_SIZE_SMALL; + int offset=(page-1)*pageSize; + return wxMpService.getMaterialService().materialNewsBatchGet(offset, pageSize); + } + + @Override + @CacheEvict(allEntries = true) + public WxMpMaterialUploadResult materialNewsUpload(String appid, List articles) throws WxErrorException { + Assert.notEmpty(articles,"图文列表不得为空"); + log.info("上传图文素材..."); + wxMpService.switchoverTo(appid); + WxMpMaterialNews news = new WxMpMaterialNews(); + news.setArticles(articles); + return wxMpService.getMaterialService().materialNewsUpload(news); + } + + /** + * 更新图文素材中的某篇文章 + * @param appid + * @param form + */ + @Override + @CacheEvict(allEntries = true) + public void materialArticleUpdate(String appid, WxMpMaterialArticleUpdate form) throws WxErrorException{ + log.info("更新图文素材..."); + wxMpService.switchoverTo(appid); + wxMpService.getMaterialService().materialNewsUpdate(form); + } + @Override + @CacheEvict(allEntries = true) + public WxMpMaterialUploadResult materialFileUpload(String appid, String mediaType, String fileName, MultipartFile file) throws WxErrorException, IOException { + log.info("上传媒体素材:{}",fileName); + wxMpService.switchoverTo(appid); + String originalFilename=file.getOriginalFilename(); + File tempFile = File.createTempFile(fileName+"--", Objects.requireNonNull(originalFilename).substring(originalFilename.lastIndexOf("."))); + file.transferTo(tempFile); + WxMpMaterial wxMaterial = new WxMpMaterial(); + wxMaterial.setFile(tempFile); + wxMaterial.setName(fileName); + if(WxConsts.MediaFileType.VIDEO.equals(mediaType)){ + wxMaterial.setVideoTitle(fileName); + } + WxMpMaterialUploadResult res = wxMpService.getMaterialService().materialFileUpload(mediaType,wxMaterial); + tempFile.deleteOnExit(); + return res; + } + + @Override + @CacheEvict(allEntries = true) + public boolean materialDelete(String appid, String mediaId) throws WxErrorException { + log.info("删除素材,mediaId={}",mediaId); + wxMpService.switchoverTo(appid); + return wxMpService.getMaterialService().materialDelete(mediaId); + } +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxMpTemplate.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxMpTemplate.java new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxMsgServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxMsgServiceImpl.java new file mode 100644 index 0000000..27e9d26 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/WxMsgServiceImpl.java @@ -0,0 +1,50 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import com.github.niefy.modules.wx.dao.WxMsgMapper; +import com.github.niefy.modules.wx.entity.WxMsg; +import com.github.niefy.modules.wx.service.WxMsgService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.Map; + + +@Service("wxMsgService") +public class WxMsgServiceImpl extends ServiceImpl implements WxMsgService { + + @Override + public PageUtils queryPage(Map params) { + String msgTypes = (String)params.get("msgTypes"); + String startTime = (String)params.get("startTime"); + String openid = (String)params.get("openid"); + String appid = (String) params.get("appid"); + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .eq(StringUtils.hasText(appid), "appid", appid) + .in(StringUtils.hasText(msgTypes),"msg_type", Arrays.asList(msgTypes.split(","))) + .eq(StringUtils.hasText(openid),"openid",openid) + .ge(StringUtils.hasText(startTime),"create_time",startTime) + ); + + return new PageUtils(page); + } + + /** + * 记录msg,异步入库 + * @param msg + */ + @Override + @Async + public void addWxMsg(WxMsg msg) { + this.baseMapper.insert(msg); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxQrCodeServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxQrCodeServiceImpl.java new file mode 100644 index 0000000..1a5c5dd --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/WxQrCodeServiceImpl.java @@ -0,0 +1,69 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.github.niefy.modules.wx.dao.WxQrCodeMapper; +import com.github.niefy.modules.wx.entity.WxQrCode; +import com.github.niefy.modules.wx.form.WxQrCodeForm; +import com.github.niefy.modules.wx.service.WxQrCodeService; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.Map; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.PageUtils; +import com.github.niefy.common.utils.Query; +import org.springframework.util.StringUtils; + + +@Service("wxQrCodeService") +@RequiredArgsConstructor +public class WxQrCodeServiceImpl extends ServiceImpl implements WxQrCodeService { + private final WxMpService wxService; + + @Override + public PageUtils queryPage(Map params) { + String sceneStr = (String) params.get("sceneStr"); + String appid = (String) params.get("appid"); + IPage page = this.page( + new Query().getPage(params), + new QueryWrapper() + .eq(StringUtils.hasText(appid), "appid", appid) + .like(StringUtils.hasText(sceneStr), "scene_str", sceneStr) + ); + + return new PageUtils(page); + } + + /** + * 创建公众号带参二维码 + * + * + * @param appid + * @param form + * @return + */ + @Override + public WxMpQrCodeTicket createQrCode(String appid, WxQrCodeForm form) throws WxErrorException { + WxMpQrCodeTicket ticket; + if (form.getIsTemp()) {//创建临时二维码 + ticket = wxService.getQrcodeService().qrCodeCreateTmpTicket(form.getSceneStr(), form.getExpireSeconds()); + } else {//创建永久二维码 + ticket = wxService.getQrcodeService().qrCodeCreateLastTicket(form.getSceneStr()); + } + WxQrCode wxQrCode = new WxQrCode(form,appid); + wxQrCode.setTicket(ticket.getTicket()); + wxQrCode.setUrl(ticket.getUrl()); + if (form.getIsTemp()) { + wxQrCode.setExpireTime(new Date(System.currentTimeMillis() + ticket.getExpireSeconds() * 1000L)); + } + this.save(wxQrCode); + return ticket; + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxUserServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxUserServiceImpl.java new file mode 100644 index 0000000..182a232 --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/WxUserServiceImpl.java @@ -0,0 +1,194 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.niefy.common.utils.Query; +import com.github.niefy.config.TaskExcutor; +import com.github.niefy.modules.wx.dao.WxUserMapper; +import com.github.niefy.modules.wx.entity.WxUser; +import com.github.niefy.modules.wx.service.WxUserService; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.WxMpUserService; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import me.chanjar.weixin.mp.bean.result.WxMpUserList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Nifury + * @date 2017-9-27 + */ +@Service +public class WxUserServiceImpl extends ServiceImpl implements WxUserService { + Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + private WxUserMapper userMapper; + @Autowired + private WxMpService wxMpService; + private volatile static boolean syncWxUserTaskRunning=false; + + @Override + public IPage queryPage(Map params) { + String openid = (String) params.get("openid"); + String nickname = (String) params.get("nickname"); + String appid = (String) params.get("appid"); + String city = (String) params.get("city"); + String tagId = (String) params.get("tagid"); + String qrSceneStr = (String) params.get("qrSceneStr"); + return this.page( + new Query().getPage(params), + new QueryWrapper() + .eq(StringUtils.hasText(appid), "appid", appid) + .eq(StringUtils.hasText(openid), "openid", openid) + .like(StringUtils.hasText(nickname), "nickname", nickname) + .eq(StringUtils.hasText(city), "city", city) + .eq(StringUtils.hasText(qrSceneStr), "qrSceneStr", qrSceneStr) + .apply(StringUtils.hasText(tagId),"JSON_CONTAINS(tagid_list,{0})",tagId) + ); + } + + /** + * 根据openid更新用户信息 + * + * @param openid + * @return + */ + @Override + public WxUser refreshUserInfo(String openid,String appid) { + try { + // 获取微信用户基本信息 + logger.info("更新用户信息,openid={}",openid); + wxMpService.switchover(appid); + WxMpUser userWxInfo = wxMpService.getUserService().userInfo(openid, null); + if (userWxInfo == null) { + logger.error("获取不到用户信息,无法更新,openid:{}",openid); + return null; + } + WxUser user = new WxUser(userWxInfo,appid); + this.saveOrUpdate(user); + return user; + } catch (Exception e) { + logger.error("更新用户信息失败,openid:{}",openid); + } + return null; + } + + /** + * 异步批量同步用户信息 + * @param openidList + */ + @Override + @Async + public void refreshUserInfoAsync(String[] openidList,String appid) { + logger.info("批量更新用户信息:任务开始"); + for(String openid:openidList){ + wxMpService.switchover(appid); + TaskExcutor.submit(()->this.refreshUserInfo(openid,appid)); + } + logger.info("批量更新用户信息:任务全部添加到线程池"); + } + + /** + * 数据存在时更新,否则新增 + * + * @param user + */ + @Override + public void updateOrInsert(WxUser user) { + int updateCount = userMapper.updateById(user); + if (updateCount < 1) { + userMapper.insert(user); + } + } + + @Override + public void unsubscribe(String openid) { + userMapper.unsubscribe(openid); + } + + @Override + public WxUser findByPhone(String phone) { + return userMapper.findByPhone(phone); + } + + /** + * 同步用户列表,公众号一次拉取调用最多拉取10000个关注者的OpenID,可以通过传入nextOpenid参数多次拉取 + */ + @Override + @Async + public void syncWxUsers(String appid) { + //同步较慢,防止个多线程重复执行同步任务 + Assert.isTrue(!syncWxUserTaskRunning,"后台有同步任务正在进行中,请稍后重试"); + wxMpService.switchoverTo(appid); + syncWxUserTaskRunning=true; + logger.info("同步公众号粉丝列表:任务开始"); + wxMpService.switchover(appid); + boolean hasMore=true; + String nextOpenid=null; + WxMpUserService wxMpUserService = wxMpService.getUserService(); + try { + int page=1; + while (hasMore){ + WxMpUserList wxMpUserList = wxMpUserService.userList(nextOpenid);//拉取openid列表,每次最多1万个 + logger.info("拉取openid列表:第{}页,数量:{}",page++,wxMpUserList.getCount()); + List openids = wxMpUserList.getOpenids(); + this.syncWxUsers(openids,appid); + nextOpenid=wxMpUserList.getNextOpenid(); + hasMore=StringUtils.hasText(nextOpenid) && wxMpUserList.getCount()>=10000; + } + } catch (WxErrorException e) { + logger.error("同步公众号粉丝出错:",e); + }finally { + syncWxUserTaskRunning=false; + } + logger.info("同步公众号粉丝列表:完成"); + } + + /** + * 通过传入的openid列表,同步用户列表 + * @param openids + */ + @Override + public void syncWxUsers(List openids,String appid) { + if(openids.size()<1) { + return; + } + final String batch=openids.get(0).substring(20);//截取首个openid的一部分做批次号(打印日志时使用,无实际意义) + WxMpUserService wxMpUserService = wxMpService.getUserService(); + int start=0,batchSize=openids.size(),end=Math.min(100,batchSize); + logger.info("开始处理批次:{},批次数量:{}",batch,batchSize); + while (start subOpenids=openids.subList(finalStart,finalEnd); + TaskExcutor.submit(()->{//使用线程池同步数据,否则大量粉丝数据需同步时会很慢 + logger.info("同步批次:【{}--{}-{}】,数量:{}",batch, finalStart, finalEnd,subOpenids.size()); + wxMpService.switchover(appid); + List wxMpUsers = null;//批量获取用户信息,每次最多100个 + try { + wxMpUsers = wxMpUserService.userInfoList(subOpenids); + } catch (WxErrorException e) { + logger.error("同步出错,批次:【{}--{}-{}】,错误信息:{}",batch, finalStart, finalEnd,e); + } + if(wxMpUsers!=null && !wxMpUsers.isEmpty()){ + List wxUsers=wxMpUsers.parallelStream().map(item->new WxUser(item,appid)).collect(Collectors.toList()); + this.saveOrUpdateBatch(wxUsers); + } + }); + start=end; + end=Math.min(end+100,openids.size()); + } + logger.info("批次:{}处理完成",batch); + } + +} diff --git a/src/main/java/com/github/niefy/modules/wx/service/impl/WxUserTagsServiceImpl.java b/src/main/java/com/github/niefy/modules/wx/service/impl/WxUserTagsServiceImpl.java new file mode 100644 index 0000000..526a28b --- /dev/null +++ b/src/main/java/com/github/niefy/modules/wx/service/impl/WxUserTagsServiceImpl.java @@ -0,0 +1,85 @@ +package com.github.niefy.modules.wx.service.impl; + +import com.github.niefy.modules.wx.service.WxUserService; +import com.github.niefy.modules.wx.service.WxUserTagsService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@CacheConfig(cacheNames = {"wxUserTagsServiceCache"}) +@Slf4j +public class WxUserTagsServiceImpl implements WxUserTagsService { + @Autowired + private WxMpService wxMpService; + @Autowired + private WxUserService wxUserService; + public static final String CACHE_KEY="'WX_USER_TAGS'"; + + @Override + @Cacheable(key = CACHE_KEY+"+ #appid") + public List getWxTags(String appid) throws WxErrorException { + log.info("拉取公众号用户标签"); + wxMpService.switchoverTo(appid); + return wxMpService.getUserTagService().tagGet(); + } + + @Override + @CacheEvict(key = CACHE_KEY+"+ #appid") + public void creatTag(String appid, String name) throws WxErrorException { + wxMpService.switchoverTo(appid); + wxMpService.getUserTagService().tagCreate(name); + } + + @Override + @CacheEvict(key = CACHE_KEY+"+ #appid") + public void updateTag(String appid, Long tagid, String name) throws WxErrorException { + wxMpService.switchoverTo(appid); + wxMpService.getUserTagService().tagUpdate(tagid,name); + } + + @Override + @CacheEvict(key = CACHE_KEY+"+ #appid") + public void deleteTag(String appid, Long tagid) throws WxErrorException { + wxMpService.switchoverTo(appid); + wxMpService.getUserTagService().tagDelete(tagid); + } + + @Override + public void batchTagging(String appid, Long tagid, String[] openidList) throws WxErrorException { + wxMpService.switchoverTo(appid); + wxMpService.getUserTagService().batchTagging(tagid,openidList); + wxUserService.refreshUserInfoAsync(openidList,appid);//标签更新后更新对应用户信息 + } + + @Override + public void batchUnTagging(String appid, Long tagid, String[] openidList) throws WxErrorException { + wxMpService.switchoverTo(appid); + wxMpService.getUserTagService().batchUntagging(tagid,openidList); + wxUserService.refreshUserInfoAsync(openidList,appid);//标签更新后更新对应用户信息 + } + + @Override + public void tagging(Long tagid, String openid) throws WxErrorException { + wxMpService.getUserTagService().batchTagging(tagid,new String[]{openid}); + String appid = WxMpConfigStorageHolder.get(); + wxUserService.refreshUserInfo(openid,appid); + } + + @Override + public void untagging(Long tagid, String openid) throws WxErrorException { + wxMpService.getUserTagService().batchUntagging(tagid,new String[]{openid}); + String appid = WxMpConfigStorageHolder.get(); + wxUserService.refreshUserInfo(openid,appid); + } + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..203004f --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,11 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://${MYSQL_IP:localhost}:${MYSQL_PORT:3318}/wx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + username: ${MYSQL_USERNAME:gly} + password: ${MYSQL_PASSWORD:glyadmin} + +springfox: + documentation: + swagger-ui: + enabled: true \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..c6caaab --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,11 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3318/wx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + username: gly + password: glyadmin + +springfox: + documentation: + swagger-ui: + enabled: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4ff7c01 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,74 @@ +# Tomcat +server: + tomcat: + uri-encoding: UTF-8 + max-threads: 1000 + min-spare-threads: 30 + port: ${SERVER_PORT:8088} + connection-timeout: 5000ms + servlet: + context-path: /wx + +debug: false + +spring: + profiles: + active: ${PROFILE_ACTIVE:dev} #此处由maven的环境选择决定,参考:https://www.jianshu.com/p/b7c75b0c364c + # jackson时间格式化 + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + enabled: true + mvc: + throw-exception-if-no-handler-found: true + pathmatch: + matching-strategy: ant_path_matcher #解决springboot2.6与swagger兼容性问题 + task: + scheduling: + pool: + size: 5 + + +#mybatis +mybatis-plus: + mapper-locations: classpath*:/mapper/**/*.xml + #实体扫描,多个package用逗号或者分号分隔 + type-aliases-package: com.github.niefy.modules.*.entity + type-handlers-package: com.github.niefy.common.handler + global-config: + #数据库相关配置 + db-config: + #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; + id-type: AUTO + logic-delete-value: -1 + logic-not-delete-value: 0 + banner: false + #原生配置 + configuration: + map-underscore-to-camel-case: true + cache-enabled: true + call-setters-on-nulls: true + jdbc-type-for-null: 'null' + +renren: + # APP模块,是通过jwt认证的,如果要使用APP模块,则需要修改【加密秘钥】 + jwt: + # 加密秘钥 + secret: kF3`eF0|bD0!eA1#gE0!eE3_hD0@kJ0#[niefy@qq.com] + # token有效时长,7天,单位秒 + expire: 604800 + header: token + +wx: + mp: + # 自动回复消息发送间隔(毫秒),适当增加间隔可提升用户体验 + autoReplyInterval: 1000 + +task: + corePoolSize: 5 #核心线程数 + maximumPoolSize: 30 #最大线程数 + keepAliveTime: 60 #线程最大空闲时间 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..58fd5de --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n + + + + + INFO + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n + + + + + /usr/local/wx/logs/wx.%d.log + 100 + + + + + + + + diff --git a/src/main/resources/mapper/oss/SysOssDao.xml b/src/main/resources/mapper/oss/SysOssDao.xml new file mode 100644 index 0000000..fe245b8 --- /dev/null +++ b/src/main/resources/mapper/oss/SysOssDao.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/sys/SysConfigDao.xml b/src/main/resources/mapper/sys/SysConfigDao.xml new file mode 100644 index 0000000..b58b322 --- /dev/null +++ b/src/main/resources/mapper/sys/SysConfigDao.xml @@ -0,0 +1,15 @@ + + + + + + + update sys_config set param_value = #{paramValue} where param_key = #{paramKey} + + + + + + diff --git a/src/main/resources/mapper/sys/SysLogDao.xml b/src/main/resources/mapper/sys/SysLogDao.xml new file mode 100644 index 0000000..6a42cd0 --- /dev/null +++ b/src/main/resources/mapper/sys/SysLogDao.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/sys/SysMenuDao.xml b/src/main/resources/mapper/sys/SysMenuDao.xml new file mode 100644 index 0000000..7beb4ed --- /dev/null +++ b/src/main/resources/mapper/sys/SysMenuDao.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/src/main/resources/mapper/sys/SysRoleDao.xml b/src/main/resources/mapper/sys/SysRoleDao.xml new file mode 100644 index 0000000..e22e89d --- /dev/null +++ b/src/main/resources/mapper/sys/SysRoleDao.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/main/resources/mapper/sys/SysRoleMenuDao.xml b/src/main/resources/mapper/sys/SysRoleMenuDao.xml new file mode 100644 index 0000000..31cd88d --- /dev/null +++ b/src/main/resources/mapper/sys/SysRoleMenuDao.xml @@ -0,0 +1,24 @@ + + + + + + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + + + diff --git a/src/main/resources/mapper/sys/SysUserDao.xml b/src/main/resources/mapper/sys/SysUserDao.xml new file mode 100644 index 0000000..8a69eff --- /dev/null +++ b/src/main/resources/mapper/sys/SysUserDao.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/src/main/resources/mapper/sys/SysUserRoleDao.xml b/src/main/resources/mapper/sys/SysUserRoleDao.xml new file mode 100644 index 0000000..c45c373 --- /dev/null +++ b/src/main/resources/mapper/sys/SysUserRoleDao.xml @@ -0,0 +1,16 @@ + + + + + + + delete from sys_user_role where role_id in + + #{roleId} + + + + + diff --git a/src/main/resources/mapper/sys/SysUserTokenDao.xml b/src/main/resources/mapper/sys/SysUserTokenDao.xml new file mode 100644 index 0000000..609db2a --- /dev/null +++ b/src/main/resources/mapper/sys/SysUserTokenDao.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/main/resources/mapper/wx/ArticleMapper.xml b/src/main/resources/mapper/wx/ArticleMapper.xml new file mode 100644 index 0000000..e0203a0 --- /dev/null +++ b/src/main/resources/mapper/wx/ArticleMapper.xml @@ -0,0 +1,10 @@ + + + + + + + + UPDATE cms_article SET open_count=open_count+1 WHERE id=#{id} + + \ No newline at end of file diff --git a/src/main/resources/mapper/wx/MsgReplyRuleMapper.xml b/src/main/resources/mapper/wx/MsgReplyRuleMapper.xml new file mode 100644 index 0000000..c10f11a --- /dev/null +++ b/src/main/resources/mapper/wx/MsgReplyRuleMapper.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/wx/MsgTemplateMapper.xml b/src/main/resources/mapper/wx/MsgTemplateMapper.xml new file mode 100644 index 0000000..7ed7a19 --- /dev/null +++ b/src/main/resources/mapper/wx/MsgTemplateMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/wx/TemplateMsgLogMapper.xml b/src/main/resources/mapper/wx/TemplateMsgLogMapper.xml new file mode 100644 index 0000000..1032598 --- /dev/null +++ b/src/main/resources/mapper/wx/TemplateMsgLogMapper.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/wx/WxAccountMapper.xml b/src/main/resources/mapper/wx/WxAccountMapper.xml new file mode 100644 index 0000000..71c8895 --- /dev/null +++ b/src/main/resources/mapper/wx/WxAccountMapper.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/wx/WxMsgMapper.xml b/src/main/resources/mapper/wx/WxMsgMapper.xml new file mode 100644 index 0000000..7d53d80 --- /dev/null +++ b/src/main/resources/mapper/wx/WxMsgMapper.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/wx/WxQrCodeMapper.xml b/src/main/resources/mapper/wx/WxQrCodeMapper.xml new file mode 100644 index 0000000..2072b75 --- /dev/null +++ b/src/main/resources/mapper/wx/WxQrCodeMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/mapper/wx/WxUserMapper.xml b/src/main/resources/mapper/wx/WxUserMapper.xml new file mode 100644 index 0000000..be25c24 --- /dev/null +++ b/src/main/resources/mapper/wx/WxUserMapper.xml @@ -0,0 +1,12 @@ + + + + + + + UPDATE wx_user SET subscribe=0 WHERE openid=#{openid} + + + diff --git a/src/test/java/com/github/niefy/modules/wx/service/TemplateMsgServiceTest.java b/src/test/java/com/github/niefy/modules/wx/service/TemplateMsgServiceTest.java new file mode 100644 index 0000000..b5606a3 --- /dev/null +++ b/src/test/java/com/github/niefy/modules/wx/service/TemplateMsgServiceTest.java @@ -0,0 +1,42 @@ +package com.github.niefy.modules.wx.service; + +import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; +import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.List; + +/** + * 程序发送模板消息demo + */ +@SpringBootTest +class TemplateMsgServiceTest { + @Autowired + TemplateMsgService templateMsgService; + + /** + * 发送模板消息给用户 + * 添加消息模板指引:https://kf.qq.com/faq/170209E3InyI170209nIF7RJ.html + * 示例消息模板为:{{first.DATA}} ↵商品名称:{{keyword1.DATA}} ↵购买时间:{{keyword2.DATA}} ↵{{remark.DATA}} + */ + @Test + void sendTemplateMsg() { + String appid = WxMpConfigStorageHolder.get(); + List data = new ArrayList<>(); + data.add(new WxMpTemplateData("first","模板消息测试")); + data.add(new WxMpTemplateData("keywords1","xxxxx")); + data.add(new WxMpTemplateData("keywords2","xxxxx")); + data.add(new WxMpTemplateData("remark","点击查看消息详情")); + WxMpTemplateMessage wxMpTemplateMessage = WxMpTemplateMessage.builder() + .templateId("模板ID") + .url("跳转链接") + .toUser("用户openid") + .data(data) + .build(); + templateMsgService.sendTemplateMsg(wxMpTemplateMessage,appid); + } +}