From b918fad14f8ebb1420141735c2e99245b50f5a46 Mon Sep 17 00:00:00 2001 From: chendt <18902722133@163.com> Date: Thu, 31 Mar 2022 15:07:11 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/yami_shop.sql | 22 - .../src/components/mul-pic-upload/index.vue | 2 - pom.xml | 30 +- yami-shop-admin/pom.xml | 8 +- .../controller/AdminLoginController.java | 129 ++++++ .../admin/controller/AttributeController.java | 29 +- .../admin/controller/CategoryController.java | 30 +- .../admin/controller/HotSearchController.java | 31 +- .../admin/controller/IndexImgController.java | 2 +- .../admin/controller/NoticeController.java | 4 +- .../admin/controller/OrderController.java | 62 +-- .../admin/controller/PickAddrController.java | 33 +- .../admin/controller/ProdTagController.java | 4 +- .../admin/controller/ProductController.java | 5 +- .../controller/ShopDetailController.java | 30 +- .../shop/admin/controller/SpecController.java | 28 +- .../admin/controller/TransportController.java | 27 +- .../security/AdminAuthenticationToken.java | 36 -- .../admin/security/AdminTokenEnhancer.java | 31 -- .../security/LoginAuthenticationFilter.java | 154 ------- .../security/ResourceServerConfiguration.java | 57 --- .../YamiSysUserDetailsServiceImpl.java | 136 ------ .../src/main/resources/application-dev.yml | 4 +- .../src/main/resources/application-docker.yml | 4 +- .../src/main/resources/application-prod.yml | 4 +- .../resources/{ => logback}/logback-dev.xml | 0 .../resources/{ => logback}/logback-prod.xml | 0 .../resources/redisson}/redisson-docker.yml | 15 +- .../resources/{ => redisson}/redisson.yml | 15 +- yami-shop-api/pom.xml | 7 +- .../shop/api/controller/AddrController.java | 31 +- .../api/controller/MyOrderController.java | 31 +- .../shop/api/controller/OrderController.java | 31 +- .../shop/api/controller/PayController.java | 31 +- .../api/controller/PayNoticeController.java | 50 +- .../api/controller/ProdCommController.java | 4 +- .../api/controller/ShopCartController.java | 19 +- .../shop/api/controller/SmsController.java | 16 +- .../controller/UserCollectionController.java | 2 +- .../shop/api/controller/UserController.java | 42 +- .../controller/UserRegisterController.java | 215 ++------- .../api/listener/ConfirmOrderListener.java | 14 +- .../api/listener/SubmitOrderListener.java | 2 +- .../shop/api/security/ApiTokenEnhancer.java | 35 -- .../api/security/AuthenticationToken.java | 95 ---- .../security/LoginAuthenticationFilter.java | 151 ------ .../security/MiniAppAuthenticationToken.java | 27 -- .../security/ResourceServerConfiguration.java | 44 -- .../api/security/WebAuthenticationToken.java | 27 -- .../WebLoginAuthenticationFilter.java | 153 ------- .../YamiAuthenticationProcessingFilter.java | 154 ------- .../api/security/YamiUserServiceImpl.java | 176 ------- .../src/main/resources/application-dev.yml | 4 +- .../src/main/resources/application-docker.yml | 4 +- .../src/main/resources/application-prod.yml | 4 +- .../resources/{ => logback}/logback-dev.xml | 0 .../resources/{ => logback}/logback-prod.xml | 0 .../resources/redisson}/redisson-docker.yml | 13 +- .../resources/{ => redisson}/redisson.yml | 15 +- .../com/yami/shop/bean/enums/SendType.java | 66 +++ .../shop/bean/param/UserRegisterParam.java | 25 +- yami-shop-common/pom.xml | 8 +- .../config/DefaultExceptionHandlerConfig.java | 8 +- .../shop/common/config/RedisCacheConfig.java | 33 +- .../common/constants/OauthCacheNames.java | 33 ++ .../shop/common/enums/YamiHttpStatus.java | 3 + .../exception/YamiShopBindException.java | 7 + .../yami/shop/common/filter/FilterConfig.java | 38 -- .../yami/shop/common/filter/XssFilter.java | 19 +- .../yami/shop/common/handler/HttpHandler.java | 51 +++ .../shop/common/serializer/FSTSerializer.java | 107 ----- .../serializer/redis/FstRedisSerializer.java | 47 -- .../serializer/redis/KryoRedisSerializer.java | 74 +++ .../common/serializer/redisson/FstCodec.java | 104 ----- .../java/com/yami/shop/common/util/Json.java | 41 +- .../yami/shop/common/util/PageAdapter.java | 5 +- .../yami/shop/common/util/PrincipalUtil.java | 21 + .../com/yami/shop/common/util/RedisUtil.java | 83 ++-- .../yami/shop/common/util/SimpleCaptcha.java | 82 ---- yami-shop-mp/pom.xml | 21 - .../yami/shop/mp/builder/AbstractBuilder.java | 27 -- .../yami/shop/mp/builder/ImageBuilder.java | 34 -- .../com/yami/shop/mp/builder/TextBuilder.java | 33 -- .../shop/mp/component/WxMaInRedisConfig.java | 156 ------- .../mp/component/WxMaServiceClusterImpl.java | 72 --- .../component/WxMpInRedisConfigStorage.java | 104 ----- .../mp/component/WxMpServiceClusterImpl.java | 70 --- .../shop/mp/config/WxMaConfiguration.java | 46 -- .../shop/mp/config/WxMpConfiguration.java | 61 --- .../shop/mp/config/WxPayConfiguration.java | 87 ---- .../yami/shop/mp/config/bean/WxMiniApp.java | 34 -- .../com/yami/shop/mp/config/bean/WxMp.java | 43 -- .../com/yami/shop/mp/config/bean/WxPay.java | 45 -- .../mp/controller/api/WxPortalController.java | 116 ----- .../yami/shop/mp/handler/AbstractHandler.java | 22 - .../com/yami/shop/mp/handler/MenuHandler.java | 46 -- yami-shop-mp/src/main/resources/ma.properties | 2 - yami-shop-mp/src/main/resources/mp.properties | 4 - .../src/main/resources/pay.properties | 3 - yami-shop-mp/src/main/resources/xxx.p12 | 0 yami-shop-quartz/pom.xml | 2 +- .../controller/ScheduleJobController.java | 23 +- .../shop/quartz/util/SpringBeanTaskUtil.java | 2 +- yami-shop-security/pom.xml | 20 +- .../config/AuthorizationServerConfig.java | 76 --- .../shop/security/config/TokenConfig.java | 66 --- .../security/constants/SecurityConstants.java | 21 - .../controller/SysLoginController.java | 70 --- .../shop/security/dao/AppConnectMapper.java | 26 -- .../com/yami/shop/security/enums/App.java | 50 -- .../exception/BadCredentialsException.java | 25 - .../BadCredentialsExceptionBase.java | 25 - .../exception/BaseYamiAuth2Exception.java | 32 -- .../exception/ImageCodeNotMatchException.java | 23 - .../ImageCodeNotMatchExceptionBase.java | 23 - .../exception/UnauthorizedException.java | 38 -- .../exception/UnauthorizedExceptionBase.java | 38 -- .../exception/UnknownGrantTypeException.java | 36 -- .../UnknownGrantTypeExceptionBase.java | 36 -- .../exception/UsernameNotFoundException.java | 23 - .../UsernameNotFoundExceptionBase.java | 23 - .../security/exception/WxErrorException.java | 25 - .../exception/WxErrorExceptionBase.java | 25 - .../exception/YamiAuth2Exception.java | 32 -- .../handler/LoginAuthFailedHandler.java | 54 --- .../handler/LoginAuthSuccessHandler.java | 85 ---- .../yami/shop/security/model/AppConnect.java | 63 --- ...ractUserDetailsAuthenticationProvider.java | 59 --- .../provider/AuthenticationTokenParser.java | 13 - .../provider/MpAuthenticationProvider.java | 109 ----- .../security/service/AppConnectService.java | 29 -- .../service/YamiClientDetailsService.java | 32 -- .../shop/security/service/YamiSysUser.java | 45 -- .../yami/shop/security/service/YamiUser.java | 114 ----- .../service/YamiUserDetailsService.java | 53 --- .../service/impl/AppConnectServiceImpl.java | 105 ----- .../security/token/MpAuthenticationToken.java | 26 -- .../security/token/MyAuthenticationToken.java | 73 --- .../shop/security/util/SecurityUtils.java | 58 --- .../shop/security/util/YamiTokenServices.java | 433 ------------------ .../resources/mapper/AppConnectMapper.xml | 27 -- .../yami-shop-security-admin/pom.xml | 24 + .../admin/adapter/ResourceServerAdapter.java | 29 ++ .../admin/dto/CaptchaAuthenticationDTO.java | 17 + .../security/admin/model/YamiSysUser.java | 36 ++ .../security/admin/util/SecurityUtils.java | 38 ++ .../yami-shop-security-api/pom.xml | 23 + .../api/adapter/ResourceServerAdapter.java | 20 + .../api/controller/LoginController.java | 80 ++++ .../shop/security/api/model/YamiUser.java | 39 ++ .../shop/security/api/util/SecurityUtils.java | 44 ++ .../yami-shop-security-common/pom.xml | 33 ++ .../common/adapter/AuthConfigAdapter.java | 27 ++ .../adapter/CaptchaCacheServiceRedisImpl.java | 36 ++ .../adapter/DefaultAuthConfigAdapter.java | 29 ++ .../MallWebSecurityConfigurerAdapter.java | 25 + .../shop/security/common/bo/TokenInfoBO.java | 38 ++ .../security/common/bo/UserInfoInTokenBO.java | 68 +++ .../security/common/config/AuthConfig.java | 61 +++ .../security/common/config/CaptchaConfig.java | 76 +++ .../security/common}/config/CorsConfig.java | 2 +- .../common/config/PasswordConfig.java} | 23 +- .../common/controller/CaptchaController.java | 54 +++ .../common/controller/LogoutController.java | 45 ++ .../common/controller/TokenController.java | 48 ++ .../common/dto/AuthenticationDTO.java | 40 ++ .../security/common/dto/RefreshTokenDTO.java | 44 ++ .../security/common/enums/SysTypeEnum.java | 40 ++ .../security/common/filter/AuthFilter.java | 107 +++++ .../common/manager/PasswordCheckManager.java | 62 +++ .../common/manager/PasswordManager.java | 48 ++ .../security/common/manager/TokenStore.java | 331 +++++++++++++ .../common}/permission/PermissionService.java | 17 +- .../security/common/util/AuthUserContext.java | 37 ++ .../shop/security/common/vo/TokenInfoVO.java | 32 ++ ...m.anji.captcha.service.CaptchaCacheService | 1 + .../src/main/resources/captcha/original/1.png | Bin 0 -> 36206 bytes .../src/main/resources/captcha/original/2.png | Bin 0 -> 43926 bytes .../src/main/resources/captcha/original/3.png | Bin 0 -> 41195 bytes .../src/main/resources/captcha/original/4.png | Bin 0 -> 52213 bytes .../src/main/resources/captcha/original/5.png | Bin 0 -> 27608 bytes .../src/main/resources/captcha/original/6.png | Bin 0 -> 59340 bytes .../main/resources/captcha/slidingBlock/1.png | Bin 0 -> 22049 bytes .../main/resources/captcha/slidingBlock/2.png | Bin 0 -> 21367 bytes .../main/resources/captcha/slidingBlock/3.png | Bin 0 -> 21660 bytes .../main/resources/captcha/slidingBlock/4.png | Bin 0 -> 21923 bytes .../com/yami/shop/dao/ProdPropMapper.java | 2 +- .../java/com/yami/shop/dao/UserMapper.java | 5 +- .../com/yami/shop/service/SmsLogService.java | 8 +- .../com/yami/shop/service/UserService.java | 3 - .../shop/service/impl/MyOrderServiceImpl.java | 17 +- .../shop/service/impl/SmsLogServiceImpl.java | 47 -- .../shop/service/impl/UserServiceImpl.java | 17 - .../main/resources/mapper/ProdPropMapper.xml | 2 +- .../src/main/resources/mapper/UserMapper.xml | 9 +- yami-shop-sys/pom.xml | 3 +- .../yami/shop/sys/aspect/SysLogAspect.java | 23 +- .../sys/controller/SysMenuController.java | 39 +- .../sys/controller/SysUserController.java | 60 ++- .../com/yami/shop/sys/dao/SysUserMapper.java | 4 +- .../java/com/yami/shop/sys/model/SysUser.java | 1 - .../yami/shop/sys/service/SysUserService.java | 8 + .../sys/service/impl/SysUserServiceImpl.java | 8 +- 203 files changed, 2512 insertions(+), 5766 deletions(-) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java delete mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminAuthenticationToken.java delete mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminTokenEnhancer.java delete mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/security/LoginAuthenticationFilter.java delete mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/security/ResourceServerConfiguration.java delete mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/security/YamiSysUserDetailsServiceImpl.java rename yami-shop-admin/src/main/resources/{ => logback}/logback-dev.xml (100%) rename yami-shop-admin/src/main/resources/{ => logback}/logback-prod.xml (100%) rename {yami-shop-api/src/main/resources => yami-shop-admin/src/main/resources/redisson}/redisson-docker.yml (77%) rename yami-shop-admin/src/main/resources/{ => redisson}/redisson.yml (77%) delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/ApiTokenEnhancer.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/AuthenticationToken.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/LoginAuthenticationFilter.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/MiniAppAuthenticationToken.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/ResourceServerConfiguration.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/WebAuthenticationToken.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/WebLoginAuthenticationFilter.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/YamiAuthenticationProcessingFilter.java delete mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/security/YamiUserServiceImpl.java rename yami-shop-api/src/main/resources/{ => logback}/logback-dev.xml (100%) rename yami-shop-api/src/main/resources/{ => logback}/logback-prod.xml (100%) rename {yami-shop-admin/src/main/resources => yami-shop-api/src/main/resources/redisson}/redisson-docker.yml (80%) rename yami-shop-api/src/main/resources/{ => redisson}/redisson.yml (77%) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/enums/SendType.java create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java delete mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/filter/FilterConfig.java create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java delete mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/serializer/FSTSerializer.java delete mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/FstRedisSerializer.java create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/KryoRedisSerializer.java delete mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/serializer/redisson/FstCodec.java delete mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/SimpleCaptcha.java delete mode 100644 yami-shop-mp/pom.xml delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/builder/AbstractBuilder.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/builder/ImageBuilder.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/builder/TextBuilder.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaInRedisConfig.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaServiceClusterImpl.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpInRedisConfigStorage.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpServiceClusterImpl.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMaConfiguration.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMpConfiguration.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxPayConfiguration.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMiniApp.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMp.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxPay.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/controller/api/WxPortalController.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/handler/AbstractHandler.java delete mode 100644 yami-shop-mp/src/main/java/com/yami/shop/mp/handler/MenuHandler.java delete mode 100644 yami-shop-mp/src/main/resources/ma.properties delete mode 100644 yami-shop-mp/src/main/resources/mp.properties delete mode 100644 yami-shop-mp/src/main/resources/pay.properties delete mode 100644 yami-shop-mp/src/main/resources/xxx.p12 delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/config/AuthorizationServerConfig.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/config/TokenConfig.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/constants/SecurityConstants.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/controller/SysLoginController.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/dao/AppConnectMapper.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/enums/App.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsException.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsExceptionBase.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/BaseYamiAuth2Exception.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchException.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchExceptionBase.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedException.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedExceptionBase.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeException.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeExceptionBase.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundException.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundExceptionBase.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorException.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorExceptionBase.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/exception/YamiAuth2Exception.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthFailedHandler.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthSuccessHandler.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/model/AppConnect.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/provider/AbstractUserDetailsAuthenticationProvider.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/provider/AuthenticationTokenParser.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/provider/MpAuthenticationProvider.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/service/AppConnectService.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/service/YamiClientDetailsService.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/service/YamiSysUser.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUser.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUserDetailsService.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/service/impl/AppConnectServiceImpl.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/token/MpAuthenticationToken.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/token/MyAuthenticationToken.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/util/SecurityUtils.java delete mode 100644 yami-shop-security/src/main/java/com/yami/shop/security/util/YamiTokenServices.java delete mode 100644 yami-shop-security/src/main/resources/mapper/AppConnectMapper.xml create mode 100644 yami-shop-security/yami-shop-security-admin/pom.xml create mode 100644 yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/adapter/ResourceServerAdapter.java create mode 100644 yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java create mode 100644 yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/model/YamiSysUser.java create mode 100644 yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/util/SecurityUtils.java create mode 100644 yami-shop-security/yami-shop-security-api/pom.xml create mode 100644 yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/adapter/ResourceServerAdapter.java create mode 100644 yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java create mode 100644 yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/model/YamiUser.java create mode 100644 yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/util/SecurityUtils.java create mode 100644 yami-shop-security/yami-shop-security-common/pom.xml create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/TokenInfoBO.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/UserInfoInTokenBO.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java rename yami-shop-security/{src/main/java/com/yami/shop/security => yami-shop-security-common/src/main/java/com/yami/shop/security/common}/config/CorsConfig.java (96%) rename yami-shop-security/{src/main/java/com/yami/shop/security/config/WebSecurityConfig.java => yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/PasswordConfig.java} (53%) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/TokenController.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/RefreshTokenDTO.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/enums/SysTypeEnum.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordCheckManager.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java rename yami-shop-security/{src/main/java/com/yami/shop/security => yami-shop-security-common/src/main/java/com/yami/shop/security/common}/permission/PermissionService.java (62%) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/vo/TokenInfoVO.java create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/2.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/3.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/4.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/5.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/6.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/1.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/2.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/3.png create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/4.png diff --git a/db/yami_shop.sql b/db/yami_shop.sql index 6b8fdc2..6845776 100644 --- a/db/yami_shop.sql +++ b/db/yami_shop.sql @@ -233,28 +233,6 @@ CREATE TABLE `qrtz_triggers` ( /*Data for the table `qrtz_triggers` */ -/*Table structure for table `tz_app_connect` */ - -DROP TABLE IF EXISTS `tz_app_connect`; - -CREATE TABLE `tz_app_connect` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', - `user_id` varchar(36) NOT NULL COMMENT '本系统userId', - `app_id` tinyint(2) DEFAULT NULL COMMENT '第三方系统id 1:微信小程序', - `nick_name` varchar(64) DEFAULT NULL COMMENT '第三方系统昵称', - `image_url` varchar(500) DEFAULT NULL COMMENT '第三方系统头像', - `biz_user_id` varchar(255) DEFAULT NULL COMMENT '第三方系统userid', - `biz_unionid` varchar(255) DEFAULT NULL COMMENT '第三方系统unionid', - PRIMARY KEY (`id`), - KEY `user_app_id` (`user_id`,`app_id`) COMMENT '用户id和appid联合索引' -) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8; - -/*Data for the table `tz_app_connect` */ - -insert into `tz_app_connect`(`id`,`user_id`,`app_id`,`nick_name`,`image_url`,`biz_user_id`,`biz_unionid`) values -(48,'51540df5255e4d22903b0f83921095ff',1,NULL,NULL,'o-lgc5CUDIn2nkk8512hKumBnjMI','o92Yz1cLnHuo70epfneTG8SaRY0c'), -(49,'5f159317be5b4dc4bf3188f1a3da0369',1,NULL,NULL,'o-lgc5IHLX-RuR1aw5qwP9bpGDuQ','o92Yz1bmhLV8CKMwQkuPk5C8lFfg'); - /*Table structure for table `tz_area` */ DROP TABLE IF EXISTS `tz_area`; diff --git a/mall4v/src/components/mul-pic-upload/index.vue b/mall4v/src/components/mul-pic-upload/index.vue index 4e0f127..d7f50c3 100644 --- a/mall4v/src/components/mul-pic-upload/index.vue +++ b/mall4v/src/components/mul-pic-upload/index.vue @@ -79,5 +79,3 @@ } - diff --git a/pom.xml b/pom.xml index dfe78c6..7406b4d 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,6 @@ yami-shop-service yami-shop-security yami-shop-quartz - yami-shop-mp @@ -24,25 +23,24 @@ UTF-8 UTF-8 3.7.0 - 2.3.6.RELEASE - 2.3.4.RELEASE + 2.3.12.RELEASE 1.8 28.2-jre - 4.5.0 + 5.7.15 1.11.3 3.17 7.2.18 3.5.0 1.5.4 2.9.2 - 1.9.3 + 1.9.6 4.0.0 4.3.9 1.1.0 3.1.0 - 3.10.6 - 2.57 - + 3.12.5 + 4.0.2 + 2.12.1 2.17.1 @@ -55,11 +53,6 @@ pom import - - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure - ${security.oauth.auto.version} - com.github.binarywang weixin-java-pay @@ -138,9 +131,9 @@ - de.ruedigermoeller - fst - ${fst.version} + com.esotericsoftware + kryo + ${kryo.version} org.apache.logging.log4j @@ -157,6 +150,11 @@ log4j-api ${log4j.version} + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + diff --git a/yami-shop-admin/pom.xml b/yami-shop-admin/pom.xml index d709d47..6140292 100644 --- a/yami-shop-admin/pom.xml +++ b/yami-shop-admin/pom.xml @@ -7,7 +7,6 @@ com.yami.shop yami-shop 0.0.1-SNAPSHOT - ../pom.xml @@ -29,7 +28,7 @@ com.yami.shop - yami-shop-security + yami-shop-security-admin ${yami.shop.version} @@ -37,11 +36,6 @@ yami-shop-quartz ${yami.shop.version} - - com.yami.shop - yami-shop-mp - ${yami.shop.version} - diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java new file mode 100644 index 0000000..1ccb566 --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.admin.controller; + +import cn.hutool.core.util.StrUtil; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaService; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.security.admin.dto.CaptchaAuthenticationDTO; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.enums.SysTypeEnum; +import com.yami.shop.security.common.manager.PasswordCheckManager; +import com.yami.shop.security.common.manager.PasswordManager; +import com.yami.shop.security.common.manager.TokenStore; +import com.yami.shop.security.common.vo.TokenInfoVO; +import com.yami.shop.sys.constant.Constant; +import com.yami.shop.sys.model.SysMenu; +import com.yami.shop.sys.model.SysUser; +import com.yami.shop.sys.service.SysMenuService; +import com.yami.shop.sys.service.SysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author FrozenWatermelon + * @date 2020/6/30 + */ +@RestController +@Api(tags = "登录") +public class AdminLoginController { + + @Autowired + private TokenStore tokenStore; + + @Autowired + private SysUserService sysUserService; + + @Autowired + private SysMenuService sysMenuService; + + @Autowired + private PasswordCheckManager passwordCheckManager; + + @Autowired + private CaptchaService captchaService; + + @Autowired + private PasswordManager passwordManager; + + @PostMapping("/adminLogin") + @ApiOperation(value = "账号密码 + 验证码登录(用于后台登录)", notes = "通过账号/手机号/用户名密码登录") + public ResponseEntity login( + @Valid @RequestBody CaptchaAuthenticationDTO captchaAuthenticationDTO) { + // 登陆后台登录需要再校验一遍验证码 + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaVerification(captchaAuthenticationDTO.getCaptchaVerification()); + ResponseModel response = captchaService.verification(captchaVO); + if (!response.isSuccess()) { + return ResponseEntity.badRequest().body("验证码有误或已过期"); + } + + SysUser sysUser = sysUserService.getByUserName(captchaAuthenticationDTO.getUserName()); + if (sysUser == null) { + throw new YamiShopBindException("账号或密码不正确"); + } + + // 半小时内密码输入错误十次,已限制登录30分钟 + String decryptPassword = passwordManager.decryptPassword(captchaAuthenticationDTO.getPassWord()); + passwordCheckManager.checkPassword(SysTypeEnum.ADMIN,captchaAuthenticationDTO.getUserName(), decryptPassword, sysUser.getPassword()); + + // 不是店铺超级管理员,并且是禁用状态,无法登录 + if (Objects.equals(sysUser.getStatus(),0)) { + // 未找到此用户信息 + throw new YamiShopBindException("未找到此用户信息"); + } + + UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO(); + userInfoInToken.setUserId(String.valueOf(sysUser.getUserId())); + userInfoInToken.setSysType(SysTypeEnum.ADMIN.value()); + userInfoInToken.setEnabled(sysUser.getStatus() == 1); + userInfoInToken.setPerms(getUserPermissions(sysUser.getUserId())); + userInfoInToken.setNickName(sysUser.getUsername()); + userInfoInToken.setShopId(sysUser.getShopId()); + // 存储token返回vo + TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken); + return ResponseEntity.ok(tokenInfoVO); + } + + + private Set getUserPermissions(Long userId) { + List permsList; + + //系统管理员,拥有最高权限 + if(userId == Constant.SUPER_ADMIN_ID){ + List menuList = sysMenuService.list(Wrappers.emptyWrapper()); + permsList = menuList.stream().map(SysMenu::getPerms).collect(Collectors.toList()); + }else{ + permsList = sysUserService.queryAllPerms(userId); + } + return permsList.stream().flatMap((perms)->{ + if (StrUtil.isBlank(perms)) { + return null; + } + return Arrays.stream(perms.trim().split(StrUtil.COMMA)); + } + ).collect(Collectors.toSet()); + } +} diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java index 6b7b232..4301e98 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AttributeController.java @@ -10,31 +10,20 @@ package com.yami.shop.admin.controller; -import java.util.Objects; - -import javax.validation.Valid; - +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.bean.enums.ProdPropRule; +import com.yami.shop.bean.model.ProdProp; +import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.PageParam; -import com.yami.shop.common.enums.YamiHttpStatus; - -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.ProdPropService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - +import org.springframework.web.bind.annotation.*; -import com.yami.shop.bean.enums.ProdPropRule; -import com.yami.shop.bean.model.ProdProp; -import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.service.ProdPropService; -import com.baomidou.mybatisplus.core.metadata.IPage; +import javax.validation.Valid; +import java.util.Objects; /** * 参数管理 diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java index c1490df..188f2b7 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java @@ -10,34 +10,20 @@ package com.yami.shop.admin.controller; -import java.util.Date; -import java.util.List; -import java.util.Objects; - - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yami.shop.bean.model.Category; +import com.yami.shop.common.annotation.SysLog; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; - -import com.yami.shop.bean.model.Category; -import com.yami.shop.common.annotation.SysLog; -import com.yami.shop.service.BrandService; -import com.yami.shop.service.CategoryService; -import com.yami.shop.service.ProdPropService; - -import cn.hutool.core.collection.CollectionUtil; +import java.util.Date; +import java.util.List; +import java.util.Objects; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java index a1f43b0..2c382dd 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/HotSearchController.java @@ -10,34 +10,21 @@ package com.yami.shop.admin.controller; -import javax.validation.Valid; -import java.util.Date; -import java.util.List; - - import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.security.util.SecurityUtils; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.bean.model.HotSearch; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.HotSearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; - - - - +import org.springframework.web.bind.annotation.*; -import com.yami.shop.service.HotSearchService; -import com.yami.shop.bean.model.HotSearch; +import javax.validation.Valid; +import java.util.Date; +import java.util.List; /** * diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java index f68f794..8b6c466 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/IndexImgController.java @@ -15,7 +15,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.yami.shop.bean.model.IndexImg; import com.yami.shop.bean.model.Product; import com.yami.shop.common.util.PageParam; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.service.IndexImgService; import com.yami.shop.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java index 17b84de..8935292 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/NoticeController.java @@ -12,10 +12,10 @@ package com.yami.shop.admin.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.common.util.PageParam; import com.yami.shop.bean.model.Notice; import com.yami.shop.common.annotation.SysLog; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.service.NoticeService; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java index 95bfb6c..3e8bf89 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java @@ -10,49 +10,42 @@ package com.yami.shop.admin.controller; -import java.io.IOException; -import java.util.Arrays; -import java.util.Date; -import java.util.List; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; - +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.poi.excel.ExcelUtil; +import cn.hutool.poi.excel.ExcelWriter; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.google.common.base.Objects; +import com.yami.shop.bean.enums.OrderStatus; +import com.yami.shop.bean.model.Order; +import com.yami.shop.bean.model.OrderItem; +import com.yami.shop.bean.model.UserAddrOrder; +import com.yami.shop.bean.param.DeliveryOrderParam; import com.yami.shop.bean.param.OrderParam; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.service.*; +import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Sheet; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.*; - -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.google.common.base.Objects; -import com.yami.shop.bean.enums.OrderStatus; -import com.yami.shop.bean.model.Order; -import com.yami.shop.bean.model.OrderItem; -import com.yami.shop.bean.model.UserAddrOrder; -import com.yami.shop.bean.param.DeliveryOrderParam; - -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.poi.excel.ExcelUtil; -import cn.hutool.poi.excel.ExcelWriter; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.List; /** * @author lgh on 2018/09/15. */ +@Slf4j @Controller @RequestMapping("/order/order") public class OrderController { @@ -282,16 +275,9 @@ public class OrderController { writer.flush(servletOutputStream); servletOutputStream.flush(); } catch (IORuntimeException | IOException e) { - e.printStackTrace(); + log.error("写出Excel错误:", e); } finally { - writer.close(); - try { - if (servletOutputStream != null) { - servletOutputStream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } + IoUtil.close(writer); } } } diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java index 9416485..36bc60e 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java @@ -10,36 +10,23 @@ package com.yami.shop.admin.controller; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import javax.validation.Valid; - - +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.bean.model.PickAddr; import com.yami.shop.common.enums.YamiHttpStatus; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.PickAddrService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; - -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.bean.model.PickAddr; -import com.yami.shop.service.PickAddrService; - -import cn.hutool.core.util.StrUtil; +import javax.validation.Valid; +import java.util.Arrays; +import java.util.Objects; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java index ffef4c9..e2277d4 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProdTagController.java @@ -14,11 +14,11 @@ package com.yami.shop.admin.controller; import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.common.util.PageParam; import com.yami.shop.bean.model.ProdTag; import com.yami.shop.common.annotation.SysLog; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.service.ProdTagService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java index 67ba873..e921f8a 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ProductController.java @@ -14,14 +14,13 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.common.util.PageParam; import com.yami.shop.bean.model.Product; import com.yami.shop.bean.model.Sku; import com.yami.shop.bean.param.ProductParam; -import com.yami.shop.common.enums.YamiHttpStatus; import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.Json; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.service.BasketService; import com.yami.shop.service.ProdTagReferenceService; import com.yami.shop.service.ProductService; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java index 8e7d6b7..45b3482 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/ShopDetailController.java @@ -10,31 +10,25 @@ package com.yami.shop.admin.controller; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -import javax.validation.Valid; - - +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.security.util.SecurityUtils; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.bean.model.ShopDetail; +import com.yami.shop.bean.param.ShopDetailParam; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.ShopDetailService; +import ma.glasnost.orika.MapperFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; - -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; - -import com.yami.shop.bean.model.ShopDetail; -import com.yami.shop.bean.param.ShopDetailParam; -import com.yami.shop.service.ShopDetailService; - -import cn.hutool.core.util.StrUtil; -import ma.glasnost.orika.MapperFacade; +import javax.validation.Valid; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java index b1ae4be..e4976d8 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/SpecController.java @@ -10,36 +10,24 @@ package com.yami.shop.admin.controller; -import java.util.List; -import java.util.Objects; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.common.enums.YamiHttpStatus; - -import com.yami.shop.security.util.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.yami.shop.common.util.PageParam; import com.baomidou.mybatisplus.core.metadata.IPage; - import com.yami.shop.bean.enums.ProdPropRule; import com.yami.shop.bean.model.ProdProp; import com.yami.shop.bean.model.ProdPropValue; import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.service.ProdPropService; import com.yami.shop.service.ProdPropValueService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import java.util.List; +import java.util.Objects; /** * 规格管理 diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java index 305d0b3..edbfdf5 100644 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java @@ -10,31 +10,20 @@ package com.yami.shop.admin.controller; -import java.util.Date; -import java.util.List; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.security.util.SecurityUtils; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.bean.model.Transport; +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.service.TransportService; import org.apache.commons.lang3.StringUtils; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; - -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; - -import com.yami.shop.bean.model.Transport; -import com.yami.shop.service.TransportService; +import java.util.Date; +import java.util.List; /** * @author lgh on 2018/11/16. diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminAuthenticationToken.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminAuthenticationToken.java deleted file mode 100644 index 2f26350..0000000 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminAuthenticationToken.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.admin.security; - -import com.yami.shop.security.token.MyAuthenticationToken; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * 系统用户账号密码登陆 - */ -@Getter -@Setter -@NoArgsConstructor -public class AdminAuthenticationToken extends MyAuthenticationToken { - - private String sessionUUID; - - private String imageCode; - - public AdminAuthenticationToken(UserDetails principal, Object credentials) { - super(principal, credentials, principal.getAuthorities()); - } - - -} diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminTokenEnhancer.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminTokenEnhancer.java deleted file mode 100644 index a554b96..0000000 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/AdminTokenEnhancer.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.yami.shop.admin.security; - -import com.yami.shop.security.service.YamiSysUser; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -/** - * token增强 - * @author LGH - */ -@Component -public class AdminTokenEnhancer implements TokenEnhancer { - - - @Override - public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { - final Map additionalInfo = new HashMap<>(8); - YamiSysUser yamiSysUser = (YamiSysUser) authentication.getUserAuthentication().getPrincipal(); - additionalInfo.put("shopId", yamiSysUser.getShopId()); - additionalInfo.put("userId", yamiSysUser.getUserId()); - additionalInfo.put("authorities", yamiSysUser.getAuthorities()); - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); - return accessToken; - } -} diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/LoginAuthenticationFilter.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/security/LoginAuthenticationFilter.java deleted file mode 100644 index 99cd6ca..0000000 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/LoginAuthenticationFilter.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.admin.security; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import com.yami.shop.common.util.Json; -import com.yami.shop.common.util.RedisUtil; -import com.yami.shop.security.constants.SecurityConstants; -import com.yami.shop.security.exception.BadCredentialsExceptionBase; -import com.yami.shop.security.exception.ImageCodeNotMatchExceptionBase; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.provider.AuthenticationTokenParser; -import com.yami.shop.security.service.YamiUserDetailsService; -import lombok.AllArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 管理员登陆: - * post: http://127.0.0.1:8085/login - * {principal:username,credentials:password} - */ -@Component -public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - - private YamiUserDetailsService yamiUserDetailsService; - - private PasswordEncoder passwordEncoder; - - @Autowired - public LoginAuthenticationFilter(YamiUserDetailsService yamiUserDetailsService, PasswordEncoder passwordEncoder) { - super("/login"); - this.yamiUserDetailsService = yamiUserDetailsService; - this.passwordEncoder = passwordEncoder; - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - if (!ServletUtil.METHOD_POST.equals(request.getMethod())) { - throw new AuthenticationServiceException( - "Authentication method not supported: " + request.getMethod()); - } - String requestBody = getStringFromStream(request); - - if (StrUtil.isBlank(requestBody)) { - throw new AuthenticationServiceException("无法获取输入信息"); - } - AdminAuthenticationToken adminAuthenticationToken = Json.parseObject(requestBody, AdminAuthenticationToken.class); - - - String username = adminAuthenticationToken.getPrincipal() == null?"NONE_PROVIDED":adminAuthenticationToken.getName(); - - - String kaptchaKey = SecurityConstants.SPRING_SECURITY_RESTFUL_IMAGE_CODE + adminAuthenticationToken.getSessionUUID(); - - String kaptcha = RedisUtil.get(kaptchaKey); - - RedisUtil.del(kaptchaKey); - - if(StrUtil.isBlank(adminAuthenticationToken.getImageCode()) || !adminAuthenticationToken.getImageCode().equalsIgnoreCase(kaptcha)){ - throw new ImageCodeNotMatchExceptionBase("验证码有误"); - } - - UserDetails user; - try { - user = yamiUserDetailsService.loadUserByUsername(username); - } catch (UsernameNotFoundExceptionBase var6) { - throw new UsernameNotFoundExceptionBase("账号或密码不正确"); - } - - String encodedPassword = user.getPassword(); - String rawPassword = adminAuthenticationToken.getCredentials().toString(); - - // 密码不正确 - if (!passwordEncoder.matches(rawPassword,encodedPassword)){ - throw new BadCredentialsExceptionBase("账号或密码不正确"); - } - - if (!user.isEnabled()) { - throw new UsernameNotFoundExceptionBase("账号已被锁定,请联系管理员"); - } - AdminAuthenticationToken result = new AdminAuthenticationToken(user, adminAuthenticationToken.getCredentials()); - result.setDetails(adminAuthenticationToken.getDetails()); - return result; - } - - - private String getStringFromStream(HttpServletRequest req) { - ServletInputStream is; - try { - is = req.getInputStream(); - int nRead = 1; - int nTotalRead = 0; - byte[] bytes = new byte[10240]; - while (nRead > 0) { - nRead = is.read(bytes, nTotalRead, bytes.length - nTotalRead); - if (nRead > 0) { - nTotalRead = nTotalRead + nRead; - } - } - return new String(bytes, 0, nTotalRead, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - @Override - @Autowired - public void setAuthenticationManager(AuthenticationManager authenticationManager) { - super.setAuthenticationManager(authenticationManager); - } - - @Override - @Autowired - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) { - super.setAuthenticationSuccessHandler(successHandler); - } - - @Override - @Autowired - public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) { - super.setAuthenticationFailureHandler(failureHandler); - } - -} diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/ResourceServerConfiguration.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/security/ResourceServerConfiguration.java deleted file mode 100644 index 0cdd65d..0000000 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/ResourceServerConfiguration.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.admin.security; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsUtils; - -@Configuration -@EnableResourceServer -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { - - @Autowired - private LoginAuthenticationFilter loginAuthenticationFilter; - - @Override - public void configure(HttpSecurity http) throws Exception { - // @formatter:off - http - .addFilterBefore(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .csrf().disable().cors() - .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) - .and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .and().requestMatchers().anyRequest() - .and().anonymous() - .and().authorizeRequests() - .antMatchers( - "/webjars/**", - "/swagger/**", - "/v2/api-docs", - "/doc.html", - "/swagger-ui.html", - "/swagger-resources/**", - "/captcha.jpg").permitAll() - .and() - .authorizeRequests() - .antMatchers("/**").authenticated();//配置所有访问控制,必须认证过后才可以访问 - // @formatter:on - } - - -} diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/YamiSysUserDetailsServiceImpl.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/security/YamiSysUserDetailsServiceImpl.java deleted file mode 100644 index 7152fba..0000000 --- a/yami-shop-admin/src/main/java/com/yami/shop/admin/security/YamiSysUserDetailsServiceImpl.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.admin.security; - -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.yami.shop.bean.model.User; -import com.yami.shop.common.util.CacheManagerUtil; -import com.yami.shop.sys.constant.Constant; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.YamiSysUser; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.service.YamiUserDetailsService; -import com.yami.shop.sys.dao.SysMenuMapper; -import com.yami.shop.sys.dao.SysUserMapper; -import com.yami.shop.sys.model.SysMenu; -import com.yami.shop.sys.model.SysUser; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.stereotype.Service; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * 用户详细信息 - * - * @author - */ -@Slf4j -@Service -@AllArgsConstructor -public class YamiSysUserDetailsServiceImpl implements YamiUserDetailsService { - private final SysMenuMapper sysMenuMapper; - private final SysUserMapper sysUserMapper; - private final CacheManagerUtil cacheManagerUtil; - - /** - * 用户密码登录 - * - * @param username 用户名 - * @return - * @throws UsernameNotFoundExceptionBase - */ - @Override - @SneakyThrows - public YamiSysUser loadUserByUsername(String username) { - return getUserDetails(username); - } - - - /** - * 构建userdetails - * - * @param username 用户名称 - * @return - */ - private YamiSysUser getUserDetails(String username) { - SysUser sysUser = sysUserMapper.selectByUsername(username); - - if (sysUser == null) { - throw new UsernameNotFoundExceptionBase("用户不存在"); - } - - Collection authorities - = AuthorityUtils.createAuthorityList(getUserPermissions(sysUser.getUserId()).toArray(new String[0])); - // 构造security用户 - return new YamiSysUser(sysUser.getUserId(), sysUser.getShopId(), sysUser.getUsername(), sysUser.getPassword(), sysUser.getStatus() == 1, - true, true, true , authorities); - } - - private Set getUserPermissions(Long userId) { - List permsList; - - //系统管理员,拥有最高权限 - if(userId == Constant.SUPER_ADMIN_ID){ - List menuList = sysMenuMapper.selectList(Wrappers.emptyWrapper()); - - - permsList = menuList.stream().map(SysMenu::getPerms).collect(Collectors.toList()); - }else{ - permsList = sysUserMapper.queryAllPerms(userId); - } - - - Set permsSet = permsList.stream().flatMap((perms)->{ - if (StrUtil.isBlank(perms)) { - return null; - } - return Arrays.stream(perms.trim().split(",")); - } - ).collect(Collectors.toSet()); - return permsSet; - } - - @Override - public YamiUser loadUserByAppIdAndBizUserId(App app, String bizUserId) { - return null; - } - - @Override - public void insertUserIfNecessary(AppConnect appConnect) { - - } - - @Override - public YamiUser loadUserByUserMail(String userMail, String loginPassword) { - return null; - } - - @Override - public User loadUserByMobileOrUserName(String mobileOrUserName, Integer loginType) { - return null; - } - - @Override - public YamiUser getYamiUser(Integer appId, User user, String bizUserId) { - return null; - } -} diff --git a/yami-shop-admin/src/main/resources/application-dev.yml b/yami-shop-admin/src/main/resources/application-dev.yml index bf3f59a..65da3e6 100644 --- a/yami-shop-admin/src/main/resources/application-dev.yml +++ b/yami-shop-admin/src/main/resources/application-dev.yml @@ -19,6 +19,6 @@ spring: cache-null-values: true redis: redisson: - config: classpath:redisson.yml + config: classpath:redisson/redisson.yml logging: - config: classpath:logback-dev.xml + config: classpath:logback/logback-dev.xml diff --git a/yami-shop-admin/src/main/resources/application-docker.yml b/yami-shop-admin/src/main/resources/application-docker.yml index a3ce25d..93b5a7a 100644 --- a/yami-shop-admin/src/main/resources/application-docker.yml +++ b/yami-shop-admin/src/main/resources/application-docker.yml @@ -15,6 +15,6 @@ spring: connection-test-query: SELECT 1 redis: redisson: - config: classpath:redisson-docker.yml + config: classpath:redisson/redisson-docker.yml logging: - config: classpath:logback-prod.xml + config: classpath:logback/logback-prod.xml diff --git a/yami-shop-admin/src/main/resources/application-prod.yml b/yami-shop-admin/src/main/resources/application-prod.yml index ebbc1ee..05652c6 100644 --- a/yami-shop-admin/src/main/resources/application-prod.yml +++ b/yami-shop-admin/src/main/resources/application-prod.yml @@ -15,6 +15,6 @@ spring: connection-test-query: SELECT 1 redis: redisson: - config: classpath:redisson.yml + config: classpath:redisson/redisson.yml logging: - config: classpath:logback-prod.xml + config: classpath:logback/logback-prod.xml diff --git a/yami-shop-admin/src/main/resources/logback-dev.xml b/yami-shop-admin/src/main/resources/logback/logback-dev.xml similarity index 100% rename from yami-shop-admin/src/main/resources/logback-dev.xml rename to yami-shop-admin/src/main/resources/logback/logback-dev.xml diff --git a/yami-shop-admin/src/main/resources/logback-prod.xml b/yami-shop-admin/src/main/resources/logback/logback-prod.xml similarity index 100% rename from yami-shop-admin/src/main/resources/logback-prod.xml rename to yami-shop-admin/src/main/resources/logback/logback-prod.xml diff --git a/yami-shop-api/src/main/resources/redisson-docker.yml b/yami-shop-admin/src/main/resources/redisson/redisson-docker.yml similarity index 77% rename from yami-shop-api/src/main/resources/redisson-docker.yml rename to yami-shop-admin/src/main/resources/redisson/redisson-docker.yml index 4ba23b7..46e08da 100644 --- a/yami-shop-api/src/main/resources/redisson-docker.yml +++ b/yami-shop-admin/src/main/resources/redisson/redisson-docker.yml @@ -4,28 +4,25 @@ singleServerConfig: database: ${REDIS_DATABASE} password: ${REDIS_PASSWORD} idleConnectionTimeout: 10000 - pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 - reconnectionTimeout: 3000 - failedAttempts: 3 clientName: null # 发布和订阅连接的最小空闲连接数 默认1 subscriptionConnectionMinimumIdleSize: 1 # 发布和订阅连接池大小 默认50 - subscriptionConnectionPoolSize: 10 + subscriptionConnectionPoolSize: 1 # 单个连接最大订阅数量 默认5 - subscriptionsPerConnection: 5 + subscriptionsPerConnection: 1 # 最小空闲连接数 默认32,现在暂时不需要那么多的线程 - connectionMinimumIdleSize: 4 + connectionMinimumIdleSize: 2 # connectionPoolSize 默认64,现在暂时不需要那么多的线程 - connectionPoolSize: 20 + connectionPoolSize: 4 # 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。 threads: 0 # 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。 nettyThreads: 0 codec: - class: com.yami.shop.common.serializer.redisson.FstCodec -transportMode: NIO \ No newline at end of file + class: org.redisson.codec.KryoCodec +transportMode: NIO diff --git a/yami-shop-admin/src/main/resources/redisson.yml b/yami-shop-admin/src/main/resources/redisson/redisson.yml similarity index 77% rename from yami-shop-admin/src/main/resources/redisson.yml rename to yami-shop-admin/src/main/resources/redisson/redisson.yml index 1c8989e..01883be 100644 --- a/yami-shop-admin/src/main/resources/redisson.yml +++ b/yami-shop-admin/src/main/resources/redisson/redisson.yml @@ -4,28 +4,25 @@ singleServerConfig: database: 0 password: null idleConnectionTimeout: 10000 - pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 - reconnectionTimeout: 3000 - failedAttempts: 3 clientName: null # 发布和订阅连接的最小空闲连接数 默认1 subscriptionConnectionMinimumIdleSize: 1 # 发布和订阅连接池大小 默认50 - subscriptionConnectionPoolSize: 10 + subscriptionConnectionPoolSize: 1 # 单个连接最大订阅数量 默认5 - subscriptionsPerConnection: 5 + subscriptionsPerConnection: 1 # 最小空闲连接数 默认32,现在暂时不需要那么多的线程 - connectionMinimumIdleSize: 4 + connectionMinimumIdleSize: 2 # connectionPoolSize 默认64,现在暂时不需要那么多的线程 - connectionPoolSize: 20 + connectionPoolSize: 4 # 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。 threads: 0 # 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。 nettyThreads: 0 codec: - class: com.yami.shop.common.serializer.redisson.FstCodec -transportMode: NIO \ No newline at end of file + class: org.redisson.codec.KryoCodec +transportMode: NIO diff --git a/yami-shop-api/pom.xml b/yami-shop-api/pom.xml index d1da7cf..d797674 100644 --- a/yami-shop-api/pom.xml +++ b/yami-shop-api/pom.xml @@ -20,12 +20,7 @@ com.yami.shop - yami-shop-security - ${yami.shop.version} - - - com.yami.shop - yami-shop-mp + yami-shop-security-api ${yami.shop.version} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java index 6c88ce9..8dcd9a7 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java @@ -10,36 +10,25 @@ package com.yami.shop.api.controller; -import java.util.Date; -import java.util.List; - -import javax.validation.Valid; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.security.util.SecurityUtils; -import lombok.AllArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import com.yami.shop.bean.app.dto.UserAddrDto; import com.yami.shop.bean.app.param.AddrParam; import com.yami.shop.bean.model.UserAddr; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.UserAddrService; - import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; import ma.glasnost.orika.MapperFacade; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.Date; +import java.util.List; @RestController diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java index 6a0d47b..f92a299 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java @@ -10,37 +10,30 @@ package com.yami.shop.api.controller; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; - import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.bean.enums.OrderStatus; -import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.common.util.PageParam; import com.yami.shop.bean.app.dto.*; -import com.yami.shop.dao.OrderMapper; -import com.yami.shop.security.util.SecurityUtils; -import com.yami.shop.service.*; -import lombok.AllArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - - +import com.yami.shop.bean.enums.OrderStatus; import com.yami.shop.bean.model.Order; import com.yami.shop.bean.model.OrderItem; import com.yami.shop.bean.model.ShopDetail; import com.yami.shop.bean.model.UserAddrOrder; +import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.Arith; - +import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.api.util.SecurityUtils; +import com.yami.shop.service.*; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; import ma.glasnost.orika.MapperFacade; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; @RestController @RequestMapping("/p/myOrder") diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java index 794c0d5..9671c3c 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java @@ -10,15 +10,21 @@ package com.yami.shop.api.controller; -import java.util.*; - -import javax.validation.Valid; - +import cn.hutool.core.collection.CollectionUtil; import com.yami.shop.bean.app.dto.*; +import com.yami.shop.bean.app.param.OrderParam; +import com.yami.shop.bean.app.param.OrderShopParam; +import com.yami.shop.bean.app.param.SubmitOrderParam; import com.yami.shop.bean.event.ConfirmOrderEvent; +import com.yami.shop.bean.model.Order; +import com.yami.shop.bean.model.UserAddr; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.Arith; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.*; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import ma.glasnost.orika.MapperFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.http.ResponseEntity; @@ -27,17 +33,10 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.yami.shop.bean.app.param.OrderParam; -import com.yami.shop.bean.app.param.OrderShopParam; -import com.yami.shop.bean.app.param.SubmitOrderParam; -import com.yami.shop.bean.model.Order; -import com.yami.shop.bean.model.UserAddr; -import com.yami.shop.common.util.Arith; - -import cn.hutool.core.collection.CollectionUtil; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import ma.glasnost.orika.MapperFacade; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; @RestController @RequestMapping("/p/order") diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java index 7855613..81aa697 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java @@ -10,18 +10,11 @@ package com.yami.shop.api.controller; -import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; -import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.service.WxPayService; -import com.yami.shop.api.config.ApiConfig; import com.yami.shop.bean.app.param.PayParam; import com.yami.shop.bean.pay.PayInfoDto; -import com.yami.shop.common.util.Arith; -import com.yami.shop.common.util.IPHelper; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.api.model.YamiUser; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.PayService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -33,8 +26,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequestMapping("/p/order") @Api(tags = "订单接口") @@ -43,10 +34,6 @@ public class PayController { private final PayService payService; - private final ApiConfig apiConfig; - - private final WxPayService wxMiniPayService; - /** * 支付接口 */ @@ -56,21 +43,11 @@ public class PayController { public ResponseEntity pay(@RequestBody PayParam payParam) { YamiUser user = SecurityUtils.getUser(); String userId = user.getUserId(); - String openId = user.getBizUserId(); PayInfoDto payInfo = payService.pay(userId, payParam); - - WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest(); - orderRequest.setBody(payInfo.getBody()); - orderRequest.setOutTradeNo(payInfo.getPayNo()); - orderRequest.setTotalFee((int) Arith.mul(payInfo.getPayAmount(), 100)); - orderRequest.setSpbillCreateIp(IPHelper.getIpAddr()); - orderRequest.setNotifyUrl(apiConfig.getDomainName() + "/notice/pay/order"); - orderRequest.setTradeType(WxPayConstants.TradeType.JSAPI); - orderRequest.setOpenid(openId); - - return ResponseEntity.ok(wxMiniPayService.createOrder(orderRequest)); + payService.paySuccess(payInfo.getPayNo(), ""); + return ResponseEntity.ok().build(); } /** diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java index bade51c..9a86416 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java @@ -10,13 +10,7 @@ package com.yami.shop.api.controller; -import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; -import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.WxPayService; -import com.yami.shop.service.PayService; import lombok.AllArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import springfox.documentation.annotations.ApiIgnore; @@ -26,26 +20,26 @@ import springfox.documentation.annotations.ApiIgnore; @RequestMapping("/notice/pay") @AllArgsConstructor public class PayNoticeController { - - /** - * 小程序支付 - */ - private final WxPayService wxMiniPayService; - - private final PayService payService; - - - @RequestMapping("/order") - public ResponseEntity submit(@RequestBody String xmlData) throws WxPayException { - WxPayOrderNotifyResult parseOrderNotifyResult = wxMiniPayService.parseOrderNotifyResult(xmlData); - - String payNo = parseOrderNotifyResult.getOutTradeNo(); - String bizPayNo = parseOrderNotifyResult.getTransactionId(); - - // 根据内部订单号更新order settlement - payService.paySuccess(payNo, bizPayNo); - - - return ResponseEntity.ok().build(); - } +//模拟支付不需要回调 +// /** +// * 小程序支付 +// */ +// private final WxPayService wxMiniPayService; +// +// private final PayService payService; +// +// +// @RequestMapping("/order") +// public ResponseEntity submit(@RequestBody String xmlData) throws WxPayException { +// WxPayOrderNotifyResult parseOrderNotifyResult = wxMiniPayService.parseOrderNotifyResult(xmlData); +// +// String payNo = parseOrderNotifyResult.getOutTradeNo(); +// String bizPayNo = parseOrderNotifyResult.getTransactionId(); +// +// // 根据内部订单号更新order settlement +// payService.paySuccess(payNo, bizPayNo); +// +// +// return ResponseEntity.ok().build(); +// } } \ No newline at end of file diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java index baf6b25..cb52945 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ProdCommController.java @@ -12,14 +12,12 @@ package com.yami.shop.api.controller; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.common.util.PageParam; import com.yami.shop.bean.app.dto.ProdCommDataDto; import com.yami.shop.bean.app.dto.ProdCommDto; import com.yami.shop.bean.app.param.ProdCommParam; import com.yami.shop.bean.model.ProdComm; -import com.yami.shop.common.util.Json; import com.yami.shop.common.util.PageParam; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.ProdCommService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java index 3ee7e65..d9c7549 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/ShopCartController.java @@ -12,24 +12,31 @@ package com.yami.shop.api.controller; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; import com.google.common.collect.Lists; import com.yami.shop.bean.app.dto.*; import com.yami.shop.bean.app.param.ChangeShopCartParam; import com.yami.shop.bean.app.param.ShopCartParam; import com.yami.shop.bean.event.ShopCartEvent; -import com.yami.shop.bean.model.*; +import com.yami.shop.bean.model.Basket; +import com.yami.shop.bean.model.Product; +import com.yami.shop.bean.model.Sku; import com.yami.shop.common.util.Arith; -import com.yami.shop.security.util.SecurityUtils; -import com.yami.shop.service.*; -import io.swagger.annotations.*; +import com.yami.shop.security.api.util.SecurityUtils; +import com.yami.shop.service.BasketService; +import com.yami.shop.service.ProductService; +import com.yami.shop.service.SkuService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.context.ApplicationContext; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @RestController diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java index 40381db..5be90ea 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/SmsController.java @@ -10,21 +10,19 @@ package com.yami.shop.api.controller; -import com.yami.shop.security.util.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import com.google.common.collect.Maps; import com.yami.shop.bean.app.param.SendSmsParam; import com.yami.shop.bean.enums.SmsType; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.SmsLogService; - import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/p/sms") diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java index 34bb6b1..2954926 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserCollectionController.java @@ -19,7 +19,7 @@ import com.yami.shop.bean.model.Product; import com.yami.shop.bean.model.UserCollection; import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.PageParam; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.ProductService; import com.yami.shop.service.UserCollectionService; import io.swagger.annotations.Api; diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java index 95bc36b..9db81c9 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserController.java @@ -10,32 +10,18 @@ package com.yami.shop.api.controller; -import cn.hutool.core.util.StrUtil; -import com.yami.shop.common.util.CacheManagerUtil; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.util.SecurityUtils; -import lombok.AllArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - +import cn.hutool.extra.emoji.EmojiUtil; import com.yami.shop.bean.app.dto.UserDto; import com.yami.shop.bean.app.param.UserInfoParam; import com.yami.shop.bean.model.User; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.UserService; - -import cn.hutool.extra.emoji.EmojiUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; import ma.glasnost.orika.MapperFacade; - -import javax.servlet.http.HttpServletRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/p/user") @@ -46,10 +32,6 @@ public class UserController { private final UserService userService; private final MapperFacade mapperFacade; - - private final CacheManagerUtil cacheManagerUtil; - - private final ConsumerTokenServices consumerTokenServices; /** * 查看用户接口 */ @@ -71,18 +53,6 @@ public class UserController { user.setPic(userInfoParam.getAvatarUrl()); user.setNickName(EmojiUtil.toAlias(userInfoParam.getNickName())); userService.updateById(user); - String cacheKey = App.MINI.value() + StrUtil.COLON + SecurityUtils.getUser().getBizUserId(); - cacheManagerUtil.evictCache("yami_user", cacheKey); - return ResponseEntity.ok(null); - } - - /** - * 退出登录,并清除redis中的token - **/ - @GetMapping("/logout") - public Boolean removeToken(HttpServletRequest httpRequest){ - String authorization = httpRequest.getHeader("authorization"); - String token = authorization.replace("bearer", ""); - return consumerTokenServices.revokeToken(token); + return ResponseEntity.ok().build(); } } diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java index 7cfbcb9..bf1dd4a 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/UserRegisterController.java @@ -1,40 +1,27 @@ package com.yami.shop.api.controller; -import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.api.security.AuthenticationToken; -import com.yami.shop.bean.enums.SmsType; import com.yami.shop.bean.model.User; import com.yami.shop.bean.param.UserRegisterParam; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.common.util.IPHelper; -import com.yami.shop.common.util.PrincipalUtil; -import com.yami.shop.mp.config.WxMaConfiguration; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.handler.LoginAuthSuccessHandler; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.AppConnectService; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.service.YamiUserDetailsService; -import com.yami.shop.security.util.SecurityUtils; -import com.yami.shop.service.SmsLogService; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.enums.SysTypeEnum; +import com.yami.shop.security.common.manager.PasswordManager; +import com.yami.shop.security.common.manager.TokenStore; +import com.yami.shop.security.common.vo.TokenInfoVO; import com.yami.shop.service.UserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.util.Date; -import java.util.Objects; /** * 用户信息 @@ -49,182 +36,66 @@ public class UserRegisterController { private final UserService userService; - private final SmsLogService smsLogService; - - private final AppConnectService appConnectService; - - private final LoginAuthSuccessHandler loginAuthSuccessHandler; - - private final WxMaConfiguration wxConfig; - - private final YamiUserDetailsService yamiUserDetailsService; - private final PasswordEncoder passwordEncoder; - public static final String CHECK_REGISTER_SMS_FLAG = "checkRegisterSmsFlag"; + private final TokenStore tokenStore; - public static final String CHECK_UPDATE_PWD_SMS_FLAG = "updatePwdSmsFlag"; + private final PasswordManager passwordManager; @PostMapping("/register") @ApiOperation(value = "注册", notes = "用户注册或绑定手机号接口") - public ResponseEntity register(@Valid @RequestBody UserRegisterParam userRegisterParam) { - userRegisterParam.setPassword(passwordEncoder.encode(userRegisterParam.getPassword())); - return ResponseEntity.ok(userService.insertUser(userRegisterParam)); + public ResponseEntity register(@Valid @RequestBody UserRegisterParam userRegisterParam) { + if (StrUtil.isBlank(userRegisterParam.getNickName())) { + userRegisterParam.setNickName(userRegisterParam.getUserName()); + } + // 正在进行申请注册 + if (userService.count(new LambdaQueryWrapper().eq(User::getNickName, userRegisterParam.getNickName())) > 0) { + // 该用户名已注册,无法重新注册 + throw new YamiShopBindException("该用户名已注册,无法重新注册"); + } + Date now = new Date(); + User user = new User(); + user.setModifyTime(now); + user.setUserRegtime(now); + user.setStatus(1); + user.setNickName(userRegisterParam.getNickName()); + user.setUserMail(userRegisterParam.getUserMail()); + String decryptPassword = passwordManager.decryptPassword(userRegisterParam.getPassWord()); + user.setLoginPassword(passwordEncoder.encode(decryptPassword)); + String userId = IdUtil.simpleUUID(); + user.setUserId(userId); + userService.save(user); + // 2. 登录 + UserInfoInTokenBO userInfoInTokenBO = new UserInfoInTokenBO(); + userInfoInTokenBO.setUserId(user.getUserId()); + userInfoInTokenBO.setSysType(SysTypeEnum.ORDINARY.value()); + userInfoInTokenBO.setIsAdmin(0); + userInfoInTokenBO.setEnabled(true); + return ResponseEntity.ok(tokenStore.storeAndGetVo(userInfoInTokenBO)); } @PutMapping("/updatePwd") @ApiOperation(value = "修改密码", notes = "修改密码") - public ResponseEntity updatePwd(@Valid @RequestBody UserRegisterParam userRegisterParam) { - User user = userService.getOne(new LambdaQueryWrapper().eq(User::getUserMobile, userRegisterParam.getUserMail())); + public ResponseEntity updatePwd(@Valid @RequestBody UserRegisterParam userPwdUpdateParam) { + User user = userService.getOne(new LambdaQueryWrapper().eq(User::getNickName, userPwdUpdateParam.getNickName())); if (user == null) { // 无法获取用户信息 throw new YamiShopBindException("无法获取用户信息"); } - if (StrUtil.isBlank(userRegisterParam.getPassword())) { + String decryptPassword = passwordManager.decryptPassword(userPwdUpdateParam.getPassWord()); + if (StrUtil.isBlank(decryptPassword)) { // 新密码不能为空 throw new YamiShopBindException("新密码不能为空"); } - if (StrUtil.equals(passwordEncoder.encode(userRegisterParam.getPassword()), user.getLoginPassword())) { + String password = passwordEncoder.encode(decryptPassword); + if (StrUtil.equals(password, user.getLoginPassword())) { // 新密码不能与原密码相同 throw new YamiShopBindException("新密码不能与原密码相同"); } user.setModifyTime(new Date()); - user.setLoginPassword(passwordEncoder.encode(userRegisterParam.getPassword())); + user.setLoginPassword(password); userService.updateById(user); return ResponseEntity.ok().build(); } - - @PutMapping("/registerOrBindUser") - @ApiOperation(value="注册或绑定手机号", notes="用户注册或绑定手机号接口") - public ResponseEntity register(HttpServletRequest request, HttpServletResponse response, @Valid @RequestBody UserRegisterParam userRegisterParam) { - - String mobile = userRegisterParam.getMobile(); - AppConnect appConnect = null; - User user = null; - String bizUserId = null; - boolean isWxAppId = Objects.equals(userRegisterParam.getAppType(), App.MINI.value()) || Objects.equals(userRegisterParam.getAppType(), App.MP.value()); - if(isWxAppId) { - bizUserId = SecurityUtils.getUser().getBizUserId(); - } - - // 正在进行注册,通过验证码校验 - if (Objects.equals(userRegisterParam.getRegisterOrBind(), 1)) { - - // 看看有没有校验验证码成功的标识 - userService.validate(userRegisterParam, CHECK_REGISTER_SMS_FLAG + userRegisterParam.getCheckRegisterSmsFlag()); - // 正在进行申请注册 - if (userService.count(new LambdaQueryWrapper().eq(User::getUserMobile,userRegisterParam.getMobile())) > 0) { - // 手机号已存在,无法注册 - throw new YamiShopBindException("yami.user.phone.exist"); - } - } - // 小程序注册/绑定手机号 - else { - YamiUser yamiUser = SecurityUtils.getUser(); - - appConnect = appConnectService.getByBizUserId(yamiUser.getBizUserId(), App.instance(yamiUser.getAppType())); - // 通过微信手机号校验 - if (Objects.equals(2, userRegisterParam.getValidateType())) { - try { - WxMaPhoneNumberInfo wxMaPhoneNumberInfo = wxConfig.wxMaService().getUserService().getPhoneNoInfo(yamiUser.getSessionKey(), userRegisterParam.getEncryptedData(), userRegisterParam.getIvStr()); - mobile = wxMaPhoneNumberInfo.getPhoneNumber(); - - } catch (Exception e) { - // 授权失败,请重新授权 - throw new YamiShopBindException(" 授权失败,请重新授权"); - } - if (StrUtil.isBlank(mobile)) { - // 无法获取用户手机号信息 - throw new YamiShopBindException("无法获取用户手机号信息"); - } - user = yamiUserDetailsService.loadUserByMobileOrUserName(mobile, 0); - } - // 通过账号密码校验 - else if (Objects.equals(3, userRegisterParam.getValidateType())) { - user = yamiUserDetailsService.loadUserByMobileOrUserName(mobile, 0); - if (user == null) { - // 账号或密码不正确 - throw new YamiShopBindException("yami.user.account.error"); - } - String encodedPassword = user.getLoginPassword(); - String rawPassword = userRegisterParam.getPassword(); - // 密码不正确 - if (StrUtil.isBlank(encodedPassword) || !passwordEncoder.matches(rawPassword,encodedPassword)){ - // 账号或密码不正确 - throw new YamiShopBindException("yami.user.account.error"); - } - } - // 通过验证码校验 - else { - if (!smsLogService.checkValidCode(userRegisterParam.getMobile(), userRegisterParam.getValidCode(), SmsType.VALID)){ - // 验证码有误或已过期 - throw new YamiShopBindException("yami.user.code.error"); - } - } - } - - Date now = new Date(); - - // 尝试用手机号获取用户信息 - if (user == null && StrUtil.isNotBlank(mobile)) { - user = userService.getOne(new LambdaQueryWrapper().eq(User::getUserMobile,mobile)); - } - - // 新建用户 - if (user == null) { - user = new User(); - if (StrUtil.isBlank(userRegisterParam.getUserName())) { - userRegisterParam.setUserName(mobile); - } - - // 如果有用户名,就判断用户名格式是否正确 - if (!PrincipalUtil.isUserName(userRegisterParam.getUserName())) { - throw new YamiShopBindException("用户名应由4-16位数字字母下划线组成"); - } - - user.setModifyTime(now); - user.setUserRegtime(now); - user.setUserRegip(IPHelper.getIpAddr()); - user.setStatus(1); - - user.setPic(userRegisterParam.getImg()); - user.setUserMobile(mobile); - if (StrUtil.isNotBlank(userRegisterParam.getPassword())) { - user.setLoginPassword(passwordEncoder.encode(userRegisterParam.getPassword())); - } - // 用户名就是默认的昵称 - user.setNickName(StrUtil.isBlank(userRegisterParam.getNickName())? userRegisterParam.getUserName(): userRegisterParam.getNickName()); -// } else { -// String userId = user.getUserId(); -// // 绑定账号 -// if (Objects.equals(userRegisterParam.getRegisterOrBind(),2)) { -// int count = appConnectService.count(new LambdaQueryWrapper().eq(AppConnect::getUserId, userId).eq(AppConnect::getAppId, userRegisterParam.getAppType())); -// if (count > 0) { -// throw new YamiShopBindException("该账号已被绑定,请换个账号试试"); -// } -// } - - } - if(Objects.nonNull(bizUserId)){ - appConnect = new AppConnect(); - appConnect.setBizUserId(bizUserId); - } - - appConnectService.registerOrBindUser(user, appConnect, userRegisterParam.getAppType()); - - - //进行授权登录 - UserDetails userDetails = yamiUserDetailsService.getYamiUser(userRegisterParam.getAppType(),user, bizUserId); - AuthenticationToken authenticationToken = new AuthenticationToken(); - authenticationToken.setPrincipal(user.getUserMobile()); - authenticationToken.setCredentials(user.getLoginPassword()); - authenticationToken.setPrincipal(userDetails.getUsername()); - authenticationToken.setDetails(userDetails); - authenticationToken.setAuthenticated(true); - loginAuthSuccessHandler.onAuthenticationSuccess(request,response,authenticationToken); - - return ResponseEntity.ok().build(); - } - } diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java index 55a8fad..4b0ded8 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java @@ -10,34 +10,26 @@ package com.yami.shop.api.listener; -import com.google.common.collect.Lists; -import com.yami.shop.bean.app.dto.*; +import com.yami.shop.bean.app.dto.ShopCartItemDto; +import com.yami.shop.bean.app.dto.ShopCartOrderDto; import com.yami.shop.bean.app.param.OrderParam; import com.yami.shop.bean.event.ConfirmOrderEvent; -import com.yami.shop.bean.event.ShopCartEvent; import com.yami.shop.bean.model.Product; import com.yami.shop.bean.model.Sku; import com.yami.shop.bean.model.UserAddr; import com.yami.shop.bean.order.ConfirmOrderOrder; -import com.yami.shop.bean.order.ShopCartEventOrder; import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.Arith; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.ProductService; import com.yami.shop.service.SkuService; import com.yami.shop.service.TransportManagerService; import com.yami.shop.service.UserAddrService; -import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; -import ma.glasnost.orika.MapperFacade; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.List; - /** * 确认订单信息时的默认操作 * @author LGH diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java index 42038ae..308238e 100644 --- a/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java @@ -23,7 +23,7 @@ import com.yami.shop.bean.order.SubmitOrderOrder; import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.Arith; import com.yami.shop.dao.*; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.security.api.util.SecurityUtils; import com.yami.shop.service.ProductService; import com.yami.shop.service.SkuService; import com.yami.shop.service.UserAddrOrderService; diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/ApiTokenEnhancer.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/ApiTokenEnhancer.java deleted file mode 100644 index 413ba0e..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/ApiTokenEnhancer.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.yami.shop.api.security; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.emoji.EmojiUtil; -import com.yami.shop.security.service.YamiUser; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -/** - * token增强 - * @author LGH - */ -@Component -public class ApiTokenEnhancer implements TokenEnhancer { - - - @Override - public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { - Map additionalInfo = new HashMap<>(8); - YamiUser yamiUser = (YamiUser) authentication.getUserAuthentication().getPrincipal(); - additionalInfo.put("userId", yamiUser.getUserId()); - additionalInfo.put("nickName", EmojiUtil.toUnicode(StrUtil.isBlank(yamiUser.getName())? "" : yamiUser.getName())); - additionalInfo.put("pic",yamiUser.getPic()); - additionalInfo.put("enabled",yamiUser.isEnabled()); - - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); - return accessToken; - } -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/AuthenticationToken.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/AuthenticationToken.java deleted file mode 100644 index dbc4bb6..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/AuthenticationToken.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.CredentialsContainer; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; - -/** - * 自定义AbstractAuthenticationToken, - * @author SJL - */ -@Getter -@Setter -public class AuthenticationToken implements Authentication, - CredentialsContainer { - - private Collection authorities; - private UserDetails details; - - /** - * 用户名 - */ - protected String principal; - - /** - * 密码 - */ - protected Object credentials; - - /** - * uuid,现在用于验证码 - */ - private String sessionUUID; - - private boolean authenticated = false; - - public void setDetails(UserDetails details) { - this.details = details; - } - - - @Override - public Collection getAuthorities() { - return details.getAuthorities(); - } - - - @Override - public Object getDetails() { - return details; - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { - this.authenticated = isAuthenticated; - } - - @Override - public String getName() { - return details.getUsername(); - } - - @Override - public void eraseCredentials() { - eraseSecret(getCredentials()); - eraseSecret(getPrincipal()); - eraseSecret(details); - } - - private void eraseSecret(Object secret) { - if (secret instanceof CredentialsContainer) { - ((CredentialsContainer) secret).eraseCredentials(); - } - } - -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/LoginAuthenticationFilter.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/LoginAuthenticationFilter.java deleted file mode 100644 index 25694f0..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/LoginAuthenticationFilter.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import com.yami.shop.common.util.Json; -import com.yami.shop.common.util.RedisUtil; -import com.yami.shop.security.constants.SecurityConstants; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.exception.BadCredentialsExceptionBase; -import com.yami.shop.security.exception.ImageCodeNotMatchExceptionBase; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.exception.WxErrorExceptionBase; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.service.YamiUserDetailsService; -import com.yami.shop.security.token.MyAuthenticationToken; -import lombok.AllArgsConstructor; -import me.chanjar.weixin.common.error.WxErrorException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 小程序登陆:此时principal为code - * post:http://127.0.0.1:8086/login - * {principal:code} - */ -@Component -public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - - private final YamiUserDetailsService yamiUserDetailsService; - - private final WxMaService wxMaService; - - @Autowired - public LoginAuthenticationFilter(YamiUserDetailsService yamiUserDetailsService, WxMaService wxMaService) { - super("/login"); - this.yamiUserDetailsService = yamiUserDetailsService; - this.wxMaService = wxMaService; - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - if (!ServletUtil.METHOD_POST.equals(request.getMethod())) { - throw new AuthenticationServiceException( - "Authentication method not supported: " + request.getMethod()); - } - String requestBody = getStringFromStream(request); - - if (StrUtil.isBlank(requestBody)) { - throw new AuthenticationServiceException("无法获取输入信息"); - } - MiniAppAuthenticationToken authentication = Json.parseObject(requestBody, MiniAppAuthenticationToken.class); - String code = String.valueOf(authentication.getPrincipal()); - YamiUser loadedUser = null; - - WxMaJscode2SessionResult session = null; - - AppConnect appConnect = new AppConnect(); - appConnect.setAppId(App.MINI.value()); - try { - - session = wxMaService.getUserService().getSessionInfo(code); - - loadedUser = yamiUserDetailsService.loadUserByAppIdAndBizUserId(App.MINI,session.getOpenid()); - } catch (WxErrorException e) { - throw new WxErrorExceptionBase(e.getMessage()); - } catch (UsernameNotFoundExceptionBase var6) { - if (session == null) { - throw new WxErrorExceptionBase("无法获取用户登陆信息"); - } - appConnect.setBizUserId(session.getOpenid()); - appConnect.setBizUnionid(session.getUnionid()); - yamiUserDetailsService.insertUserIfNecessary(appConnect); - } - - if (loadedUser == null) { - loadedUser = yamiUserDetailsService.loadUserByAppIdAndBizUserId(App.MINI, appConnect.getBizUserId()); - } - MiniAppAuthenticationToken result = new MiniAppAuthenticationToken(loadedUser, authentication.getCredentials()); - result.setDetails(authentication.getDetails()); - return result; - } - - - private String getStringFromStream(HttpServletRequest req) { - ServletInputStream is; - try { - is = req.getInputStream(); - int nRead = 1; - int nTotalRead = 0; - byte[] bytes = new byte[10240]; - while (nRead > 0) { - nRead = is.read(bytes, nTotalRead, bytes.length - nTotalRead); - if (nRead > 0) { - nTotalRead = nTotalRead + nRead; - } - } - return new String(bytes, 0, nTotalRead, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - @Override - @Autowired - public void setAuthenticationManager(AuthenticationManager authenticationManager) { - super.setAuthenticationManager(authenticationManager); - } - - @Override - @Autowired - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) { - super.setAuthenticationSuccessHandler(successHandler); - } - - @Override - @Autowired - public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) { - super.setAuthenticationFailureHandler(failureHandler); - } -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/MiniAppAuthenticationToken.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/MiniAppAuthenticationToken.java deleted file mode 100644 index b72b40c..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/MiniAppAuthenticationToken.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import com.yami.shop.security.token.MyAuthenticationToken; -import lombok.NoArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * 二维码Token - */ -@NoArgsConstructor -public class MiniAppAuthenticationToken extends MyAuthenticationToken { - - - public MiniAppAuthenticationToken(UserDetails principal, Object credentials) { - super(principal, credentials, principal.getAuthorities()); - } -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/ResourceServerConfiguration.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/ResourceServerConfiguration.java deleted file mode 100644 index e4830a5..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/ResourceServerConfiguration.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsUtils; - -@Configuration -@EnableResourceServer -public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { - - @Autowired - private LoginAuthenticationFilter loginAuthenticationFilter; - - @Override - public void configure(HttpSecurity http) throws Exception { - http - .addFilterBefore(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .csrf().disable().cors() - .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .and().requestMatchers().anyRequest() - .and().anonymous() - .and().authorizeRequests() - //配置/p访问控制,必须认证过后才可以访问 - .antMatchers("/p/**").authenticated(); - } - - -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/WebAuthenticationToken.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/WebAuthenticationToken.java deleted file mode 100644 index 5c9bef4..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/WebAuthenticationToken.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import com.yami.shop.security.token.MyAuthenticationToken; -import lombok.NoArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * H5端Token - */ -@NoArgsConstructor -public class WebAuthenticationToken extends MyAuthenticationToken { - - - public WebAuthenticationToken(UserDetails principal, Object credentials) { - super(principal, credentials, principal.getAuthorities()); - } -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/WebLoginAuthenticationFilter.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/WebLoginAuthenticationFilter.java deleted file mode 100644 index 0717541..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/WebLoginAuthenticationFilter.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import com.yami.shop.common.util.Json; -import com.yami.shop.common.util.RedisUtil; -import com.yami.shop.security.constants.SecurityConstants; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.exception.BadCredentialsExceptionBase; -import com.yami.shop.security.exception.ImageCodeNotMatchExceptionBase; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.exception.WxErrorExceptionBase; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.service.YamiUserDetailsService; -import com.yami.shop.security.token.MyAuthenticationToken; -import lombok.AllArgsConstructor; -import me.chanjar.weixin.common.error.WxErrorException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 账号密码登录 - */ -@Component -public class WebLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - - private final YamiUserDetailsService yamiUserDetailsService; - - private final WxMaService wxMaService; - - @Autowired - public WebLoginAuthenticationFilter(YamiUserDetailsService yamiUserDetailsService, WxMaService wxMaService) { - super("/webLogin"); - this.yamiUserDetailsService = yamiUserDetailsService; - this.wxMaService = wxMaService; - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - if (!ServletUtil.METHOD_POST.equals(request.getMethod())) { - throw new AuthenticationServiceException( - "Authentication method not supported: " + request.getMethod()); - } - String requestBody = getStringFromStream(request); - - if (StrUtil.isBlank(requestBody)) { - throw new AuthenticationServiceException("无法获取输入信息"); - } - WebAuthenticationToken authentication = Json.parseObject(requestBody, WebAuthenticationToken.class); - String userMail = String.valueOf(authentication.getPrincipal()); - String loginPassword = String.valueOf(authentication.getCredentials()); - - YamiUser loadedUser = null; - -// WxMaJscode2SessionResult session = null; - - AppConnect appConnect = new AppConnect(); - appConnect.setAppId(App.H5.value()); - loadedUser = yamiUserDetailsService.loadUserByUserMail(userMail,loginPassword); -// try { -// -//// session = wxMaService.getUserService().getSessionInfo(code); -// -// loadedUser = yamiUserDetailsService.loadUserByAppIdAndBizUserId(App.MINI,session.getOpenid()); -// } catch (WxErrorException e) { -// throw new WxErrorExceptionBase(e.getMessage()); -// } catch (UsernameNotFoundExceptionBase var6) { -// if (session == null) { -// throw new WxErrorExceptionBase("无法获取用户登陆信息"); -// } -// appConnect.setBizUserId(session.getOpenid()); -// appConnect.setBizUnionid(session.getUnionid()); -// yamiUserDetailsService.insertUserIfNecessary(appConnect); -// } - -// if (loadedUser == null) { -// loadedUser = yamiUserDetailsService.loadUserByAppIdAndBizUserId(App.MINI, appConnect.getBizUserId()); -// } -// MiniAppAuthenticationToken result = new MiniAppAuthenticationToken(loadedUser, authentication.getCredentials()); - WebAuthenticationToken result = new WebAuthenticationToken(loadedUser, authentication.getCredentials()); - result.setDetails(authentication.getDetails()); - return result; - } - - - private String getStringFromStream(HttpServletRequest req) { - ServletInputStream is; - try { - is = req.getInputStream(); - int nRead = 1; - int nTotalRead = 0; - byte[] bytes = new byte[10240]; - while (nRead > 0) { - nRead = is.read(bytes, nTotalRead, bytes.length - nTotalRead); - if (nRead > 0) { - nTotalRead = nTotalRead + nRead; - } - } - return new String(bytes, 0, nTotalRead, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - @Override - @Autowired - public void setAuthenticationManager(AuthenticationManager authenticationManager) { - super.setAuthenticationManager(authenticationManager); - } - - @Override - @Autowired - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) { - super.setAuthenticationSuccessHandler(successHandler); - } - - @Override - @Autowired - public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) { - super.setAuthenticationFailureHandler(failureHandler); - } -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/YamiAuthenticationProcessingFilter.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/YamiAuthenticationProcessingFilter.java deleted file mode 100644 index 85763c2..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/YamiAuthenticationProcessingFilter.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.yami.shop.api.security; - -import cn.hutool.extra.servlet.ServletUtil; -import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.common.util.Json; -import com.yami.shop.common.xss.XssUtil; -import com.yami.shop.security.exception.BadCredentialsException; -import com.yami.shop.security.exception.UsernameNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.web.HttpRequestMethodNotSupportedException; - -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * - * @author SJL - */ -public class YamiAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { - - private UserDetailsService userDetailsService; - - private PasswordEncoder passwordEncoder; - /** - * 请求字符串的最大长度 1m - */ - public static final int MAX_STRING_SIZE = 1024 * 1024; - - protected YamiAuthenticationProcessingFilter(String defaultFilterProcessesUrl) { - super(defaultFilterProcessesUrl); - } - - @Autowired - public void setPasswordEncoder(PasswordEncoder passwordEncoder) { - this.passwordEncoder = passwordEncoder; - } - - @Autowired - public void setUserDetailsService(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; - } - - @Override - @Autowired - public void setAuthenticationManager(AuthenticationManager authenticationManager) { - super.setAuthenticationManager(authenticationManager); - } - - @Override - @Autowired - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) { - super.setAuthenticationSuccessHandler(successHandler); - } - - @Override - @Autowired - public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) { - super.setAuthenticationFailureHandler(failureHandler); - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, ServletException{ - if (!ServletUtil.METHOD_POST.equals(request.getMethod())) { - throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" }); - } - - AuthenticationToken authenticationToken = Json.parseObject(getStringFromStream(request), AuthenticationToken.class); - UserDetails userDetails = getUserDetails(authenticationToken); - return handleAuthenticationToken(authenticationToken,userDetails); - } - - /** - * 获取用户信息 - * @param authenticationToken - * @return - */ - protected UserDetails getUserDetails(AuthenticationToken authenticationToken) { - UserDetails user; - try { - user = userDetailsService.loadUserByUsername(authenticationToken.getPrincipal()); - } catch (UsernameNotFoundException var6) { - // 账号或密码不正确 - throw new UsernameNotFoundException("账号或密码不正确"); - } - if (!user.isEnabled()) { - // 账号已被锁定,请联系管理员 - throw new UsernameNotFoundException("账号已被锁定,请联系管理员"); - } - - String encodedPassword = user.getPassword(); - String rawPassword = authenticationToken.getCredentials().toString(); - - // 密码不正确 - if (!passwordEncoder.matches(rawPassword,encodedPassword)){ - // 账号或密码不正确 - throw new BadCredentialsException("账号或密码不正确"); - } - return user; - } - - - /** - * 保存用户信息 - */ - protected AuthenticationToken handleAuthenticationToken(AuthenticationToken authentication, UserDetails userDetails) { - // 保存用户信息 - authentication.setPrincipal(userDetails.getUsername()); - authentication.setDetails(userDetails); - authentication.setAuthenticated(true); - return authentication; - } - - - public String getStringFromStream(HttpServletRequest req) { - if (req.getContentLength() > MAX_STRING_SIZE) { - // 请求数据过长 - throw new YamiShopBindException("yami.request.data.too.long"); - } - ServletInputStream is; - try { - is = req.getInputStream(); - int nRead = 1; - int nTotalRead = 0; - byte[] bytes = new byte[1024]; - while (nRead > 0) { - nRead = is.read(bytes, nTotalRead, bytes.length - nTotalRead); - if (nRead > 0) { - nTotalRead = nTotalRead + nRead; - } - } - if (nTotalRead > MAX_STRING_SIZE) { - // 请求数据过长 - throw new YamiShopBindException("yami.request.data.too.long"); - } - return XssUtil.clean(new String(bytes, 0, nTotalRead, StandardCharsets.UTF_8)); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } -} diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/security/YamiUserServiceImpl.java b/yami-shop-api/src/main/java/com/yami/shop/api/security/YamiUserServiceImpl.java deleted file mode 100644 index f760821..0000000 --- a/yami-shop-api/src/main/java/com/yami/shop/api/security/YamiUserServiceImpl.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.api.security; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.emoji.EmojiUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.yami.shop.bean.model.User; -import com.yami.shop.common.annotation.RedisLock; -import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.common.util.CacheManagerUtil; -import com.yami.shop.common.util.PrincipalUtil; -import com.yami.shop.dao.UserMapper; -import com.yami.shop.security.dao.AppConnectMapper; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.exception.UsernameNotFoundException; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.service.YamiUserDetailsService; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Caching; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; - -/** - * 用户详细信息 - * - * @author - */ -@Slf4j -@Service -@AllArgsConstructor -public class YamiUserServiceImpl implements YamiUserDetailsService { - - private final UserMapper userMapper; - - private final AppConnectMapper appConnectMapper; - - private final PasswordEncoder passwordEncoder; - @Override - @SneakyThrows - public YamiUser loadUserByUsername(String username) { - if (StrUtil.isBlank(username) || !username.contains(StrUtil.COLON) ) { - throw new UsernameNotFoundExceptionBase("无法获取用户信息"); - } - String[] splitInfo = username.split(StrUtil.COLON); - App app = App.instance(Integer.valueOf(splitInfo[0])); - String bizUserId = splitInfo[1]; - return loadUserByAppIdAndBizUserId(app,bizUserId); - } - - /** - * 获取前端登陆的用户信息 - * - * @param app - * @param bizUserId openId - * @return UserDetails - * @throws UsernameNotFoundExceptionBase - */ - @Override - public YamiUser loadUserByAppIdAndBizUserId(App app, String bizUserId) { - - String cacheKey = app.value() + StrUtil.COLON + bizUserId; - - User user = userMapper.getUserByBizUserId(app.value(), bizUserId); - if (user == null) { - throw new UsernameNotFoundExceptionBase("无法获取用户信息"); - } - String name = StrUtil.isBlank(user.getRealName()) ? user.getNickName() : user.getRealName(); - YamiUser yamiUser = new YamiUser(user.getUserId(), bizUserId, app.value(), user.getStatus() == 1); - yamiUser.setName(name); - yamiUser.setPic(user.getPic()); - - return yamiUser; - } - - @Override - @Transactional(rollbackFor = Exception.class) - @RedisLock(lockName = "insertUser", key = "#appConnect.appId + ':' + #appConnect.bizUserId") - @Caching(evict = { - @CacheEvict(cacheNames = "yami_user", key = "#appConnect.appId + ':' + #appConnect.bizUserId"), - @CacheEvict(cacheNames = "AppConnect", key = "#appConnect.appId + ':' + #appConnect.bizUserId") - }) - public void insertUserIfNecessary(AppConnect appConnect) { - // 进入锁后再重新判断一遍用户是否创建 - AppConnect dbAppConnect = appConnectMapper.getByBizUserId(appConnect.getBizUserId(), appConnect.getAppId()); - if(dbAppConnect != null) { - return; - } - - String bizUnionId = appConnect.getBizUnionid(); - String userId = null; - User user; - - if (StrUtil.isNotBlank(bizUnionId)) { - userId = appConnectMapper.getUserIdByUnionId(bizUnionId); - } - if (StrUtil.isBlank(userId)) { - userId = IdUtil.simpleUUID(); - Date now = new Date(); - user = new User(); - user.setUserId(userId); - user.setModifyTime(now); - user.setUserRegtime(now); - user.setStatus(1); - user.setNickName(EmojiUtil.toAlias(StrUtil.isBlank(appConnect.getNickName()) ? "" : appConnect.getNickName())); - user.setPic(appConnect.getImageUrl()); - userMapper.insert(user); - } else { - user = userMapper.selectById(userId); - } - - appConnect.setUserId(user.getUserId()); - - appConnectMapper.insert(appConnect); - } - - @Override - public YamiUser loadUserByUserMail(String userMail, String loginPassword) { - User user = userMapper.getUserByUserMail(userMail); - if (user == null) { - throw new UsernameNotFoundException("用户不存在"); - } - - if (!passwordEncoder.matches(loginPassword, user.getLoginPassword())) { - // 原密码不正确 - throw new UsernameNotFoundException("密码不正确"); - } - String name = StrUtil.isBlank(user.getRealName()) ? user.getNickName() : user.getRealName(); - YamiUser yamiUser = new YamiUser(user.getUserId(), loginPassword, user.getStatus() == 1); - yamiUser.setName(name); - yamiUser.setPic(user.getPic()); - return yamiUser; - } - - @Override - public User loadUserByMobileOrUserName(String mobileOrUserName, Integer loginType) { - User user = null; - // 手机验证码登陆,或传过来的账号很像手机号 - if (Objects.equals(loginType, 1) || PrincipalUtil.isMobile(mobileOrUserName)) { - user = userMapper.selectOne(new LambdaQueryWrapper().eq(User::getUserMobile, mobileOrUserName)); - } - return user; - } - - @Override - public YamiUser getYamiUser(Integer appId, User user, String bizUserId) { - String name = StrUtil.isBlank(user.getRealName()) ? user.getNickName() : user.getRealName(); - YamiUser yamiUser = new YamiUser(); - yamiUser.setEnabled(user.getStatus() == 1); - yamiUser.setUserId(user.getUserId()); - yamiUser.setBizUserId(bizUserId); - yamiUser.setAppType(appId); - yamiUser.setName(name); - yamiUser.setPic(user.getPic()); - yamiUser.setPassword(user.getLoginPassword()); - return yamiUser; - } -} diff --git a/yami-shop-api/src/main/resources/application-dev.yml b/yami-shop-api/src/main/resources/application-dev.yml index 5d74e21..de4328f 100644 --- a/yami-shop-api/src/main/resources/application-dev.yml +++ b/yami-shop-api/src/main/resources/application-dev.yml @@ -14,7 +14,7 @@ spring: connection-test-query: select 1 redis: redisson: - config: classpath:redisson.yml + config: classpath:redisson/redisson.yml logging: - config: classpath:logback-dev.xml + config: classpath:logback/logback-dev.xml diff --git a/yami-shop-api/src/main/resources/application-docker.yml b/yami-shop-api/src/main/resources/application-docker.yml index b947ee4..a26ed59 100644 --- a/yami-shop-api/src/main/resources/application-docker.yml +++ b/yami-shop-api/src/main/resources/application-docker.yml @@ -14,6 +14,6 @@ spring: connection-test-query: select 1 redis: redisson: - config: classpath:redisson-docker.yml + config: classpath:redisson/redisson-docker.yml logging: - config: classpath:logback-prod.xml + config: classpath:logback/logback-prod.xml diff --git a/yami-shop-api/src/main/resources/application-prod.yml b/yami-shop-api/src/main/resources/application-prod.yml index 923143e..09322d9 100644 --- a/yami-shop-api/src/main/resources/application-prod.yml +++ b/yami-shop-api/src/main/resources/application-prod.yml @@ -16,6 +16,6 @@ spring: connection-test-query: select 1 redis: redisson: - config: classpath:redisson.yml + config: classpath:redisson/redisson.yml logging: - config: classpath:logback-prod.xml + config: classpath:logback/logback-prod.xml diff --git a/yami-shop-api/src/main/resources/logback-dev.xml b/yami-shop-api/src/main/resources/logback/logback-dev.xml similarity index 100% rename from yami-shop-api/src/main/resources/logback-dev.xml rename to yami-shop-api/src/main/resources/logback/logback-dev.xml diff --git a/yami-shop-api/src/main/resources/logback-prod.xml b/yami-shop-api/src/main/resources/logback/logback-prod.xml similarity index 100% rename from yami-shop-api/src/main/resources/logback-prod.xml rename to yami-shop-api/src/main/resources/logback/logback-prod.xml diff --git a/yami-shop-admin/src/main/resources/redisson-docker.yml b/yami-shop-api/src/main/resources/redisson/redisson-docker.yml similarity index 80% rename from yami-shop-admin/src/main/resources/redisson-docker.yml rename to yami-shop-api/src/main/resources/redisson/redisson-docker.yml index 4ba23b7..a79943a 100644 --- a/yami-shop-admin/src/main/resources/redisson-docker.yml +++ b/yami-shop-api/src/main/resources/redisson/redisson-docker.yml @@ -4,28 +4,25 @@ singleServerConfig: database: ${REDIS_DATABASE} password: ${REDIS_PASSWORD} idleConnectionTimeout: 10000 - pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 - reconnectionTimeout: 3000 - failedAttempts: 3 clientName: null # 发布和订阅连接的最小空闲连接数 默认1 subscriptionConnectionMinimumIdleSize: 1 # 发布和订阅连接池大小 默认50 - subscriptionConnectionPoolSize: 10 + subscriptionConnectionPoolSize: 1 # 单个连接最大订阅数量 默认5 - subscriptionsPerConnection: 5 + subscriptionsPerConnection: 1 # 最小空闲连接数 默认32,现在暂时不需要那么多的线程 connectionMinimumIdleSize: 4 # connectionPoolSize 默认64,现在暂时不需要那么多的线程 - connectionPoolSize: 20 + connectionPoolSize: 4 # 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。 threads: 0 # 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。 nettyThreads: 0 codec: - class: com.yami.shop.common.serializer.redisson.FstCodec -transportMode: NIO \ No newline at end of file + class: org.redisson.codec.KryoCodec +transportMode: NIO diff --git a/yami-shop-api/src/main/resources/redisson.yml b/yami-shop-api/src/main/resources/redisson/redisson.yml similarity index 77% rename from yami-shop-api/src/main/resources/redisson.yml rename to yami-shop-api/src/main/resources/redisson/redisson.yml index 1c8989e..01883be 100644 --- a/yami-shop-api/src/main/resources/redisson.yml +++ b/yami-shop-api/src/main/resources/redisson/redisson.yml @@ -4,28 +4,25 @@ singleServerConfig: database: 0 password: null idleConnectionTimeout: 10000 - pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 - reconnectionTimeout: 3000 - failedAttempts: 3 clientName: null # 发布和订阅连接的最小空闲连接数 默认1 subscriptionConnectionMinimumIdleSize: 1 # 发布和订阅连接池大小 默认50 - subscriptionConnectionPoolSize: 10 + subscriptionConnectionPoolSize: 1 # 单个连接最大订阅数量 默认5 - subscriptionsPerConnection: 5 + subscriptionsPerConnection: 1 # 最小空闲连接数 默认32,现在暂时不需要那么多的线程 - connectionMinimumIdleSize: 4 + connectionMinimumIdleSize: 2 # connectionPoolSize 默认64,现在暂时不需要那么多的线程 - connectionPoolSize: 20 + connectionPoolSize: 4 # 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。 threads: 0 # 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。 nettyThreads: 0 codec: - class: com.yami.shop.common.serializer.redisson.FstCodec -transportMode: NIO \ No newline at end of file + class: org.redisson.codec.KryoCodec +transportMode: NIO diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/SendType.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/SendType.java new file mode 100644 index 0000000..85b7959 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/SendType.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.bean.enums; + +/** + * @author lh + */ +public enum SendType { + /** + * 用户注册验证码 + */ + REGISTER(12, 1,"用户注册验证码"), + /** + * 发送登录验证码 + */ + LOGIN(13, 1,"发送登录验证码"), + /** + * 修改密码验证码 + */ + UPDATE_PASSWORD(14, 1,"修改密码验证码"), + /** + * 身份验证验证码 + */ + VALID(15, 1,"身份验证验证码") + ; + + private Integer value; + /** + * 1为全部平台发送的消息,2为根据情况 + */ + private Integer type; + private String desc; + SendType(Integer value, Integer type, String desc) { + this.value = value; + this.type = type; + this.desc = desc; + } + public Integer getValue() { + return value; + } + + public String getDesc() { + return desc; + } + + public static SendType instance(Integer value) { + SendType[] enums = values(); + for (SendType statusEnum : enums) { + if (statusEnum.getValue().equals(value)) { + return statusEnum; + } + } + return null; + } + + public Integer getType() { + return type; + } +} diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java index a13a1e8..8973120 100644 --- a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/UserRegisterParam.java @@ -14,12 +14,15 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +/** + * @author lh + */ @Data @ApiModel(value= "设置用户信息") public class UserRegisterParam { @ApiModelProperty(value = "密码") - private String password; + private String passWord; @ApiModelProperty(value = "邮箱") private String userMail; @@ -30,30 +33,18 @@ public class UserRegisterParam { @ApiModelProperty(value = "用户名") private String userName; - @ApiModelProperty(value = "应用类型 1小程序 2微信公众号 3 PC 4 h5") - private Integer appType; - @ApiModelProperty(value = "手机号") private String mobile; - @ApiModelProperty(value = "验证码") - private String validCode; - - @ApiModelProperty(value = "微信小程序的encryptedData") - private String encryptedData; - - @ApiModelProperty(value = "微信小程序的ivStr") - private String ivStr; - @ApiModelProperty(value = "头像") private String img; @ApiModelProperty(value = "校验登陆注册验证码成功的标识") private String checkRegisterSmsFlag; - @ApiModelProperty(value = "验证类型 1验证码验证 2 小程序encryptedData验证 3 密码验证 ") - private Integer validateType; + @ApiModelProperty(value = "当账户未绑定时,临时的uid") + private String tempUid; - @ApiModelProperty(value = "验证类型 1注册 2绑定 ") - private Integer registerOrBind; + @ApiModelProperty(value = "用户id") + private Long userId; } diff --git a/yami-shop-common/pom.xml b/yami-shop-common/pom.xml index 9fcbfb9..e4c3c85 100644 --- a/yami-shop-common/pom.xml +++ b/yami-shop-common/pom.xml @@ -82,8 +82,8 @@ emoji-java - de.ruedigermoeller - fst + com.esotericsoftware + kryo com.github.binarywang @@ -97,5 +97,9 @@ com.github.binarywang weixin-java-mp + + com.alibaba + transmittable-thread-local + diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java index cc27d86..4cf17cc 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java @@ -11,6 +11,7 @@ package com.yami.shop.common.config; import com.yami.shop.common.exception.YamiShopBindException; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; * 自定义错误处理器 * @author LGH */ +@Slf4j @Controller @RestControllerAdvice public class DefaultExceptionHandlerConfig { @@ -30,20 +32,20 @@ public class DefaultExceptionHandlerConfig { @ExceptionHandler(BindException.class) public ResponseEntity bindExceptionHandler(BindException e){ - e.printStackTrace(); + log.error("BindException:", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){ - e.printStackTrace(); + log.error("MethodArgumentNotValidException:", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getBindingResult().getFieldErrors().get(0).getDefaultMessage()); } @ExceptionHandler(YamiShopBindException.class) public ResponseEntity unauthorizedExceptionHandler(YamiShopBindException e){ - e.printStackTrace(); + log.error("YamiShopBindException Message :{}",e.getMessage()); return ResponseEntity.status(e.getHttpStatusCode()).body(e.getMessage()); } } \ No newline at end of file diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java index 8526f29..d600dc0 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/RedisCacheConfig.java @@ -10,11 +10,7 @@ package com.yami.shop.common.config; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - -import com.yami.shop.common.serializer.redis.FstRedisSerializer; +import com.yami.shop.common.serializer.redis.KryoRedisSerializer; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -24,7 +20,13 @@ import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.*; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; /** * redis 缓存配置,仅当配置文件中spring.cache.type = redis时生效 @@ -57,14 +59,11 @@ public class RedisCacheConfig { } private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) { - - FstRedisSerializer kryoRedisSerializer = new FstRedisSerializer(); - RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith( RedisSerializationContext .SerializationPair - .fromSerializer(kryoRedisSerializer) + .fromSerializer(new KryoRedisSerializer<>()) ).entryTtl(Duration.ofSeconds(seconds)); return redisCacheConfiguration; @@ -72,14 +71,24 @@ public class RedisCacheConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new FstRedisSerializer()); - redisTemplate.setHashValueSerializer(new FstRedisSerializer()); + redisTemplate.setValueSerializer(kryoRedisSerializer); + redisTemplate.setHashValueSerializer(kryoRedisSerializer); + redisTemplate.setEnableTransactionSupport(false); + redisTemplate.afterPropertiesSet(); return redisTemplate; } + @Bean + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){ + StringRedisTemplate redisTemplate = new StringRedisTemplate(redisConnectionFactory); + redisTemplate.setEnableTransactionSupport(false); + return redisTemplate; + } + } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java b/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java new file mode 100644 index 0000000..dac1954 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java @@ -0,0 +1,33 @@ +package com.yami.shop.common.constants; + +/** + * @author 菠萝凤梨 + * @date 2022/3/28 14:32 + */ +public interface OauthCacheNames { + + /** + * oauth 授权相关key + */ + String OAUTH_PREFIX = "mall4j_oauth:"; + + /** + * token 授权相关key + */ + String OAUTH_TOKEN_PREFIX = OAUTH_PREFIX + "token:"; + + /** + * 保存token 缓存使用key + */ + String ACCESS = OAUTH_TOKEN_PREFIX + "access:"; + + /** + * 刷新token 缓存使用key + */ + String REFRESH_TO_ACCESS = OAUTH_TOKEN_PREFIX + "refresh_to_access:"; + + /** + * 根据uid获取保存的token key缓存使用的key + */ + String UID_TO_ACCESS = OAUTH_TOKEN_PREFIX + "uid_to_access:"; +} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/enums/YamiHttpStatus.java b/yami-shop-common/src/main/java/com/yami/shop/common/enums/YamiHttpStatus.java index 4bc0692..017550c 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/enums/YamiHttpStatus.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/enums/YamiHttpStatus.java @@ -21,6 +21,9 @@ public enum YamiHttpStatus { UNAUTHORIZED(401, "未授权"), COUPONCANNOTUSETOGETHER(601, "优惠券不能共用"), + + SOCIAL_ACCOUNT_NOT_BIND(475, "social account not bind"), + ACCOUNT_NOT_REGISTER(476, "account not register"), ; diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java b/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java index 3aef841..5b063a5 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/exception/YamiShopBindException.java @@ -25,6 +25,8 @@ public class YamiShopBindException extends RuntimeException{ */ private Integer httpStatusCode; + private Object object; + /** * @param httpStatus http状态码 @@ -48,6 +50,11 @@ public class YamiShopBindException extends RuntimeException{ this.httpStatusCode = HttpStatus.BAD_REQUEST.value(); } + public YamiShopBindException(String msg, Object object) { + super(msg); + this.httpStatusCode = HttpStatus.BAD_REQUEST.value(); + this.object = object; + } public Integer getHttpStatusCode() { return httpStatusCode; diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/filter/FilterConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/filter/FilterConfig.java deleted file mode 100644 index 1ba995c..0000000 --- a/yami-shop-common/src/main/java/com/yami/shop/common/filter/FilterConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.common.filter; - -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.servlet.DispatcherType; - -/** - * @author lgh - */ -@Configuration -public class FilterConfig { - - @Bean - public FilterRegistrationBean filterRegistration() { - FilterRegistrationBean registration = new FilterRegistrationBean<>(); - //添加过滤器 - registration.setFilter(new XssFilter()); - //设置过滤路径,/*所有路径 - registration.addUrlPatterns("/*"); - registration.setName("xssFilter"); - //设置优先级 - registration.setOrder(Integer.MAX_VALUE); - registration.setDispatcherTypes(DispatcherType.REQUEST); - return registration; - } -} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java b/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java index 0109b43..89fc5f7 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/filter/XssFilter.java @@ -10,27 +10,22 @@ package com.yami.shop.common.filter; -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import com.yami.shop.common.xss.XssWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; -import com.yami.shop.common.xss.XssWrapper; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; /** * 一些简单的安全过滤: * xss * @author lgh */ +@Component public class XssFilter implements Filter { Logger logger = LoggerFactory.getLogger(getClass().getName()); diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java b/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java new file mode 100644 index 0000000..7e42838 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java @@ -0,0 +1,51 @@ +package com.yami.shop.common.handler; + +import cn.hutool.core.util.CharsetUtil; +import com.yami.shop.common.exception.YamiShopBindException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @author 菠萝凤梨 + * @date 2022/3/28 14:15 + */ +@Component +public class HttpHandler { + + private static final Logger logger = LoggerFactory.getLogger(HttpHandler.class); + + public void printServerResponseToWeb(String str, int status) { + + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + if (requestAttributes == null) { + logger.error("requestAttributes is null, can not print to web"); + return; + } + HttpServletResponse response = requestAttributes.getResponse(); + if (response == null) { + logger.error("httpServletResponse is null, can not print to web"); + return; + } + logger.error("response error: " + str); + response.setCharacterEncoding(CharsetUtil.UTF_8); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(status); + PrintWriter printWriter = null; + try { + printWriter = response.getWriter(); + printWriter.write(str); + } + catch (IOException e) { + throw new YamiShopBindException("io 异常", e); + } + } +} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/FSTSerializer.java b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/FSTSerializer.java deleted file mode 100644 index 12d616e..0000000 --- a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/FSTSerializer.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.common.serializer; - -import org.nustaq.serialization.FSTConfiguration; -import org.nustaq.serialization.FSTDecoder; -import org.nustaq.serialization.FSTEncoder; -import org.nustaq.serialization.coders.FSTStreamDecoder; -import org.nustaq.serialization.coders.FSTStreamEncoder; - -import java.io.IOException; -import java.lang.reflect.Field; - -/** - * 使用fts进行序列化 - * @author LGH - */ -public class FSTSerializer { - - static class FSTDefaultStreamCoderFactory implements FSTConfiguration.StreamCoderFactory { - - Field chBufField; - Field ascStringCacheField; - - { - try { - chBufField = FSTStreamDecoder.class.getDeclaredField("chBufS"); - ascStringCacheField = FSTStreamDecoder.class.getDeclaredField("ascStringCache"); - } catch (Exception e) { - throw new IllegalStateException(e); - } - ascStringCacheField.setAccessible(true); - chBufField.setAccessible(true); - } - - private FSTConfiguration fstConfiguration; - - FSTDefaultStreamCoderFactory(FSTConfiguration fstConfiguration) { - this.fstConfiguration = fstConfiguration; - } - - @Override - public FSTEncoder createStreamEncoder() { - return new FSTStreamEncoder(fstConfiguration); - } - - @Override - public FSTDecoder createStreamDecoder() { - return new FSTStreamDecoder(fstConfiguration) { - @Override - public String readStringUTF() throws IOException { - try { - String res = super.readStringUTF(); - chBufField.set(this, null); - return res; - } catch (Exception e) { - throw new IOException(e); - } - } - - @Override - public String readStringAsc() throws IOException { - try { - String res = super.readStringAsc(); - ascStringCacheField.set(this, null); - return res; - } catch (Exception e) { - throw new IOException(e); - } - } - }; - } - - static ThreadLocal input = new ThreadLocal(); - static ThreadLocal output = new ThreadLocal(); - - @Override - public ThreadLocal getInput() { - return input; - } - - @Override - public ThreadLocal getOutput() { - return output; - } - - } - - private static class InstanceHolder { - private static final FSTConfiguration INSTANCE = FSTConfiguration.createDefaultConfiguration(); - static { - INSTANCE.setStreamCoderFactory(new FSTDefaultStreamCoderFactory(INSTANCE)); - } - } - - public FSTConfiguration getConfig() { - return InstanceHolder.INSTANCE; - } -} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/FstRedisSerializer.java b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/FstRedisSerializer.java deleted file mode 100644 index aa55cae..0000000 --- a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/FstRedisSerializer.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.common.serializer.redis; - -import com.yami.shop.common.serializer.FSTSerializer; -import lombok.SneakyThrows; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.lang.Nullable; - -/** - * 使用fst 进行reids的序列化 - * @author LGH - */ -public class FstRedisSerializer implements RedisSerializer { - - private static final byte[] EMPTY_ARRAY = new byte[0]; - - @Override - @SneakyThrows - public byte[] serialize(Object o) { - if (o == null) { - return EMPTY_ARRAY; - } - return new FSTSerializer().getConfig().asByteArray(o); - } - - @Override - @SneakyThrows - public Object deserialize(byte[] bytes) { - if (isEmpty(bytes)) { - return null; - } - return new FSTSerializer().getConfig().asObject(bytes); - } - - private static boolean isEmpty(@Nullable byte[] data) { - return (data == null || data.length == 0); - } -} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/KryoRedisSerializer.java b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/KryoRedisSerializer.java new file mode 100644 index 0000000..d27ed3d --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redis/KryoRedisSerializer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.common.serializer.redis; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import lombok.extern.slf4j.Slf4j; +import org.redisson.codec.KryoCodec; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; + +/** + * 使用Kryo 进行reids的序列化 + * @author LGH + */ +@Slf4j +public class KryoRedisSerializer implements RedisSerializer { + + private final KryoCodec kryoPool; + + public KryoRedisSerializer() { + kryoPool = new KryoCodec(Collections.emptyList(), null); + } + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + + @Override + public byte[] serialize(T t) throws SerializationException { + if (t == null) { + return EMPTY_BYTE_ARRAY; + } + + Kryo kryo = kryoPool.get(); + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Output output = new Output(baos)) { + kryo.writeClassAndObject(output, t); + output.flush(); + return baos.toByteArray(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + return EMPTY_BYTE_ARRAY; + } + + @Override + @SuppressWarnings("unchecked") + public T deserialize(byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length <= 0) { + return null; + } + Kryo kryo = kryoPool.get(); + + try (Input input = new Input(bytes)) { + return (T) kryo.readClassAndObject(input); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } +} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redisson/FstCodec.java b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redisson/FstCodec.java deleted file mode 100644 index d2f9e95..0000000 --- a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/redisson/FstCodec.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.common.serializer.redisson; - -import java.io.IOException; - -import com.yami.shop.common.serializer.FSTSerializer; -import org.nustaq.serialization.FSTConfiguration; -import org.nustaq.serialization.FSTObjectInput; -import org.nustaq.serialization.FSTObjectOutput; -import org.redisson.client.codec.BaseCodec; -import org.redisson.client.handler.State; -import org.redisson.client.protocol.Decoder; -import org.redisson.client.protocol.Encoder; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; - -/** - * 被redisson使用 - * Efficient and speedy serialization codec fully - * compatible with JDK Serialization codec. - * - * https://github.com/RuedigerMoeller/fast-serialization - * - * @author Nikita Koksharov - * - */ -public class FstCodec extends BaseCodec { - - - private final FSTConfiguration config; - - public FstCodec() { - config = new FSTSerializer().getConfig(); - } - - - private final Decoder decoder = new Decoder() { - @Override - public Object decode(ByteBuf buf, State state) throws IOException { - ByteBufInputStream in = new ByteBufInputStream(buf); - FSTObjectInput inputStream = config.getObjectInput(in); - try { - return inputStream.readObject(); - } catch (IOException e) { - throw e; - } catch (Exception e) { - throw new IOException(e); - } - } - }; - - private final Encoder encoder = new Encoder() { - - @Override - public ByteBuf encode(Object in) throws IOException { - ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); - ByteBufOutputStream os = new ByteBufOutputStream(out); - FSTObjectOutput oos = config.getObjectOutput(os); - try { - oos.writeObject(in); - oos.flush(); - return os.buffer(); - } catch (IOException e) { - out.release(); - throw e; - } catch (Exception e) { - out.release(); - throw new IOException(e); - } - } - }; - - @Override - public Decoder getValueDecoder() { - return decoder; - } - - @Override - public Encoder getValueEncoder() { - return encoder; - } - - @Override - public ClassLoader getClassLoader() { - if (config.getClassLoader() != null) { - return config.getClassLoader(); - } - - return super.getClassLoader(); - } - -} diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java index 2cdaa7a..d6825f9 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java @@ -11,23 +11,20 @@ package com.yami.shop.common.util; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonGenerator.Feature; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Slf4j public class Json { private static ObjectMapper objectMapper = new ObjectMapper(); @@ -80,7 +77,7 @@ public class Json { try { return objectMapper.writeValueAsString(object); } catch (JsonProcessingException e) { - e.printStackTrace(); + log.error("对象转json错误:", e); } return null; } @@ -96,7 +93,7 @@ public class Json { try { result = objectMapper.readValue(json, clazz); } catch (Exception e) { - e.printStackTrace(); + log.error("对象转json错误:", e); } return result; } @@ -117,7 +114,7 @@ public class Json { try { result = objectMapper.readValue(json, clazz); } catch (Exception e) { - e.printStackTrace(); + log.error("Json转换错误:", e); } if (result == null) { return Collections.emptyList(); @@ -136,18 +133,8 @@ public class Json { try { jsonNode = objectMapper.readTree(jsonStr); } catch (Exception e) { - e.printStackTrace(); + log.error("Json转换错误:", e); } return jsonNode; } - -// public static void main(String[] args){ -// String arr = "[1.01,1.03,1.23]"; -// -// List doubles = parseArray(arr, Double[].class); -// for (Double aDouble : doubles) { -// System.out.println(aDouble); -// } -// -// } } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java index 53a7a28..6a40c51 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java @@ -14,6 +14,9 @@ import cn.hutool.core.util.PageUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.Data; +/** + * @author lh + */ @Data public class PageAdapter{ @@ -22,7 +25,7 @@ public class PageAdapter{ private int size; public PageAdapter(Page page) { - int[] startEnd = PageUtil.transToStartEnd((int) page.getCurrent(), (int) page.getSize()); + int[] startEnd = PageUtil.transToStartEnd((int) page.getCurrent() - 1, (int) page.getSize()); this.begin = startEnd[0]; this.size = (int)page.getSize(); } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/PrincipalUtil.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/PrincipalUtil.java index b08aa1d..6a97064 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/util/PrincipalUtil.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/PrincipalUtil.java @@ -20,6 +20,11 @@ public class PrincipalUtil { */ public static final String USER_NAME_REGEXP = "([a-zA-Z0-9_]{4,16})"; + /** + * 由简单的字母数字拼接而成的字符串 不含有下划线,大写字母 + */ + public static final String SIMPLE_CHAR_REGEXP = "([a-z0-9]+)"; + public static boolean isMobile(String value) { if(StrUtil.isBlank(value)) { return false; @@ -33,4 +38,20 @@ public class PrincipalUtil { } return Pattern.matches(USER_NAME_REGEXP, value); } + + public static boolean isMatching(String regexp, String value) { + if (StrUtil.isBlank(value)) { + return false; + } + return Pattern.matches(regexp, value); + } + + /** + * 是否是由简单的字母数字拼接而成的字符串 + * @param value 输入值 + * @return 匹配结果 + */ + public static boolean isSimpleChar(String value) { + return isMatching(SIMPLE_CHAR_REGEXP, value); + } } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/RedisUtil.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/RedisUtil.java index fb71ecb..bf16ecd 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/util/RedisUtil.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/RedisUtil.java @@ -10,97 +10,107 @@ package com.yami.shop.common.util; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.CollectionUtils; +import java.util.concurrent.TimeUnit; + +/** + * @author lh + */ +@Slf4j public class RedisUtil { - private static RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate",RedisTemplate.class); - + private static RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate", RedisTemplate.class); + //=============================common============================ + /** * 指定缓存失效时间 - * @param key 键 + * + * @param key 键 * @param time 时间(秒) * @return */ - public static boolean expire(String key,long time){ + public static boolean expire(String key, long time) { try { - if(time>0){ + if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { - e.printStackTrace(); + log.error("设置redis指定key失效时间错误:", e); return false; } } /** * 根据key 获取过期时间 + * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1) */ - public static long getExpire(String key){ - return redisTemplate.getExpire(key,TimeUnit.SECONDS); + public static long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 + * * @param key 键 * @return true 存在 false 不存在 */ - public static boolean hasKey(String key){ + public static boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { - e.printStackTrace(); + log.error("redis判断key是否存在错误:", e); return false; } } /** * 删除缓存 + * * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") - public static void del(String ... key){ - if(key!=null&&key.length>0){ - if(key.length==1){ + public static void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { redisTemplate.delete(key[0]); - }else{ + } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } //============================String============================= + /** * 普通缓存获取 + * * @param key 键 * @return 值 */ @SuppressWarnings("unchecked") - public static T get(String key){ - return key==null?null:(T)redisTemplate.opsForValue().get(key); + public static T get(String key) { + return key == null ? null : (T) redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 - * @param key 键 + * + * @param key 键 * @param value 值 * @return true成功 false失败 */ - public static boolean set(String key,Object value) { + public static boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { - e.printStackTrace(); + log.error("设置redis缓存错误:", e); return false; } @@ -108,16 +118,17 @@ public class RedisUtil { /** * 普通缓存放入并设置时间 - * @param key 键 + * + * @param key 键 * @param value 值 - * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ - public static boolean set(String key,Object value,long time){ + public static boolean set(String key, Object value, long time) { try { - if(time>0){ + if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); - }else{ + } else { set(key, value); } return true; @@ -129,12 +140,13 @@ public class RedisUtil { /** * 递增 此时value值必须为int类型 否则报错 - * @param key 键 + * + * @param key 键 * @param delta 要增加几(大于0) * @return */ - public static long incr(String key, long delta){ - if(delta<0){ + public static long incr(String key, long delta) { + if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); @@ -142,12 +154,13 @@ public class RedisUtil { /** * 递减 - * @param key 键 + * + * @param key 键 * @param delta 要减少几(小于0) * @return */ - public static long decr(String key, long delta){ - if(delta<0){ + public static long decr(String key, long delta) { + if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/SimpleCaptcha.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/SimpleCaptcha.java deleted file mode 100644 index 0e0c7bd..0000000 --- a/yami-shop-common/src/main/java/com/yami/shop/common/util/SimpleCaptcha.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.common.util; - -import cn.hutool.captcha.LineCaptcha; -import cn.hutool.core.util.ImageUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.concurrent.ThreadLocalRandom; - -public class SimpleCaptcha extends LineCaptcha{ - - private static final long serialVersionUID = -9042552338521307038L; - - private static final String CAPTCHA_CODE = "abcdefhjkmnpqrstuvwxyz2345678"; - - public SimpleCaptcha(int width, int height, int codeCount, int interfereCount) { - - super(width, height, codeCount, interfereCount); - } - - @Override - protected void generateCode() { - this.code = RandomUtil.randomString(CAPTCHA_CODE,this.generator.getLength()); - } - - @Override - public Image createImage(String code) { - // 图像buffer - final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - final ThreadLocalRandom random = RandomUtil.getRandom(); - final Graphics2D g = ImageUtil.createGraphics(image, new Color(249,249,249)); - - // 干扰线 - drawInterfere(g, random); - - // 创建字体 - g.setFont(this.font); - final FontMetrics metrics = g.getFontMetrics(); - int minY = metrics.getAscent() - metrics.getLeading() - metrics.getDescent(); - // 文字 - final int len = this.generator.getLength(); - int charWidth = width / len; - for (int i = 0; i < len; i++) { - // 产生随机的颜色值,让输出的每个字符的颜色值都将不同。 - g.setColor(ImageUtil.randomColor(random)); - g.drawString(String.valueOf(code.charAt(i)), i * charWidth, RandomUtil.randomInt(minY, this.height)); - } - - return image; - } - - /** - * 绘制干扰线 - * - * @param g {@link Graphics2D}画笔 - * @param random 随机对象 - */ - private void drawInterfere(Graphics2D g, ThreadLocalRandom random) { - // 干扰线 - for (int i = 0; i < this.interfereCount; i++) { - int xs = random.nextInt(width); - int ys = random.nextInt(height); - int xe = xs + random.nextInt(width / 8); - int ye = ys + random.nextInt(height / 8); - g.setColor(ImageUtil.randomColor(random)); - g.drawLine(xs, ys, xe, ye); - } - } - -} diff --git a/yami-shop-mp/pom.xml b/yami-shop-mp/pom.xml deleted file mode 100644 index 01098e2..0000000 --- a/yami-shop-mp/pom.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - yami-shop - com.yami.shop - 0.0.1-SNAPSHOT - - 4.0.0 - - yami-shop-mp - - - - com.yami.shop - yami-shop-security - ${yami.shop.version} - - - \ No newline at end of file diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/AbstractBuilder.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/AbstractBuilder.java deleted file mode 100644 index ccc7a01..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/AbstractBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.builder; - -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.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Binary Wang(https://github.com/binarywang) - */ -public abstract class AbstractBuilder { - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - public abstract WxMpXmlOutMessage build(String content, - WxMpXmlMessage wxMessage, WxMpService service); -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/ImageBuilder.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/ImageBuilder.java deleted file mode 100644 index e718a38..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/ImageBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.builder; - -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; - -/** - * @author Binary Wang(https://github.com/binarywang) - */ -public class ImageBuilder extends AbstractBuilder { - - @Override - public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, - WxMpService service) { - - WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content) - .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) - .build(); - - return m; - } - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/TextBuilder.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/TextBuilder.java deleted file mode 100644 index d68939e..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/builder/TextBuilder.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.builder; - - -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.bean.message.WxMpXmlOutTextMessage; - -/** - * @author Binary Wang(https://github.com/binarywang) - */ -public class TextBuilder extends AbstractBuilder { - - @Override - public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, - WxMpService service) { - WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content) - .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) - .build(); - return m; - } - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaInRedisConfig.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaInRedisConfig.java deleted file mode 100644 index f5730af..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaInRedisConfig.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.component; - -import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; -import com.yami.shop.common.annotation.RedisLock; -import com.yami.shop.common.util.RedisUtil; -import com.yami.shop.mp.config.bean.WxMiniApp; -import me.chanjar.weixin.common.bean.WxAccessToken; -import org.redisson.api.RedissonClient; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.concurrent.locks.Lock; - -/** - * 基于Redis的微信配置provider. - * - * 已加入分布式锁的实现 - * - * @author LGH - */ -@Component -public class WxMaInRedisConfig extends WxMaDefaultConfigImpl { - - private static final String ACCESS_TOKEN_KEY = "wxMa:access_token:"; - - private static final String JSAPI_TICKET_KEY = "wxMa:jsapi_ticket:"; - - private static final String CARD_API_TICKET_KEY = "wxMa:card_api_ticket:"; - - private static final String WX_MA_ACCESS_TOKEN_LOCK = "wxMa:access_token_lock:"; - - private static final String WX_MA_JSAPI_TICKET_LOCK = "wxMa:jsapi_ticket_lock:"; - - private static final String WX_MA_CARD_API_TICKET_LOCK = "wxMa:card_api_ticket_lock:"; - - private String accessTokenKey; - - private String jsapiTicketKey; - - private String cardApiTicketKey; - - @Autowired - private RedissonClient redissonClient; - - public WxMaInRedisConfig (WxMiniApp wxMiniApp) { - this.setAppid(wxMiniApp.getAppid()); - this.setSecret(wxMiniApp.getSecret()); - } - - /** - * 每个公众号生成独有的存储key. - */ - @Override - public void setAppid(String appId) { - super.setAppid(appId); - this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId); - this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(appId); - this.cardApiTicketKey = CARD_API_TICKET_KEY.concat(appId); - } - - @Override - public String getAccessToken() { - return RedisUtil.get(accessTokenKey); - } - - @Override - public Lock getAccessTokenLock(){ - return redissonClient.getLock(WX_MA_ACCESS_TOKEN_LOCK); - } - - @Override - public boolean isAccessTokenExpired() { - return !RedisUtil.hasKey(accessTokenKey); - } - - @Override - public void expireAccessToken() { - RedisUtil.del(accessTokenKey); - } - - @Override - @RedisLock(lockName = "updateMaAccessToken") - public void updateAccessToken(WxAccessToken accessToken) { - updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); - } - - - @Override - @RedisLock(lockName = "updateMaAccessToken") - public void updateAccessToken(String accessToken, int expiresInSeconds) { - RedisUtil.set(accessTokenKey, accessToken, expiresInSeconds - 200); - } - - @Override - public String getJsapiTicket() { - return RedisUtil.get(jsapiTicketKey); - } - - @Override - public Lock getJsapiTicketLock() { - return redissonClient.getLock(WX_MA_JSAPI_TICKET_LOCK); - } - - @Override - public boolean isJsapiTicketExpired() { - return !RedisUtil.hasKey(jsapiTicketKey); - } - - @Override - public void expireJsapiTicket() { - RedisUtil.del(jsapiTicketKey); - } - - @Override - @RedisLock(lockName = "updateMaJsapiTicket") - public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { - RedisUtil.set(jsapiTicketKey, jsapiTicket, expiresInSeconds - 200); - } - - @Override - public String getCardApiTicket() { - return RedisUtil.get(cardApiTicketKey); - } - - @Override - public Lock getCardApiTicketLock() { - return redissonClient.getLock(WX_MA_CARD_API_TICKET_LOCK); - } - - @Override - public boolean isCardApiTicketExpired() { - return !RedisUtil.hasKey(cardApiTicketKey); - } - - @Override - public void expireCardApiTicket() { - RedisUtil.del(cardApiTicketKey); - } - - @Override - @RedisLock(lockName = "updateMaCardJsapiTicket") - public void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) { - RedisUtil.set(cardApiTicketKey, cardApiTicket, expiresInSeconds - 200); - } - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaServiceClusterImpl.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaServiceClusterImpl.java deleted file mode 100644 index a36601c..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMaServiceClusterImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.yami.shop.mp.component; - -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; -import cn.hutool.http.HttpUtil; -import com.yami.shop.common.exception.YamiShopBindException; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.error.WxError; -import me.chanjar.weixin.common.error.WxErrorException; -import org.redisson.api.RLock; -import org.redisson.api.RedissonClient; - -import java.util.concurrent.TimeUnit; - -/** - * WxMaServiceImpl 在集群模式获取accessToken的方式 - * @author LGH - */ -@Slf4j -public class WxMaServiceClusterImpl extends WxMaServiceImpl { - - private static final String REDISSON_LOCK_PREFIX = "redisson_lock:"; - - private RedissonClient redissonClient; - - public void setRedissonClient(RedissonClient redissonClient) { - this.redissonClient = redissonClient; - } - - @Override - public String getAccessToken(boolean forceRefresh) throws WxErrorException { - if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) { - return this.getWxMaConfig().getAccessToken(); - } - - RLock rLock = redissonClient.getLock(REDISSON_LOCK_PREFIX + ":WxMaServiceCluster:getAccessToken"); - - try { - boolean lockSuccess; - try { - lockSuccess = rLock.tryLock(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - return this.getWxMaConfig().getAccessToken(); - } - - if (!lockSuccess) { - throw new YamiShopBindException("服务器繁忙,请稍后再试"); - } - - if (!this.getWxMaConfig().isAccessTokenExpired()) { - return this.getWxMaConfig().getAccessToken(); - } - - String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), - this.getWxMaConfig().getSecret()); - String resultContent = HttpUtil.get(url); - WxError error = WxError.fromJson(resultContent); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); - this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); - - return this.getWxMaConfig().getAccessToken(); - - } finally { - rLock.unlock(); - } - - } -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpInRedisConfigStorage.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpInRedisConfigStorage.java deleted file mode 100644 index fb6e9ac..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpInRedisConfigStorage.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.component; - -import com.yami.shop.common.annotation.RedisLock; -import com.yami.shop.common.util.RedisUtil; -import com.yami.shop.mp.config.bean.WxMp; -import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; -import me.chanjar.weixin.mp.enums.TicketType; -import org.springframework.stereotype.Component; - -/** - * 基于Redis的微信配置provider. - * - * 已加入分布式锁的实现 - * - * @author LGH - */ -@Component -public class WxMpInRedisConfigStorage extends WxMpDefaultConfigImpl { - - private static final String ACCESS_TOKEN_KEY = "wxMp:access_token:"; - - private String accessTokenKey; - - public WxMpInRedisConfigStorage (WxMp wxMp) { - this.setAppId(wxMp.getAppid()); - this.setSecret(wxMp.getSecret()); - this.setToken(wxMp.getToken()); - this.setAesKey(wxMp.getAesKey()); - } - - /** - * 每个公众号生成独有的存储key. - */ - @Override - public void setAppId(String appId) { - super.setAppId(appId); - this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId); - } - - private String getTicketRedisKey(TicketType type) { - return String.format("wx:ticket:key:%s:%s", this.appId, type.getCode()); - } - - @Override - public String getAccessToken() { - return RedisUtil.get(accessTokenKey); - } - - @Override - public boolean isAccessTokenExpired() { - return !RedisUtil.hasKey(accessTokenKey); - } - - @Override - @RedisLock(lockName = "updateMpAccessToken") - public void updateAccessToken(WxAccessToken accessToken) { - updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); - } - - - @Override - @RedisLock(lockName = "updateMpAccessToken") - public void updateAccessToken(String accessToken, int expiresInSeconds) { - RedisUtil.set(accessTokenKey, accessToken, expiresInSeconds - 200); - } - - @Override - public void expireAccessToken() { - RedisUtil.del(accessTokenKey); - } - - @Override - public String getTicket(TicketType type) { - return RedisUtil.get(this.getTicketRedisKey(type)); - } - - @Override - public boolean isTicketExpired(TicketType type) { - return !RedisUtil.hasKey(this.getTicketRedisKey(type)); - } - - @Override - @RedisLock(lockName = "updateMpJsapiTicket") - public void updateTicket(TicketType type, String jsapiTicket, int expiresInSeconds) { - RedisUtil.set(this.getTicketRedisKey(type), jsapiTicket, expiresInSeconds - 200); - } - - @Override - public void expireTicket(TicketType type) { - RedisUtil.del(this.getTicketRedisKey(type)); - } - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpServiceClusterImpl.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpServiceClusterImpl.java deleted file mode 100644 index bae99df..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/component/WxMpServiceClusterImpl.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.yami.shop.mp.component; - -import cn.hutool.http.HttpUtil; -import com.yami.shop.common.exception.YamiShopBindException; -import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.error.WxError; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; -import org.redisson.api.RLock; -import org.redisson.api.RedissonClient; - -import java.util.concurrent.TimeUnit; - -import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GET_ACCESS_TOKEN_URL; - -/** - * WxMpServiceImpl 在集群模式获取accessToken的方式 - * @author LGH - */ -public class WxMpServiceClusterImpl extends WxMpServiceHttpClientImpl { - - - private static final String REDISSON_LOCK_PREFIX = "redisson_lock:"; - - private RedissonClient redissonClient; - - public void setRedissonClient(RedissonClient redissonClient) { - this.redissonClient = redissonClient; - } - - @Override - public String getAccessToken(boolean forceRefresh) throws WxErrorException { - if (!this.getWxMpConfigStorage().isAccessTokenExpired() && !forceRefresh) { - return this.getWxMpConfigStorage().getAccessToken(); - } - - RLock rLock = redissonClient.getLock(REDISSON_LOCK_PREFIX + ":WxMpServiceCluster:getAccessToken"); - - try { - boolean doingUpdateAccessToken; - try { - doingUpdateAccessToken = rLock.tryLock(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - return this.getWxMpConfigStorage().getAccessToken(); - } - - if (!doingUpdateAccessToken) { - throw new YamiShopBindException("服务器繁忙,请稍后再试"); - } - - if (!this.getWxMpConfigStorage().isAccessTokenExpired()) { - return this.getWxMpConfigStorage().getAccessToken(); - } - String url = String.format(GET_ACCESS_TOKEN_URL.getUrl(this.getWxMpConfigStorage()), this.getWxMpConfigStorage().getAppId(), this.getWxMpConfigStorage().getSecret()); - String resultContent = HttpUtil.get(url); - - WxError error = WxError.fromJson(resultContent, WxType.MP); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); - this.getWxMpConfigStorage().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); - return this.getWxMpConfigStorage().getAccessToken(); - - } finally { - rLock.unlock(); - } - } -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMaConfiguration.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMaConfiguration.java deleted file mode 100644 index 6670f37..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMaConfiguration.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.config; - -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; -import com.yami.shop.mp.component.WxMaInRedisConfig; -import com.yami.shop.mp.component.WxMaServiceClusterImpl; -import lombok.AllArgsConstructor; -import org.redisson.api.RedissonClient; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * 微信小程序配置文件 - * @author LGH - */ -@Configuration -@AllArgsConstructor -@ConditionalOnClass(WxMaService.class) -public class WxMaConfiguration { - - - private final WxMaInRedisConfig wxMaInRedisConfig; - - private final RedissonClient redissonClient; - - @Bean - public WxMaService wxMaService() { - WxMaServiceClusterImpl service = new WxMaServiceClusterImpl(); - service.setWxMaConfig(wxMaInRedisConfig); - service.setRedissonClient(redissonClient); - return service; - } - - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMpConfiguration.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMpConfiguration.java deleted file mode 100644 index 44d5c61..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxMpConfiguration.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.config; - -import com.yami.shop.mp.component.WxMpInRedisConfigStorage; -import com.yami.shop.mp.component.WxMpServiceClusterImpl; -import com.yami.shop.mp.handler.MenuHandler; -import lombok.AllArgsConstructor; -import me.chanjar.weixin.mp.api.WxMpMessageRouter; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; -import org.redisson.api.RedissonClient; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; -import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; - -/** - * 微信公众号配置文件 - * @author LGH - */ -@Configuration -@AllArgsConstructor -@ConditionalOnClass(WxMpService.class) -public class WxMpConfiguration { - - private final MenuHandler menuHandler; - private final WxMpInRedisConfigStorage wxMpInRedisConfigStorage; - private final RedissonClient redissonClient; - - @Bean - public WxMpService wxMpService() { - WxMpServiceClusterImpl service = new WxMpServiceClusterImpl(); - service.setWxMpConfigStorage(wxMpInRedisConfigStorage); - service.setRedissonClient(redissonClient); - return service; - } - - @Bean - public WxMpMessageRouter messageRouter() { - final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService()); - - // 自定义菜单事件 - newRouter.rule().async(false).msgType(XmlMsgType.EVENT) - .event(MenuButtonType.CLICK).handler(this.menuHandler).end(); - - - return newRouter; - } - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxPayConfiguration.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxPayConfiguration.java deleted file mode 100644 index 260c3fd..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/WxPayConfiguration.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.config; - -import com.github.binarywang.wxpay.config.WxPayConfig; -import com.github.binarywang.wxpay.constant.WxPayConstants; -import com.github.binarywang.wxpay.exception.WxPayException; -import com.github.binarywang.wxpay.service.WxPayService; -import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; -import com.yami.shop.mp.config.bean.WxMiniApp; -import com.yami.shop.mp.config.bean.WxMp; -import com.yami.shop.mp.config.bean.WxPay; -import lombok.AllArgsConstructor; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.Objects; - -/** - * 微信公众号配置文件 - * - * @author LGH - */ -@Configuration -@RequiredArgsConstructor -@ConditionalOnClass(WxPayService.class) -public class WxPayConfiguration { - - private final WxMp wxMp; - - private final WxMiniApp wxMiniApp; - - private final WxPay wxPay; - - @Value("${spring.profiles.active}") - private String profile; - - - @Bean - public WxPayService wxMiniPayService() { - return getWxMpPayServiceByAppId(wxMiniApp.getAppid()); - } - - @Bean - public WxPayService wxMpPayService() { - return getWxMpPayServiceByAppId(wxMp.getAppid()); - } - - - private WxPayService getWxMpPayServiceByAppId(String appid) { - WxPayConfig payConfig = new WxPayConfig(); - payConfig.setAppId(appid); - payConfig.setMchId(wxPay.getMchId()); - payConfig.setMchKey(wxPay.getMchKey()); - payConfig.setKeyPath(wxPay.getKeyPath()); - payConfig.setSignType(WxPayConstants.SignType.MD5); - - WxPayService wxPayService = new WxPayServiceImpl(); - -// 打开下面的代码,开启沙箱模式 -// if (Objects.equals(profile, "dev")) { -// String sandboxSignKey = null; -// try { -// wxPayService.setConfig(payConfig); -// sandboxSignKey = wxPayService.getSandboxSignKey(); -// } catch (WxPayException e) { -// e.printStackTrace(); -// } -// payConfig.setUseSandboxEnv(true); -// payConfig.setMchKey(sandboxSignKey); -// } - - wxPayService.setConfig(payConfig); - return wxPayService; - } -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMiniApp.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMiniApp.java deleted file mode 100644 index 5c268c6..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMiniApp.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.config.bean; - - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Component; - -@Data -@Component -@PropertySource("classpath:ma.properties") -@ConfigurationProperties(prefix = "ma") -public class WxMiniApp { - /** - * 设置微信小程序的appid - */ - private String appid; - - /** - * 设置微信小程序的Secret - */ - private String secret; - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMp.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMp.java deleted file mode 100644 index e372019..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxMp.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.config.bean; - - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Component; - -@Data -@Component -@PropertySource("classpath:mp.properties") -@ConfigurationProperties(prefix = "mp") -public class WxMp { - /** - * 设置微信公众号的appid - */ - private String appid; - - /** - * 设置微信公众号的Secret - */ - private String secret; - - /** - * 微信公众号消息加解密token - */ - private String token; - - /** - * 微信公众号消息加解密aesKey - */ - private String aesKey; -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxPay.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxPay.java deleted file mode 100644 index 8dc1541..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/config/bean/WxPay.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.config.bean; - - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Component; - -@Data -@Component -@PropertySource("classpath:pay.properties") -@ConfigurationProperties(prefix = "pay") -public class WxPay { - /** - * 微信支付mchId - */ - private String mchId; - - /** - * 微信支付mchKey - */ - private String mchKey; - - /** - * 签名类型 - */ - private String signType; - - /** - * 支付证书路径 - */ - private String keyPath; - - -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/controller/api/WxPortalController.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/controller/api/WxPortalController.java deleted file mode 100644 index f38633d..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/controller/api/WxPortalController.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.controller.api; - -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.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.*; - -/** - */ -@RestController -@RequestMapping("/wx/portal") -public class WxPortalController { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - @Autowired - private WxMpService wxMpService; - - @Autowired - private WxMpMessageRouter wxMpMessageRouter; - - /** - * 用来进行微信认证,也就是当在: - * 公众号官方管理后台->开发->基本配置->修改配置 时,需要进行的校验 - * - * @return - */ - @GetMapping(produces = "text/plain;charset=utf-8") - public String authGet(@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) { - - this.logger.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, - timestamp, nonce, echostr); - if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { - throw new IllegalArgumentException("请求参数非法,请核实!"); - } - - if (wxMpService.checkSignature(timestamp, nonce, signature)) { - return echostr; - } - - return "非法请求"; - } - - /** - * 接收公众号的消息 - * - * @return - */ - @PostMapping(produces = "application/xml; charset=UTF-8") - public String post(@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) { - this.logger.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," - + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", - openid, signature, encType, msgSignature, timestamp, nonce, requestBody); - - - String out = null; - if (encType == null) { - // 明文传输的消息 - WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); - WxMpXmlOutMessage outMessage = this.route(inMessage); - if (outMessage == null) { - return ""; - } - - out = outMessage.toXml(); - } else if ("aes".equalsIgnoreCase(encType)) { - // aes加密的消息 - WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxMpService.getWxMpConfigStorage(), - timestamp, nonce, msgSignature); - this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); - WxMpXmlOutMessage outMessage = this.route(inMessage); - if (outMessage == null) { - return ""; - } - - out = outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage()); - } - - this.logger.debug("\n组装回复信息:{}", out); - return out; - } - - private WxMpXmlOutMessage route(WxMpXmlMessage message) { - try { - return wxMpMessageRouter.route(message); - } catch (Exception e) { - this.logger.error("路由消息时出现异常!", e); - } - return null; - } - -} \ No newline at end of file diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/handler/AbstractHandler.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/handler/AbstractHandler.java deleted file mode 100644 index b9415f1..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/handler/AbstractHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.handler; - -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Binary Wang(https://github.com/binarywang) - */ -public abstract class AbstractHandler implements WxMpMessageHandler { - protected Logger logger = LoggerFactory.getLogger(getClass()); -} diff --git a/yami-shop-mp/src/main/java/com/yami/shop/mp/handler/MenuHandler.java b/yami-shop-mp/src/main/java/com/yami/shop/mp/handler/MenuHandler.java deleted file mode 100644 index 465a1c3..0000000 --- a/yami-shop-mp/src/main/java/com/yami/shop/mp/handler/MenuHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.mp.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.MenuButtonType; - -/** - * @author Binary Wang(https://github.com/binarywang) - */ -@Component -public class MenuHandler extends AbstractHandler { - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, - Map context, WxMpService weixinService, - WxSessionManager sessionManager) { - - String msg = String.format("type:%s, event:%s, key:%s", - wxMessage.getMsgType(), wxMessage.getEvent(), - wxMessage.getEventKey()); - if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) { - return null; - } - - return WxMpXmlOutMessage.TEXT().content(msg) - .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) - .build(); - } - -} diff --git a/yami-shop-mp/src/main/resources/ma.properties b/yami-shop-mp/src/main/resources/ma.properties deleted file mode 100644 index 5b3cc3b..0000000 --- a/yami-shop-mp/src/main/resources/ma.properties +++ /dev/null @@ -1,2 +0,0 @@ -ma.appid= -ma.secret= \ No newline at end of file diff --git a/yami-shop-mp/src/main/resources/mp.properties b/yami-shop-mp/src/main/resources/mp.properties deleted file mode 100644 index a3c4d4d..0000000 --- a/yami-shop-mp/src/main/resources/mp.properties +++ /dev/null @@ -1,4 +0,0 @@ -mp.appid= -mp.secret= -mp.token= -mp.aesKey= diff --git a/yami-shop-mp/src/main/resources/pay.properties b/yami-shop-mp/src/main/resources/pay.properties deleted file mode 100644 index 8e802d4..0000000 --- a/yami-shop-mp/src/main/resources/pay.properties +++ /dev/null @@ -1,3 +0,0 @@ -pay.mchId= -pay.mchKey= -pay.keyPath=classpath:xxx.p12 \ No newline at end of file diff --git a/yami-shop-mp/src/main/resources/xxx.p12 b/yami-shop-mp/src/main/resources/xxx.p12 deleted file mode 100644 index e69de29..0000000 diff --git a/yami-shop-quartz/pom.xml b/yami-shop-quartz/pom.xml index cf6fe32..944f36b 100644 --- a/yami-shop-quartz/pom.xml +++ b/yami-shop-quartz/pom.xml @@ -23,7 +23,7 @@ com.yami.shop - yami-shop-security + yami-shop-security-admin ${yami.shop.version} diff --git a/yami-shop-quartz/src/main/java/com/yami/shop/quartz/controller/ScheduleJobController.java b/yami-shop-quartz/src/main/java/com/yami/shop/quartz/controller/ScheduleJobController.java index d231cce..8e12466 100644 --- a/yami-shop-quartz/src/main/java/com/yami/shop/quartz/controller/ScheduleJobController.java +++ b/yami-shop-quartz/src/main/java/com/yami/shop/quartz/controller/ScheduleJobController.java @@ -10,32 +10,21 @@ package com.yami.shop.quartz.controller; -import javax.validation.Valid; - - +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.common.annotation.SysLog; import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.util.PageParam; import com.yami.shop.quartz.model.ScheduleJob; import com.yami.shop.quartz.service.ScheduleJobService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; - -import com.yami.shop.common.util.PageParam; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.yami.shop.common.annotation.SysLog; - -import cn.hutool.core.util.StrUtil; +import javax.validation.Valid; diff --git a/yami-shop-quartz/src/main/java/com/yami/shop/quartz/util/SpringBeanTaskUtil.java b/yami-shop-quartz/src/main/java/com/yami/shop/quartz/util/SpringBeanTaskUtil.java index f80e9a3..09bf2b1 100644 --- a/yami-shop-quartz/src/main/java/com/yami/shop/quartz/util/SpringBeanTaskUtil.java +++ b/yami-shop-quartz/src/main/java/com/yami/shop/quartz/util/SpringBeanTaskUtil.java @@ -39,7 +39,7 @@ public class SpringBeanTaskUtil { method.invoke(target); } } catch (Exception e) { - e.printStackTrace(); + log.error("执行定时任务失败:", e); throw new RuntimeException("执行定时任务失败", e); } } diff --git a/yami-shop-security/pom.xml b/yami-shop-security/pom.xml index a5c1db0..d1c4c72 100644 --- a/yami-shop-security/pom.xml +++ b/yami-shop-security/pom.xml @@ -11,16 +11,12 @@ yami-shop-security - - - com.yami.shop - yami-shop-service - ${yami.shop.version} - - - - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure - - + 商城安全模块 + pom + + + yami-shop-security-api + yami-shop-security-admin + yami-shop-security-common + \ No newline at end of file diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/config/AuthorizationServerConfig.java b/yami-shop-security/src/main/java/com/yami/shop/security/config/AuthorizationServerConfig.java deleted file mode 100644 index 87a179a..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/config/AuthorizationServerConfig.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.config; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.security.oauth2.provider.token.TokenStore; - -/** - * @author LGH - */ -@Configuration -//@Order(2) -@EnableAuthorizationServer -public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { - - @Autowired - private AuthenticationManager authenticationManager; - - @Autowired - private TokenStore tokenStore; - - @Autowired - private AuthorizationServerTokenServices yamiTokenServices; - - @Autowired - private TokenEnhancer tokenEnhancer; - - @Autowired - private UserDetailsService userDetailsService; - - - @Override - public void configure(AuthorizationServerEndpointsConfigurer endpoints) { - endpoints.authenticationManager(authenticationManager) - .tokenStore(tokenStore) - .tokenEnhancer(tokenEnhancer) - // refresh_token需要userDetailsService - .reuseRefreshTokens(false) - .userDetailsService(userDetailsService); - endpoints.tokenServices(yamiTokenServices); - } - - - @Override - public void configure(AuthorizationServerSecurityConfigurer oauthServer) { - oauthServer - // 开启/oauth/token_key验证端口无权限访问 - .tokenKeyAccess("permitAll()") - // 开启/oauth/check_token验证端口认证权限访问 - .checkTokenAccess("isAuthenticated()"); - } - - - - - - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/config/TokenConfig.java b/yami-shop-security/src/main/java/com/yami/shop/security/config/TokenConfig.java deleted file mode 100644 index 3e31c24..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/config/TokenConfig.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.yami.shop.security.config; - -import com.yami.shop.security.constants.SecurityConstants; -import com.yami.shop.security.util.YamiTokenServices; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; -import org.springframework.context.annotation.Primary; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; - -import java.util.Collections; - -/** - * @author LGH - */ -@Configuration -public class TokenConfig { - - - @Autowired - private UserDetailsService userDetailsService; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - @Autowired - private TokenEnhancer tokenEnhancer; - - @Bean - public TokenStore tokenStore() { - RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory); - tokenStore.setPrefix(SecurityConstants.YAMI_OAUTH_PREFIX); - return tokenStore; - } - - - @Primary - @Bean - @Lazy - public AuthorizationServerTokenServices yamiTokenServices() { - YamiTokenServices tokenServices = new YamiTokenServices(); - tokenServices.setTokenStore(tokenStore()); - //支持刷新token - tokenServices.setSupportRefreshToken(true); - tokenServices.setReuseRefreshToken(true); - tokenServices.setTokenEnhancer(tokenEnhancer); - addUserDetailsService(tokenServices); - return tokenServices; - } - - private void addUserDetailsService(YamiTokenServices tokenServices) { - PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); - provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService)); - tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider))); - } - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/constants/SecurityConstants.java b/yami-shop-security/src/main/java/com/yami/shop/security/constants/SecurityConstants.java deleted file mode 100644 index b6560c8..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/constants/SecurityConstants.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.constants; - -public interface SecurityConstants { - /** - * oauth 相关前缀 - */ - String YAMI_OAUTH_PREFIX = "yami_oauth:"; - - - String SPRING_SECURITY_RESTFUL_IMAGE_CODE = "imageCode"; -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/controller/SysLoginController.java b/yami-shop-security/src/main/java/com/yami/shop/security/controller/SysLoginController.java deleted file mode 100644 index e5ee1d3..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/controller/SysLoginController.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.controller; - - -import java.awt.*; -import java.io.IOException; - -import javax.servlet.http.HttpServletResponse; - -import com.yami.shop.security.constants.SecurityConstants; - -import com.yami.shop.security.service.YamiSysUser; -import com.yami.shop.security.util.SecurityUtils; -import lombok.AllArgsConstructor; -import org.redisson.api.RedissonClient; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; - -import com.yami.shop.common.util.RedisUtil; -import com.yami.shop.common.util.SimpleCaptcha; -import springfox.documentation.annotations.ApiIgnore; - -/** - * 登录相关 - * @author lgh - */ -@Controller -@AllArgsConstructor -@ApiIgnore -public class SysLoginController { - - private final CacheManager cacheManager; - - - @GetMapping("/captcha.jpg") - public void login(HttpServletResponse response,String uuid) { - //定义图形验证码的长、宽、验证码字符数、干扰元素个数 - SimpleCaptcha simpleCaptcha = new SimpleCaptcha(200, 50, 4, 20); - try { - simpleCaptcha.write(response.getOutputStream()); - RedisUtil.set(SecurityConstants.SPRING_SECURITY_RESTFUL_IMAGE_CODE+uuid, simpleCaptcha.getCode(), 300); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * 退出 - */ - @PostMapping(value = "/sys/logout") - public ResponseEntity logout() { - SecurityContextHolder.clearContext(); - return ResponseEntity.ok().build(); - } - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/dao/AppConnectMapper.java b/yami-shop-security/src/main/java/com/yami/shop/security/dao/AppConnectMapper.java deleted file mode 100644 index b77fbe3..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/dao/AppConnectMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.dao; - -import com.yami.shop.security.model.AppConnect; -import org.apache.ibatis.annotations.Param; - - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface AppConnectMapper extends BaseMapper { - - AppConnect getByBizUserId(@Param("bizUserId") String bizUserId, @Param("appId") Integer appId); - - AppConnect getByUserId(@Param("userId") String userId, @Param("appId") Integer appId); - - String getUserIdByUnionId(@Param("bizUnionId") String bizUnionId); -} \ No newline at end of file diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/enums/App.java b/yami-shop-security/src/main/java/com/yami/shop/security/enums/App.java deleted file mode 100644 index ad1e182..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/enums/App.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.enums; - -public enum App { - - /** - * 小程序 - */ - MINI(1), - - /** - * 微信公众号 - */ - MP(2), - - /** - * H5 - */ - H5(3) - ; - - private Integer num; - - public Integer value() { - return num; - } - - App(Integer num){ - this.num = num; - } - - public static App instance(Integer value) { - App[] enums = values(); - for (App statusEnum : enums) { - if (statusEnum.value().equals(value)) { - return statusEnum; - } - } - return null; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsException.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsException.java deleted file mode 100644 index b485b98..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -/** - * 密码不正确 - */ -public class BadCredentialsException extends YamiAuth2Exception{ - public BadCredentialsException(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "bad_credentials"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsExceptionBase.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsExceptionBase.java deleted file mode 100644 index 325cc7a..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/BadCredentialsExceptionBase.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -/** - * 密码不正确 - */ -public class BadCredentialsExceptionBase extends BaseYamiAuth2Exception { - public BadCredentialsExceptionBase(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "bad_credentials"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/BaseYamiAuth2Exception.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/BaseYamiAuth2Exception.java deleted file mode 100644 index aa72e23..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/BaseYamiAuth2Exception.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; - -/** - */ -public abstract class BaseYamiAuth2Exception extends AuthenticationException { - - public BaseYamiAuth2Exception(String msg) { - super(msg); - } - - public int getHttpErrorCode() { - // 400 not 401 - return HttpStatus.BAD_REQUEST.value(); - } - - public abstract String getOAuth2ErrorCode(); -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchException.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchException.java deleted file mode 100644 index b3716d2..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -public class ImageCodeNotMatchException extends YamiAuth2Exception { - - public ImageCodeNotMatchException(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "image_code_not_match"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchExceptionBase.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchExceptionBase.java deleted file mode 100644 index 7d66a45..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/ImageCodeNotMatchExceptionBase.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -public class ImageCodeNotMatchExceptionBase extends BaseYamiAuth2Exception { - - public ImageCodeNotMatchExceptionBase(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "image_code_not_match"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedException.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedException.java deleted file mode 100644 index 0765a8c..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedException.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -import org.springframework.http.HttpStatus; - -/** - */ -public class UnauthorizedException extends YamiAuth2Exception { - - public UnauthorizedException(String msg) { - super(msg); - } - - - public UnauthorizedException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "unauthorized"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.UNAUTHORIZED.value(); - } - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedExceptionBase.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedExceptionBase.java deleted file mode 100644 index ae56ecc..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnauthorizedExceptionBase.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -import org.springframework.http.HttpStatus; - -/** - */ -public class UnauthorizedExceptionBase extends BaseYamiAuth2Exception { - - public UnauthorizedExceptionBase(String msg) { - super(msg); - } - - - public UnauthorizedExceptionBase(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "unauthorized"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.UNAUTHORIZED.value(); - } - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeException.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeException.java deleted file mode 100644 index 415e819..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -import org.springframework.http.HttpStatus; - -public class UnknownGrantTypeException extends YamiAuth2Exception { - - public UnknownGrantTypeException(String msg) { - super(msg); - } - - - public UnknownGrantTypeException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "unknown_grant_type"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.UNAUTHORIZED.value(); - } - -} \ No newline at end of file diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeExceptionBase.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeExceptionBase.java deleted file mode 100644 index 9a57a52..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UnknownGrantTypeExceptionBase.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -import org.springframework.http.HttpStatus; - -public class UnknownGrantTypeExceptionBase extends BaseYamiAuth2Exception { - - public UnknownGrantTypeExceptionBase(String msg) { - super(msg); - } - - - public UnknownGrantTypeExceptionBase(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "unknown_grant_type"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.UNAUTHORIZED.value(); - } - -} \ No newline at end of file diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundException.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundException.java deleted file mode 100644 index 5eb6afa..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -public class UsernameNotFoundException extends BaseYamiAuth2Exception { - - public UsernameNotFoundException(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "username_not_found"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundExceptionBase.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundExceptionBase.java deleted file mode 100644 index 1ebcad7..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/UsernameNotFoundExceptionBase.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -public class UsernameNotFoundExceptionBase extends BaseYamiAuth2Exception { - - public UsernameNotFoundExceptionBase(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "username_not_found"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorException.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorException.java deleted file mode 100644 index 22817f7..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -/** - * 密码不正确 - */ -public class WxErrorException extends YamiAuth2Exception{ - public WxErrorException(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "wx_error_exception"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorExceptionBase.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorExceptionBase.java deleted file mode 100644 index ad2aa76..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/WxErrorExceptionBase.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -/** - * 密码不正确 - */ -public class WxErrorExceptionBase extends BaseYamiAuth2Exception { - public WxErrorExceptionBase(String msg) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "wx_error_exception"; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/exception/YamiAuth2Exception.java b/yami-shop-security/src/main/java/com/yami/shop/security/exception/YamiAuth2Exception.java deleted file mode 100644 index a513e93..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/exception/YamiAuth2Exception.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; - -/** - */ -public abstract class YamiAuth2Exception extends AuthenticationException { - - public YamiAuth2Exception(String msg) { - super(msg); - } - - public int getHttpErrorCode() { - // 400 not 401 - return HttpStatus.BAD_REQUEST.value(); - } - - public abstract String getOAuth2ErrorCode(); -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthFailedHandler.java b/yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthFailedHandler.java deleted file mode 100644 index fed60b4..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthFailedHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.handler; - -import cn.hutool.core.util.CharsetUtil; -import com.yami.shop.security.exception.BaseYamiAuth2Exception; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.PrintWriter; - -/** - * 登陆失败处理 - */ -@Component -@Slf4j -public class LoginAuthFailedHandler implements AuthenticationFailureHandler { - - /** - * {@inheritDoc} - */ - @Override - @SneakyThrows - public void onAuthenticationFailure(HttpServletRequest request, - HttpServletResponse response, AuthenticationException exception) { - - if (!(exception instanceof BaseYamiAuth2Exception)) { - return; - } - - BaseYamiAuth2Exception auth2Exception = (BaseYamiAuth2Exception) exception; - - response.setCharacterEncoding(CharsetUtil.UTF_8); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setStatus(auth2Exception.getHttpErrorCode()); - PrintWriter printWriter = response.getWriter(); - printWriter.append(auth2Exception.getMessage()); - } - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthSuccessHandler.java b/yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthSuccessHandler.java deleted file mode 100644 index a1993a4..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/handler/LoginAuthSuccessHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.handler; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.CharsetUtil; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.provider.*; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; - - -/** - * 登录成功,返回oauth token - * - */ -@Component -@Slf4j -public class LoginAuthSuccessHandler implements AuthenticationSuccessHandler { - @Autowired - private ObjectMapper objectMapper; - @Autowired - @Lazy - private AuthorizationServerTokenServices yamiTokenServices; - - /** - * Called when a user has been successfully authenticated. - * 调用spring security oauth API 生成 oAuth2AccessToken - * - * @param request the request which caused the successful authentication - * @param response the response - * @param authentication the Authentication object which was created during - */ - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - - try { - - TokenRequest tokenRequest = new TokenRequest(null, null, null, null); - - // 简化 - OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(new BaseClientDetails()); - OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication); - - - OAuth2AccessToken oAuth2AccessToken = yamiTokenServices.createAccessToken(oAuth2Authentication); - log.info("获取token 成功:{}", oAuth2AccessToken.getValue()); - - response.setCharacterEncoding(CharsetUtil.UTF_8); - response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); - PrintWriter printWriter = response.getWriter(); - printWriter.append(objectMapper.writeValueAsString(oAuth2AccessToken)); - } catch (IOException e) { - throw new BadCredentialsException( - "Failed to decode basic authentication token"); - } - - } - - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/model/AppConnect.java b/yami-shop-security/src/main/java/com/yami/shop/security/model/AppConnect.java deleted file mode 100644 index 13d011e..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/model/AppConnect.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.model; - - -import java.util.Date; -import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; - -@Data -@TableName("tz_app_connect") -public class AppConnect { - /** - * id - */ - @TableId - private Long id; - - /** - * 本系统userId - */ - - private String userId; - - /** - * 第三方系统id 1:微信小程序 - */ - - private Integer appId; - - /** - * 第三方系统昵称 - */ - - private String nickName; - - /** - * 第三方系统头像 - */ - - private String imageUrl; - - /** - * 第三方系统userid - */ - - private String bizUserId; - - /** - * 第三方系统unionid - */ - - private String bizUnionid; - -} \ No newline at end of file diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/provider/AbstractUserDetailsAuthenticationProvider.java b/yami-shop-security/src/main/java/com/yami/shop/security/provider/AbstractUserDetailsAuthenticationProvider.java deleted file mode 100644 index f9c284e..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/provider/AbstractUserDetailsAuthenticationProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.provider; - -import com.yami.shop.security.enums.App; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; - -/** - * 自定义 AuthenticationProvider, 以使用自定义的 MyAuthenticationToken - * @author LGH - */ -@Slf4j -public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean { - - - - @Override - public final void afterPropertiesSet() { - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - String username = authentication.getPrincipal() == null?"NONE_PROVIDED":authentication.getName(); - UserDetails user; - - try { - user = this.retrieveUser(username, authentication); - } catch (UsernameNotFoundException var6) { - log.debug("User \'" + username + "\' not found"); - - throw var6; - } - - return this.createSuccessAuthentication(authentication, user); - } - - protected abstract Authentication createSuccessAuthentication(Authentication authentication, UserDetails user); - - protected abstract App getAppInfo(); - - protected abstract UserDetails retrieveUser(String username, Authentication authentication) throws AuthenticationException; - - - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/provider/AuthenticationTokenParser.java b/yami-shop-security/src/main/java/com/yami/shop/security/provider/AuthenticationTokenParser.java deleted file mode 100644 index 94d6add..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/provider/AuthenticationTokenParser.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.yami.shop.security.provider; - -import org.springframework.security.authentication.AbstractAuthenticationToken; - -/** - * AuthenticationTokenParser - * - * @author hanfeng - * @date 2019-08-21 - */ -public interface AuthenticationTokenParser { - AbstractAuthenticationToken parse(String authenticationTokenStr); -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/provider/MpAuthenticationProvider.java b/yami-shop-security/src/main/java/com/yami/shop/security/provider/MpAuthenticationProvider.java deleted file mode 100644 index 0e54efc..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/provider/MpAuthenticationProvider.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.provider; - - - -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.emoji.EmojiUtil; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.exception.WxErrorExceptionBase; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.YamiUser; -import com.yami.shop.security.service.YamiUserDetailsService; -import com.yami.shop.security.token.MpAuthenticationToken; -import com.yami.shop.security.token.MyAuthenticationToken; -import lombok.AllArgsConstructor; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; -import me.chanjar.weixin.mp.bean.result.WxMpUser; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * 微信公众号登陆 - * @author LGH - */ -//@Component -@AllArgsConstructor -public class MpAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { - - private final YamiUserDetailsService yamiUserDetailsService; - - private final WxMpService wxMpService; - - - - @Override - protected Authentication createSuccessAuthentication(Authentication authentication, UserDetails user) { - MpAuthenticationToken result = new MpAuthenticationToken(user, authentication.getCredentials()); - result.setDetails(authentication.getDetails()); - return result; - } - - @Override - protected UserDetails retrieveUser(String code, Authentication authentication) throws AuthenticationException { - YamiUser loadedUser = null; - // 如果使用debugger 模式,则返回debugger的用户 - if (BooleanUtil.isTrue(((MyAuthenticationToken)authentication).getDebugger())) { - loadedUser = new YamiUser("1" , "debuggerOpenId" , this.getAppInfo().value(), true); - loadedUser.setDebugger(true); - return loadedUser; - } - - WxMpOAuth2AccessToken wxMpOAuth2AccessToken = null; - - AppConnect appConnect = new AppConnect(); - appConnect.setAppId(this.getAppInfo().value()); - - try { - - wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code); - loadedUser = yamiUserDetailsService.loadUserByAppIdAndBizUserId(this.getAppInfo(),wxMpOAuth2AccessToken.getOpenId()); - - } catch (WxErrorException e) { - throw new WxErrorExceptionBase(e.getMessage()); - } catch (UsernameNotFoundExceptionBase var6) { - WxMpUser wxMpUser = null; - try { - wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null); - } catch (WxErrorException e) { - throw new WxErrorExceptionBase(e.getMessage()); - } - appConnect.setNickName(EmojiUtil.toAlias(StrUtil.isBlank(wxMpUser.getNickname())? "": wxMpUser.getNickname())); - appConnect.setImageUrl(wxMpUser.getHeadImgUrl()); - appConnect.setBizUserId(wxMpUser.getOpenId()); - appConnect.setBizUnionid(wxMpUser.getUnionId()); - yamiUserDetailsService.insertUserIfNecessary(appConnect); - - } - - if (loadedUser == null) { - loadedUser = yamiUserDetailsService.loadUserByAppIdAndBizUserId(this.getAppInfo(), appConnect.getBizUserId()); - } - return loadedUser; - } - - @Override - public boolean supports(Class authentication) { - return MpAuthenticationToken.class.isAssignableFrom(authentication); - } - - - @Override - protected App getAppInfo() { - return App.MP; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/service/AppConnectService.java b/yami-shop-security/src/main/java/com/yami/shop/security/service/AppConnectService.java deleted file mode 100644 index 6c0e58f..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/service/AppConnectService.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.service; - - -import com.baomidou.mybatisplus.extension.service.IService; -import com.yami.shop.bean.model.User; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.model.AppConnect; - -/** - * - * @author lgh on 2018/09/07. - */ -public interface AppConnectService extends IService { - - AppConnect getByBizUserId(String bizUserId, App app); - - User registerOrBindUser(User user, AppConnect appConnect, Integer appId); - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiClientDetailsService.java b/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiClientDetailsService.java deleted file mode 100644 index da1402b..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiClientDetailsService.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.service; - -import com.yami.shop.security.constants.SecurityConstants; -import lombok.SneakyThrows; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; - -import javax.sql.DataSource; - -/** - * @author - * @tate 2019/03/30 - * 获取客户端 - */ -public class YamiClientDetailsService extends JdbcClientDetailsService { - - public YamiClientDetailsService(DataSource dataSource) { - super(dataSource); - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiSysUser.java b/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiSysUser.java deleted file mode 100644 index 62bfe51..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiSysUser.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.service; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.User; - -import java.util.Collection; - -/** - * 用户详细信息 - * - * @author - */ -@Getter -@Setter -public class YamiSysUser extends User { - /** - * 用户ID - */ - @Getter - private Long userId; - - /** - * 租户ID - */ - @Getter - private Long shopId; - - public YamiSysUser(Long userId, Long shopId, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) { - super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); - this.userId = userId; - this.shopId = shopId; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUser.java b/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUser.java deleted file mode 100644 index 85f967c..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUser.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.service; - -import cn.hutool.core.util.StrUtil; -import lombok.Data; -import lombok.Getter; -import lombok.Setter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; -import java.util.Collections; - -/** - * 用户详细信息 - */ -@Data -public class YamiUser implements UserDetails { - - /** - * 用户ID - */ - private String userId; - - private String bizUserId; - - private String pic; - - private String name; - - private boolean debugger; - - private String password; - - private Integer appType; - - private boolean enabled; - - /** - * 自提点Id - */ - private Long stationId; - - /** - * 店铺Id - */ - private Long shopId; - /** - * 小程序session_key - */ - private String sessionKey; - - public YamiUser() { - - } - - public YamiUser(String userId, String bizUserId, Integer appType, boolean enabled) { - this.userId = userId; - this.bizUserId = bizUserId; - this.appType = appType; - this.enabled = enabled; - } - - public YamiUser(String userId, String password, boolean enabled) { - this.userId = userId; - this.password = password; - this.enabled = enabled; - } - - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - - @Override - public String getPassword() { - return this.password; - } - - @Override - public String getUsername() { - return StrUtil.isBlank(userId)? bizUserId: userId; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return this.enabled; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUserDetailsService.java b/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUserDetailsService.java deleted file mode 100644 index ff08abf..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/service/YamiUserDetailsService.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.service; - -import com.yami.shop.bean.model.User; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.exception.UsernameNotFoundExceptionBase; -import com.yami.shop.security.model.AppConnect; -import org.springframework.security.core.userdetails.UserDetailsService; - -/** - * 用户详细信息 - * - * @author - */ -public interface YamiUserDetailsService extends UserDetailsService { - - /** - * 获取前端登陆的用户信息 - * - * @param app 所登陆的应用 - * @param bizUserId openId - * @return UserDetails - * @throws UsernameNotFoundExceptionBase - */ - YamiUser loadUserByAppIdAndBizUserId(App app, String bizUserId); - - /** - * 如果必要的话,插入新增用户 - * @param appConnect - */ - void insertUserIfNecessary(AppConnect appConnect); - - /** - * 账号、密码登录 - * @param userMail - * @param loginPassword - * @return - */ - YamiUser loadUserByUserMail(String userMail, String loginPassword); - - User loadUserByMobileOrUserName(String mobileOrUserName, Integer loginType); - - YamiUser getYamiUser(Integer appId, User user, String bizUserId); -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/service/impl/AppConnectServiceImpl.java b/yami-shop-security/src/main/java/com/yami/shop/security/service/impl/AppConnectServiceImpl.java deleted file mode 100644 index 32e01e4..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/service/impl/AppConnectServiceImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.service.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.yami.shop.bean.model.User; -import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.dao.UserMapper; -import com.yami.shop.security.dao.AppConnectMapper; -import com.yami.shop.security.enums.App; -import com.yami.shop.security.model.AppConnect; -import com.yami.shop.security.service.AppConnectService; -import com.yami.shop.service.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Date; -import java.util.Objects; - - -/** - * - * @author lgh on 2018/09/07. - */ -@Service -public class AppConnectServiceImpl extends ServiceImpl implements AppConnectService { - - @Autowired - private AppConnectMapper appConnectMapper; - - @Autowired - private UserMapper userMapper; - - @Autowired - private UserService userService; - - /** - * YamiUserServiceImpl#insertUserIfNecessary 将会清楚该缓存信息 - * @param bizUserId - * @param app - * @return - */ - @Override - @Cacheable(cacheNames = "AppConnect", key = "#app.value() + ':' + #bizUserId") - public AppConnect getByBizUserId(String bizUserId, App app) { - return appConnectMapper.getByBizUserId(bizUserId, app.value()); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public User registerOrBindUser(User user, AppConnect appConnect, Integer appId) { - if (StrUtil.isBlank(user.getUserId())) { - if (userMapper.selectCount(new LambdaQueryWrapper().eq(User::getUserMobile, user.getUserMobile())) > 0) { - // 该电话号码已存在 - throw new YamiShopBindException("该电话号码已存在"); - } - String userId = IdUtil.simpleUUID(); - user.setUserId(userId); - userMapper.insert(user); - } else { - if (appConnect != null&& StrUtil.isBlank(user.getPic())) { - User userParam = new User(); - userParam.setUserId(user.getUserId()); - userParam.setModifyTime(new Date()); - userParam.setPic(appConnect.getImageUrl()); - userService.updateById(userParam); - } - } - if (appConnect == null) { - // 避免重复插入数据 - if (appConnectMapper.getByBizUserId(user.getUserId(), appId) != null) { - return user; - } - appConnect = new AppConnect(); - appConnect.setUserId(user.getUserId()); - appConnect.setNickName(user.getNickName()); - appConnect.setImageUrl(user.getPic()); - - // 0表示是系统的用户,不是第三方的 - appConnect.setAppId(appId); - appConnectMapper.insert(appConnect); - } else if (StrUtil.isBlank(appConnect.getUserId()) || Objects.isNull(appId)) { - appConnect.setAppId(appId); - appConnect.setUserId(user.getUserId()); - appConnect.setUserId(user.getUserId()); - appConnectMapper.updateById(appConnect); - } - - return user; - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/token/MpAuthenticationToken.java b/yami-shop-security/src/main/java/com/yami/shop/security/token/MpAuthenticationToken.java deleted file mode 100644 index 011e90f..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/token/MpAuthenticationToken.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.token; - -import lombok.NoArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * 二维码Token - */ -@NoArgsConstructor -public class MpAuthenticationToken extends MyAuthenticationToken { - - - public MpAuthenticationToken(UserDetails principal, Object credentials) { - super(principal, credentials, principal.getAuthorities()); - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/token/MyAuthenticationToken.java b/yami-shop-security/src/main/java/com/yami/shop/security/token/MyAuthenticationToken.java deleted file mode 100644 index e15b676..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/token/MyAuthenticationToken.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.token; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; - -import java.util.Collection; - -/** - * 自定义AbstractAuthenticationToken, - */ -@Getter -@Setter -public class MyAuthenticationToken extends AbstractAuthenticationToken { - - private static final long serialVersionUID = 110L; - protected Object principal; - protected Object credentials; - protected Boolean debugger; - - public MyAuthenticationToken() { - super(null); - } - /** - * This constructor should only be used by AuthenticationManager or AuthenticationProvider - * implementations that are satisfied with producing a trusted (i.e. {@link #isAuthenticated()} = true) - * token token. - * - * @param principal - * @param credentials - * @param authorities - */ - public MyAuthenticationToken(Object principal, Object credentials, Collection authorities) { - super(authorities); - this.principal = principal; - this.credentials = credentials; - super.setAuthenticated(true); - } - public MyAuthenticationToken(Object principal, Object credentials) { - super(null); - this.principal = principal; - this.credentials = credentials; - this.setAuthenticated(false); - } - - - @Override - public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { - if(isAuthenticated) { - throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); - } else { - super.setAuthenticated(false); - } - } - - @Override - public void eraseCredentials() { - super.eraseCredentials(); - this.credentials = null; - } - -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/util/SecurityUtils.java b/yami-shop-security/src/main/java/com/yami/shop/security/util/SecurityUtils.java deleted file mode 100644 index b1bbbe5..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/util/SecurityUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.util; - - -import com.yami.shop.security.exception.UnauthorizedExceptionBase; -import com.yami.shop.security.service.YamiSysUser; -import com.yami.shop.security.service.YamiUser; -import lombok.experimental.UtilityClass; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * 安全工具类 - * - * @author L.cm - */ -@UtilityClass -public class SecurityUtils { - /** - * 获取Authentication - */ - public Authentication getAuthentication() { - return SecurityContextHolder.getContext().getAuthentication(); - } - - /** - * 获取用户 - */ - public YamiUser getUser() { - Authentication authentication = getAuthentication(); - Object principal = authentication.getPrincipal(); - if (principal instanceof YamiUser) { - return (YamiUser) principal; - } - throw new UnauthorizedExceptionBase("无法获取普通用户信息"); - } - - /** - * 获取系统用户 - */ - public YamiSysUser getSysUser() { - Authentication authentication = getAuthentication(); - Object principal = authentication.getPrincipal(); - if (principal instanceof YamiSysUser) { - return (YamiSysUser) principal; - } - throw new UnauthorizedExceptionBase("无法获取系统用户信息"); - } -} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/util/YamiTokenServices.java b/yami-shop-security/src/main/java/com/yami/shop/security/util/YamiTokenServices.java deleted file mode 100644 index acf6bfa..0000000 --- a/yami-shop-security/src/main/java/com/yami/shop/security/util/YamiTokenServices.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. - * - * https://www.mall4j.com/ - * - * 未经允许,不可做商业用途! - * - * 版权所有,侵权必究! - */ - -package com.yami.shop.security.util; - -import java.util.Date; -import java.util.Set; -import java.util.UUID; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; -import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.ClientRegistrationException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.security.oauth2.provider.TokenRequest; -import org.springframework.security.oauth2.provider.token.*; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.Assert; - -/** - * Base implementation for token services using random UUID values for the access token and refresh token values. The - * main extension point for customizations is the {@link TokenEnhancer} which will be called after the access and - * refresh tokens have been generated but before they are stored. - *

- * Persistence is delegated to a {@code TokenStore} implementation and customization of the access token to a - * {@link TokenEnhancer}. - * - * @author Ryan Heaton - * @author Luke Taylor - * @author Dave Syer - * @author LGH - */ -public class YamiTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, - ConsumerTokenServices, InitializingBean { - - // default 30 days. - private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; - - // default 12 hours. - private int accessTokenValiditySeconds = 60 * 60 * 12; - - private boolean supportRefreshToken = false; - - private boolean reuseRefreshToken = true; - - private TokenStore tokenStore; - - private ClientDetailsService clientDetailsService; - - private TokenEnhancer accessTokenEnhancer; - - private AuthenticationManager authenticationManager; - - /** - * Initialize these token services. If no random generator is set, one will be created. - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.notNull(tokenStore, "tokenStore must be set"); - } - - @Transactional(rollbackFor = Exception.class) - @Override - public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) { - -// OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); - OAuth2RefreshToken refreshToken = null; - // 如果有token,直接删除,更新token,避免出现缓存问题 -// if (existingAccessToken != null) { -// if (existingAccessToken.getRefreshToken() != null) { -// refreshToken = existingAccessToken.getRefreshToken(); -// // The token store could remove the refresh token when the -// // access token is removed, but we want to -// // be sure... -// tokenStore.removeRefreshToken(refreshToken); -// } -// tokenStore.removeAccessToken(existingAccessToken); -// -// } - - // Only create a new refresh token if there wasn't an existing one - // associated with an expired access token. - // Clients might be holding existing refresh tokens, so we re-use it in - // the case that the old access token - // expired. - if (refreshToken == null) { - refreshToken = createRefreshToken(authentication); - } - // But the refresh token itself might need to be re-issued if it has - // expired. - else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; - if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { - refreshToken = createRefreshToken(authentication); - } - } - - OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); - tokenStore.storeAccessToken(accessToken, authentication); - // In case it was modified - refreshToken = accessToken.getRefreshToken(); - if (refreshToken != null) { - tokenStore.storeRefreshToken(refreshToken, authentication); - } - return accessToken; - - } - - @Override - @Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class}, rollbackFor = Exception.class) - public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) - throws AuthenticationException { - - if (!supportRefreshToken) { - throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); - } - - OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue); - if (refreshToken == null) { - throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); - } - - OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken); - if (this.authenticationManager != null && !authentication.isClientOnly()) { - // The client has already been authenticated, but the user authentication might be old now, so give it a - // chance to re-authenticate. - Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities()); - user = authenticationManager.authenticate(user); - Object details = authentication.getDetails(); - authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user); - authentication.setDetails(details); - } - String clientId = authentication.getOAuth2Request().getClientId(); - if (clientId == null || !clientId.equals(tokenRequest.getClientId())) { - throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue); - } - - // clear out any access tokens already associated with the refresh - // token. - tokenStore.removeAccessTokenUsingRefreshToken(refreshToken); - - if (isExpired(refreshToken)) { - tokenStore.removeRefreshToken(refreshToken); - throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken); - } - - authentication = createRefreshedAuthentication(authentication, tokenRequest); - - if (!reuseRefreshToken) { - tokenStore.removeRefreshToken(refreshToken); - refreshToken = createRefreshToken(authentication); - } - - OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); - tokenStore.storeAccessToken(accessToken, authentication); - if (!reuseRefreshToken) { - tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication); - } - return accessToken; - } - - @Override - public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { - return tokenStore.getAccessToken(authentication); - } - - /** - * Create a refreshed authentication. - * - * @param authentication The authentication. - * @param request The scope for the refreshed token. - * @return The refreshed authentication. - * @throws InvalidScopeException If the scope requested is invalid or wider than the original scope. - */ - private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) { - OAuth2Authentication narrowed = authentication; - Set scope = request.getScope(); - OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request); - if (scope != null && !scope.isEmpty()) { - Set originalScope = clientAuth.getScope(); - if (originalScope == null || !originalScope.containsAll(scope)) { - throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope - + ".", originalScope); - } - else { - clientAuth = clientAuth.narrowScope(scope); - } - } - narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication()); - return narrowed; - } - - protected boolean isExpired(OAuth2RefreshToken refreshToken) { - if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken; - return expiringToken.getExpiration() == null - || System.currentTimeMillis() > expiringToken.getExpiration().getTime(); - } - return false; - } - - @Override - public OAuth2AccessToken readAccessToken(String accessToken) { - return tokenStore.readAccessToken(accessToken); - } - - @Override - public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, - InvalidTokenException { - OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); - if (accessToken == null) { - throw new InvalidTokenException("Invalid access token: " + accessTokenValue); - } - else if (accessToken.isExpired()) { - tokenStore.removeAccessToken(accessToken); - throw new InvalidTokenException("Access token expired: " + accessTokenValue); - } - - OAuth2Authentication result = tokenStore.readAuthentication(accessToken); - if (result == null) { - // in case of race condition - throw new InvalidTokenException("Invalid access token: " + accessTokenValue); - } - if (clientDetailsService != null) { - String clientId = result.getOAuth2Request().getClientId(); - try { - clientDetailsService.loadClientByClientId(clientId); - } - catch (ClientRegistrationException e) { - throw new InvalidTokenException("Client not valid: " + clientId, e); - } - } - return result; - } - - public String getClientId(String tokenValue) { - OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue); - if (authentication == null) { - throw new InvalidTokenException("Invalid access token: " + tokenValue); - } - OAuth2Request clientAuth = authentication.getOAuth2Request(); - if (clientAuth == null) { - throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue); - } - return clientAuth.getClientId(); - } - - @Override - public boolean revokeToken(String tokenValue) { - OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue); - if (accessToken == null) { - return false; - } - if (accessToken.getRefreshToken() != null) { - tokenStore.removeRefreshToken(accessToken.getRefreshToken()); - } - tokenStore.removeAccessToken(accessToken); - return true; - } - - private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { - if (!isSupportRefreshToken(authentication.getOAuth2Request())) { - return null; - } - int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request()); - String value = UUID.randomUUID().toString(); - if (validitySeconds > 0) { - return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis() - + (validitySeconds * 1000L))); - } - return new DefaultOAuth2RefreshToken(value); - } - - private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { - DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); - int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); - if (validitySeconds > 0) { - token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); - } - token.setRefreshToken(refreshToken); - token.setScope(authentication.getOAuth2Request().getScope()); - - return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; - } - - /** - * The access token validity period in seconds - * - * @param clientAuth the current authorization request - * @return the access token validity period in seconds - */ - protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - Integer validity = client.getAccessTokenValiditySeconds(); - if (validity != null) { - return validity; - } - } - return accessTokenValiditySeconds; - } - - /** - * The refresh token validity period in seconds - * - * @param clientAuth the current authorization request - * @return the refresh token validity period in seconds - */ - protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - Integer validity = client.getRefreshTokenValiditySeconds(); - if (validity != null) { - return validity; - } - } - return refreshTokenValiditySeconds; - } - - /** - * Is a refresh token supported for this client (or the global setting if - * {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set. - * - * @param clientAuth the current authorization request - * @return boolean to indicate if refresh token is supported - */ - protected boolean isSupportRefreshToken(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - return client.getAuthorizedGrantTypes().contains("refresh_token"); - } - return this.supportRefreshToken; - } - - /** - * An access token enhancer that will be applied to a new token before it is saved in the token store. - * - * @param accessTokenEnhancer the access token enhancer to set - */ - public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) { - this.accessTokenEnhancer = accessTokenEnhancer; - } - - /** - * The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be - * non-expiring. - * - * @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token. - */ - public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) { - this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; - } - - /** - * The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client - * details service is set the validity period will be read from the client, defaulting to this value if not defined - * by the client. - * - * @param accessTokenValiditySeconds The validity (in seconds) of the access token. - */ - public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) { - this.accessTokenValiditySeconds = accessTokenValiditySeconds; - } - - /** - * Whether to support the refresh token. - * - * @param supportRefreshToken Whether to support the refresh token. - */ - public void setSupportRefreshToken(boolean supportRefreshToken) { - this.supportRefreshToken = supportRefreshToken; - } - - /** - * Whether to reuse refresh tokens (until expired). - * - * @param reuseRefreshToken Whether to reuse refresh tokens (until expired). - */ - public void setReuseRefreshToken(boolean reuseRefreshToken) { - this.reuseRefreshToken = reuseRefreshToken; - } - - /** - * The persistence strategy for token storage. - * - * @param tokenStore the store for access and refresh tokens. - */ - public void setTokenStore(TokenStore tokenStore) { - this.tokenStore = tokenStore; - } - - /** - * An authentication manager that will be used (if provided) to check the user authentication when a token is - * refreshed. - * - * @param authenticationManager the authenticationManager to set - */ - public void setAuthenticationManager(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - } - - /** - * The client details service to use for looking up clients (if necessary). Optional if the access token expiry is - * set globally via {@link #setAccessTokenValiditySeconds(int)}. - * - * @param clientDetailsService the client details service - */ - public void setClientDetailsService(ClientDetailsService clientDetailsService) { - this.clientDetailsService = clientDetailsService; - } - -} diff --git a/yami-shop-security/src/main/resources/mapper/AppConnectMapper.xml b/yami-shop-security/src/main/resources/mapper/AppConnectMapper.xml deleted file mode 100644 index c650a8b..0000000 --- a/yami-shop-security/src/main/resources/mapper/AppConnectMapper.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/yami-shop-security/yami-shop-security-admin/pom.xml b/yami-shop-security/yami-shop-security-admin/pom.xml new file mode 100644 index 0000000..2efe489 --- /dev/null +++ b/yami-shop-security/yami-shop-security-admin/pom.xml @@ -0,0 +1,24 @@ + + + + com.yami.shop + yami-shop-security + 0.0.1-SNAPSHOT + + + yami-shop-security-admin + 4.0.0 + + 商城安全模块后台管理部分 + + + + com.yami.shop + yami-shop-security-common + ${yami.shop.version} + + + + \ No newline at end of file diff --git a/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/adapter/ResourceServerAdapter.java b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/adapter/ResourceServerAdapter.java new file mode 100644 index 0000000..ad8f519 --- /dev/null +++ b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/adapter/ResourceServerAdapter.java @@ -0,0 +1,29 @@ +package com.yami.shop.security.admin.adapter; + +import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +/** + * @author 菠萝凤梨 + * @date 2022/3/28 14:57 + */ +@Component +public class ResourceServerAdapter extends DefaultAuthConfigAdapter { + public static final List EXCLUDE_PATH = Arrays.asList( + "/webjars/**", + "/swagger/**", + "/v2/api-docs", + "/doc.html", + "/swagger-ui.html", + "/swagger-resources/**", + "/captcha/**", + "/adminLogin"); + + @Override + public List excludePathPatterns() { + return EXCLUDE_PATH; + } +} diff --git a/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java new file mode 100644 index 0000000..82d41a0 --- /dev/null +++ b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java @@ -0,0 +1,17 @@ +package com.yami.shop.security.admin.dto; + +import com.yami.shop.security.common.dto.AuthenticationDTO; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 验证码登录 + * @author 菠萝凤梨 + * @date 2022/3/28 14:57 + */ +@Data +public class CaptchaAuthenticationDTO extends AuthenticationDTO { + + @ApiModelProperty(value = "验证码", required = true) + private String captchaVerification; +} diff --git a/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/model/YamiSysUser.java b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/model/YamiSysUser.java new file mode 100644 index 0000000..24c1821 --- /dev/null +++ b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/model/YamiSysUser.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.admin.model; + +import lombok.Data; + +import java.util.Set; + +/** + * 用户详细信息 + * + * @author + */ +@Data +public class YamiSysUser { + + /** + * 用户ID + */ + private Long userId; + + private boolean enabled; + + private Set authorities; + + private String username; + + private Long shopId; +} diff --git a/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/util/SecurityUtils.java b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/util/SecurityUtils.java new file mode 100644 index 0000000..76916d7 --- /dev/null +++ b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/util/SecurityUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.admin.util; + +import com.yami.shop.security.admin.model.YamiSysUser; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.util.AuthUserContext; +import lombok.experimental.UtilityClass; + +/** + * + * @author LGH + */ +@UtilityClass +public class SecurityUtils { + /** + * 获取用户 + */ + public YamiSysUser getSysUser() { + UserInfoInTokenBO userInfoInTokenBO = AuthUserContext.get(); + + YamiSysUser details = new YamiSysUser(); + details.setUserId(Long.valueOf(userInfoInTokenBO.getUserId())); + details.setEnabled(userInfoInTokenBO.getEnabled()); + details.setUsername(userInfoInTokenBO.getNickName()); + details.setAuthorities(userInfoInTokenBO.getPerms()); + details.setShopId(userInfoInTokenBO.getShopId()); + return details; + } +} + diff --git a/yami-shop-security/yami-shop-security-api/pom.xml b/yami-shop-security/yami-shop-security-api/pom.xml new file mode 100644 index 0000000..081f370 --- /dev/null +++ b/yami-shop-security/yami-shop-security-api/pom.xml @@ -0,0 +1,23 @@ + + + + yami-shop-security + com.yami.shop + 0.0.1-SNAPSHOT + + 4.0.0 + + yami-shop-security-api + 商城安全模块接口部分 + + + + com.yami.shop + yami-shop-security-common + ${yami.shop.version} + + + + \ No newline at end of file diff --git a/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/adapter/ResourceServerAdapter.java b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/adapter/ResourceServerAdapter.java new file mode 100644 index 0000000..5d5bb51 --- /dev/null +++ b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/adapter/ResourceServerAdapter.java @@ -0,0 +1,20 @@ +package com.yami.shop.security.api.adapter; + +import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +/** + * @author 菠萝凤梨 + * @date 2022/3/28 15:17 + */ +@Component +public class ResourceServerAdapter extends DefaultAuthConfigAdapter { + + @Override + public List pathPatterns() { + return Collections.singletonList("/p/*"); + } +} diff --git a/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java new file mode 100644 index 0000000..d2b49a7 --- /dev/null +++ b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java @@ -0,0 +1,80 @@ +package com.yami.shop.security.api.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yami.shop.bean.model.User; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.util.PrincipalUtil; +import com.yami.shop.dao.UserMapper; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.dto.AuthenticationDTO; +import com.yami.shop.security.common.enums.SysTypeEnum; +import com.yami.shop.security.common.manager.PasswordCheckManager; +import com.yami.shop.security.common.manager.PasswordManager; +import com.yami.shop.security.common.manager.TokenStore; +import com.yami.shop.security.common.vo.TokenInfoVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +/** + * @author 菠萝凤梨 + * @date 2022/3/28 15:20 + */ +@RestController +@Api(tags = "登录") +public class LoginController { + @Autowired + private TokenStore tokenStore; + + @Autowired + private UserMapper userMapper; + + @Autowired + private PasswordCheckManager passwordCheckManager; + + @Autowired + private PasswordManager passwordManager; + + @PostMapping("/login") + @ApiOperation(value = "账号密码(用于前端登录)", notes = "通过账号/手机号/用户名密码登录,还要携带用户的类型,也就是用户所在的系统") + public ResponseEntity login( + @Valid @RequestBody AuthenticationDTO authenticationDTO) { + String mobileOrUserName = authenticationDTO.getUserName(); + User user = getUser(mobileOrUserName); + + String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord()); + + // 半小时内密码输入错误十次,已限制登录30分钟 + passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,authenticationDTO.getUserName(), decryptPassword, user.getLoginPassword()); + + UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO(); + userInfoInToken.setUserId(user.getUserId()); + userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value()); + userInfoInToken.setEnabled(user.getStatus() == 1); + // 存储token返回vo + TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken); + return ResponseEntity.ok(tokenInfoVO); + } + + private User getUser(String mobileOrUserName) { + User user = null; + // 手机验证码登陆,或传过来的账号很像手机号 + if (PrincipalUtil.isMobile(mobileOrUserName)) { + user = userMapper.selectOne(new LambdaQueryWrapper().eq(User::getUserMobile, mobileOrUserName)); + } + // 如果不是手机验证码登陆, 找不到手机号就找用户名 + if (user == null) { + user = userMapper.selectOneByUserName(mobileOrUserName); + } + if (user == null) { + throw new YamiShopBindException("账号或密码不正确"); + } + return user; + } +} diff --git a/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/model/YamiUser.java b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/model/YamiUser.java new file mode 100644 index 0000000..ac39c8b --- /dev/null +++ b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/model/YamiUser.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.api.model; + +import lombok.Data; + +/** + * 用户详细信息 + * @author LGH + */ +@Data +public class YamiUser { + + /** + * 用户ID + */ + private String userId; + + private String bizUserId; + + private Boolean enabled; + + /** + * 自提点Id + */ + private Long stationId; + + /** + * 店铺Id + */ + private Long shopId; +} diff --git a/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/util/SecurityUtils.java b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/util/SecurityUtils.java new file mode 100644 index 0000000..d015960 --- /dev/null +++ b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/util/SecurityUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.api.util; + +import com.yami.shop.common.util.HttpContextUtils; +import com.yami.shop.security.api.model.YamiUser; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.util.AuthUserContext; +import lombok.experimental.UtilityClass; + +/** + * @author LGH + */ +@UtilityClass +public class SecurityUtils { + + private static final String USER_REQUEST = "/p/"; + + /** + * 获取用户 + */ + public YamiUser getUser() { + if (!HttpContextUtils.getHttpServletRequest().getRequestURI().startsWith(USER_REQUEST)) { + // 用户相关的请求,应该以/p开头!!! + throw new RuntimeException("yami.user.request.error"); + } + UserInfoInTokenBO userInfoInTokenBO = AuthUserContext.get(); + + YamiUser yamiUser = new YamiUser(); + yamiUser.setUserId(userInfoInTokenBO.getUserId()); + yamiUser.setBizUserId(userInfoInTokenBO.getBizUserId()); + yamiUser.setEnabled(userInfoInTokenBO.getEnabled()); + yamiUser.setShopId(userInfoInTokenBO.getShopId()); + yamiUser.setStationId(userInfoInTokenBO.getOtherId()); + return yamiUser; + } +} diff --git a/yami-shop-security/yami-shop-security-common/pom.xml b/yami-shop-security/yami-shop-security-common/pom.xml new file mode 100644 index 0000000..ebbe526 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/pom.xml @@ -0,0 +1,33 @@ + + + + com.yami.shop + yami-shop-security + 0.0.1-SNAPSHOT + + + 4.0.0 + + yami-shop-security-common + + + + com.yami.shop + yami-shop-service + ${yami.shop.version} + + + + org.springframework.boot + spring-boot-starter-security + + + com.anji-plus + captcha + 1.3.0 + + + + \ No newline at end of file diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java new file mode 100644 index 0000000..3ee2153 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java @@ -0,0 +1,27 @@ +package com.yami.shop.security.common.adapter; + +import java.util.List; + +/** + * 实现该接口之后,修改需要授权登陆的路径,不需要授权登陆的路径 + * @author 菠萝凤梨 + * @date 2022/3/25 17:31 + */ +public interface AuthConfigAdapter { + /** + * 也许需要登录才可用的url + */ + String MAYBE_AUTH_URI = "/**/ma/**"; + + /** + * 需要授权登陆的路径 + * @return 需要授权登陆的路径列表 + */ + List pathPatterns(); + + /** + * 不需要授权登陆的路径 + * @return 不需要授权登陆的路径列表 + */ + List excludePathPatterns(); +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java new file mode 100644 index 0000000..748ec0d --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java @@ -0,0 +1,36 @@ +package com.yami.shop.security.common.adapter; + +import com.anji.captcha.service.CaptchaCacheService; +import com.yami.shop.common.util.RedisUtil; + +/** + * 适配验证码在redis的存储 + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +public class CaptchaCacheServiceRedisImpl implements CaptchaCacheService { + @Override + public void set(String key, String value, long expiresInSeconds) { + RedisUtil.set(key, value, expiresInSeconds); + } + + @Override + public boolean exists(String key) { + return RedisUtil.hasKey(key); + } + + @Override + public void delete(String key) { + RedisUtil.del(key); + } + + @Override + public String get(String key) { + return RedisUtil.get(key); + } + + @Override + public String type() { + return "redis"; + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java new file mode 100644 index 0000000..e5c35f5 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java @@ -0,0 +1,29 @@ +package com.yami.shop.security.common.adapter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; + +/** + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +public class DefaultAuthConfigAdapter implements AuthConfigAdapter { + private static final Logger logger = LoggerFactory.getLogger(DefaultAuthConfigAdapter.class); + + public DefaultAuthConfigAdapter() { + logger.info("not implement other AuthConfigAdapter, use DefaultAuthConfigAdapter... all url need auth..."); + } + + @Override + public List pathPatterns() { + return Collections.singletonList("/*"); + } + + @Override + public List excludePathPatterns() { + return Collections.emptyList(); + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java new file mode 100644 index 0000000..ed289d4 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java @@ -0,0 +1,25 @@ +package com.yami.shop.security.common.adapter; + +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.CorsUtils; + +/** + * 使用security的防火墙功能,但不使用security的认证授权登录 + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Component +public class MallWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + @Override + public void configure(HttpSecurity http) throws Exception { + http.csrf().disable().cors() // We don't need CSRF for token based authentication + .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .and() + .authorizeRequests().antMatchers( + "/**").permitAll(); + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/TokenInfoBO.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/TokenInfoBO.java new file mode 100644 index 0000000..0219166 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/TokenInfoBO.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.bo; + + +import lombok.Data; + +/** + * token信息,该信息存在redis中 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Data +public class TokenInfoBO { + + /** + * 保存在token信息里面的用户信息 + */ + private UserInfoInTokenBO userInfoInToken; + + private String accessToken; + + private String refreshToken; + + /** + * 在多少秒后过期 + */ + private Integer expiresIn; + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/UserInfoInTokenBO.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/UserInfoInTokenBO.java new file mode 100644 index 0000000..8b74ed5 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/bo/UserInfoInTokenBO.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.bo; + +import lombok.Data; + +import java.util.Set; + +/** + * 保存在token信息里面的用户信息 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Data +public class UserInfoInTokenBO { + + /** + * 用户在自己系统的用户id + */ + private String userId; + + /** + * 租户id (商家id) + */ + private Long shopId; + + /** + * 昵称 + */ + private String nickName; + + /** + * 系统类型 + * @see com.yami.shop.security.common.enums.SysTypeEnum + */ + private Integer sysType; + + /** + * 是否是管理员 + */ + private Integer isAdmin; + + private String bizUserId; + + /** + * 权限列表 + */ + private Set perms; + + /** + * 状态 1 正常 0 无效 + */ + private Boolean enabled; + + /** + * 其他Id + */ + private Long otherId; + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java new file mode 100644 index 0000000..e841b12 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.config; + +import cn.hutool.core.util.ArrayUtil; +import com.yami.shop.security.common.adapter.AuthConfigAdapter; +import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter; +import com.yami.shop.security.common.filter.AuthFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; + +import javax.servlet.DispatcherType; + +/** + * 授权配置 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled=true) +public class AuthConfig { + + @Autowired + private AuthFilter authFilter; + + @Bean + @ConditionalOnMissingBean + public AuthConfigAdapter authConfigAdapter() { + return new DefaultAuthConfigAdapter(); + } + + + @Bean + @Lazy + public FilterRegistrationBean filterRegistration(AuthConfigAdapter authConfigAdapter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + // 添加过滤器 + registration.setFilter(authFilter); + // 设置过滤路径,/*所有路径 + registration.addUrlPatterns(ArrayUtil.toArray(authConfigAdapter.pathPatterns(), String.class)); + registration.setName("authFilter"); + // 设置优先级 + registration.setOrder(0); + registration.setDispatcherTypes(DispatcherType.REQUEST); + return registration; + } + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java new file mode 100644 index 0000000..a9aa3df --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.config; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.common.Const; +import com.anji.captcha.service.CaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import com.anji.captcha.util.ImageUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.Base64Utils; +import org.springframework.util.FileCopyUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * 这里把验证码的底图存入redis中,如果报获取验证码失败找管理员什么的可以看下redis的情况 + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Configuration +public class CaptchaConfig { + + @Bean + public CaptchaService captchaService() { + Properties config = new Properties(); + config.put(Const.CAPTCHA_CACHETYPE, "redis"); + config.put(Const.CAPTCHA_WATER_MARK, ""); + // 滑动验证 + config.put(Const.CAPTCHA_TYPE, CaptchaTypeEnum.BLOCKPUZZLE.getCodeValue()); + config.put(Const.CAPTCHA_INIT_ORIGINAL, "true"); + initializeBaseMap(); + return CaptchaServiceFactory.getInstance(config); + } + + private static void initializeBaseMap() { + ImageUtils.cacheBootImage(getResourcesImagesFile("classpath:captcha" + "/original/*.png"), getResourcesImagesFile("classpath:captcha" + "/slidingBlock/*.png"), Collections.emptyMap()); + } + + public static Map getResourcesImagesFile(String path) { + Map imgMap = new HashMap<>(16); + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + try { + Resource[] resources = resolver.getResources(path); + Resource[] var4 = resources; + int var5 = resources.length; + + for(int var6 = 0; var6 < var5; ++var6) { + Resource resource = var4[var6]; + byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); + String string = Base64Utils.encodeToString(bytes); + String filename = resource.getFilename(); + imgMap.put(filename, string); + } + } catch (Exception var11) { + var11.printStackTrace(); + } + + return imgMap; + } + +} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/config/CorsConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java similarity index 96% rename from yami-shop-security/src/main/java/com/yami/shop/security/config/CorsConfig.java rename to yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java index 0534790..890272a 100644 --- a/yami-shop-security/src/main/java/com/yami/shop/security/config/CorsConfig.java +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java @@ -1,4 +1,4 @@ -package com.yami.shop.security.config; +package com.yami.shop.security.common.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/config/WebSecurityConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/PasswordConfig.java similarity index 53% rename from yami-shop-security/src/main/java/com/yami/shop/security/config/WebSecurityConfig.java rename to yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/PasswordConfig.java index 93f35ec..e09ec7a 100644 --- a/yami-shop-security/src/main/java/com/yami/shop/security/config/WebSecurityConfig.java +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/PasswordConfig.java @@ -7,37 +7,22 @@ * * 版权所有,侵权必究! */ +package com.yami.shop.security.common.config; -package com.yami.shop.security.config; - - -import lombok.SneakyThrows; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; - /** - * @author LGH + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 */ @Configuration -@Order(90) -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - - @Override - @Bean - @SneakyThrows - public AuthenticationManager authenticationManagerBean() { - return super.authenticationManagerBean(); - } +public class PasswordConfig { @Bean public PasswordEncoder passwordEncoder(){ return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } - } diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java new file mode 100644 index 0000000..eee5070 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.controller; + +import com.anji.captcha.model.common.RepCodeEnum; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaService; +import io.swagger.annotations.Api; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@RestController +@RequestMapping("/captcha") +@Api(tags = "验证码") +public class CaptchaController { + + private final CaptchaService captchaService; + + public CaptchaController(CaptchaService captchaService) { + this.captchaService = captchaService; + } + + @PostMapping({ "/get" }) + public ResponseEntity get(@RequestBody CaptchaVO captchaVO) { + return ResponseEntity.ok(captchaService.get(captchaVO)); + } + + @PostMapping({ "/check" }) + public ResponseEntity check(@RequestBody CaptchaVO captchaVO) { + ResponseModel responseModel; + try { + responseModel = captchaService.check(captchaVO); + }catch (Exception e) { + return ResponseEntity.ok(ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR)); + } + return ResponseEntity.ok(responseModel); + } + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java new file mode 100644 index 0000000..88510ce --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.controller; + +import cn.hutool.core.util.StrUtil; +import com.yami.shop.security.common.manager.TokenStore; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@RestController +@Api(tags = "注销") +public class LogoutController { + + @Autowired + private TokenStore tokenStore; + + @PostMapping("/logOut") + @ApiOperation(value = "退出登陆", notes = "点击退出登陆,清除token,清除菜单缓存") + public ResponseEntity logOut(HttpServletRequest request) { + String accessToken = request.getHeader("Authorization"); + if (StrUtil.isBlank(accessToken)) { + return ResponseEntity.ok().build(); + } + // 删除该用户在该系统当前的token + tokenStore.deleteCurrentToken(accessToken); + return ResponseEntity.ok().build(); + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/TokenController.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/TokenController.java new file mode 100644 index 0000000..acb1528 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/TokenController.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.controller; + +import com.yami.shop.security.common.bo.TokenInfoBO; +import com.yami.shop.security.common.dto.RefreshTokenDTO; +import com.yami.shop.security.common.manager.TokenStore; +import com.yami.shop.security.common.vo.TokenInfoVO; +import io.swagger.annotations.Api; +import ma.glasnost.orika.MapperFacade; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +/** + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@RestController +@Api(tags = "token") +public class TokenController { + + @Autowired + private TokenStore tokenStore; + + @Autowired + private MapperFacade mapperFacade; + + @PostMapping("/token/refresh") + public ResponseEntity refreshToken(@Valid @RequestBody RefreshTokenDTO refreshTokenDTO) { + TokenInfoBO tokenInfoServerResponseEntity = tokenStore + .refreshToken(refreshTokenDTO.getRefreshToken()); + return ResponseEntity + .ok(mapperFacade.map(tokenInfoServerResponseEntity, TokenInfoVO.class)); + } + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java new file mode 100644 index 0000000..d58040c --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 用于登陆传递账号密码 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Data +public class AuthenticationDTO { + + /** + * 用户名 + */ + @NotBlank(message = "userName不能为空") + @ApiModelProperty(value = "用户名/邮箱/手机号", required = true) + protected String userName; + + /** + * 密码 + */ + @NotBlank(message = "passWord不能为空") + @ApiModelProperty(value = "一般用作密码", required = true) + protected String passWord; + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/RefreshTokenDTO.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/RefreshTokenDTO.java new file mode 100644 index 0000000..57b6b9d --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/RefreshTokenDTO.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.dto; + +import io.swagger.annotations.ApiModelProperty; + +import javax.validation.constraints.NotBlank; + +/** + * 刷新token + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +public class RefreshTokenDTO { + + /** + * refreshToken + */ + @NotBlank(message = "refreshToken不能为空") + @ApiModelProperty(value = "refreshToken", required = true) + private String refreshToken; + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + @Override + public String toString() { + return "RefreshTokenDTO{" + "refreshToken='" + refreshToken + '\'' + '}'; + } + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/enums/SysTypeEnum.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/enums/SysTypeEnum.java new file mode 100644 index 0000000..8622a8d --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/enums/SysTypeEnum.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.enums; + +/** + * 系统类型 + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +public enum SysTypeEnum { + + /** + * 普通用户系统 + */ + ORDINARY(0), + + /** + * 后台 + */ + ADMIN(1), + ; + + private final Integer value; + + public Integer value() { + return value; + } + + SysTypeEnum(Integer value) { + this.value = value; + } + +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java new file mode 100644 index 0000000..6f71748 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.filter; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.handler.HttpHandler; +import com.yami.shop.security.common.adapter.AuthConfigAdapter; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.manager.TokenStore; +import com.yami.shop.security.common.util.AuthUserContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * 授权过滤,只要实现AuthConfigAdapter接口,添加对应路径即可: + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Component +public class AuthFilter implements Filter { + + private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class); + + @Autowired + private AuthConfigAdapter authConfigAdapter; + + @Autowired + private HttpHandler httpHandler; + + @Autowired + private TokenStore tokenStore; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String requestUri = req.getRequestURI(); + + List excludePathPatterns = authConfigAdapter.excludePathPatterns(); + + AntPathMatcher pathMatcher = new AntPathMatcher(); + // 如果匹配不需要授权的路径,就不需要校验是否需要授权 + if (CollectionUtil.isNotEmpty(excludePathPatterns)) { + for (String excludePathPattern : excludePathPatterns) { + if (pathMatcher.match(excludePathPattern, requestUri)) { + chain.doFilter(req, resp); + return; + } + } + } + + String accessToken = req.getHeader("Authorization"); + // 也许需要登录,不登陆也能用的uri + boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri); + + + UserInfoInTokenBO userInfoInToken = null; + + try { + // 如果有token,就要获取token + if (StrUtil.isNotBlank(accessToken)) { + userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true); + } + else if (!mayAuth) { + // 返回前端401 + httpHandler.printServerResponseToWeb(HttpStatus.UNAUTHORIZED.getReasonPhrase(), HttpStatus.UNAUTHORIZED.value()); + return; + } + // 保存上下文 + AuthUserContext.set(userInfoInToken); + + chain.doFilter(req, resp); + + }catch (Exception e) { + // 手动捕获下非controller异常 + if (e instanceof YamiShopBindException) { + httpHandler.printServerResponseToWeb(e.getMessage(), ((YamiShopBindException) e).getHttpStatusCode()); + } else { + throw e; + } + } finally { + AuthUserContext.clean(); + } + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordCheckManager.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordCheckManager.java new file mode 100644 index 0000000..ff35036 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordCheckManager.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.manager; + +import cn.hutool.core.util.StrUtil; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.util.IPHelper; +import com.yami.shop.common.util.RedisUtil; +import com.yami.shop.security.common.enums.SysTypeEnum; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +/** + * @date 2022/3/25 17:33 + * @author lh + */ +@Component +public class PasswordCheckManager { + + + @Autowired + private PasswordEncoder passwordEncoder; + + /** + * 半小时内最多错误10次 + */ + private static final int TIMES_CHECK_INPUT_PASSWORD_NUM = 10; + + /** + * 检查用户输入错误的验证码次数 + */ + private static final String CHECK_VALID_CODE_NUM_PREFIX = "checkUserInputErrorPassword_"; + public void checkPassword(SysTypeEnum sysTypeEnum,String userNameOrMobile, String rawPassword, String encodedPassword) { + + String checkPrefix = sysTypeEnum.value() + CHECK_VALID_CODE_NUM_PREFIX + IPHelper.getIpAddr(); + + int count = 0; + if(RedisUtil.hasKey(checkPrefix + userNameOrMobile)){ + count = RedisUtil.get(checkPrefix + userNameOrMobile); + } + if(count > TIMES_CHECK_INPUT_PASSWORD_NUM){ + throw new YamiShopBindException("半小时内密码输入错误十次,已限制登录30分钟"); + } + // 半小时后失效 + RedisUtil.set(checkPrefix + userNameOrMobile,count,1800); + // 密码不正确 + if (StrUtil.isBlank(encodedPassword) || !passwordEncoder.matches(rawPassword,encodedPassword)){ + count++; + // 半小时后失效 + RedisUtil.set(checkPrefix + userNameOrMobile,count,1800); + throw new YamiShopBindException("账号或密码不正确"); + } + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java new file mode 100644 index 0000000..e73f98e --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.manager; + +import cn.hutool.crypto.symmetric.AES; +import com.yami.shop.common.exception.YamiShopBindException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +/** + * @author 菠萝凤梨 + * @date 2022/1/19 16:02 + */ +@Component +public class PasswordManager { + private static final Logger logger = LoggerFactory.getLogger(PasswordManager.class); + + /** + * 用于aes签名的key,16位 + */ + @Value("${auth.password.signKey:-mall4j-password}") + public String passwordSignKey; + + public String decryptPassword(String data) { + AES aes = new AES(passwordSignKey.getBytes(StandardCharsets.UTF_8)); + String decryptStr; + String decryptPassword; + try { + decryptStr = aes.decryptStr(data); + decryptPassword = decryptStr.substring(13); + } catch (Exception e) { + logger.error("Exception:", e); + throw new YamiShopBindException("AES解密错误", e); + } + return decryptPassword; + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java new file mode 100644 index 0000000..1379e58 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.manager; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.symmetric.AES; +import com.yami.shop.common.constants.OauthCacheNames; +import com.yami.shop.common.enums.YamiHttpStatus; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.serializer.redis.KryoRedisSerializer; +import com.yami.shop.common.util.PrincipalUtil; +import com.yami.shop.security.common.bo.TokenInfoBO; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.enums.SysTypeEnum; +import com.yami.shop.security.common.vo.TokenInfoVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * token管理 1. 登陆返回token 2. 刷新token 3. 清除用户过去token 4. 校验token + * + * @author FrozenWatermelon + * @date 2020/7/2 + */ +@Component +public class TokenStore { + + private static final Logger logger = LoggerFactory.getLogger(TokenStore.class); + + /** + * 用于aes签名的key,16位 + */ + @Value("${auth.token.signKey:-mall4j--mall4j-}") + public String tokenSignKey; + + private final RedisTemplate redisTemplate; + + private final RedisSerializer redisSerializer; + + private final StringRedisTemplate stringRedisTemplate; + + public TokenStore(RedisTemplate redisTemplate, + StringRedisTemplate stringRedisTemplate) { + this.redisTemplate = redisTemplate; + this.redisSerializer = new KryoRedisSerializer<>(); + this.stringRedisTemplate = stringRedisTemplate; + } + + /** + * 将用户的部分信息存储在token中,并返回token信息 + * @param userInfoInToken 用户在token中的信息 + * @return token信息 + */ + public TokenInfoBO storeAccessToken(UserInfoInTokenBO userInfoInToken) { + TokenInfoBO tokenInfoBO = new TokenInfoBO(); + String accessToken = IdUtil.simpleUUID(); + String refreshToken = IdUtil.simpleUUID(); + + tokenInfoBO.setUserInfoInToken(userInfoInToken); + tokenInfoBO.setExpiresIn(getExpiresIn(userInfoInToken.getSysType())); + + String uidToAccessKeyStr = getUserIdToAccessKey(getApprovalKey(userInfoInToken)); + String accessKeyStr = getAccessKey(accessToken); + String refreshToAccessKeyStr = getRefreshToAccessKey(refreshToken); + + // 一个用户会登陆很多次,每次登陆的token都会存在 uid_to_access里面 + // 但是每次保存都会更新这个key的时间,而key里面的token有可能会过期,过期就要移除掉 + List existsAccessTokensBytes = new ArrayList<>(); + // 新的token数据 + existsAccessTokensBytes.add((accessToken + StrUtil.COLON + refreshToken).getBytes(StandardCharsets.UTF_8)); + + Long size = redisTemplate.opsForSet().size(uidToAccessKeyStr); + if (size != null && size != 0) { + List tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidToAccessKeyStr, size); + if (tokenInfoBoList != null) { + for (String accessTokenWithRefreshToken : tokenInfoBoList) { + String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); + String accessTokenData = accessTokenWithRefreshTokenArr[0]; + if (BooleanUtil.isTrue(stringRedisTemplate.hasKey(getAccessKey(accessTokenData)))) { + existsAccessTokensBytes.add(accessTokenWithRefreshToken.getBytes(StandardCharsets.UTF_8)); + } + } + } + } + + redisTemplate.executePipelined((RedisCallback) connection -> { + + long expiresIn = tokenInfoBO.getExpiresIn(); + + byte[] uidKey = uidToAccessKeyStr.getBytes(StandardCharsets.UTF_8); + byte[] refreshKey = refreshToAccessKeyStr.getBytes(StandardCharsets.UTF_8); + byte[] accessKey = accessKeyStr.getBytes(StandardCharsets.UTF_8); + + connection.sAdd(uidKey, ArrayUtil.toArray(existsAccessTokensBytes, byte[].class)); + + // 通过uid + sysType 保存access_token,当需要禁用用户的时候,可以根据uid + sysType 禁用用户 + connection.expire(uidKey, expiresIn); + + // 通过refresh_token获取用户的access_token从而刷新token + connection.setEx(refreshKey, expiresIn, accessToken.getBytes(StandardCharsets.UTF_8)); + + // 通过access_token保存用户的租户id,用户id,uid + connection.setEx(accessKey, expiresIn, Objects.requireNonNull(redisSerializer.serialize(userInfoInToken))); + + return null; + }); + + // 返回给前端是加密的token + tokenInfoBO.setAccessToken(encryptToken(accessToken,userInfoInToken.getSysType())); + tokenInfoBO.setRefreshToken(encryptToken(refreshToken,userInfoInToken.getSysType())); + + return tokenInfoBO; + } + + private int getExpiresIn(int sysType) { + // 3600秒 + int expiresIn = 3600; + + // 普通用户token过期时间 1小时 + if (Objects.equals(sysType, SysTypeEnum.ORDINARY.value())) { + expiresIn = expiresIn * 24 * 30; + } + // 系统管理员的token过期时间 2小时 + if (Objects.equals(sysType, SysTypeEnum.ADMIN.value())) { + expiresIn = expiresIn * 24 * 30; + } + return expiresIn; + } + + /** + * 根据accessToken 获取用户信息 + * @param accessToken accessToken + * @param needDecrypt 是否需要解密 + * @return 用户信息 + */ + public UserInfoInTokenBO getUserInfoByAccessToken(String accessToken, boolean needDecrypt) { + if (StrUtil.isBlank(accessToken)) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"accessToken is blank"); + } + String realAccessToken; + if (needDecrypt) { + realAccessToken = decryptToken(accessToken); + } + else { + realAccessToken = accessToken; + } + UserInfoInTokenBO userInfoInTokenBO = (UserInfoInTokenBO) redisTemplate.opsForValue() + .get(getAccessKey(realAccessToken)); + + if (userInfoInTokenBO == null) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"accessToken 已过期"); + } + return userInfoInTokenBO; + } + + /** + * 刷新token,并返回新的token + * @param refreshToken + * @return + */ + public TokenInfoBO refreshToken(String refreshToken) { + if (StrUtil.isBlank(refreshToken)) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"refreshToken is blank"); + } + String realRefreshToken = decryptToken(refreshToken); + String accessToken = stringRedisTemplate.opsForValue().get(getRefreshToAccessKey(realRefreshToken)); + + if (StrUtil.isBlank(accessToken)) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"refreshToken 已过期"); + } + UserInfoInTokenBO userInfoInTokenBO = getUserInfoByAccessToken(accessToken, + false); + + // 删除旧的refresh_token + stringRedisTemplate.delete(getRefreshToAccessKey(realRefreshToken)); + // 删除旧的access_token + stringRedisTemplate.delete(getAccessKey(accessToken)); + // 保存一份新的token + return storeAccessToken(userInfoInTokenBO); + } + + /** + * 删除全部的token + */ + public void deleteAllToken(String sysType, String userId) { + String uidKey = getUserIdToAccessKey(getApprovalKey(sysType, userId)); + Long size = redisTemplate.opsForSet().size(uidKey); + if (size == null || size == 0) { + return; + } + List tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size); + + if (CollUtil.isEmpty(tokenInfoBoList)) { + return; + } + + for (String accessTokenWithRefreshToken : tokenInfoBoList) { + String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); + String accessToken = accessTokenWithRefreshTokenArr[0]; + String refreshToken = accessTokenWithRefreshTokenArr[1]; + redisTemplate.delete(getRefreshToAccessKey(refreshToken)); + redisTemplate.delete(getAccessKey(accessToken)); + } + redisTemplate.delete(uidKey); + + } + + private static String getApprovalKey(UserInfoInTokenBO userInfoInToken) { + return getApprovalKey(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId()); + } + + private static String getApprovalKey(String sysType, String userId) { + return userId == null? sysType : sysType + StrUtil.COLON + userId; + } + + private String encryptToken(String accessToken,Integer sysType) { + AES aes = new AES(tokenSignKey.getBytes(StandardCharsets.UTF_8)); + return aes.encryptBase64(accessToken + System.currentTimeMillis() + sysType); + } + + private String decryptToken(String data) { + AES aes = new AES(tokenSignKey.getBytes(StandardCharsets.UTF_8)); + String decryptStr; + String decryptToken; + try { + decryptStr = aes.decryptStr(data); + decryptToken = decryptStr.substring(0,32); + // 创建token的时间,token使用时效性,防止攻击者通过一堆的尝试找到aes的密码,虽然aes是目前几乎最好的加密算法 + long createTokenTime = Long.parseLong(decryptStr.substring(32,45)); + // 系统类型 + int sysType = Integer.parseInt(decryptStr.substring(45)); + // token的过期时间 + int expiresIn = getExpiresIn(sysType); + long second = 1000L; + if (System.currentTimeMillis() - createTokenTime > expiresIn * second) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"token error"); + } + } + catch (Exception e) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"token error"); + } + + // 防止解密后的token是脚本,从而对redis进行攻击,uuid只能是数字和小写字母 + if (!PrincipalUtil.isSimpleChar(decryptToken)) { + throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"token error"); + } + return decryptToken; + } + + public String getAccessKey(String accessToken) { + return OauthCacheNames.ACCESS + accessToken; + } + + public String getUserIdToAccessKey(String approvalKey) { + return OauthCacheNames.UID_TO_ACCESS + approvalKey; + } + + public String getRefreshToAccessKey(String refreshToken) { + return OauthCacheNames.REFRESH_TO_ACCESS + refreshToken; + } + + public TokenInfoVO storeAndGetVo(UserInfoInTokenBO userInfoInToken) { + TokenInfoBO tokenInfoBO = storeAccessToken(userInfoInToken); + + TokenInfoVO tokenInfoVO = new TokenInfoVO(); + tokenInfoVO.setAccessToken(tokenInfoBO.getAccessToken()); + tokenInfoVO.setRefreshToken(tokenInfoBO.getRefreshToken()); + tokenInfoVO.setExpiresIn(tokenInfoBO.getExpiresIn()); + return tokenInfoVO; + } + + public void deleteCurrentToken(String accessToken) { + String decryptToken = decryptToken(accessToken); + + UserInfoInTokenBO userInfoInToken = getUserInfoByAccessToken(accessToken, true); + + String uidKey = getUserIdToAccessKey(getApprovalKey(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId())); + Long size = redisTemplate.opsForSet().size(uidKey); + if (size == null || size == 0) { + return; + } + List tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size); + + if (CollUtil.isEmpty(tokenInfoBoList)) { + return; + } + String dbAccessToken = null; + String dbRefreshToken = null; + List list = new ArrayList<>(); + for (String accessTokenWithRefreshToken : tokenInfoBoList) { + String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); + dbAccessToken = accessTokenWithRefreshTokenArr[0]; + if (decryptToken.equals(dbAccessToken)) { + dbRefreshToken = accessTokenWithRefreshTokenArr[1]; + redisTemplate.delete(getRefreshToAccessKey(dbRefreshToken)); + redisTemplate.delete(getAccessKey(dbAccessToken)); + continue; + } + list.add(accessTokenWithRefreshToken.getBytes(StandardCharsets.UTF_8)); + } + + if (CollUtil.isNotEmpty(list)) { + redisTemplate.executePipelined((RedisCallback) connection -> { + connection.sAdd(uidKey.getBytes(StandardCharsets.UTF_8), ArrayUtil.toArray(list, byte[].class)); + return null; + }); + } + } +} diff --git a/yami-shop-security/src/main/java/com/yami/shop/security/permission/PermissionService.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/permission/PermissionService.java similarity index 62% rename from yami-shop-security/src/main/java/com/yami/shop/security/permission/PermissionService.java rename to yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/permission/PermissionService.java index 15cd26b..b728c86 100644 --- a/yami-shop-security/src/main/java/com/yami/shop/security/permission/PermissionService.java +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/permission/PermissionService.java @@ -8,21 +8,18 @@ * 版权所有,侵权必究! */ -package com.yami.shop.security.permission; +package com.yami.shop.security.common.permission; import cn.hutool.core.util.StrUtil; +import com.yami.shop.security.common.util.AuthUserContext; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; -import java.util.Collection; - /** * 接口权限判断工具 + * @author lh */ @Slf4j @Component("pms") @@ -37,14 +34,8 @@ public class PermissionService { if (StrUtil.isBlank(permission)) { return false; } - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - return false; - } - return authentication - .getAuthorities() + return AuthUserContext.get().getPerms() .stream() - .map(GrantedAuthority::getAuthority) .filter(StringUtils::hasText) .anyMatch(x -> PatternMatchUtils.simpleMatch(permission, x)); } diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java new file mode 100644 index 0000000..747f67c --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.util; + + +import com.alibaba.ttl.TransmittableThreadLocal; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; + +/** + * @author FrozenWatermelon + * @date 2020/7/16 + */ +public class AuthUserContext { + + private static final ThreadLocal USER_INFO_IN_TOKEN_HOLDER = new TransmittableThreadLocal<>(); + + public static UserInfoInTokenBO get() { + return USER_INFO_IN_TOKEN_HOLDER.get(); + } + + public static void set(UserInfoInTokenBO userInfoInTokenBo) { + USER_INFO_IN_TOKEN_HOLDER.set(userInfoInTokenBo); + } + + public static void clean() { + if (USER_INFO_IN_TOKEN_HOLDER.get() != null) { + USER_INFO_IN_TOKEN_HOLDER.remove(); + } + } +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/vo/TokenInfoVO.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/vo/TokenInfoVO.java new file mode 100644 index 0000000..a1642f1 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/vo/TokenInfoVO.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * token信息,该信息用户返回给前端,前端请求携带accessToken进行用户校验 + * + * @author FrozenWatermelon + * @date 2020/7/2 + */ +@Data +public class TokenInfoVO { + + @ApiModelProperty("accessToken") + private String accessToken; + + @ApiModelProperty("refreshToken") + private String refreshToken; + + @ApiModelProperty("在多少秒后过期") + private Integer expiresIn; +} diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService b/yami-shop-security/yami-shop-security-common/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService new file mode 100644 index 0000000..544f3e4 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService @@ -0,0 +1 @@ +com.yami.shop.security.common.adapter.CaptchaCacheServiceRedisImpl diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png new file mode 100644 index 0000000000000000000000000000000000000000..51573a0c4e1e72a0566fa1ba57422b5f06cd90d8 GIT binary patch literal 36206 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 E*~8IL`v3p{ literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/2.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/2.png new file mode 100644 index 0000000000000000000000000000000000000000..909dc39ef60eae026a09a82fde781ccc44858bd4 GIT binary patch literal 43926 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 Mk^SDDo}BCd*$(rtZ2$lO literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/4.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/4.png new file mode 100644 index 0000000000000000000000000000000000000000..c856f4d97e3f939230a461d3f9f8c22b92d64e0c GIT binary patch literal 52213 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%0AyI5~SbJb(!qX?O)j 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

`kgs zEA`c|2_f55#YRzYju3mVRbR8(dgifOl4`$$Cvip4AR?49wlXK6?|#PGk;(RYsTR}N zZM3d|3^iv(oVszoX^8Z+Y+Y{wG**?m+#9Spx`i?465U%W!eYGq_BeblF6%!s!SnmF zGF`t@>?x2LrJZLFwVsniP+2kfo_W}^JB3emRo|=9No$WI0UX#_>?H!|BK(xkV-%Te zixwLrr)DgKmeG@5dMVP9s?UOls)BI1O7^J@2A2G0sILQxdF73|_Za0idEu)Dr8)<* zBE3}XGGeKXxTepQ-W|o>$~hE`+r88w^8#ZoM6}IgM9%iiP7L^1NYX=to+5dT_z2HT zW0G2&%xHr>g|gW18O*ldwO`7LDW=w_THQth&gflfNUes5ggrPY5ia^>f?88=MW-;V z*gpq(O}S~s#Kmk!aHWIVl~pB)r-xgRf(D#X_>@Tqx|5tGnR0fEg$ZLe0iw;7(N~)) zX}OJ?Y6VPk7XyI``P(~s0>v`&6GrH>^812Fo&x0_;dL4u5R;2V)ksP@Y6Z%$!(gh! zkwcQkfn5@`5K?>!h)Xz*{3!aYds=Lb7uFlJvoPF9fqk#GL*7~;9ydMRXHDljE}s}{A{{iM zV0>I(&hMlssM4i^XI@pZs*Q8?Vcf4%?K0p|WB>QfO=_WJpH<}N);;tJHF!z&JNd?% zm~@peII07kjC=5%Rcs@5T)q+(smAj>ug(b0Wwn zq*wv1&*%l0y;DeI`q;Oh-SWLK`6lcDbI**u_Tbc1Ud``8^OofG;>!OQ`YHw0YMa9( z0VJU8!XNtDk1FzwVgM?tN&rfZ)j;V43C1EF7gL+JRie^RNiils@feYc#?REfKHjnE z&UL1)Zx*qd>AcCiOt4$6MUsn9E!OMB1eX(h3X?StfC&l`V>dKgb!}G3&v7rJ?KIL& zX=s~_%IgG=G9-YJF|guk*XK5v1U;)!adO}W38ev+Gg*NYF6Lh4yRO^5l83F`TMnd& z6uQdP+liJx3eza2+{Xf1>_m@zOj}SEhc^Nv;cvEB?U%AJQ};lZ6N0Y@7`7X|oxjEh zm+yd55lKk++g5^8-H&VmrV&a;YfR*^_tOu3WM&{aKxGEjNk#kOJmrNs=k8zR=R+l= zzERO%Hd(Szsi_U!>mHxGqAgzFOO4IZXvNnrh=g%e%mWwy0O{_9HuW9V%l@X?$nZx4 z?o?kKDxP2O|GuxALFLx48{RUaRVid%gg=Z^S+04k1=(8$K22wYTDt+ z0mXHd<;`U`V5KENDoxFT!2p_wr#hC)@?A-|S|9pX)!RPGuJ}dI(Rcp<)Os^$zQoqr z?Oc2LEMJZ@#vTqp-AZiSC-%c0fslzeF=Z9a=6^Fq=Ti6<8*)6?q|+=W`XmggDkw$GQ_Wlm-p#r^s&3SHpmTwOL=NhLyH zqv26Vote|*4xej@29u3P=lo=<9Caf^(bVKG3%gGcS!qRS^B`K7N zQ?MDsWy?Vwv8u|2cnD~9Qv{`Sl);rS8@ysIxdmxR*{1mra9C!XxBNxyufs973hb34 zB=P3#IL5}bK;IY#(@wa`oua-5Ff}Cs%qWm!R_BJ|pWLUuT=pxB7&1q@<{*Xh7$W?P zN0biE-QK;>;6lS1fK;Dvx)bJ%@#WWOBNkLWN%xZpRA>zH;v*dLTRg->9KSgkZo9M# zUjSXfXoI{crc%PDFHV!9&BLbMC2(v|y8{-LH9$VzvtOzfTX+_Y*50Sg2X@~TFD zxT&|hKg;fJX$1^h-9LCP_O?5Op`+`I6Ud%Xl=()u`k{uX(^;cEL-xbqa4qaejlNj3 zBW7kX;_*t5aq$7 z!jztm2f8P2M4-0NXF>rUueJ+wO+x1q8Z|PF&dSkO4)BD7DH(qq(V*6sITAKp@X(}` zhYKsqooFPkNk{=oROAu{$-~XA-1j4@l&A4A72S8cdO*{q=D5K)c9_vRx2|#xKc#bK z@1JEMc5fi1WiwgeG8`w}8vRAO?b|-tQP&S^Kxs+g55Al9w)wtw1skB(mTDYOso}yo z`p$s3*XzzQyG6HceRq87xhNq;my50NszO{USOzK$lXx9LNNzNWwY418;sypUFI75M z{{SEAHriWLIxAXIsC0>I!RVc$B>w0GBVwK|J1O6<9x zjMcBkTu4HaNK$Y~D(@16s&!9JTHdyu{Y`eW1dOHcJ`p0e^tY>SZ5>{XHqyD%9^5vz)>TJB~B!s)Cg?qZldVx>q>T; zGsCaBpacX-3aL1Qmjey9odwrc2EG3J%3{Wy65ty-hXC(^S;cYY)}cINQl1;VQC(dy zxE>-JtF$-i_A#mQq)&Xky}K(6W>U#xUsik|VY+SmaiGRBMn(rw0!on6_2V*sA?UAE z^NU56uB>!Ee(j@)B`QV9vvH;zO2nk3I^ZDhgsRG!bjP+L9Cns-w>x@7Y7VN|aSMTs zgZ8dl>e4wYS%RHS{%vWOKX|aGS$FE$wrwWf>x~Fa%CFkED;#A-!y*;MjnC=3^w`bB z>Z%HrQkfi%pjK+QtS##mo6XbS)`q#PAb~IzN+w7xBP=jTznszM7T%vqEgeYvu5booS37EjIdvF`s-Jml@3%K#iaqJI~!M<_KHa8LVVw0XDTV z?c1|(@qjG}HH_9U&LNja6y|sP-g$n>t`Z!uu<0GvmaTTD1o#eX5Hq31w;ApAG zUK!1}4Ys5e0+3a(^dF$kr%vZ`t%Ir9)Nwo)NN>kHlo;oqUv2IxTkS0&Xxv}ZU>rk5 zQuD_YP$opPhZo9c80fv0O7eIvGMIj%>MxUZgYOXB1#KskR&8j1~`excSHaM ziz`t`tB$ASmzn;cvecPl%FR=&wa&43FC`@`HGik|Q`$=Z03{az!pkel(UT#^3gsj` zm6Y(vmp5%MeV*NSK&Q|YFw4QU9XO?6p$2qmn zGR(T|t9G4vgg9KWdfjZg0pUxw>j@@c3j&>`8*Qe|^eeLKZm(|2l}(sIKGt!HQEUd zLZlqQ`NWRtg_q&7vYpDV^IsW2w3GfL40km!5c%F(@TF-Wf+zgFu-{y%A~pL)5YTFi zVp6JYROgLH@QB6u@~ke$O(B|o+woWM%DX)`#7W^D;~OiGg8rUf2420g_P`OG6cVYN z8rvTW^#14x(++zWjhJBq0n&TEVgTw<$L@&A+BeEokr&Q5Wu{r|# z^BZe_gtQ+RQ`Yvu)0YNS46knK#dmO=){~JDVll-fm@xs8h4TJtpdU#0$4S0lvsTw*W+zgH>JRo)w`5N6a7-g9sSS5@Sl*Fo?7_Qs zt2?*-CjfK&K}LxJ9=WJLW6?)VGmJm&m^)6T)5IWopDrBxJS#_ zkYFlNrDn};9#QPDOIp=;TDmDNoB~9D>K~XKt4cKGsvIl1QdE}E+JcB68juJAARSZc zj?>e$94feTumECD+ZLBedN*5qtlO%M{#YtW!Yiw(-Am53!W|-FmX~Mz^-eJLz0_FYJ!P=+2DWdRo{DENQqvu1*{{MRn@^XR37excxfc zFf7^Jais|e6HJxGB&do`aSx%^8~)beYh_u?pzMToCehNJH@l|0Fm#9 zmrrUO(*EAx>az#jLQgOyP7r{l045R$6Cgy>YTY+I&?U`kk7}q3pBS&Vowsk>OO3rM zY-%Kcz-14nNb>IOnksQiC=x#rR?V|CiDg=KKmp{CL{}FNrlrgQ4+z`cWIdN%B2t@u z=7OFzgsog?UQ(}{UJrtZi*FYH0GzP6v>`=W6&vDOQ{s@y=T@WwK-zI0BzHxVa?qwo zvBtLm2^kR@1B{U26cz58*|*9XxO`iqnBBWBuu$E?Ze4LIVcSR0kf2oAZN#cXd@*r)bR0Iu z)N+<9OvJ#ELZ)p)B$5zPQ0war2EOvODrKSS@KDLl3owFCQRZ2wGn&Qf-!q>(kmZPB zn=h-&wQmVm)HK_iIklbN3R+Sv((7t{wKmz}DpBGELP(1Pt0>%7vJGMijjXujL6W4G z49wRkp?XfGwHxP}gy?m=!Y|U`r4)iepMqx&-cfGV?n`j%`Ntu#Yyyz;*A{k7xx$;3 zI4)4qgTGI1u+k)=mbh2KD>#L?TDsiSsZ_R@rd3?v(m~a!MkS-l(hraVTQW>PTve@3 z>8)iQZr3CYbCAnQ6D>!~W#(3cf<<+C!_tAA*G+1M5~rJahjMG1!lJ*Z;U8I-xLhCw z&80YycFSfF3+gIbg&U@QCAt+!Bx2Hk^-nx=jrtCA9^i-!$iWE2C%f#3To^MVSWDaA z5t7gVA~v)3hdn~pI-UF5Ul1P#Iyhb#|gkbns@<> zz0gwCl+hqVleEKgzjE5NPD`h)v_7V?YV}My zmTr*8GV^BV7X{T>l_)BCLa+pLuuVWG34@GOU#LB@qqhBp zzz_7|mD7T&kkosXjm3FxpBWK=FxUBYb*p`6=Eq00;c=ZO=7U<@*#)IYSLvF&DaiAi zW$>*Hua@o8R|*7yJa)q$t@g&3U))#6oko-L#IX(%#AOEb9;kn&_OzSy(ydv5b_e4* z&m1C9C6uK>L{L=FU@KAK6&MAx%tgZDqfSvr-5}~tqP(^028&hmPULxqRCOk;v}OA{ zN3OEVoa-=k8M?BZ$;MTfWvpi{SX&FWS#d2t$w^X4nKj2)^>fWEtx_M{QE~Vu!=XM%N3wuOn_V=r`%yL_6hn~K<$ewTU zEpKvXub*+o_?r*fxa#>%1gNA*JtXy3!)Dq40P!BJp;GH(qD$@E!UakWblkwQbvHU` zi%BG6kwAL$`hBU@9a4JVq;7?V^?Rk4jVuqPa2SkjRK=Cr$!Y~EHLVY6AQ)0LBK1?y{4b z)(T^>p^ZJHlZi3O{;>4M(bC)XwT{LBwWVWDgYeY)l#7X~^ZU=JUgF|&mI)H$r#74n zFeNF3&YNXV0T0ZRyP}q^RnYQg3>;)WBq2b`q!i*eiHOm<8eUQp4pyWy`k|URP3N?o z)iSI`FTpG#4^7ik!WpZEcwJpK(9)By+Yvg9{$y=6`F-?JXvJ0om?sZJ2UUI0-CxY$w_nNDaTUFjlofHcm6 zFd241n^6j^Rfn0K3ifNNRPs227V1IZ1W1K~3Gs=Sr<>{6@9Md-)$WSp)NY%l)Vo`4 zY6GMZf@ERL=q>%mK=xmjUAm*oErcm#)ie?uP^VaKt=%hgLQMQ&=c6_XyH4E7NOZNR z7qDXz&#TB6nn~ADH6?b$f&~x4OrpKJAT_Qyh?~-BbxkAcFjKv@0$8D+OFUI09RlDiR_> za-AY=ORB61X&Oi|Ij89M^-9!MyVTH%WfsGtH3pu`>{2aO(o|9Nk~pRXE4aj>+kLjE z%|L+811N=W9T9g-@R*}Km&ii%0U(~y- zN*5FhfXX{xWw4>PS~l8r_2u7boqe{c6b&zhP8it$^`gJDBtyHa`$&tc84_9U^0`1Mh#9(yvhg? zUdX-#>Fe!5VR3nvSr=^{PM*$g64Q(lvRc0vX$?N}S17u&%dS%5%Vd=$zSR?B@BE94 zT6XoHMVB`O8cciI*Hv>`u5C8Cz`ejsh5~Q~Ih$v?rM2#4RC9$UYhiE%hJr?t;sMee zLIUY$8wQ$Y=F2K;ENyJ%jz4j7n_~UCuGFWmKwZ05$V;s~dv>KQFZQ?h7j1g8KG0`mAlV^PCEi%uOMn59VoV zIrwR8!HS=4z9BKVOS^94whRCm=kJY$Uieg&X|vPqj59tlfPf_*DVhpz!+&fQCQ0`8 z`(q1&ffx;?BrQ@XNh?(4@_{mZ0DuxOB*_4oiB>9pAByUgXLZBU(CpXXYo$6r(L zqo1E=*D87S{X*1~sl;<8fE3DjQh8xu@l1Udx~z68yNP%$x~o;-fKWBn2m#7SRB4+~ z5ETK%Z(eMz*}7WoS_pletqMdPDoZG|lt2j{V4=!EKT#Mr-E{KXqk2Kr9edP`BbKi! zcdR+iTXm@!MZ?Zo-R3%S%KFa6{M#vJ-HZ1v-gOSCq^FGmu(_!sH~RYb`lVZYmo#%J zwbYjaTGx3&%0me%8Jr@%=smKo&24dH3H+<7HmgQK&kE$uWezB~XAGVA3|L>ihbqSL z+4i?=>VxT&*D~dQ3WXm|(kal&%Cu zkZ=dILp5;FE~xtH+dOwfQm8z`C_7vc1SjPKL5#aFG@Sbc5Ct8Pj(8)=R|q+_ei!LZ zSnroTkF&-EHbiIlh%Tn<(!NNFs7?S1&Nh;8%bA>^NN#fj1H1nK37#^6B2&TzN$-eB z*6`J~YNfb@nXFGNC*m_UwP#L)?5~=n+Lcg)Fk9aYmvu|17mgofWvu~E zaulB+Tt3*VTddCGPIXytp~W*RiF`DaoE0MjSd2dA>f>&yT7!tH!c)RM5A-jhc74H= zU2w)YJPcyvIw|HB=P%1_ZO<}oA*2N( zX7>?5HMCMgvM0uq^B0_4Fw<76SmFu!)MO?$M8=n^7aYA5MxsV*9NE2kO<_){ePAWP zM%jC+GfhN8_xCGvio7L9g(6cD5e4c#rQCPIw=O)anlOf&U6RLW9J^Kkk*;2&;U-d* zPi`aEIEB%Xhl_V%8ZKhz2N2zi_g5HM?L#c$M`+DOOhxXy+I?0+FAyfXH=1}6LINC0 zr@T^du8`uwe~f#4V$$fU=7Z}}$<&?j3-XO~L|&H|wlk*+eWFL(7Z*p{BieIVsJ&9$ zlHRGw4skL1c)lWp{{UZxoxC#l#k0CwhT370$2xRY=Ie{Pj3g8%IDPG!2p~#TP8&D- z;t6w1l`&%0a02jUDe`Mtj_>B?0y*^c2Fmuy8$qPcXRJQh5lou0pRXpA7ezTW7HUU8aA`dgDW zxt5o%@)fQ?%51TixS5f%Vq>&KZ>nm#meyL|sqas7Tw|iYO7;|QyEK%dVp~t zHKX0CCO_@Lsx;oG$P;0k-sRbC=9WfnWvcG)ZZ56Vax2ukdyr?0rL*-HP9aV?W5U2L zm)St!CBR}+=})P>BhwuldsnRQXj$6qwBJ&zsuigFcNJl#z)R~@4(fDpvz z^>^u>{b$vbw|h}~GMn6+eJT$IfS_EuYWYP<)esuBON-U0(F*C#j@i0CtLhq$6>Qc95Tb`e(T$)cBpHh%2l&b(W5YkUBpeIvNuJZ*tPgiek z^#);_*jl*L_I7y_xf4#c%Qp85OIL4Qyvb|u)`i|)FXA6R4;I+qFVzpJdv96(qra|f z6@|vfvedG&qg9V<>DBzDLsqp~PHME%eR{QYIlowFc3~%-0ku}!`gWUc+F5mTRn=V5 z2y;Wxt!tKQ7|@{LsgzVgSSV7PB1HtF{v?u;J(21qC$_aV)oGL32=M^?;<~5{DmjFq z4j}jxg+Z`3W8dqabSgko#uHQH11)-p_R|jd1SL#iDTWcRYTKihX>CiGB7Dm{hcexNz#tV| zNhiWk!L>Sz?3-DGNiGc`2nvTJK_CJMt!-ZLv(nm%;K&wo4C&d_3AL78EPGQBYk~zb zID_^?pJrjLx?8YdZYhs_Mu?yehBOmm^SCL;Qc9#LG!k)~hs6{pUFxfKN|R<1J?!bC z%YC~}2*LJ5@zU(B;npOTD~E@K8h=F~nF&O{)7_cRstFP)IOV@z`l6MV-ueu8nXNwa z6Mv>TJE1oJ0D`O&Krtq#Wn-sot@JQ7B%{pB=T)y=_YV#(aVO&wr$uEK1-62R0%Rq> z4;&+P+^TIRtA}hi(%SFtoU~~<7ZWIRZ?eYF&OA!t6C-?~px#?Cg{|UoiADEKUS3rd zoZzCHTAjifZBuFj9N#i0v}04g8LlhRddw;L#B%=tWVy3(BpFR%jk-Zg&LoTp!jzn+ zSdCV1^HeM|>J20$5yC4;%#Q7nq$U6*1fZqRYi*7P#E0bx9iN`b!VWVlB>BQbpQpAW zi)g}l&a>{vutvg5vKlpI3eb(RohODHBM5^7ID)=qKg!*Q2#_XZj{g98hRT1-yA35F za<8IO2(Qk(DfDT39OBq4Ic8gUEEP-y5Km;JAZoOmfY)H<5isd`&1ur?J-x7rhvf^Y z%OCG}21MZtJF0tedE5 zx=MHuyQNnN{6LQBN*FfMKh)Zu`0o$X&hM_?gyz!U*%Hg7S(h@-(1lNlmQ{|>C5MDk z8{HmN{Yb0sXlcBQC+~88g@sY-##&Nh> zp?G{ETemk;LPHO)(45O8%`H#I$8Pry+JqTXu!sP2GXR6`F<|T?lXAnv5PU*IJJb7_ zvcd_dN>fv`ND<*EzWPFl*3!|!IlI+ez&3?et_nTTI68Lhg9m6SudCW(;l8)#TK6~Q z9M{nsk)ynptep6f$V!1ig?NA?bwv+^; z^wafN)uQZKRej^`ibvIJZ99df;E<_7%suRMz7B?0J+?eV@W=_{QZV%Hjl0TXXDFvC z$ghNIsb*A^%!r?}7-bErG>WXpj^nufz=N+mNWNo>ks1cDQ? zH}>aNwcprnoEqn9?8F@S%)~pY>9Mt((maH!c&u&9NK%%GQCvyJe=w1(YhQmU+QKVJ z$mTO#@WvS0QK8v{iqXz(evdoOZ6z z7`1pslj#McZ#_fX-13yI`L*153N)Ve@F^C7-QGzh8h4V{FdSD)zc-b z>I$6XC+1BO-1mUT)U;|Fm$h1HX(B)g`HvM-EOk{YHf5Lid?-TsTDcP<;ic2FpAdg;jKU6KV(?l9c=rqqp+p|>%O-h`pgkV{TF2{ z#2GI&Ckw!Af|(Q7KnsrxqVd?yRz+ z9OnRovxIWzshu5LeY!O2w3>OWSZ7Vzx3y)3JW>_#%`>jf=|z)o)g0D_(<|E)Mdznm zZr?Xk4u5o_Q{>u(n`TmmN>d;tM7zC_vUf*3e=*8TMy_@SFzKei){X%q)Lx}3OqAh2 z5~o~8svGOxwGf%4B;A~YAsPmyx=YQez*F&KxOf`vhSu&UUWZ|nHt)SXPQ8PwG$Yz4 z47Jz_d1~Heg>gS*ZtB)lPC3hQq$u4wk-$Q35wKEppJiacDD8&#Ov(;y9tZyb_(N#_ z02z~v7z{>9^AJU!j71jz0O8q$;dgwYmotaAw4qynYH`EwfEXAyW4a)pnwd!-FU&q! z@$BD8ZidhQ0C};pKL!5)F6e@tVd4J(wysa}MQAG@|EyO;f& zRTh6%KM6z4=89kL8!yP>=A-4}=i&V??)bOFOK<-GbE*ChWsau(JS@Kv7bHxuhg+g^ z{XWC<{{SIBHwS03DlV%30CcN|)s%W;eg462xbpMI;V7{#p3jAk%>Ir%Kh+S+Vf6^- z{;9>-M8ng+>6*vo8@FxQ59jIj?Zzmb)A60(8y&~wtGHn~;9;5Hiw%_iq2CQYq@Nl2 zu>OR%ZJyoG;LoSoVrjK~BMftfTRr@%ss8|k=jKa?1CI@50DhRCZNG@m@eDDBD?xr? zl;zKJaKOPBrMjQFrl?Z0Gfbl25!{$A|y?TWY6g#3k%(!AQ3s{JDjlaFdcF`vtX{FwIf zctf@4(G*2*oM{_={4p@ZaLtwkNC&NE#E;G{{Wb_B-j4{cz%Rm!+tB5@n)DrU-G3t`-P`? zc>e%3Uy;E%Ni*j2DZ{^R=;NNIg4bO*41Vn6p-m_M07zng z;i6N+1n#W&&OH9zzwe#?Bklc>=T`Io0DSy0_E>THe?O&j^JO(kd_0+R9Q>c@OTMwM z{{ZyqrYqz0{jm}1pT%#}mwp2n-%sWzxO#Vekfr|s;kUc?{30?wgB8=QrT+5O66OB@ z*KLa(g#LwPsX2eu?H5w={-=H>pFW-%XQ>uZbovax`HkM6ulgp9eIv)e$0Ubat(-sYoZ^3_&+2ntFRA9?`c=mlGF0G~=Wth- zE*i=reL%m|1`Evn{BZYsN*3vFz^RIUS>H|=oOAB&j(&RgKl^8{zdQc`;&m_O@bQ1q zFFsl9-#Dr_55s;q7`nYPPyDxQT_5f?-%_XJGlWw&N$2`Q5|{UXB&G8Vc&ijARWJ1d zn!G;v_YY}KN%}B2tWTbnPHp~Y&9jG#6NbrsvAeaeATKxIp9roxxivfR{+yO4K9S<( zxBjya`CEqguy&=;6Pe(jKZJ9}`kK$-{9*CR`1xb~W&`{U!+c2_tN#GYkN0A@yVI!t zAI3f5$1nbKANdo*Y^eo$r~OPbNNw_|{6CC&Ij0xWJ3pz}`JBGrmxph%8t3_e_?UKl zV_R`g$kqHmpz+}i%D!#BKQ<@Zz9rWG0D?V_x57B{eP8O8SNM;xND0IF&mVW$8;!uj dA;fpVt^BWH`D1tZ%=;jt1KslxpS|NB|JnMFf?5Co literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/6.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/6.png new file mode 100644 index 0000000000000000000000000000000000000000..1e04492979a234b9d1999634e8d671b2fcf827e6 GIT binary patch literal 59340 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 literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/1.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/1.png new file mode 100644 index 0000000000000000000000000000000000000000..1905026606d04c80eba0d7e03a5e286519257b9b GIT binary patch literal 22049 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 Cg^4== literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/2.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/2.png new file mode 100644 index 0000000000000000000000000000000000000000..b1482d48b4f2f323979ead105e074fbcc6c0e6d9 GIT binary patch literal 21367 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 literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/3.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/3.png new file mode 100644 index 0000000000000000000000000000000000000000..cdbb0b18c46c25c9d4573f60ad6031d70b99b872 GIT binary patch literal 21660 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 M?a|Ho_2AL}0V@#a%K!iX literal 0 HcmV?d00001 diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/4.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/4.png new file mode 100644 index 0000000000000000000000000000000000000000..bc69c96224d02597aa7c34d122bde3a563727dc6 GIT binary patch literal 21923 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 TfEwH4YgBdi>ucOpI~Mp~LU3*+ literal 0 HcmV?d00001 diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/ProdPropMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/ProdPropMapper.java index 85aeec5..779eb2e 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/dao/ProdPropMapper.java +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/ProdPropMapper.java @@ -19,7 +19,7 @@ import java.util.List; public interface ProdPropMapper extends BaseMapper { - List listPropAndValue(@Param("adapter") PageAdapter adapter, @Param("prodProp") ProdProp prodProp); + List listPropAndValue(@Param("com.yami.shop.security.common.adapter") PageAdapter adapter, @Param("prodProp") ProdProp prodProp); long countPropAndValue(@Param("prodProp") ProdProp prodProp); diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/UserMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/UserMapper.java index 9447e36..f5a2b95 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/dao/UserMapper.java +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/UserMapper.java @@ -12,12 +12,11 @@ package com.yami.shop.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.yami.shop.bean.model.User; -import com.yami.shop.bean.vo.UserVO; import org.apache.ibatis.annotations.Param; public interface UserMapper extends BaseMapper { - User getUserByBizUserId(@Param("appId")Integer appId, @Param("bizUserId")String bizUserId); - User getUserByUserMail(@Param("userMail") String userMail); + + User selectOneByUserName(@Param("userName") String userName); } diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/SmsLogService.java b/yami-shop-service/src/main/java/com/yami/shop/service/SmsLogService.java index e67593d..84d377b 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/service/SmsLogService.java +++ b/yami-shop-service/src/main/java/com/yami/shop/service/SmsLogService.java @@ -10,19 +10,17 @@ package com.yami.shop.service; -import java.util.Map; - import com.baomidou.mybatisplus.extension.service.IService; import com.yami.shop.bean.enums.SmsType; import com.yami.shop.bean.model.SmsLog; +import java.util.Map; + /** * * @author lgh on 2018/11/29. */ public interface SmsLogService extends IService { - public void sendSms(SmsType smsType,String userId,String mobile,Map params); - - public boolean checkValidCode(String mobile, String code,SmsType smsType); + void sendSms(SmsType smsType, String userId, String mobile, Map params); } diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/UserService.java b/yami-shop-service/src/main/java/com/yami/shop/service/UserService.java index 4643c43..5643d09 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/service/UserService.java +++ b/yami-shop-service/src/main/java/com/yami/shop/service/UserService.java @@ -13,7 +13,6 @@ package com.yami.shop.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yami.shop.bean.model.User; import com.yami.shop.bean.param.UserRegisterParam; -import com.yami.shop.bean.vo.UserVO; /** * @@ -23,7 +22,5 @@ public interface UserService extends IService { User getUserByUserId(String userId); - Boolean insertUser(UserRegisterParam userRegisterParam); - void validate(UserRegisterParam userRegisterParam, String checkRegisterSmsFlag); } diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java index 965b676..e34f841 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java @@ -10,24 +10,16 @@ package com.yami.shop.service.impl; -import java.util.List; - 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.yami.shop.bean.app.dto.MyOrderItemDto; -import com.yami.shop.bean.app.dto.ProductDto; -import com.yami.shop.common.util.PageAdapter; -import org.apache.commons.collections.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - import com.yami.shop.bean.app.dto.MyOrderDto; import com.yami.shop.bean.model.Order; +import com.yami.shop.common.util.PageAdapter; import com.yami.shop.dao.OrderMapper; import com.yami.shop.service.MyOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; /** * @author lgh on 2018/09/15. @@ -38,9 +30,6 @@ public class MyOrderServiceImpl extends ServiceImpl implemen @Autowired private OrderMapper orderMapper; - private static final Logger log = LoggerFactory.getLogger(MyOrderServiceImpl.class); - - @Override public IPage pageMyOrderByUserIdAndStatus(Page page, String userId, Integer status) { page.setRecords(orderMapper.listMyOrderByUserIdAndStatus(new PageAdapter(page), userId, status)); diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/SmsLogServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/SmsLogServiceImpl.java index 28af4b0..c5a830e 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/service/impl/SmsLogServiceImpl.java +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/SmsLogServiceImpl.java @@ -11,7 +11,6 @@ package com.yami.shop.service.impl; import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.RandomUtil; import com.aliyuncs.DefaultAcsClient; @@ -26,17 +25,12 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yami.shop.bean.enums.SmsType; import com.yami.shop.bean.model.SmsLog; import com.yami.shop.common.bean.ALiDaYu; -import com.yami.shop.common.enums.YamiHttpStatus; import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.Json; -import com.yami.shop.common.util.RedisUtil; import com.yami.shop.dao.SmsLogMapper; import com.yami.shop.service.SmsLogService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -125,47 +119,6 @@ public class SmsLogServiceImpl extends ServiceImpl impleme } - @Override - @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) - public boolean checkValidCode(String mobile, String code, SmsType smsType) { - long checkValidCodeNum = RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1); - if (checkValidCodeNum == 0) { - // 半小时后失效 - RedisUtil.expire(CHECK_VALID_CODE_NUM_PREFIX, 1800); - } - if (checkValidCodeNum >= TIMES_CHECK_VALID_CODE_NUM) { - throw new YamiShopBindException("验证码校验过频繁,请稍后再试"); - } - SmsLog sms = new SmsLog(); - sms.setUserPhone(mobile); - sms.setMobileCode(code); - sms.setStatus(1); - sms.setType(smsType.value()); - - SmsLog dbSms = smsLogMapper.selectOne(new LambdaQueryWrapper() - .eq(SmsLog::getUserPhone, mobile) - .eq(SmsLog::getMobileCode, code) - .eq(SmsLog::getStatus, 1) - .eq(SmsLog::getType, smsType.value())); - // 没有找到当前的验证码 - if (dbSms == null) { - RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1); - return false; - } - RedisUtil.del(CHECK_VALID_CODE_NUM_PREFIX + mobile); - // 标记为失效状态 - dbSms.setStatus(0); - smsLogMapper.updateById(dbSms); - // 验证码已过期 - DateTime offsetMinute = DateUtil.offsetMinute(dbSms.getRecDate(), 5); - if (offsetMinute.getTime() < System.currentTimeMillis()) { - RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1); - return false; - } - - return true; - } - private void sendSms(String mobile, String templateCode, Map params) throws ClientException { //可自助调整超时时间 diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/UserServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/UserServiceImpl.java index 3c0ee50..0a0cf5c 100644 --- a/yami-shop-service/src/main/java/com/yami/shop/service/impl/UserServiceImpl.java +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/UserServiceImpl.java @@ -46,23 +46,6 @@ public class UserServiceImpl extends ServiceImpl implements Us return userMapper.selectById(userId); } - @Override - public Boolean insertUser(UserRegisterParam uParam) { - User mail = userMapper.getUserByUserMail(uParam.getUserMail()); - if (mail != null) { - throw new YamiShopBindException("账号已存在"); - } - Date now = new Date(); - User user = new User(); - user.setUserId(IdUtil.simpleUUID()); - user.setModifyTime(now); - user.setUserRegtime(now); - user.setStatus(1); - user.setNickName(uParam.getUserMail()); - user.setUserMail(uParam.getUserMail()); - user.setLoginPassword(uParam.getPassword()); - return userMapper.insert(user) == 1; - } /** * 看看有没有校验验证码成功的标识 * @param userRegisterParam diff --git a/yami-shop-service/src/main/resources/mapper/ProdPropMapper.xml b/yami-shop-service/src/main/resources/mapper/ProdPropMapper.xml index a797b85..6a250e9 100644 --- a/yami-shop-service/src/main/resources/mapper/ProdPropMapper.xml +++ b/yami-shop-service/src/main/resources/mapper/ProdPropMapper.xml @@ -38,7 +38,7 @@ and prop_name like concat('%',#{prodProp.propName},'%') - LIMIT #{adapter.begin} , #{adapter.size} + LIMIT #{com.yami.shop.security.common.adapter.begin} , #{com.yami.shop.security.common.adapter.size} ) pp left join tz_prod_prop_value ppv on pp.prop_id = ppv.prop_id diff --git a/yami-shop-service/src/main/resources/mapper/UserMapper.xml b/yami-shop-service/src/main/resources/mapper/UserMapper.xml index 0e5ac47..2b07e33 100644 --- a/yami-shop-service/src/main/resources/mapper/UserMapper.xml +++ b/yami-shop-service/src/main/resources/mapper/UserMapper.xml @@ -24,15 +24,12 @@ - - - - + diff --git a/yami-shop-sys/pom.xml b/yami-shop-sys/pom.xml index 2b89e62..3d2f242 100644 --- a/yami-shop-sys/pom.xml +++ b/yami-shop-sys/pom.xml @@ -7,14 +7,13 @@ com.yami.shop yami-shop 0.0.1-SNAPSHOT - ../pom.xml com.yami.shop - yami-shop-security + yami-shop-security-admin ${yami.shop.version} diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/aspect/SysLogAspect.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/aspect/SysLogAspect.java index 386b888..7dd0a49 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/aspect/SysLogAspect.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/aspect/SysLogAspect.java @@ -10,32 +10,21 @@ package com.yami.shop.sys.aspect; -import java.lang.reflect.Method; -import java.util.Date; - -import javax.servlet.http.HttpServletRequest; - - +import cn.hutool.core.date.SystemClock; import com.yami.shop.common.util.IPHelper; -import com.yami.shop.security.util.SecurityUtils; +import com.yami.shop.common.util.Json; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.sys.model.SysLog; +import com.yami.shop.sys.service.SysLogService; 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 org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import com.yami.shop.sys.service.SysLogService; -import com.yami.shop.sys.model.SysLog; -import com.yami.shop.common.util.Json; - -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.util.StrUtil; +import java.util.Date; /** * @author lgh diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysMenuController.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysMenuController.java index 4630132..18d60f7 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysMenuController.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysMenuController.java @@ -10,39 +10,25 @@ package com.yami.shop.sys.controller; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import javax.validation.Valid; - import cn.hutool.core.map.MapUtil; -import com.yami.shop.security.util.SecurityUtils; - +import cn.hutool.core.util.StrUtil; +import com.yami.shop.common.annotation.SysLog; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.security.admin.util.SecurityUtils; import com.yami.shop.sys.constant.Constant; import com.yami.shop.sys.constant.MenuType; import com.yami.shop.sys.model.SysMenu; +import com.yami.shop.sys.service.SysMenuService; +import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; - -import com.yami.shop.sys.service.SysMenuService; -import com.yami.shop.common.annotation.SysLog; -import com.yami.shop.common.exception.YamiShopBindException; - -import cn.hutool.core.util.StrUtil; -import io.swagger.annotations.ApiOperation; +import javax.validation.Valid; +import java.util.List; +import java.util.Map; +import java.util.Objects; /** * 系统菜单 @@ -60,9 +46,8 @@ public class SysMenuController{ @ApiOperation(value="获取用户所拥有的菜单和权限", notes="通过登陆用户的userId获取用户所拥有的菜单和权限") public ResponseEntity> nav(){ List menuList = sysMenuService.listMenuByUserId(SecurityUtils.getSysUser().getUserId()); - Collection authorities = SecurityUtils.getSysUser().getAuthorities(); - return ResponseEntity.ok(MapUtil.builder().put("menuList", menuList).put("authorities", authorities).build()); + return ResponseEntity.ok(MapUtil.builder().put("menuList", menuList).put("authorities", SecurityUtils.getSysUser().getAuthorities()).build()); } /** diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysUserController.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysUserController.java index 780b433..6a3f99f 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysUserController.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/controller/SysUserController.java @@ -11,41 +11,32 @@ package com.yami.shop.sys.controller; -import java.util.List; -import java.util.Objects; - -import javax.validation.Valid; - +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.yami.shop.common.annotation.SysLog; +import com.yami.shop.common.exception.YamiShopBindException; import com.yami.shop.common.util.PageParam; +import com.yami.shop.security.admin.util.SecurityUtils; +import com.yami.shop.security.common.enums.SysTypeEnum; +import com.yami.shop.security.common.manager.PasswordManager; +import com.yami.shop.security.common.manager.TokenStore; import com.yami.shop.sys.constant.Constant; -import com.yami.shop.security.util.SecurityUtils; - - import com.yami.shop.sys.dto.UpdatePasswordDto; import com.yami.shop.sys.model.SysUser; +import com.yami.shop.sys.service.SysRoleService; +import com.yami.shop.sys.service.SysUserService; +import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.yami.shop.sys.service.SysRoleService; -import com.yami.shop.sys.service.SysUserService; -import com.yami.shop.common.annotation.SysLog; -import com.yami.shop.common.exception.YamiShopBindException; +import org.springframework.web.bind.annotation.*; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import io.swagger.annotations.ApiOperation; +import javax.validation.Valid; +import java.util.List; +import java.util.Objects; /** * 系统用户 @@ -58,21 +49,22 @@ public class SysUserController { private SysUserService sysUserService; @Autowired private SysRoleService sysRoleService; - @Autowired private PasswordEncoder passwordEncoder; + @Autowired + private PasswordManager passwordManager; + @Autowired + private TokenStore tokenStore; + /** * 所有用户列表 */ @GetMapping("/page") @PreAuthorize("@pms.hasPermission('sys:user:page')") public ResponseEntity> page(String username,PageParam page){ - IPage sysUserPage = sysUserService.page(page, new LambdaQueryWrapper() .eq(SysUser::getShopId, SecurityUtils.getSysUser().getShopId()) .like(StrUtil.isNotBlank(username), SysUser::getUsername, username)); - - return ResponseEntity.ok(sysUserPage); } @@ -99,13 +91,15 @@ public class SysUserController { throw new YamiShopBindException("禁止修改admin的账号密码"); } SysUser dbUser = sysUserService.getSysUserById(userId); - if (!passwordEncoder.matches(param.getPassword(), dbUser.getPassword())) { + String password = passwordManager.decryptPassword(param.getPassword()); + if (!passwordEncoder.matches(password, dbUser.getPassword())) { return ResponseEntity.badRequest().body("原密码不正确"); } //新密码 - String newPassword = passwordEncoder.encode(param.getNewPassword()); + String newPassword = passwordEncoder.encode(passwordManager.decryptPassword(param.getNewPassword())); // 更新密码 sysUserService.updatePasswordByUserId(userId, newPassword); + tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ADMIN.value()),String.valueOf(userId)); return ResponseEntity.ok().build(); } @@ -140,7 +134,7 @@ public class SysUserController { return ResponseEntity.badRequest().body("该用户已存在"); } user.setShopId(SecurityUtils.getSysUser().getShopId()); - user.setPassword(passwordEncoder.encode(user.getPassword())); + user.setPassword(passwordEncoder.encode(passwordManager.decryptPassword(user.getPassword()))); sysUserService.saveUserAndUserRole(user); return ResponseEntity.ok().build(); } @@ -152,7 +146,7 @@ public class SysUserController { @PutMapping @PreAuthorize("@pms.hasPermission('sys:user:update')") public ResponseEntity update(@Valid @RequestBody SysUser user){ - String password = user.getPassword(); + String password = passwordManager.decryptPassword(user.getPassword()); SysUser dbUser = sysUserService.getSysUserById(user.getUserId()); if (!Objects.equals(dbUser.getShopId(), SecurityUtils.getSysUser().getShopId())) { @@ -201,6 +195,4 @@ public class SysUserController { sysUserService.deleteBatch(userIds,SecurityUtils.getSysUser().getShopId()); return ResponseEntity.ok().build(); } - - } diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/dao/SysUserMapper.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/dao/SysUserMapper.java index e32351c..c1b3260 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/dao/SysUserMapper.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/dao/SysUserMapper.java @@ -25,7 +25,7 @@ public interface SysUserMapper extends BaseMapper { * 查询用户的所有权限 * @param userId 用户ID */ - List queryAllPerms(Long userId); + List queryAllPerms(@Param("userId") Long userId); /** * 根据用户id 批量删除用户 @@ -38,6 +38,6 @@ public interface SysUserMapper extends BaseMapper { * @param username * @return */ - SysUser selectByUsername(String username); + SysUser selectByUsername(@Param("username") String username); } diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/model/SysUser.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/model/SysUser.java index 9abed9a..a8e24e7 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/model/SysUser.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/model/SysUser.java @@ -14,7 +14,6 @@ package com.yami.shop.sys.model; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/service/SysUserService.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/service/SysUserService.java index 3a21c0c..4c6ea5b 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/service/SysUserService.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/service/SysUserService.java @@ -13,6 +13,8 @@ package com.yami.shop.sys.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yami.shop.sys.model.SysUser; +import java.util.List; + /** * 系统用户 @@ -60,4 +62,10 @@ public interface SysUserService extends IService { */ SysUser getSysUserById(Long userId); + /** + * 查询用户的所有权限 + * @param userId 用户ID + */ + List queryAllPerms(Long userId); + } diff --git a/yami-shop-sys/src/main/java/com/yami/shop/sys/service/impl/SysUserServiceImpl.java b/yami-shop-sys/src/main/java/com/yami/shop/sys/service/impl/SysUserServiceImpl.java index 92dfb6c..e74cae7 100644 --- a/yami-shop-sys/src/main/java/com/yami/shop/sys/service/impl/SysUserServiceImpl.java +++ b/yami-shop-sys/src/main/java/com/yami/shop/sys/service/impl/SysUserServiceImpl.java @@ -18,12 +18,11 @@ import com.yami.shop.sys.dao.SysUserRoleMapper; import com.yami.shop.sys.model.SysUser; import com.yami.shop.sys.service.SysUserService; import lombok.AllArgsConstructor; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; +import java.util.List; /** @@ -88,4 +87,9 @@ public class SysUserServiceImpl extends ServiceImpl impl public SysUser getSysUserById(Long userId) { return sysUserMapper.selectById(userId); } + + @Override + public List queryAllPerms(Long userId) { + return sysUserMapper.queryAllPerms(userId); + } }