基于mall4j产品的二开项目后端
lee
2024-12-19 aa9a1253bb11dfc16463e72c5b91ee2a9cc50f71
init
189 files added
20697 ■■■■■ changed files
yami-shop-platform/Dockerfile 18 ●●●●● patch | view | raw | blame | history
yami-shop-platform/Dockerfile-test 18 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/PlatformApplication.java 35 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/config/FlowUserAnalysisType.java 44 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/config/PlatformConstant.java 45 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/config/SwaggerConfiguration.java 44 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/config/XxlJobConfig.java 62 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AccountDetailController.java 151 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AgentController.java 84 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AreaController.java 163 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AttachFileGroupController.java 82 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/BrandController.java 166 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/BrandShopController.java 46 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CategoryController.java 324 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CategoryShopController.java 55 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CompanyAuditingController.java 62 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CustomerAnalysisController.java 708 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/DeliveryController.java 129 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/DeviceController.java 186 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/DeviceProfitLogController.java 22 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FileController.java 155 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowAnalysisController.java 107 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowCustomerAnalysisController.java 112 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowPageAnalysisController.java 43 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowRouteAnalysisController.java 44 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowUserAnalysisController.java 88 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FormController.java 152 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/HotSearchController.java 99 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/IndexImgController.java 126 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/MarsBlackLogController.java 44 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/MarsHandCardController.java 96 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/MarsTranOrderController.java 60 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/NoticeController.java 126 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/NotifyLogController.java 70 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/NotifyTemplateController.java 143 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderController.java 107 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderItemController.java 46 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderRefundController.java 68 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/PlatformNotifyController.java 98 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ProdAnalysisController.java 159 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ProductController.java 277 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/RevenueOverviewController.java 68 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ScoreProductController.java 221 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopAuditingController.java 198 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopBankCardController.java 46 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopCompanyController.java 65 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopDetailController.java 236 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopRenovationController.java 106 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopRenovationUpDelController.java 137 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopTemplateController.java 132 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopTemplateUpDelController.java 107 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopWalletController.java 97 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopWithdrawCashController.java 102 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/SigningAuditingController.java 112 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/SkuController.java 46 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/SpecController.java 102 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/StatisticsController.java 83 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/StatisticsOrderController.java 107 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/StockChangeReasonController.java 104 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/TaskRuleController.java 95 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/TaskUserController.java 82 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/TestController.java 70 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/UserAddrController.java 49 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/UserController.java 472 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/WebConfigController.java 95 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/C2cRealNameAuditController.java 76 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnConfigController.java 42 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnRealNameController.java 116 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserAtlasController.java 49 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserController.java 487 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserFlowController.java 208 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserLevelController.java 93 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserWalletController.java 128 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserWithdrawalController.java 306 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnWalletController.java 60 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/test/TestTaskController.java 277 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/AnalysisSalseTask.java 44 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/CpsShopOrderTask.java 377 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/EnterprisePayTask.java 86 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/FlowAnalysisTask.java 53 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/FossickTask.java 140 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/NotifyTask.java 51 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/OrderRefundTask.java 100 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/OrderTask.java 199 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/ProdTask.java 128 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/ShopTask.java 40 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/TaskReleaseTask.java 140 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/UserLevelTask.java 1132 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/java/com/yami/shop/platform/task/V1BoxTask.java 109 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/application-dev.yml 31 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/application-docker.yml 38 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/application-prod.yml 34 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/application-test.yml 35 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/application.yml 42 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/banner.txt 11 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/cert/alipayCertPublicKey_RSA2.crt 43 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/cert/alipayRootCert.crt 88 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/cert/appCertPublicKey_2021004183677087.crt 23 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/logback-spring-test.xml 81 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/logback-spring.xml 81 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/logback/logback-dev.xml 15 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/logback/logback-docker.xml 62 ●●●●● patch | view | raw | blame | history
yami-shop-platform/src/main/resources/logback/logback-prod.xml 62 ●●●●● patch | view | raw | blame | history
yami-shop-search/pom.xml 22 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-api/pom.xml 21 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-api/src/main/java/com/yami/shop/search/api/config/SwaggerConfiguration.java 34 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-api/src/main/java/com/yami/shop/search/api/controller/ProductSearchController.java 148 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/pom.xml 43 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/config/ElasticConfig.java 79 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/config/EsConfig.java 34 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/constant/EsConstant.java 111 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/constant/EsIndexEnum.java 37 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/constant/EsProductSortEnum.java 171 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/EsProductUpdateListener.java 101 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/ProdChangeListener.java 65 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/ProdChangeStatusListener.java 66 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/SkuDeleteListener.java 65 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/manager/ProductSearchManager.java 96 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/param/EsPageParam.java 152 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/EsProductService.java 114 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/SearchProductService.java 80 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/impl/EsProductServiceImpl.java 294 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/impl/SearchProductServiceImpl.java 736 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/util/EsSearchUtil.java 350 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/util/SearchResponseUtil.java 225 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/vo/EsPageVO.java 32 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-multishop/pom.xml 26 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-multishop/src/main/java/com/yami/shop/search/multishop/config/SwaggerConfiguration.java 35 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-multishop/src/main/java/com/yami/shop/search/multishop/controller/EsProductController.java 106 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-platform/pom.xml 26 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-platform/src/main/java/com/yami/shop/search/platform/config/SwaggerConfiguration.java 35 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-platform/src/main/java/com/yami/shop/search/platform/controller/EsProdctController.java 108 ●●●●● patch | view | raw | blame | history
yami-shop-search/yami-shop-search-platform/src/main/java/com/yami/shop/search/platform/task/SearchTask.java 259 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/pom.xml 26 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/pom.xml 34 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/config/SwaggerConfiguration.java 36 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/controller/SeckillOrderController.java 299 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/listener/CheckSecKillOrderListener.java 53 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/listener/LoadProdActivistListener.java 97 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/param/ConfirmParam.java 40 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/param/SeckillOrderParam.java 32 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/service/SeckillCacheManager.java 41 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/service/impl/SeckillCacheManagerImpl.java 154 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/pom.xml 21 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/constant/SeckillCacheNames.java 24 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dao/SeckillMapper.java 119 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dao/SeckillOrderMapper.java 50 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dao/SeckillSkuMapper.java 81 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillOrderMergerDto.java 68 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillPageDto.java 14 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillProductSimpleDto.java 62 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillShopCartItemDto.java 35 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/enums/SeckillEnum.java 37 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/CancelOrderListener.java 45 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/CategorySeckillListener.java 35 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/EsProductActivityInfoListener.java 69 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/EsProductListener.java 88 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/GetSeckillOrderInofListener.java 58 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/PaySuccessHandleOrderStockListener.java 65 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/PaySuccessOrderListener.java 52 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/ProcessActivityProdPriceListener.java 56 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/ProdChangeStatusListener.java 71 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/ShopChangeStatusListener.java 58 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/model/Seckill.java 85 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/model/SeckillOrder.java 58 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/model/SeckillSku.java 61 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/SeckillOrderService.java 72 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/SeckillService.java 178 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/SeckillSkuService.java 66 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/impl/SeckillOrderServiceImpl.java 185 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/impl/SeckillServiceImpl.java 384 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/impl/SeckillSkuServiceImpl.java 87 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/resources/mapper/SeckillMapper.xml 151 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/resources/mapper/SeckillOrderMapper.xml 37 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-common/src/main/resources/mapper/SeckillSkuMapper.xml 68 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/pom.xml 29 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/config/SwaggerConfiguration.java 37 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/controller/SeckillController.java 231 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/controller/SeckillOrderController.java 83 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/dto/SeckillProdDto.java 30 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/listener/OrderRefundListener.java 56 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/listener/UpdateSeckillOrderSkuInfoListener.java 48 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/param/SeckillParam.java 71 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-platform/pom.xml 30 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/config/SwaggerConfiguration.java 35 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/controller/SeckillController.java 178 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/controller/SeckillOrderController.java 50 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/dto/SeckillProdDto.java 30 ●●●●● patch | view | raw | blame | history
yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/task/SeckillOrderTask.java 83 ●●●●● patch | view | raw | blame | history
yami-shop-platform/Dockerfile
New file
@@ -0,0 +1,18 @@
FROM openjdk:8-jdk-alpine
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV  TIME_ZONE Asia/Shanghai
RUN apk add --no-cache tzdata  \
############设置时区
&& echo "Asia/Shanghai" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime
RUN apk add --no-cache font-adobe-100dpi ttf-dejavu fontconfig
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
COPY src/main/resources/cert /root/test/hxsl/cert
EXPOSE 8088
ENTRYPOINT ["java","-XX:+UnlockExperimentalVMOptions","-XX:+UseCGroupMemoryLimitForHeap", "-jar","/application/application.jar","--spring.profiles.active=prod"]
yami-shop-platform/Dockerfile-test
New file
@@ -0,0 +1,18 @@
FROM openjdk:8-jdk-alpine
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV  TIME_ZONE Asia/Shanghai
RUN apk add --no-cache tzdata  \
############设置时区
&& echo "Asia/Shanghai" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime
RUN apk add --no-cache font-adobe-100dpi ttf-dejavu fontconfig
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
COPY src/main/resources/cert /root/test/hxsl/cert
EXPOSE 8088
ENTRYPOINT ["java","-XX:+UnlockExperimentalVMOptions","-XX:+UseCGroupMemoryLimitForHeap", "-jar","/application/application.jar","--spring.profiles.active=test"]
yami-shop-platform/src/main/java/com/yami/shop/platform/PlatformApplication.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
/**
 * @author lgh
 */
@SpringBootApplication
@ComponentScan("com.yami.shop")
public class PlatformApplication extends SpringBootServletInitializer{
    public static void main(String[] args) {
        SpringApplication.run(PlatformApplication.class, args);
    }
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(PlatformApplication.class);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/config/FlowUserAnalysisType.java
New file
@@ -0,0 +1,44 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.config;
/**
 * @author Yami
 */
public enum FlowUserAnalysisType {
    /** 团购商品类型 */
    WEEK(1),
    /** 近30天 */
    MONTH(2),
    /** 自定义 */
    CUSTOM(3);
    private Integer num;
    public Integer value() {
        return num;
    }
    FlowUserAnalysisType(Integer num){
        this.num = num;
    }
    public static FlowUserAnalysisType instance(Integer value) {
        FlowUserAnalysisType[] enums = values();
        for (FlowUserAnalysisType statusEnum : enums) {
            if (statusEnum.value().equals(value)) {
                return statusEnum;
            }
        }
        return null;
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/config/PlatformConstant.java
New file
@@ -0,0 +1,45 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.config;
/**
 * 常量
 * @author yami
 */
public class PlatformConstant {
    /** 最大分类层级 */
    public static final int MAX_CATEGORY_GRADE = 2;
    /**
     * 短信包上限数量
     */
    public static final long SMS_PACKAGE_MAX_NUM = 10;
    /**
     * 6
     */
    public static final long SIX_MAONTH = 6;
    /**
     * 2
     */
    public static final int TWO = 2;
    /**
     * 3
     */
    public static final int THREE = 3;
    /**
     * 4
     */
    public static final int FOUR = 4;
    /**
     * 5
     */
    public static final long FIVE = 5;
}
yami-shop-platform/src/main/java/com/yami/shop/platform/config/SwaggerConfiguration.java
New file
@@ -0,0 +1,44 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * //@Profile("dev")
 * Swagger文档,只有在测试环境才会使用
 * @author LGH
 */
@Configuration
public class SwaggerConfiguration {
    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("公共接口")
                .packagesToScan("com.yami.shop.platform")
                .pathsToMatch("/**")
                .build();
    }
    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info().title("Mall4j接口文档")
                        .description("Mall4j宇宙版接口文档,openapi3.0 接口,用于平台端对接")
                        .version("v0.0.1")
                        .license(new License().name("使用请遵守商用授权协议").url("https://www.mall4j.com")));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/config/XxlJobConfig.java
New file
@@ -0,0 +1,62 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * xxl-job config
 *
 * @author FrozenWatermelon
 * @date 2021/1/18
 */
@Configuration
public class XxlJobConfig {
    private final Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
    @Value("${xxl-job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl-job.accessToken}")
    private String accessToken;
    @Value("${xxl-job.logPath}")
    private String logPath;
    @Value("${server.port}")
    private int port;
    @Autowired
    private InetUtils inetUtils;
    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname("mall4j-bbc");
        // 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP
        xxlJobSpringExecutor.setIp(inetUtils.findFirstNonLoopbackAddress().getHostAddress());
        xxlJobSpringExecutor.setPort(port + 1000);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(3);
        return xxlJobSpringExecutor;
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AccountDetailController.java
New file
@@ -0,0 +1,151 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.param.CustomerReqParam;
import com.yami.shop.bean.vo.AccountDetailVO;
import com.yami.shop.bean.vo.ShopAccountDetailVO;
import com.yami.shop.bean.vo.ShopAccountVO;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.PoiExcelUtil;
import com.yami.shop.service.PayInfoService;
import com.yami.shop.service.RefundInfoService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.apache.poi.ss.usermodel.Sheet;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
 * @author lhd
 * @date 2021/8/11 15:57
 */
@RestController("platformAccountDetailController")
@RequestMapping("/platform/accountDetail")
@Tag(name = "账户详情")
public class AccountDetailController {
    @Autowired
    private PayInfoService payInfoService;
    @Autowired
    private RefundInfoService refundInfoService;
    @GetMapping("/getIncomeAccountDetail")
    @Operation(summary = "获取收入账户详情" , description = "根据时间获取")
    public ServerResponseEntity<AccountDetailVO> getIncomeAccountDetail(@ParameterObject CustomerReqParam accountSearchDTO){
        AccountDetailVO accountDetailVO = payInfoService.getIncomeAccountDetail(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), accountSearchDTO.getShopName());
        return ServerResponseEntity.success(accountDetailVO);
    }
    @GetMapping("/getRefundAccountDetail")
    @Operation(summary = "获取退款账户详情" , description = "根据时间获取")
    public ServerResponseEntity<AccountDetailVO> getRefundAccountDetail(@ParameterObject CustomerReqParam accountSearchDTO){
        AccountDetailVO accountDetailVO = refundInfoService.getRefundAccountDetail(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), accountSearchDTO.getShopName());
        return ServerResponseEntity.success(accountDetailVO);
    }
    @GetMapping("/getIncomeAccountDetailInfo")
    @Operation(summary = "分页获取收入账户详情" , description = "分页获取收入账户详情")
    public ServerResponseEntity<IPage<ShopAccountVO>> getIncomeAccountDetailInfo(PageParam<ShopAccountVO> page, @ParameterObject CustomerReqParam accountSearchDTO) {
        IPage<ShopAccountVO> accountDetailPage = payInfoService.pageIncomeAccountDetail(page, accountSearchDTO);
        return ServerResponseEntity.success(accountDetailPage);
    }
    @GetMapping("/getRefundAccountDetailInfo")
    @Operation(summary = "分页获取退款账户详情" , description = "分页获取退款账户详情")
    public ServerResponseEntity<IPage<ShopAccountVO>> getRefundAccountDetailInfo(PageParam<ShopAccountVO> page, @ParameterObject CustomerReqParam accountSearchDTO) {
        IPage<ShopAccountVO> accountDetailPage = refundInfoService.listRefundAccountDetail(page, accountSearchDTO);
        return ServerResponseEntity.success(accountDetailPage);
    }
    @GetMapping("/pageShopIncomeAccountDetail")
    @Operation(summary = "分页获取指定店铺的收入结算明细" , description = "根据店铺id,时间获取")
    public ServerResponseEntity<IPage<ShopAccountDetailVO>> pageShopIncomeAccountDetail(PageParam<ShopAccountDetailVO> page, @ParameterObject CustomerReqParam customerReqParam) {
        IPage<ShopAccountDetailVO> accountDetailPage = payInfoService.pageShopIncomeAccountDetail(page, customerReqParam);
        return ServerResponseEntity.success(accountDetailPage);
    }
    @GetMapping("/pageShopRefundAccountDetail")
    @Operation(summary = "分页获取指定店铺的退款结算明细" , description = "根据店铺id,时间获取")
    public ServerResponseEntity<IPage<ShopAccountDetailVO>> pageShopRefundAccountDetail(PageParam<ShopAccountDetailVO> page, @ParameterObject CustomerReqParam customerReqParam) {
        IPage<ShopAccountDetailVO> accountDetailPage = refundInfoService.pageShopRefundAccountDetail(page, customerReqParam);
        return ServerResponseEntity.success(accountDetailPage);
    }
    @GetMapping("/getPayAndRefundInfoForm")
    @Operation(summary = "导出报表" , description = "导出收入以及退款报表")
    @PreAuthorize("@pms.hasPermission('pay:refund:excel')")
    public void getPayAndRefundInfoForm(@ParameterObject CustomerReqParam customerReqParam, HttpServletResponse response) {
        if (Objects.isNull(customerReqParam.getStartTime()) || Objects.isNull(customerReqParam.getEndTime())) {
            // 请选择导出报表的交易时间
            throw new YamiShopBindException("yami.finance.form.excel");
        }
        List<ShopAccountDetailVO> incomeAccountDetails = payInfoService.listIncomeAccountDetail(customerReqParam);
        List<ShopAccountDetailVO> refundAccountDetails = refundInfoService.listRefundAccountDetail(customerReqParam);
        Map<Integer, List<ShopAccountDetailVO>> shopAccountDetailMap = new HashMap<>(2);
        shopAccountDetailMap.put(0, incomeAccountDetails);
        shopAccountDetailMap.put(1, refundAccountDetails);
        excelMultiSheet(response, shopAccountDetailMap);
    }
    public void excelMultiSheet(HttpServletResponse response, Map<Integer, List<ShopAccountDetailVO>> shopAccountDetailMap) {
        ExcelWriter writer = ExcelUtil.getBigWriter();
        writer.renameSheet(0, I18nMessage.getMessage("yami.pay.info.reportTitle"));
        for (int i = 0; i < shopAccountDetailMap.size(); i++) {
            writer.setSheet(i == 0 ? I18nMessage.getMessage("yami.pay.info.reportTitle") : I18nMessage.getMessage("yami.refund.info.reportTitle"));
            handleSheet(writer);
            List<ShopAccountDetailVO> shopAccountDetails = shopAccountDetailMap.get(i);
            int row = 1;
            for (ShopAccountDetailVO shopAccountDetail : shopAccountDetails) {
                int firstRow = ++row;
                int col = -1;
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, row - 1);
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getShopName());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getPayTime());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getOrderNumber());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getPayNo());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getBizPayNo());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, I18nMessage.getMessage("yami.payType.pay" + shopAccountDetail.getPayType()));
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getAlipayAmount());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getWechatAmount());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getScoreCount());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getBalanceAmount());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getPaypalAmount());
                PoiExcelUtil.mergeIfNeed(writer, firstRow, firstRow, ++col, col, shopAccountDetail.getTotal());
            }
        }
        PoiExcelUtil.writeExcel(response, writer);
    }
    private void handleSheet(ExcelWriter writer) {
        String[] headerCn = {"序号","店铺名称","支付时间","订单编号","支付单号","外部流水号","支付方式","支付宝","微信","支付积分","余额支付","PayPal支付","合计"};
        String[] headerEn = {"Serial Number", "Store Name", "Payment Time", "Order Number", "Payment Order Number", "External Streaming Number", "Payment Method", "Alipay", "WeChat", "Payment Credits", "Balance Payment", "PayPal Payment", "Total"};
        List<String> header = Arrays.asList(Objects.equals(I18nMessage.getDbLang(), 0) ? headerCn : headerEn);
        writer.merge(header.size() - 1, I18nMessage.getMessage("yami.account.info.reportTitle"));
        writer.writeRow(header);
        Sheet sheet = writer.getSheet();
        for (int j = 1; j < header.size() - 1; j++) {
            sheet.setColumnWidth(j, 20 * 256);
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AgentController.java
New file
@@ -0,0 +1,84 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
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.Agent;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.AgentService;
import com.yami.shop.task.common.model.TaskDetail;
import com.yami.shop.task.common.service.TaskDetailService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/platform/agent")
@Tag(name = "代理商")
public class AgentController {
    @Autowired
    private AgentService agentService;
    @GetMapping("/page")
    @Operation(summary = "分页获取代理商" , description = "分页获取代理商")
    public ServerResponseEntity<IPage<Agent>> page(@ParameterObject Agent agent, PageParam<Agent> page){
        IPage<Agent> agentPage = agentService.page(page,new LambdaQueryWrapper<Agent>()
                .like(StrUtil.isNotBlank(agent.getAgentName()), Agent::getAgentName,agent.getAgentName())
                .like(StrUtil.isNotBlank(agent.getAgentRegion()), Agent::getAgentRegion,agent.getAgentRegion())
                .like(StrUtil.isNotBlank(agent.getAgentRegion()), Agent::getAgentRegion,agent.getAgentRegion())
                .like(StrUtil.isNotBlank(agent.getRemark()), Agent::getRemark,agent.getRemark())
                .eq(agent.getAgentMobile()!=null, Agent::getAgentMobile,agent.getAgentMobile())
                .orderByAsc(Agent::getCreatTime)
        );
        return ServerResponseEntity.success(agentPage);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "获取信息" , description = "获取信息")
    @Parameter(name = "id", description = "任务id" )
    public ServerResponseEntity<Agent> info(@PathVariable("id") Long id){
        Agent agent = agentService.getById(id);
        return ServerResponseEntity.success(agent);
    }
    @PostMapping
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@RequestBody @Valid Agent agent){
        agentService.save(agent);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Void> update(@RequestBody @Valid Agent agent){
        agentService.updateById(agent);
        return ServerResponseEntity.success();
    }
    @DeleteMapping
    @Operation(summary = "删除" , description = "删除")
    @Parameter(name = "ids", description = "任务id列表" )
    public ServerResponseEntity<Void> delete(@RequestBody List<Long> ids){
        agentService.removeByIds(ids);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AreaController.java
New file
@@ -0,0 +1,163 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.AreaDto;
import com.yami.shop.bean.enums.AreaLevelEnum;
import com.yami.shop.bean.model.Area;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.AreaService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
/**
 * @author lgh on 2018/10/26.
 */
@RestController
@Tag(name = "省市区")
@RequestMapping("/admin/area")
public class AreaController {
    @Autowired
    private AreaService areaService;
    @GetMapping("/page")
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<Area>> page(@ParameterObject Area area, PageParam<Area> page) {
        IPage<Area> sysUserPage = areaService.page(page, new LambdaQueryWrapper<Area>());
        return ServerResponseEntity.success(sysUserPage);
    }
    @GetMapping("/list")
    @Operation(summary = "获取列表" , description = "获取列表")
    public ServerResponseEntity<List<Area>> list(@ParameterObject Area area) {
        List<Area> areas = areaService.list(new LambdaQueryWrapper<Area>()
                .like(area.getAreaName() != null, Area::getAreaName, area.getAreaName())
                .lt(!Objects.isNull(area.getMaxGrade()),Area::getLevel,area.getMaxGrade())
        );
        return ServerResponseEntity.success(areas);
    }
    @GetMapping("/listByPid")
    @Operation(summary = "通过父级id获取列表" , description = "通过父级id获取列表")
    public ServerResponseEntity<List<Area>> listByPid(Long pid, Integer level) {
        List<Area> list = areaService.listByPid(pid, level);
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "获取信息" , description = "获取信息")
    public ServerResponseEntity<Area> info(@PathVariable("id") Long id) {
        Area area = areaService.getById(id);
        return ServerResponseEntity.success(area);
    }
    @PostMapping
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@Valid @RequestBody Area area) {
        if (area.getParentId() != null && !Objects.equals(area.getParentId(), 0L)) {
            areaService.removeAreaCacheByParentId(null, 1);
            Area parentArea = areaService.getById(area.getParentId());
            area.setLevel(parentArea.getLevel() + 1);
            areaService.removeAreaCacheByParentId(area.getParentId(), null);
            // 因为获取地址的时候,过滤掉了没有三级地址的一级地址,情况缓存的时候,一级地址也需要清空一下
            if (Objects.equals(area.getLevel(), AreaLevelEnum.THIRD_LEVEL.value())) {
                areaService.removeAreaCacheByParentId(0L, null);
            }
        }
        hasSameName(area);
        areaService.removeAreaListCache();
        areaService.save(area);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Void> update(@Valid @RequestBody Area area) {
        Area areaDb = areaService.getById(area.getAreaId());
        // 判断当前省市区级别,如果是1级、2级则不能修改级别,不能修改成别人的下级
        if(Objects.equals(areaDb.getLevel(),AreaLevelEnum.FIRST_LEVEL.value()) && !Objects.equals(area.getLevel(),AreaLevelEnum.FIRST_LEVEL.value())){
            throw new YamiShopBindException("不能改变一级行政地区的级别");
        }
        if(Objects.equals(areaDb.getLevel(),AreaLevelEnum.SECOND_LEVEL.value()) && !Objects.equals(area.getLevel(),AreaLevelEnum.SECOND_LEVEL.value())){
            throw new YamiShopBindException("不能改变二级行政地区的级别");
        }
        hasSameName(area);
        areaService.updateById(area);
        areaService.removeAreaCacheByParentId(null, 1);
        areaService.removeAreaCacheByParentId(area.getParentId(), null);
        areaService.removeAreaListCache();
        return ServerResponseEntity.success();
    }
    @DeleteMapping("/{id}")
    @Operation(summary = "删除" , description = "删除")
    public ServerResponseEntity<Void> delete(@PathVariable Long id) {
        if (areaService.count(new LambdaQueryWrapper<Area>().eq(Area::getParentId,id)) > 0) {
            // 请先删除子地区
            throw new YamiShopBindException("yami.area.delete");
        }
        Area area = areaService.getById(id);
        if(area==null){
            throw new YamiShopBindException("地址不存在或已被删除");
        }
        areaService.removeById(id);
        areaService.removeAreaCacheByParentId(null, 1);
        areaService.removeAreaCacheByParentId(area.getParentId(), null);
        areaService.removeAreaListCache();
        // 因为获取地址的时候,过滤掉了没有三级地址的一级地址,情况缓存的时候,一级地址也需要清空一下
        if (Objects.equals(area.getLevel(), AreaLevelEnum.THIRD_LEVEL.value())) {
            areaService.removeAreaCacheByParentId(0L, null);
        }
        return ServerResponseEntity.success();
    }
    @GetMapping("/areaListInfo")
    @Operation(summary = "获取省市信息" , description = "获取省市信息")
    public ServerResponseEntity<List<AreaDto>> getAreaListInfo() {
        return ServerResponseEntity.success(areaService.getAreaListInfo());
    }
    @GetMapping("/listAreaOfEnable")
    @Operation(summary = "获取可用的省市区列表(完整路径)" , description = "获取可用的省市区列表(完整路径)")
    public ServerResponseEntity<List<AreaDto>> listAreaOfEnable() {
        return ServerResponseEntity.success(areaService.listAreaOfEnable());
    }
    @GetMapping("/areaList")
    @Operation(summary = "获取可用的区域省市区信息(四级地址列表)" , description = "获取可用的区域省市区信息")
    public ServerResponseEntity<List<AreaDto>> getAllAreaList() {
        return ServerResponseEntity.success(areaService.getAllAreaList());
    }
    private void hasSameName(Area area) {
        int count = areaService.count(new LambdaQueryWrapper<Area>()
                .eq(Area::getParentId, area.getParentId())
                .eq(Area::getAreaName, area.getAreaName())
                .ne(Objects.nonNull(area.getAreaId()) && !Objects.equals(area.getAreaId(), Constant.ZERO_LONG), Area::getAreaId, area.getAreaId())
        );
        if (count > 0) {
            throw new YamiShopBindException("该地区已存在");
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/AttachFileGroupController.java
New file
@@ -0,0 +1,82 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.model.AttachFileGroup;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.AttachFileGroupService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
/**
 * @author chendt
 * @date 2021/7/7 17:18
 */
@RestController
@RequestMapping("/admin/fileGroup")
@Tag(name = "文件分组")
public class AttachFileGroupController {
    @Autowired
    private AttachFileGroupService attachFileGroupService;
    @GetMapping("/list")
    @Operation(summary = "获取列表" , description = "分页获取列表")
    @Parameter(name = "type", description = "文件类型: 1:图片 2:视频 3:文件" )
    public ServerResponseEntity<List<AttachFileGroup>> list(@RequestParam(value = "type", defaultValue = "1") Integer type) {
        List<AttachFileGroup> attachFileGroupPage = attachFileGroupService.list(Constant.PLATFORM_SHOP_ID,type);
        return ServerResponseEntity.success(attachFileGroupPage);
    }
    @GetMapping
    @Operation(summary = "获取" , description = "根据attachFileGroupId获取")
    @Parameter(name = "attachFileGroupId", description = "文件分组id" )
    public ServerResponseEntity<AttachFileGroup> getByAttachFileGroupId(@RequestParam Long attachFileGroupId) {
        return ServerResponseEntity.success(attachFileGroupService.getByAttachFileGroupId(attachFileGroupId));
    }
    @PostMapping
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@Valid @RequestBody AttachFileGroup attachFileGroup) {
        attachFileGroup.setAttachFileGroupId(null);
        attachFileGroup.setShopId(Constant.PLATFORM_SHOP_ID);
        attachFileGroupService.saveGroup(attachFileGroup);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "更新" , description = "更新")
    public ServerResponseEntity<Void> update(@Valid @RequestBody AttachFileGroup attachFileGroup) {
        attachFileGroup.setShopId(Constant.PLATFORM_SHOP_ID);
        attachFileGroupService.update(attachFileGroup);
        return ServerResponseEntity.success();
    }
    @DeleteMapping
    @Operation(summary = "删除" , description = "根据id删除")
    @Parameter(name = "attachFileGroupId", description = "文件分组id" )
    public ServerResponseEntity<Void> delete(@RequestParam Long attachFileGroupId) {
        AttachFileGroup dbAttachFileGroup = attachFileGroupService.getByAttachFileGroupId(attachFileGroupId);
        if (!Objects.equals(dbAttachFileGroup.getShopId(), Constant.PLATFORM_SHOP_ID)) {
            throw new YamiShopBindException("未授权");
        }
        attachFileGroupService.deleteById(attachFileGroupId);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/BrandController.java
New file
@@ -0,0 +1,166 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.BrandShopDTO;
import com.yami.shop.bean.dto.BrandSigningDTO;
import com.yami.shop.bean.model.Brand;
import com.yami.shop.bean.vo.BrandShopVO;
import com.yami.shop.bean.vo.BrandSigningVO;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * 品牌管理
 *
 * @author lgh
 */
@RestController
@RequestMapping("/platform/brand")
@Tag(name = "品牌相关接口")
public class BrandController {
    @Autowired
    private BrandService brandService;
    @Autowired
    private BrandShopService brandShopService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private ProductService productService;
    @Autowired
    private CategoryBrandService categoryBrandService;
    @GetMapping("/page")
    @Operation(summary = "分页获取品牌信息列表" , description = "分页获取品牌信息列表")
    public ServerResponseEntity<IPage<Brand>> page( PageParam<Brand> pageDTO, @ParameterObject Brand brandDTO) {
        brandDTO.setShopId(Constant.PLATFORM_SHOP_ID);
        IPage<Brand> brandPage = brandService.page(pageDTO, brandDTO);
        return ServerResponseEntity.success(brandPage);
    }
    @GetMapping("/list")
    @Operation(summary = "获取平台品牌列表(不分页)" , description = "获取平台品牌列表(不分页)")
    public ServerResponseEntity<List<Brand>> list(@ParameterObject Brand brand) {
        brand.setShopId(Constant.PLATFORM_SHOP_ID);
        List<Brand> brandList = brandService.listByParams(brand);
        return ServerResponseEntity.success(brandList);
    }
    @GetMapping("/info/{brandId}")
    @Operation(summary = "获取品牌信息" , description = "根据brandId获取品牌信息")
    @Parameter(name = "brandId", description = "品牌id" )
    public ServerResponseEntity<Brand> getByBrandId(@PathVariable Long brandId) {
        Brand brand = brandService.getInfo(brandId);
        categoryService.getPathNames(brand.getCategories());
        return ServerResponseEntity.success(brand);
    }
    @PostMapping
    @Operation(summary = "保存品牌信息" , description = "保存品牌信息")
    public ServerResponseEntity<Void> save(@Valid @RequestBody Brand brand) {
        brand.setShopId(Constant.PLATFORM_SHOP_ID);
        brandService.saveBrand(brand);
        brandService.removeCache(brand.getCategoryIds());
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "更新品牌信息" , description = "更新品牌信息")
    public ServerResponseEntity<Void> update(@Valid @RequestBody Brand brand) {
        if (Objects.isNull(brand.getCategoryIds())) {
            brand.setCategoryIds(new ArrayList<>());
        }
        List<Long> prodIds = brandService.updateBrand(brand);
        // 清除缓存
        List<Long> categoryIds = categoryBrandService.getCategoryIdBrandId(brand.getBrandId());
        categoryIds.addAll(brand.getCategoryIds());
        productService.removeProductCacheByProdIds(prodIds);
        brandService.removeCache(categoryIds);
        return ServerResponseEntity.success();
    }
    @DeleteMapping
    @Operation(summary = "删除品牌信息" , description = "根据品牌信息id删除品牌信息")
    @Parameter(name = "brandId", description = "品牌id" )
    public ServerResponseEntity<Void> delete(@RequestParam Long brandId) {
        List<Long> prodIds = brandService.deleteById(brandId);
        productService.removeProductCacheByProdIds(prodIds);
        brandService.removeCache(categoryBrandService.getCategoryIdBrandId(brandId));
        return ServerResponseEntity.success();
    }
    @PutMapping(value = "/updateBrandStatus")
    @Operation(summary = "更新品牌状态(启用或禁用)" , description = "更新品牌状态(启用或禁用)")
    public ServerResponseEntity<Void> updateBrandStatus(@RequestBody Brand brand) {
        if (Objects.isNull(brand.getStatus())) {
            throw new YamiShopBindException("状态不能为空");
        }
        if (Objects.isNull(brand.getBrandId())) {
            throw new YamiShopBindException("品牌id不能为空");
        }
        List<Long> prodIds = brandService.updateBrandStatus(brand);
        productService.removeProductCacheByProdIds(prodIds);
        // 清楚缓存
        List<Long> categoryIds = categoryBrandService.getCategoryIdBrandId(brand.getBrandId());
        // 获取当前节点所有父节点的分类ids,以及当前分类节点的父级节点的父级几点的分类ids
        List<Long> parentCategoryIds = categoryService.getParentIdsByCategoryId(categoryIds);
        if (CollUtil.isNotEmpty(parentCategoryIds)) {
            categoryIds.addAll(parentCategoryIds);
        }
        brandService.removeCache(CollUtil.isNotEmpty(categoryIds) ? categoryIds : new ArrayList<>());
        return ServerResponseEntity.success();
    }
    @GetMapping("/listSigningByShopId")
    @Operation(summary = "根据店铺id获取签约的品牌列表" , description = "根据店铺id获取签约的品牌列表")
    @Parameters(value = {
            @Parameter(name = "shopId", description = "店铺id" , required = true),
            @Parameter(name = "status", description = "状态 1:启用, 0:禁用" )
    })
    public ServerResponseEntity<BrandSigningVO> listSigningByShopId(@RequestParam(value = "shopId") Long shopId, @RequestParam(value = "status", required = false) Integer status) {
        BrandSigningVO brandSigningVO = brandShopService.listSigningByShopId(shopId, status);
        return ServerResponseEntity.success(brandSigningVO);
    }
    @GetMapping("/pageSigningByShopId")
    @Operation(summary = "根据店铺id分页获取签约的品牌列表" , description = "根据店铺id分页获取签约的品牌列表")
    public ServerResponseEntity<IPage<BrandShopVO>> pageSigningByShopId(PageParam<BrandShopVO> page, @ParameterObject BrandShopDTO brandShop) {
        IPage<BrandShopVO> brandShopPage = brandShopService.pageSigningByShopId(page, brandShop);
        return ServerResponseEntity.success(brandShopPage);
    }
    @PutMapping("/signing")
    @Operation(summary = "根据店铺id更新店铺下的签约品牌" , description = "根据店铺id更新店铺下的签约品牌")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<Void> signingBrands(@RequestBody BrandSigningDTO brandSigningDTO, @RequestParam(value = "shopId") Long shopId) {
        brandShopService.signingBrands(brandSigningDTO, shopId, true);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/BrandShopController.java
New file
@@ -0,0 +1,46 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.BrandShopService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Author lth
 * @Date 2021/8/23 11:18
 */
@RestController
@RequestMapping("/platform/brandShop")
@Tag(name = "签约品牌相关接口")
public class BrandShopController {
    @Autowired
    private BrandShopService brandShopService;
    @DeleteMapping
    @Operation(summary = "删除签约品牌" , description = "删除签约品牌")
    @Parameters(value = {
            @Parameter(name = "shopId", description = "店铺id" ),
            @Parameter(name = "brandId", description = "品牌id" )
    })
    public ServerResponseEntity<Void> delete(@RequestParam("shopId") Long shopId, @RequestParam("brandId") Long brandId) {
        brandShopService.deleteByShopIdAndBrandId(shopId, brandId);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CategoryController.java
New file
@@ -0,0 +1,324 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.app.dto.CategoryDto;
import com.yami.shop.bean.dto.CategoryShopDTO;
import com.yami.shop.bean.enums.EsOperationType;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.bean.model.Category;
import com.yami.shop.bean.model.CategoryShop;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.ProductExportParam;
import com.yami.shop.bean.vo.CategoryShopVO;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.i18n.LanguageEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.platform.config.PlatformConstant;
import com.yami.shop.service.CategoryExcelService;
import com.yami.shop.service.CategoryService;
import com.yami.shop.service.CategoryShopService;
import com.yami.shop.service.ProductService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import ma.glasnost.orika.MapperFacade;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 分类管理
 * @author lgh
 *
 */
@RestController
@RequestMapping("/prod/category")
@Tag(name = "分类相关接口")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private ProductService productService;
    @Autowired
    private CategoryShopService categoryShopService;
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private CategoryExcelService categoryExcelService;
    @GetMapping("/info/{categoryId}")
    @Operation(summary = "获取分类信息" , description = "获取分类信息")
    @Parameter(name = "categoryId", description = "分类id" )
    public ServerResponseEntity<Category> info(@PathVariable("categoryId") Long categoryId){
        Category category = categoryService.getCategoryByCategoryId(categoryId);
        return ServerResponseEntity.success(category);
    }
    @SysLog("保存分类")
    @PostMapping
    @PreAuthorize("@pms.hasPermission('prod:category:save')")
    @Operation(summary = "保存分类" , description = "保存分类")
    public ServerResponseEntity<Long> save(@RequestBody Category category){
        category.setShopId(Constant.PLATFORM_SHOP_ID);
        category.setRecTime(new Date());
//        int count = categoryService.count(new LambdaQueryWrapper<Category>().eq(Category::getCategoryName, category.getCategoryName())
//                .eq(Category::getParentId, category.getParentId()).eq(Category::getShopId,Constant.PLATFORM_SHOP_ID))
        Integer count = categoryService.getCategoryName(category);
        if(count > 0){
            // 类目名称已存在
            throw new YamiShopBindException(I18nMessage.getMessage("yami.category.name.exist"));
        }
        category.setGrade(getGradeByParentId(category.getParentId()));
        category.setSuperiorId(-1L);
        // 获取上级的上级
        if(category.getGrade() == PlatformConstant.MAX_CATEGORY_GRADE) {
            Long superiorId = categoryService.getParentCategoryByParentId(category.getParentId());
            category.setSuperiorId(superiorId);
        }
        categoryService.saveCategroy(category);
        // 清除缓存
        removeCategoryCacheByParentId(category);
        return ServerResponseEntity.success(category.getCategoryId());
    }
    @SysLog("更新分类")
    @PutMapping
    @PreAuthorize("@pms.hasPermission('prod:category:update')")
    @Operation(summary = "更新分类" , description = "更新分类")
    public ServerResponseEntity<String> update(@RequestBody Category category){
        category.setShopId(Constant.PLATFORM_SHOP_ID);
        Category categoryDb = categoryService.getCategoryByCategoryId(category.getCategoryId());
        if (!Objects.equals(category.getGrade(), categoryDb.getGrade())) {
            throw new YamiShopBindException("不能改变分类层级");
        }
        Integer count = categoryService.getCategoryName(category);
        if(count > 0){
            // 类目名称已存在
            throw new YamiShopBindException(I18nMessage.getMessage("yami.category.name.exist"));
        }
        // 如果从下线改成正常,则需要判断上级的状态
        if (Objects.equals(categoryDb.getStatus(),0) && Objects.equals(category.getStatus(),1) && !Objects.equals(category.getParentId(),0L)){
            Category parentCategory = categoryService.getOne(new LambdaQueryWrapper<Category>().eq(Category::getCategoryId, category.getParentId()));
            if(Objects.isNull(parentCategory) || Objects.equals(parentCategory.getStatus(),0)){
                // 修改失败,上级分类不存在或者不为正常状态
                throw new YamiShopBindException("yami.category.status.check");
            }
        }
        category.setGrade(getGradeByParentId(category.getParentId()));
        category.setOldCategoryName(categoryDb.getCategoryName());
        category.setOldCategoryNameEn(categoryDb.getCategoryNameEn());
        category.setSuperiorId(-1L);
        // 获取上级的上级
        if(category.getGrade() == PlatformConstant.MAX_CATEGORY_GRADE) {
            Long superiorId = categoryService.getParentCategoryByParentId(category.getParentId());
            category.setSuperiorId(superiorId);
        }
        categoryService.updateCategroy(category);
        // 清除缓存
        removeCategoryCacheByParentId(category);
        eventPublisher.publishEvent(new EsProductUpdateEvent(category.getCategoryId(), null, EsOperationType.UPDATE_BY_CATEGORY_ID));
        return ServerResponseEntity.success();
    }
    @SysLog("删除分类")
    @DeleteMapping("/{categoryId}")
    @PreAuthorize("@pms.hasPermission('prod:category:delete')")
    @Operation(summary = "删除分类" , description = "删除分类")
    @Parameter(name = "categoryId", description = "分类id" )
    public ServerResponseEntity<String> delete(@PathVariable("categoryId") Long categoryId){
        if (categoryService.count(new LambdaQueryWrapper<Category>().eq(Category::getParentId,categoryId)) >0) {
            // 请删除子分类,再删除该分类
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("yami.category.delete.child"));
        }
        int categoryProdCount = productService.count(new LambdaQueryWrapper<Product>().eq(Product::getCategoryId, categoryId).ne(Product::getStatus, -1));
        if (categoryProdCount>0){
            // 该分类下还有商品,请先删除该分类下的商品
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("yami.category.delete.check"));
        }
        Category category = categoryService.getById(categoryId);
        category.setSuperiorId(-1L);
        // 获取上级的上级
        if(category.getGrade() == PlatformConstant.MAX_CATEGORY_GRADE) {
            Long superiorId = categoryService.getParentCategoryByParentId(category.getParentId());
            category.setSuperiorId(superiorId);
        }
        categoryService.deleteCategroy(category);
        // 清除缓存
        removeCategoryCacheByParentId(category);
        return ServerResponseEntity.success();
    }
    @GetMapping("/listCategory")
    @Operation(summary = "获取全部分类" , description = "获取全部分类")
    @Parameters(value = {
            @Parameter(name = "maxGrade", description = "0:一级分类,1:二级分类,2:三级分类(后面的包含前面等级的分类)" ),
            @Parameter(name = "status", description = "默认是1,表示正常状态,0为下线状态" )
    })
    public ServerResponseEntity<List<Category>> listCategory(@RequestParam(value = "maxGrade", required = false, defaultValue = "2") Integer maxGrade,
                                                       @RequestParam(value = "status", required = false) Integer status) {
        List<Category> categories =  categoryService.listByLang(I18nMessage.getLang(),maxGrade,null,status,Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success(categories);
    }
    @GetMapping("/listCategoryByGrade")
    @Operation(summary = "根据等级与状态获取平台分类列表" , description = "根据等级与状态获取平台分类列表")
    @Parameters(value = {
            @Parameter(name = "grade", description = "0:一级分类,1:二级分类,2:三级分类" ),
            @Parameter(name = "status", description = "默认是1,表示正常状态,0为下线状态" )
    })
    public ServerResponseEntity<List<Category>> listCategoryByGrade(@RequestParam(value = "grade", defaultValue = "2") Integer grade, @RequestParam(value = "status", required = false) Integer status) {
        List<Category> categories = categoryService.listByGrade(I18nMessage.getLang(), grade, status, Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success(categories);
    }
    @GetMapping("/upAndCurrCategoryList/{categoryId}")
    @Operation(summary = "获取上架分类和当前选中分类的父类" , description = "获取上架分类和当前选中分类的父类")
    @Parameters(value = {
            @Parameter(name = "maxGrade", description = "0:一级分类,1:二级分类,2:三级分类" ),
            @Parameter(name = "categoryId", description = "分类id" )
    })
    public ServerResponseEntity<List<Category>> upAndCurrCategoryList(
            @RequestParam(value = "maxGrade", required = false, defaultValue = "2") Integer maxGrade,
            @PathVariable("categoryId") Long categoryId){
        Category category = new Category();
        category.setLang(I18nMessage.getDbLang());
        category.setStatus(1);
        category.setShopId(Constant.PLATFORM_SHOP_ID);
        category.setGrade(maxGrade);
        //获取上架的分类
        List<Category> upList = categoryService.categoryList(category);
        //如果是新增的,直接返回上架的分类即可
        if (categoryId==0){
            return ServerResponseEntity.success(upList);
        }
        Category currCategory = categoryService.getCategoryByCategoryId(categoryId);
        if (currCategory == null) {
            return ServerResponseEntity.success(upList);
        }
        while (currCategory.getParentId() != 0) {
            currCategory=categoryService.getCategoryByCategoryId(currCategory.getParentId());
            if (!Objects.equals(currCategory.getStatus(), 1)) {
                upList.add(currCategory);
            }
        }
        return ServerResponseEntity.success(upList);
    }
    private int getGradeByParentId(Long parentId) {
        // 如果上级为id为0,则设置分类等级为0
        if (Objects.equals(parentId,0L)) {
            return 0;
        }
        Category parentCategory = categoryService.getById(parentId);
        return parentCategory.getGrade() + 1;
    }
    @GetMapping("/platformCategory")
    @Operation(summary = "平台可用分类-必须是启用分类且分类下包含启动的三级分类" , description = "平台可用分类-必须是启用分类且分类下包含启动的三级分类")
    public ServerResponseEntity<List<Category>> platformCategory(){
        List<Category> list =  categoryService.platformCategory();
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/signingInfoByShopId")
    @Operation(summary = "获取签约的分类列表(状态参数为空则返回所有)" , description = "获取签约的分类列表(状态参数为空则返回所有)")
    @Parameters(value = {
            @Parameter(name = "shopId", description = "店铺id" ),
            @Parameter(name = "status", description = "签约状态:1:已通过 0待审核 -1未通过" )
    })
    public ServerResponseEntity<List<CategoryShopVO>> listSigningByShopId(@RequestParam(value = "shopId") Long shopId, @RequestParam(value = "status", required = false) Integer status) {
        List<CategoryShopVO> categoryShopList = categoryShopService.listSigningCategoryByShopId(shopId, I18nMessage.getLang());
        if (Objects.nonNull(status)) {
            categoryShopList = categoryShopList.stream().filter(item -> Objects.equals(item.getStatus(), status)).collect(Collectors.toList());
        }
        return ServerResponseEntity.success(categoryShopList);
    }
    @GetMapping("/pageSigningInfo")
    @Operation(summary = "分页获取签约的分类列表" , description = "分页获取签约的分类列表")
    public ServerResponseEntity<IPage<CategoryShopVO>> pageSigningInfo(PageParam<CategoryShopVO> page, @ParameterObject CategoryShopDTO categoryShop) {
        IPage<CategoryShopVO> categoryShopPage = categoryShopService.pageSigningInfo(page, categoryShop);
        return ServerResponseEntity.success(categoryShopPage);
    }
    @PutMapping("/signing")
    @Operation(summary = "更新店铺签约分类" , description = "更新店铺签约分类")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<Void> signing(@Valid @RequestBody List<CategoryShop> categoryShopDTOList, @RequestParam(value = "shopId") Long shopId) {
        categoryShopService.signingCategory(categoryShopDTOList, shopId, true);
        return ServerResponseEntity.success();
    }
    private void removeCategoryCacheByParentId(Category category) {
        categoryService.removeListRateCache();
        categoryService.removeCacheByParentIdAndLang(category.getParentId(), category.getShopId(), LanguageEnum.LANGUAGE_ZH_CN.getLang());
        categoryService.removeCacheByParentIdAndLang(category.getParentId(), category.getShopId(), LanguageEnum.LANGUAGE_EN.getLang());
        categoryService.removeCacheByParentIdAndLang(category.getSuperiorId(), category.getShopId(), LanguageEnum.LANGUAGE_ZH_CN.getLang());
        categoryService.removeCacheByParentIdAndLang(category.getSuperiorId(), category.getShopId(), LanguageEnum.LANGUAGE_EN.getLang());
        // 如果是2/3级分类,第一分类也需要清空缓存数据
        if(category.getGrade() == 1 || category.getGrade() == PlatformConstant.MAX_CATEGORY_GRADE) {
            categoryService.removeCacheByParentIdAndLang(Constant.CATEGORY_ID, Constant.PLATFORM_SHOP_ID, LanguageEnum.LANGUAGE_ZH_CN.getLang());
            categoryService.removeCacheByParentIdAndLang(Constant.CATEGORY_ID, Constant.PLATFORM_SHOP_ID, LanguageEnum.LANGUAGE_EN.getLang());
        }
    }
    @GetMapping("/categoryInfo")
    @Operation(summary = "分类信息列表" , description = "获取所有的产品分类信息,顶级分类的parentId为0,默认为顶级分类")
    @Parameters({
            @Parameter(name = "parentId", description = "分类ID" ),
            @Parameter(name = "shopId", description = "店铺id" )
    })
    public ServerResponseEntity<List<CategoryDto>> categoryInfo(@RequestParam(value = "parentId", defaultValue = "0") Long parentId,
                                                          @RequestParam(value = "shopId", defaultValue = "0") Long shopId) {
        List<Category> categories = categoryService.listByParentIdAndShopId(parentId, shopId, I18nMessage.getDbLang());
        List<CategoryDto> categoryDtos = mapperFacade.mapAsList(categories, CategoryDto.class);
        return ServerResponseEntity.success(categoryDtos);
    }
    @GetMapping("/getCategoryAndParent")
    @Operation(summary = "获取平台分类及所有上级分类" , description = "获取平台分类及所有上级分类")
    public ServerResponseEntity<List<Category>> getCategoryAndParent(@RequestParam(value = "categoryId") Long categoryId){
        List<Category> categories = categoryService.getCategoryAndParent(categoryId);
        return ServerResponseEntity.success(categories);
    }
    @GetMapping("/export")
    @PreAuthorize("@pms.hasPermission('prod:prod:exportProd')")
    @Operation(summary = "导出分类" , description = "导出分类")
    public void export(HttpServletResponse response) {
        categoryExcelService.export(response, Constant.PLATFORM_SHOP_ID);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CategoryShopController.java
New file
@@ -0,0 +1,55 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.CategoryShopService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @Author lth
 * @Date 2021/8/23 9:44
 */
@RestController
@RequestMapping("/platform/categoryShop")
@Tag(name = "签约分类相关接口")
public class CategoryShopController {
    @Autowired
    private CategoryShopService categoryShopService;
    @DeleteMapping
    @Operation(summary = "删除签约分类" , description = "删除签约分类")
    @Parameters(value = {
            @Parameter(name = "shopId", description = "店铺id" ),
            @Parameter(name = "categoryId", description = "分类id" )
    })
    public ServerResponseEntity<Void> delete(@RequestParam("shopId") Long shopId, @RequestParam("categoryId") Long categoryId) {
        categoryShopService.delete(shopId, categoryId);
        return ServerResponseEntity.success();
    }
    @PutMapping("updateRate")
    @Operation(summary = "更新自定义扣率" , description = "更新自定义扣率")
    @Parameters(value = {
            @Parameter(name = "shopId", description = "店铺id" ),
            @Parameter(name = "rate", description = "扣率" )
    })
    public ServerResponseEntity<Void> updateRate(@RequestParam("shopId") Long shopId, @RequestParam("categoryId") Long categoryId,
                                           @RequestParam(value = "rate", required = false) Double rate) {
        categoryShopService.updateRate(shopId, categoryId, rate);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CompanyAuditingController.java
New file
@@ -0,0 +1,62 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.CompanyAuditing;
import com.yami.shop.bean.param.CompanyInfoAuditParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.CompanyAuditingService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
 * @author chiley
 * @date 2022/9/21 10:23
 */
@RestController
@RequestMapping("/platform/companyAuditing")
@Tag(name = "审核工商信息接口")
public class CompanyAuditingController {
    @Autowired
    private CompanyAuditingService companyAuditingService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('shop:companyAuditing:page')")
    @Operation(summary = "分页获取待审核的工商信息" , description = "分页获取待审核的工商信息")
    public ServerResponseEntity<IPage<CompanyAuditing>> getCompanyAuditingPage(PageParam<CompanyAuditing> page, @ParameterObject CompanyInfoAuditParam auditParam) {
        return ServerResponseEntity.success(companyAuditingService.page(page, auditParam));
    }
    @PutMapping("/audit")
    @Operation(summary = "审核签约信息" , description = "审核签约信息")
    @PreAuthorize("@pms.hasPermission('shop:companyAuditing:audit')")
    public ServerResponseEntity<Void> audit(@Valid @RequestBody CompanyAuditing companyAuditing) {
        companyAuditing.setAuditorId(SecurityUtils.getSysUser().getUserId());
        companyAuditingService.audit(companyAuditing);
        return ServerResponseEntity.success();
    }
    @GetMapping("/auditInfo")
    @Operation(summary = "查看申请审核情况" , description = "查看申请审核情况")
    public ServerResponseEntity<CompanyAuditing> auditInfo(@RequestParam("shopId") Long shopId) {
        return ServerResponseEntity.success(companyAuditingService.getAuditInfo(shopId));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/CustomerAnalysisController.java
New file
@@ -0,0 +1,708 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.yami.shop.bean.enums.RetainedDateType;
import com.yami.shop.bean.param.*;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.Arith;
import com.yami.shop.common.util.DateParam;
import com.yami.shop.common.util.DateUtils;
import com.yami.shop.coupon.common.service.CouponUserService;
import com.yami.shop.dao.FlowPageAnalyseUserMapper;
import com.yami.shop.dao.OrderMapper;
import com.yami.shop.dao.UserExtensionMapper;
import com.yami.shop.platform.config.PlatformConstant;
import com.yami.shop.service.*;
import com.yami.shop.user.common.dao.UserLevelLogMapper;
import com.yami.shop.user.common.service.UserLevelLogService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toMap;
/**
 * 顾客分析接口
 * @author
 */
@Tag(name = "客户分析接口")
@RestController
@RequestMapping("/platform/customerAnalysis")
@AllArgsConstructor
public class CustomerAnalysisController {
    private final UserService userService;
    private final UserExtensionService userExtensionService;
    private final UserExtensionMapper userExtensionMapper;
    private final UserLevelLogService userLevelLogService;
    private final UserLevelLogMapper userLevelLogMapper;
    private final OrderDataAnalysisService orderDataAnalysisService;
    private final OrderMapper orderMapper;
    private final CouponUserService couponUserService;
    private final FlowPageAnalyseUserMapper flowPageAnalyseUserMapper;
    private final CustomerAnalysisService customerAnalysisService;
    private final FlowLogService flowLogService;
    private final FlowUserAnalysisService flowUserAnalysisService;
    private final FlowService flowService;
    /**
     * 客户分析,客户概况及趋势(会员概览)
     */
    @Operation(summary = "客户分析,客户概况及趋势" , description = "客户分析,客户概况及趋势")
    @GetMapping("/getCustomerAnalysis")
    public ServerResponseEntity<CustomerRespParam> getCustomerAnalysis(@ParameterObject CustomerReqParam param) {
        Date startTime = param.getStartTime();
        Date endTime = param.getEndTime();
        param.setAppId(1);
        // 访客数,统计时间类进入商城的所有访客
        Integer visitor;
        Integer preVisitor;
        if (Objects.equals(1,param.getDateType())) {
            flowLogService.getFlowLogListByCache();
            visitor = flowLogService.countAllVisitor(null,startTime,endTime);
            preVisitor = flowLogService.countAllVisitor(endTime,null,null);
        } else {
            visitor = flowPageAnalyseUserMapper.countAllVisitor(param.getEndTime(),null,null);
            preVisitor = visitor;
        }
        // 累积粉丝数 平台为商城所有的用户数;商家端为用户收藏该店铺的人数
        Integer allPersonNum = userExtensionMapper.countAllPersonNum(param.getEndTime(),null,null,null);
        Integer preAllPersonNum = userExtensionMapper.countAllPersonNum(param.getStartTime(),null,null,null);
        // 累积会员数
        CustomerRespParam respParam = userService.countUserByParam(param);
        // 成交客户数
        param.setStartTime(startTime);
        param.setEndTime(endTime);
        CustomerRespParam res = orderDataAnalysisService.countPayNum(param);
        respParam.setPayNum(res.getPayNum());
        respParam.setPrePayNum(res.getPrePayNum());
        respParam.setPayNumRate(res.getPayNumRate());
        respParam.setVisitor(visitor);
        respParam.setPreVisitor(preVisitor);
        respParam.setVisitorRate(divAverage(visitor-preVisitor,preVisitor,4));
        respParam.setMember(allPersonNum);
        respParam.setPreMember(preAllPersonNum);
        respParam.setMemberRate(divAverage(allPersonNum-preAllPersonNum,preAllPersonNum,4));
        param.setStartTime(startTime);
        param.setEndTime(endTime);
        List<CustomerPayParam> levelParams = userLevelLogMapper.countGrowthMemberByTime(param);
        respParam.setFashNum(userLevelLogMapper.countPayUserByTime(param));
        Map<Date, Integer> levelMap = levelParams.stream().collect(toMap(CustomerPayParam::getCreateDate, CustomerPayParam::getNumber));
        respParam.setCustomerTrend(orderDataAnalysisService.countCustomerParam(param, levelMap));
        if (Objects.equals(1, param.getDateType())) {
            for (CustomerPayParam customerPayParam : respParam.getCustomerTrend()) {
                customerPayParam.setVisitor(respParam.getVisitor());
            }
        }
        return ServerResponseEntity.success(respParam);
    }
    /**
     * 微信粉丝统计
     *  累积粉丝数:截至到筛选时间的最后一天,店铺的微信粉丝累积人数
     *  新增粉丝数: 筛选时间内,新关注的粉丝去重人数
     *  跑路粉丝数:筛选时间内,取消关注的粉丝去重人数
     *  净增粉丝数:筛选时间内,实际增长的粉丝人数,新增粉丝数减去跑路粉丝数
     */
    @Operation(summary = "微信粉丝统计,暂时不统计" , description = "客户分析,微信粉丝统计")
    @GetMapping("/getCustomerWxFash")
    public ServerResponseEntity<?> getCustomerWxFash(@ParameterObject CustomerReqParam param) {
        // 暂不统计,bbc小程序没有店铺关注的接口,b2c 暂不适用,
        return ServerResponseEntity.success(null);
    }
    /**
     * 会员统计 ,商城分成普通会员和付费会员,普通会员注册就是普通会员
     * 累积会员数:截至到筛选时间的最后一天,店铺的会员累积人数
     * 新增会员数: 筛选时间内,通过领取会员卡,新成为会员的客户数量
     * 升级会员数:筛选时间内,通过会员规则升级的会员数量, 一人多次升级记为一人
     * 储值会员数:筛选时间内,进行储值的会员数量,一人多次储值记为一人。我们商城没有储值设计,
     *              将储值会员数,改为付费会员数
     */
    @Operation(summary = "会员统计" , description = "客户分析,会员统计")
    @GetMapping("/getMemberCount")
    public ServerResponseEntity<CustomerMemberRespParam> getMemberCount(@ParameterObject MemberReqParam param) {
        Date startTime = param.getStartTime();
        Date endTime = param.getEndTime();
        CustomerMemberRespParam respParam = new CustomerMemberRespParam();
        // 会员统计
        CustomerMemberParam res = userLevelLogService.countMemberNum(param);
        respParam.setMemberCount(res);
        // 会员活跃
        param.setStartTime(startTime);
        param.setEndTime(endTime);
        CustomerMemberLivelyParam memberLivelyParam = getCustomerMemberLively(param);
        memberLivelyParam.setMemberVRate(divAverage(memberLivelyParam.getMemberV(),res.getAccumulate(),4));
        respParam.setMemberLively(memberLivelyParam);
        return ServerResponseEntity.success(respParam);
    }
    private CustomerMemberLivelyParam getCustomerMemberLively(CustomerReqParam param) {
        CustomerMemberLivelyParam res = new CustomerMemberLivelyParam();
        List<DateParam> everyDays = DateUtils.findEveryDays(param.getStartTime(), param.getEndTime());
        param.setDateTime(null);
        Integer num = 0;
        Integer addCartUserNum = 0;
        if(Objects.equals(1,param.getDateType())) {
            num = flowLogService.countUserNum(param.getStartTime(), param.getEndTime());
            addCartUserNum = flowLogService.countAddCartUserNum(param.getStartTime(), param.getEndTime());
        } else {
            num = userExtensionMapper.countVisitMemberNum(param.getDateTime(), param.getStartTime(), param.getEndTime());
            addCartUserNum = flowUserAnalysisService.countAddCartUserNum(param.getStartTime(), param.getEndTime());
        }
        // 访问会员数
        res.setMemberV(num);
        Integer allPersonNum = userExtensionMapper.countAllPersonNum(param.getEndTime(), null, null,null);
        res.setMemberVRate(divAverage(res.getMemberV(),allPersonNum,4));
        // 加购会员数
        res.setMemberAddCat(addCartUserNum);
        res.setMemberAddCatRate(divAverage(res.getMemberAddCat(),res.getMemberV(),4));
        // 领券会员数
        res.setMemberGetCoupon(couponUserService.countMemberGetCoupon(param));
        res.setMemberGetCouponRate(divAverage(res.getMemberGetCoupon(),res.getMemberV(),4));
        // 成交会员数
        res.setMemberPay(orderDataAnalysisService.countMemberPayNum(param));
        res.setMemberPayRate(divAverage(res.getMemberPay(),res.getMemberV(),4));
        List<CustomerMemberLivelyTrendParam> resList = new ArrayList<>();
        for (DateParam everyDay : everyDays) {
            CustomerMemberLivelyTrendParam liveParam = new CustomerMemberLivelyTrendParam();
            param.setStartTime(everyDay.getStartTime());
            param.setEndTime(everyDay.getEndTime());
            liveParam.setCurrentDay(DateUtils.dateToStrYmd(param.getEndTime()));
            // 访问会员数、加购会员数
            if(Objects.equals(1,param.getDateType())) {
                liveParam.setMemberV(num);
                liveParam.setMemberAddCat(addCartUserNum);
            } else {
                liveParam.setMemberV(userExtensionMapper.countVisitMemberNum(null,param.getStartTime(),param.getEndTime()));
                liveParam.setMemberAddCat(userExtensionMapper.countAddCartMemberNum(null,param.getStartTime(),param.getEndTime()));
            }
            // 领券会员数
            liveParam.setMemberGetCoupon(couponUserService.countMemberGetCoupon(param));
            // 成交会员数
            liveParam.setMemberPay(orderDataAnalysisService.countMemberPayNum(param));
            resList.add(liveParam);
        }
        res.setMemberLivelyTrend(resList);
        return res;
    }
    /**
     * 客户分析,成交客户分析
     */
    @Operation(summary = "客户分析,成交客户分析" , description = "客户分析,成交客户分析")
    @GetMapping("/getCustomerDeal")
    public ServerResponseEntity<CustomerDealRespParam> getCustomerDeal(@ParameterObject CustomerReqParam param) {
        CustomerDealRespParam res = orderDataAnalysisService.getCustomerDeal(param);
        return ServerResponseEntity.success(res);
    }
    /**
     * 客户分析,客户留存分析
     */
    @Operation(summary = "客户分析,客户留存分析" , description = "客户分析,客户留存分析,不做周留存数据")
    @GetMapping("/getCustomerRetained")
    public ServerResponseEntity<List<CustomerRetainRespParam>> refreshRetainedData(@ParameterObject CustomerRetainedReqParam param) {
        Integer dateType = param.getDateType();
        Integer dateRetainType = param.getDateRetainType();
        List<CustomerRetainRespParam> respParam = new ArrayList<>();
        if (Objects.equals(1,dateType) && !Objects.equals(1,dateRetainType)) {
            // 最近一月,月留存。此时不显示数据
            return ServerResponseEntity.success(respParam);
        }
        Integer retainType = param.getRetainType();
        if (Objects.equals(1,retainType)) {
            // 访问留存
            respParam = getVisitRetained(param);
        } else if (Objects.equals(PlatformConstant.TWO,retainType)) {
            // 成交留存
            respParam = getTradeRetained(param);
        }
        return ServerResponseEntity.success(respParam);
    }
    /**
     * 客户分析,客户留存分析
     */
    @Operation(summary = "客户分析,客户留存分析,刷新数据缓存" , description = "刷新数据缓存,重新拉取数据")
    @GetMapping("/refreshRetainedData")
    public ServerResponseEntity<List<CustomerRetainRespParam>> refreshRetainedData() {
        flowService.refreshRetainedData();
        return ServerResponseEntity.success();
    }
    private List<CustomerRetainRespParam> getVisitRetained(CustomerRetainedReqParam param) {
        // 访问留存,包含未注册了的用户访问留存数
        List<CustomerRetainRespParam> list = flowUserAnalysisService.getVisitRetained(param);
        list = retainedAnalysis(list, param);
        return list;
    }
    private List<CustomerRetainRespParam> getTradeRetained(CustomerRetainedReqParam param) {
        List<CustomerRetainRespParam> list = orderDataAnalysisService.getTradeRetained(param);
        list = retainedAnalysis(list, param);
        return list;
    }
    private List<CustomerRetainRespParam> retainedAnalysis(List<CustomerRetainRespParam> list,CustomerRetainedReqParam param) {
        // 不在sql中统计了,在代码中来计算留存率
        // 因为最多就统计一年的留存数据,最多12行数据
        // 保留位数
        int scale = 2;
        // 放大倍数
        Integer percentage = 100;
        int size = list.size();
        int avgNewCustomers = 0;
        int avgFirstMonthRemain = 0;
        int avgSecondMonthRemain = 0;
        int avgThirdMonthRemain = 0;
        int avgFourthMonthRemain = 0;
        int avgFifthMonthRemain = 0;
        int avgSixthMonthRemain = 0;
        // 可能有一些不存在的数据
        List<String> rangeDate = getRangeDate(param.getStartTime(), param.getEndTime());
        List<String> collect = list.stream().map(CustomerRetainRespParam::getCurrentMonth).collect(Collectors.toList());
        rangeDate.removeAll(collect);
        if (CollUtil.isNotEmpty(rangeDate)) {
            // 初始化未有新增月份的情况
            List<CustomerRetainRespParam> customerRetainRespParams = initCustomerRetain(rangeDate);
            list.addAll(customerRetainRespParams);
            list = list.stream().sorted(Comparator.comparing(CustomerRetainRespParam::getCurrentMonth)).collect(Collectors.toList());
        }
        for (CustomerRetainRespParam customerRetain : list) {
            Integer newCustomers = customerRetain.getNewCustomers();
            avgNewCustomers += newCustomers;
            Integer firstMonthRemain = customerRetain.getFirstMonthRemain();
            avgFirstMonthRemain += firstMonthRemain;
            customerRetain.setFirstMonthRemainRate(Arith.calculatePercentage(firstMonthRemain,newCustomers,scale, percentage));
            Integer secondMonthRemain = customerRetain.getSecondMonthRemain();
            avgSecondMonthRemain += secondMonthRemain;
            customerRetain.setSecondMonthRemainRate(Arith.calculatePercentage(secondMonthRemain,newCustomers,scale, percentage));
            Integer thirdMonthRemain = customerRetain.getThirdMonthRemain();
            avgThirdMonthRemain += thirdMonthRemain;
            customerRetain.setThirdMonthRemainRate(Arith.calculatePercentage(thirdMonthRemain,newCustomers,scale, percentage));
            Integer fourthMonthRemain = customerRetain.getFourthMonthRemain();
            avgFourthMonthRemain += fourthMonthRemain;
            customerRetain.setFourthMonthRemainRate(Arith.calculatePercentage(fourthMonthRemain,newCustomers,scale, percentage));
            Integer fifthMonthRemain = customerRetain.getFifthMonthRemain();
            avgFifthMonthRemain += fifthMonthRemain;
            customerRetain.setFifthMonthRemainRate(Arith.calculatePercentage(fifthMonthRemain,newCustomers,scale, percentage));
            Integer sixthMonthRemain = customerRetain.getSixthMonthRemain();
            avgSixthMonthRemain += sixthMonthRemain;
            customerRetain.setSixthMonthRemainRate(Arith.calculatePercentage(sixthMonthRemain,newCustomers,scale, percentage));
        }
        // 平均留存率
        CustomerRetainRespParam avgCustomerRetain = new CustomerRetainRespParam();
        avgNewCustomers = ceil(avgNewCustomers,size);
        avgCustomerRetain.setCurrentMonth(I18nMessage.getMessage("yami.average.retention.rate"));
        avgCustomerRetain.setNewCustomers(avgNewCustomers);
        avgCustomerRetain.setFirstMonthRemain(ceil(avgFirstMonthRemain,size));
        avgCustomerRetain.setFirstMonthRemainRate(Arith.calculatePercentage(avgCustomerRetain.getFirstMonthRemain(),avgNewCustomers,scale, percentage));
        avgCustomerRetain.setSecondMonthRemain(ceil(avgSecondMonthRemain,size));
        avgCustomerRetain.setSecondMonthRemainRate(Arith.calculatePercentage(avgCustomerRetain.getSecondMonthRemain(),avgNewCustomers,scale, percentage));
        avgCustomerRetain.setThirdMonthRemain(ceil(avgThirdMonthRemain,size));
        avgCustomerRetain.setThirdMonthRemainRate(Arith.calculatePercentage(avgCustomerRetain.getThirdMonthRemain(),avgNewCustomers,scale, percentage));
        avgCustomerRetain.setFourthMonthRemain(ceil(avgFourthMonthRemain,size));
        avgCustomerRetain.setFourthMonthRemainRate(Arith.calculatePercentage(avgCustomerRetain.getFourthMonthRemain(),avgNewCustomers,scale, percentage));
        avgCustomerRetain.setFifthMonthRemain(ceil(avgFifthMonthRemain,size));
        avgCustomerRetain.setFifthMonthRemainRate(Arith.calculatePercentage(avgCustomerRetain.getFifthMonthRemain(),avgNewCustomers,scale, percentage));
        avgCustomerRetain.setSixthMonthRemain(ceil(avgSixthMonthRemain,size));
        avgCustomerRetain.setSixthMonthRemainRate(Arith.calculatePercentage(avgCustomerRetain.getSixthMonthRemain(),avgNewCustomers,scale, percentage));
        list.add(avgCustomerRetain);
        return list;
    }
    private List<CustomerRetainRespParam> initCustomerRetain(List<String> rangeDate) {
        if (CollUtil.isEmpty(rangeDate)) {
            return Collections.emptyList();
        }
        List<CustomerRetainRespParam> list = new ArrayList<>();
        BigDecimal zero = new BigDecimal("0");
        for (String date : rangeDate) {
            CustomerRetainRespParam customerRetainRespParam = new CustomerRetainRespParam();
            customerRetainRespParam.setCurrentMonth(date);
            customerRetainRespParam.setNewCustomers(0);
            customerRetainRespParam.setFirstMonthRemain(0);
            customerRetainRespParam.setFirstMonthRemainRate(zero);
            customerRetainRespParam.setSecondMonthRemain(0);
            customerRetainRespParam.setSecondMonthRemainRate(zero);
            customerRetainRespParam.setThirdMonthRemain(0);
            customerRetainRespParam.setThirdMonthRemainRate(zero);
            customerRetainRespParam.setFourthMonthRemain(0);
            customerRetainRespParam.setFourthMonthRemainRate(zero);
            customerRetainRespParam.setFifthMonthRemain(0);
            customerRetainRespParam.setFifthMonthRemainRate(zero);
            customerRetainRespParam.setSixthMonthRemain(0);
            customerRetainRespParam.setSixthMonthRemainRate(zero);
            list.add(customerRetainRespParam);
        }
        return list;
    }
    private List<String> getRangeDate(Date startTime, Date endTime) {
        List<DateTime> dateTimes = DateUtil.rangeToList(DateUtil.beginOfMonth(startTime), endTime, DateField.MONTH);
        List<String> list = new ArrayList<>();
        for (DateTime dateTime : dateTimes) {
            list.add(DateUtil.format(dateTime,"yyyy-MM"));
        }
        // 控制月在最近 3 、6、 12个月
        int size = list.size();
        boolean isReduce = size == RetainedDateType.THREE_MONTH.getMonthNum() + 1 ||
                           size == RetainedDateType.SIX_MONTH.getMonthNum() + 1 ||
                           size == RetainedDateType.ONE_YEAR.getMonthNum() + 1;
        if (isReduce) {
            list.remove(list.get(0));
        }
        return list;
    }
    /**
     * @param a dividend
     * @param b divisor
     * @return  向上取整
     */
    private int ceil(int a, int b) {
        if (a==0 || b==0) {
            return 0;
        }
        BigDecimal dividend = new BigDecimal(Integer.toString(a));
        BigDecimal divisor = new BigDecimal(Integer.toString(b));
        BigDecimal divide = dividend.divide(divisor,0,BigDecimal.ROUND_UP);
        return divide.intValue();
    }
    // ------------------------------------------------------------------------------------------------
    // 客户洞察
    /**
     * 客户洞察,RFM模型分析
     */
    @Operation(summary = "客户洞察,RFM模型分析" , description = "客户洞察,RFM模型分析")
    @GetMapping("/getCustomerRFM")
    public ServerResponseEntity<List<CustomerRFMRespParam>> getCustomerRfm(@ParameterObject CustomerRFMReqParam param) {
        Date recentTime = param.getRecentTime();
        if (Objects.isNull(recentTime)) {
            // 请选择起止时间
            throw new YamiShopBindException("yami.form.time.check");
        }
        List<CustomerRFMRespParam> list = orderDataAnalysisService.countPayNumRfm(param);
        return ServerResponseEntity.success(list);
    }
    /**
     * 客户洞察,RFM模型分析
     * 目前设计是只统计最近两年时间的数据
     */
    @Operation(summary = "客户洞察,RFM模型分析" , description = "客户洞察,RFM模型分析,直接返回表格数据,最最后一行合计行除外")
    @GetMapping("/getCustomerRFMSecond")
    public ServerResponseEntity<List<CustomerRFMRespTableParam>> getCustomerRfmSecond(@ParameterObject CustomerRFMReqParam param) {
        Date recentTime = param.getRecentTime();
        if (Objects.isNull(recentTime)) {
            // 请选择起止时间
            throw new YamiShopBindException("yami.form.time.check");
        }
        List<CustomerRFMRespTableParam> dateList = orderDataAnalysisService.countPayNumRfm2(param);
        //最后一行的统计数据
        CustomerRFMRespTableParam statistics = new CustomerRFMRespTableParam();
        statistics.setRecencyName("列合计");
        //Map<recency(消费时间), CustomerRFMRespTableParam>
        List<CustomerRFMRespTableParam> list = new ArrayList<>();
        Map<Integer, List<CustomerRFMRespTableParam>> collect = dateList.stream().filter(a->!Objects.equals(a.getRecency(), 6)).collect(Collectors.groupingBy(CustomerRFMRespTableParam::getRecency));
        String[] recencyNameArray = {"","R<=30","30<R<=90","90<R<=180","180<R<=365","R > 365"};
        Integer userNum = 0;
        Double amount = 0.0;
        for (int i=1;i <= PlatformConstant.FIVE;i++){
            CustomerRFMRespTableParam customer = new CustomerRFMRespTableParam();
            customer.setRecencyName(recencyNameArray[i]);
            if(collect.containsKey(i)){
                List<CustomerRFMRespTableParam> customerRfmRespTableList = collect.get(i);
                for (CustomerRFMRespTableParam rfm:customerRfmRespTableList) {
                    calculation(statistics, customer, rfm);
                    userNum += rfm.getUserNum();
                    amount = Arith.add(amount,rfm.getAmount());
                }
            }
            list.add(customer);
        }
        //最后一列的数据统计
        statistics.setPayBuyersTotal(userNum);
        statistics.setPayAmountTotal(amount);
        statistics.setPriceSingle1(divAverage(statistics.getPayAmount1(),statistics.getPayBuyers1(),2));
        statistics.setPriceSingle2(divAverage(statistics.getPayAmount2(),statistics.getPayBuyers2(),2));
        statistics.setPriceSingle3(divAverage(statistics.getPayAmount3(),statistics.getPayBuyers3(),2));
        statistics.setPriceSingle4(divAverage(statistics.getPayAmount4(),statistics.getPayBuyers4(),2));
        statistics.setPriceSingle5(divAverage(statistics.getPayAmount5(),statistics.getPayBuyers5(),2));
        list.add(statistics);
        for (CustomerRFMRespTableParam tableParam : list) {
            tableParam.setPayBuyers1Rate(divAverage(tableParam.getPayBuyers1(),tableParam.getPayBuyersTotal(),4));
            tableParam.setPayBuyers2Rate(divAverage(tableParam.getPayBuyers2(),tableParam.getPayBuyersTotal(),4));
            tableParam.setPayBuyers3Rate(divAverage(tableParam.getPayBuyers3(),tableParam.getPayBuyersTotal(),4));
            tableParam.setPayBuyers4Rate(divAverage(tableParam.getPayBuyers4(),tableParam.getPayBuyersTotal(),4));
            tableParam.setPayBuyers5Rate(divAverage(tableParam.getPayBuyers5(),tableParam.getPayBuyersTotal(),4));
            tableParam.setPayBuyersTotalRate(divAverage(tableParam.getPayBuyersTotal(),userNum,4));
            tableParam.setPriceSingleTotal(divAverage(tableParam.getPayAmountTotal(),tableParam.getPayBuyersTotal(),2));
        }
        return ServerResponseEntity.success(list);
    }
    private void calculation(CustomerRFMRespTableParam statistics, CustomerRFMRespTableParam customer, CustomerRFMRespTableParam rfm) {
        Integer frequency = rfm.getFrequency();
        if (frequency == 1) {
            customer.setPayBuyers1(rfm.getUserNum());
            customer.setPayAmount1(rfm.getAmount());
            customer.setPriceSingle1(divAverage(customer.getPayAmount1(),customer.getPayBuyers1(),2));
            statistics.setPayBuyers1(statistics.getPayBuyers1() + rfm.getUserNum());
            statistics.setPayAmount1(Arith.add(statistics.getPayAmount1(), rfm.getAmount()));
        } else if (frequency == PlatformConstant.TWO) {
            customer.setPayBuyers2(rfm.getUserNum());
            customer.setPayAmount2(rfm.getAmount());
            customer.setPriceSingle2(divAverage(customer.getPayAmount2(),customer.getPayBuyers2(),2));
            statistics.setPayBuyers2(statistics.getPayBuyers2() + rfm.getUserNum());
            statistics.setPayAmount2(Arith.add(statistics.getPayAmount2(), rfm.getAmount()));
        } else if (frequency == PlatformConstant.THREE) {
            customer.setPayBuyers3(rfm.getUserNum());
            customer.setPayAmount3(rfm.getAmount());
            customer.setPriceSingle3(divAverage(customer.getPayAmount3(),customer.getPayBuyers3(),2));
            statistics.setPayBuyers3(statistics.getPayBuyers3() + rfm.getUserNum());
            statistics.setPayAmount3(Arith.add(statistics.getPayAmount3(), rfm.getAmount()));
        } else if (frequency == PlatformConstant.FOUR) {
            customer.setPayBuyers4(rfm.getUserNum());
            customer.setPayAmount4(rfm.getAmount());
            customer.setPriceSingle4(divAverage(customer.getPayAmount4(),customer.getPayBuyers4(),2));
            statistics.setPayBuyers4(statistics.getPayBuyers4() + rfm.getUserNum());
            statistics.setPayAmount4(Arith.add(statistics.getPayAmount4(), rfm.getAmount()));
        } else {
            customer.setPayBuyers5(rfm.getUserNum());
            customer.setPayAmount5(rfm.getAmount());
            customer.setPriceSingle5(divAverage(customer.getPayAmount5(),customer.getPayBuyers5(),2));
            statistics.setPayBuyers5(statistics.getPayBuyers5() + rfm.getUserNum());
            statistics.setPayAmount5(Arith.add(statistics.getPayAmount5(), rfm.getAmount()));
        }
        customer.setPayBuyersTotal(customer.getPayBuyersTotal() + rfm.getUserNum());
        customer.setPayAmountTotal(Arith.add(customer.getPayAmountTotal(), rfm.getAmount()));
    }
    /**
     * 客户洞察,消费能力分析
     */
    @Operation(summary = "客户洞察,消费能力分析" , description = "客户洞察,消费能力分析")
    @GetMapping("/getConsumePower")
    public ServerResponseEntity<CustomerConsumeRespParam> getConsumePower(@ParameterObject CustomerConsumeReqParam param) {
        CustomerConsumeRespParam  respParam = orderDataAnalysisService.getConsumePower(param);
        return ServerResponseEntity.success(respParam);
    }
    /**
     * 客户洞察,消费频次分析
     */
    @Operation(summary = "客户洞察,消费频次分析" , description = "客户洞察,消费频次分析")
    @GetMapping("/getConsumeFrequency")
    public ServerResponseEntity<CustomerConsumeRespParam> getConsumeFrequency(@ParameterObject CustomerConsumeReqParam param) {
        CustomerConsumeRespParam  respParam = orderDataAnalysisService.getConsumeFrequency(param);
        return ServerResponseEntity.success(respParam);
    }
    /**
     * 客户洞察,回购周期分析
     */
    @Operation(summary = "客户洞察,回购周期分析" , description = "客户洞察,回购周期分析")
    @GetMapping("/getConsumeRepurchaseCount")
    public ServerResponseEntity<CustomerRepurchaseRespParam> getConsumeRepurchaseCount(@ParameterObject CustomerConsumeReqParam param) {
        CustomerRepurchaseRespParam respParam = orderDataAnalysisService.getConsumeRepurchaseCount(param);
        return ServerResponseEntity.success(respParam);
    }
    // -----------------------------------------------------------------------------------------------
    // 会员分析 CustomerReqParam
    /**
     * 会员分析,会员概况
     */
    @Operation(summary = "会员分析,会员概况" , description = "会员分析,会员概况")
    @GetMapping("/getMemberSurvey")
    public ServerResponseEntity<MemberSurveyRespParam> getMemberSurvey(@ParameterObject MemberReqParam param) {
        MemberSurveyRespParam customerRespParam = customerAnalysisService.generalize(param);
//        MemberSurveyRespParam res = getMemberAnalysisSurvey(param);
        customerRespParam.setMemberOverviewModelList(getMemberAnalysisSurvey(param));
        return ServerResponseEntity.success(customerRespParam);
    }
    private List<MemberOverviewListParam> getMemberAnalysisSurvey(MemberReqParam param) {
        Date startTime = param.getStartTime();
        Date endTime = param.getEndTime();
        List<DateParam> everyDays = DateUtils.findEveryDays(startTime,endTime);
        MemberSurveyRespParam respParam = new MemberSurveyRespParam();
        MemberOverviewParam resParam = new MemberOverviewParam();
        List<MemberOverviewListParam> resList = new ArrayList<>();
        if (Objects.isNull(param.getMemberType())){
            param.setMemberType(0);
        }
        if (everyDays.size() == 1 ) {
            Date beforeDate = DateUtils.getBeforeDate(everyDays.get(0).getStartTime());
            everyDays = DateUtils.findEveryDays(beforeDate, param.getEndTime());
        }
        for (DateParam everyDay : everyDays) {
            MemberOverviewListParam res = new MemberOverviewListParam();
            // 累积会员数
            param.setDateTime(everyDay.getEndTime());
            res.setCurrentDay(DateUtils.dateToNumber(everyDay.getEndTime()));
            res.setTotalMember(userExtensionMapper.countMemberByParam(param));
            // 新增会员数
            param.setDateTime(everyDay.getStartTime());
            Integer preAccumulate = userExtensionMapper.countMemberByParam(param);
            res.setNewMember(res.getTotalMember() - preAccumulate);
            // 支付会员数,再筛选时间内,购买商品的会员人数
            param.setDateTime(null);
            param.setStartTime(everyDay.getStartTime());
            param.setEndTime(everyDay.getEndTime());
            res.setPayMember(orderMapper.countPaidMemberByParam(param,null));
            // 领券会员数
            res.setCouponMember(couponUserService.countMemberCouponByParam(param));
            // 会员支付金额
            res.setMemberPayAmount(handleDouble(orderMapper.countMemberPaidAmount(param)));
            // 会员支付订单数
            res.setMemberPayOrder(orderMapper.countMemberPayOrder(param));
            // 会员客单价
            Integer payMemberNum = orderMapper.countPayMemberNum(param);
            res.setMemberSingleAmount(divAverage(res.getMemberPayAmount(),payMemberNum,2));
            resList.add(res);
        }
        return resList;
//        respParam.setMemberOverviewModelList(resList);
//        MemberOverviewListParam first = resList.get(0);
//        MemberOverviewListParam last = resList.get(resList.size() - 1);
//        resParam.setCurrentDay(DateUtils.dateToNumber(endTime));
//        resParam.setTotalMember(last.getTotalMember());
//        resParam.setTotalMemberRate(divAverage(last.getTotalMember()-first.getTotalMember(),first.getTotalMember(),4));
//        resParam.setNewMember(last.getNewMember());
//        resParam.setNewMemberRate(divAverage(last.getNewMember(),first.getNewMember(),4));
//        //
//        param.setDateTime(endTime);
//        param.setStartTime(null);
//        resParam.setPayMember(orderMapper.countPaidMemberByParam(param));
//        // 会员支付金额
//        resParam.setMemberPayAmount(handleDouble(orderMapper.countMemberPaidAmount(param)));
//        // 领券会员数
//        resParam.setCouponMember(couponUserService.countMemberCouponByParam(param));
//        // 会员支付订单数
//        resParam.setMemberPayOrder(orderMapper.countMemberPayOrder(param));
//        // 会员客单价
//        Integer payMemberNum = orderMapper.countPayMemberNum(param);
//        resParam.setMemberSingleAmount(divAverage(resParam.getMemberPayAmount(),payMemberNum,2));
//
//        param.setDateTime(startTime);
//        Integer prePayMember = orderMapper.countPaidMemberByParam(param);
//        Double prePaidAmount = handleDouble(orderMapper.countMemberPaidAmount(param));
//        Integer preMemberCoupon = couponUserService.countMemberCouponByParam(param);
//        Integer prePayOrder = orderMapper.countMemberPayOrder(param);
//        Integer payMemberNum1 = orderMapper.countPayMemberNum(param);
//        resParam.setPayMemberRate(divAverage(resParam.getPayMember()-prePayMember,prePayMember,4));
//        resParam.setMemberPayAmountRate(divAverage(Arith.sub(resParam.getMemberPayAmount(),prePaidAmount),prePaidAmount,4));
//        resParam.setCouponMemberRate(divAverage(resParam.getCouponMember()-preMemberCoupon,preMemberCoupon,4));
//        resParam.setMemberPayOrderRate(divAverage(resParam.getMemberPayOrder()-prePayOrder,prePayOrder,4));
//        resParam.setMemberSingleAmountRate(divAverage(payMemberNum1-payMemberNum,payMemberNum,4));
//        respParam.setMemberOverviewModel(resParam);
//        return respParam;
    }
    /**
     * 会员分析,会员升级分析
     */
    @Operation(summary = "会员分析,会员升级分析" , description = "会员分析,会员升级分析")
    @GetMapping("/getGrowthMember")
    public ServerResponseEntity<List<MemberGrowthDetailParam>> getGrowthMember(@ParameterObject MemberGrowthReqParam param) {
        List<MemberGrowthDetailParam> res = userLevelLogMapper.countGrowthMemberByParam(param);
        if (CollectionUtils.isEmpty(res)) {
            return ServerResponseEntity.success(null);
        }
        Integer total = res.stream().collect(Collectors.summingInt(MemberGrowthDetailParam::getMemberNum));
        for (MemberGrowthDetailParam re : res) {
            re.setRate(divAverage(re.getMemberNum(),total,4));
        }
        return ServerResponseEntity.success(res);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 会员分析,会员人数趋势/ 会员占比趋势
     */
    @Operation(summary = "会员分析,会员人数趋势/ 会员占比趋势" , description = "会员分析,会员人数趋势/ 会员占比趋势")
    @GetMapping("/getMemberTrend")
    public ServerResponseEntity<List<MemberTrendRespParam>> getMemberTrend(@ParameterObject MemberReqParam param) {
        // 筛选时间类的每一天注册的会员数数据,不是每一天平台的总会员数
        List<MemberTrendRespParam> respParam = userExtensionService.getMemberTrend(param);
        return ServerResponseEntity.success(respParam);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 会员分析,会员贡献价值分析
     */
    @Operation(summary = "会员分析,会员贡献价值分析" , description = "会员分析,会员贡献价值分析")
    @GetMapping("/getMemberContributeValue")
    public ServerResponseEntity<MemberContributeRespParam> getMemberContributeValue(@ParameterObject MemberReqParam param) {
        MemberContributeRespParam respParam = orderDataAnalysisService.getMemberContributeValue(param);
        return ServerResponseEntity.success(respParam);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 会员分析,新老会员成交分析
     */
    @GetMapping("/getMemberDeal")
    @Operation(summary = "会员分析,新老会员成交分析" , description = "会员分析,新老会员成交分析")
    public ServerResponseEntity<MemberDealRespParam> getMemberDeal(@ParameterObject MemberReqParam param) {
        MemberDealRespParam respParam = orderDataAnalysisService.getMemberDeal(param);
        return ServerResponseEntity.success(respParam);
    }
    private Double handleDouble(Double value){
        if (Objects.isNull(value)){
            return 0.0;
        }
        return value;
    }
    private Double divAverage(Double a, Integer b, Integer scale) {
        if (Objects.isNull(b) || b == 0 || Objects.isNull(a)) {
            return 0.0;
        }
        return Arith.div(a, b, scale);
    }
    private Double divAverage(Double a, Double b, Integer scale) {
        if (Objects.isNull(b) || b == 0 || Objects.isNull(a)) {
            return 0.0;
        }
        return Arith.div(a, b, scale);
    }
    private Double divAverage(Integer a, Integer b,Integer scale) {
        if (Objects.isNull(b) || b == 0 || Objects.isNull(a)) {
            return 0.0;
        }
        return Arith.div(a,b,scale);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/DeliveryController.java
New file
@@ -0,0 +1,129 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.Delivery;
import com.yami.shop.common.enums.StatusEnum;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.DeliveryService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
/**
 * 物流公司
 *
 * @author LGH
 * @date 2019-10-29 11:42:24
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/delivery")
@Tag(name = "物流公司")
public class DeliveryController {
    private final DeliveryService deliveryService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('platform:delivery:page')")
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<Delivery>> getDeliveryPage(PageParam<Delivery> page, @ParameterObject Delivery delivery) {
        return ServerResponseEntity.success(deliveryService.page(page, new LambdaQueryWrapper<Delivery>()
                .eq(!ObjectUtils.isEmpty(delivery.getDvyId()), Delivery::getDvyId, delivery.getDvyId())
                .like(StrUtil.isNotBlank(delivery.getDvyName()), Delivery::getDvyName, delivery.getDvyName())
                .like(StrUtil.isNotBlank(delivery.getDvyNo()), Delivery::getDvyNo, delivery.getDvyNo())
                .like(StrUtil.isNotBlank(delivery.getDvyNoHd()), Delivery::getDvyNoHd, delivery.getDvyNoHd())
                .gt(Delivery::getStatus, StatusEnum.DELETE.value())
        ));
    }
    @GetMapping("/info/{dvyId}")
    @Operation(summary = "查询物流公司信息" , description = "查询物流公司信息")
    @Parameter(name = "dvyId", description = "物流id" )
    public ServerResponseEntity<Delivery> getById(@PathVariable("dvyId") Long dvyId) {
        return ServerResponseEntity.success(deliveryService.getById(dvyId));
    }
    @PostMapping
    @PreAuthorize("@pms.hasPermission('platform:delivery:save')")
    @Operation(summary = "新增物流公司" , description = "新增物流公司")
    public ServerResponseEntity<Boolean> save(@RequestBody @Valid Delivery delivery) {
        delivery.setRecTime(new Date());
        delivery.setModifyTime(new Date());
        List<Delivery> deliveryList = deliveryService.list(new LambdaQueryWrapper<Delivery>().eq(Delivery::getDvyName, delivery.getDvyName()));
        if (CollUtil.isNotEmpty(deliveryList)) {
            // 物流名称不能重复
            throw new YamiShopBindException("yami.platform.delivery.name.repeat");
        }
        return ServerResponseEntity.success(deliveryService.save(delivery));
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:delivery:update')")
    @Operation(summary = "修改物流公司信息" , description = "修改物流公司信息")
    public ServerResponseEntity<Boolean> updateById(@RequestBody @Valid Delivery delivery) {
        delivery.setModifyTime(new Date());
        delivery.setQueryUrl(null);
        List<Delivery> deliveryList = deliveryService.list(new LambdaQueryWrapper<Delivery>().eq(Delivery::getDvyName, delivery.getDvyName())
                .ne(Delivery::getDvyId, delivery.getDvyId()));
        if (CollUtil.isNotEmpty(deliveryList)) {
            // 物流名称不能重复
            throw new YamiShopBindException("yami.platform.delivery.name.repeat");
        }
        boolean updateRes = deliveryService.update(delivery, Wrappers.lambdaUpdate(Delivery.class)
                .eq(Delivery::getDvyId, delivery.getDvyId())
                .gt(Delivery::getStatus, StatusEnum.DELETE.value())
        );
        if (!updateRes) {
            throw new YamiShopBindException("修改失败,当前物流公司信息可能已经被删除");
        }
        return ServerResponseEntity.success(Boolean.TRUE);
    }
    @DeleteMapping("/{dvyId}")
    @PreAuthorize("@pms.hasPermission('platform:delivery:delete')")
    @Operation(summary = "删除物流公司" , description = "删除物流公司")
    @Parameter(name = "dvyId", description = "物流id" )
    public ServerResponseEntity<Boolean> removeById(@PathVariable Long dvyId) {
        // 逻辑删除
        deliveryService.update(Wrappers.lambdaUpdate(Delivery.class)
                .set(Delivery::getStatus, StatusEnum.DELETE.value())
                .eq(Delivery::getDvyId, dvyId)
        );
        return ServerResponseEntity.success(Boolean.TRUE);
    }
    @GetMapping("/list")
    @Operation(summary = "获取物流公司列表" , description = "获取物流公司列表")
    public ServerResponseEntity<List<Delivery>> page() {
        List<Delivery> list = deliveryService.list(Wrappers.lambdaQuery(Delivery.class)
                .eq(Delivery::getStatus, StatusEnum.ENABLE.value())
        );
        return ServerResponseEntity.success(list);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/DeviceController.java
New file
@@ -0,0 +1,186 @@
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.CdnDeviceRelease;
import com.yami.shop.bean.model.Device;
import com.yami.shop.bean.param.DeviceIncomeParam;
import com.yami.shop.bean.param.TypeParam;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.DeviceService;
import com.yami.shop.service.QRCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@RestController
@RequestMapping("/device")
@Tag(name = "设备库存接口")
public class DeviceController {
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private QRCodeService cdnService;
    /**
     * 分页查询
     * @param page 分页对象
     * @param device
     * @return 分页数据
     */
    @GetMapping("/page" )
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<Device>> getPurchaseProdPage(PageParam<Device> page, @ParameterObject Device device) {
        IPage<Device> deviceIPage = deviceService.pageDevice(page, device);
        return ServerResponseEntity.success(deviceIPage);
    }
    /**
     * 设备分类(店铺分类)
     * @return
     */
    @GetMapping("/deviceType" )
    @Operation(summary = "设备分类" , description = "设备分类")
    public ServerResponseEntity<List<TypeParam>> deviceType() {
        List<TypeParam> list = deviceService.selectdeviceTypeList();
        return ServerResponseEntity.success(list);
    }
    /**
     * 用户扫码激活设备
     * @return
     */
//    @GetMapping("/qrcodeBind" )
//    @Operation(summary = "用户扫码激活设备" , description = "用户扫码激活设备")
//    public ServerResponseEntity<?> qrcodeBind(@RequestParam("sn") String sn) {
//        return cdnService.qrcodeBind(sn);
//    }
    @Operation(summary = "导入设备文件" , description = "导入设备文件")
    @PostMapping("/exportDeviceExcel")
    @ResponseBody
    public ServerResponseEntity<String> exportDeviceExcel(@RequestParam("deviceExcelFile") MultipartFile deviceExcelFile) throws Exception {
        if (Objects.isNull(deviceExcelFile)) {
            throw new YamiShopBindException("yami.network.busy");
        }
        List<Device> list = new ArrayList<>();
        String errorMsg = deviceService.exportOrderExcel(deviceExcelFile, list);
        if (Objects.nonNull(errorMsg)) {
            return ServerResponseEntity.success(errorMsg);
        }
        return ServerResponseEntity.success("导入成功!");
    }
    @GetMapping(value = "/downloadModel")
    @Operation(summary = "下载设备导入模板" , description = "下载设备导入模板")
    public void downloadModel(HttpServletResponse response) {
        deviceService.downloadModel(response);
    }
    /**
     * 通过id查询
     * @param id
     * @return 单个数据
     */
    @GetMapping("/info/{id}" )
    @Operation(summary = "通过id查询" , description = "通过id查询")
    public ServerResponseEntity<Device> getById(@PathVariable("id") Long id) {
        Device device = deviceService.getById(id);
        if (device != null) {
            CdnDeviceRelease cdnDevice =deviceService.selectSnById(id);
            if(cdnDevice != null){
                device.setSn(cdnDevice.getSn());
            }
        }
        return ServerResponseEntity.success(device);
    }
    /**
     * 新增
     * @param device
     * @return 是否新增成功
     */
    @SysLog("新增" )
    @PostMapping
    @Operation(summary = "新增" , description = "新增")
    public ServerResponseEntity<?> save(@RequestBody  Device device) {
        deviceService.saveDevice(device);
        return ServerResponseEntity.success();
    }
    /**
     * 修改
     * @param device
     * @return 是否修改成功
     */
    @SysLog("修改" )
    @PutMapping
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<?> updateById(@RequestBody @Valid Device device) {
        device.setUpdateTime(new Date());
        return ServerResponseEntity.success(deviceService.updateById(device));
    }
    /**
     * 通过id删除
     * @param id
     * @return 是否删除成功
     */
    @SysLog("删除" )
    @DeleteMapping("/{id}" )
    @Operation(summary = "通过id删除" , description = "通过id删除")
    public ServerResponseEntity<?> removeById(@PathVariable Long id) {
        deviceService.deleteById(id);
        return ServerResponseEntity.success();
    }
    /**
     * 绑定设备
     * @param cdnDeviceRelease
     * @return
     */
    @GetMapping("/setSnDevice" )
    @Operation(summary = "绑定设备" , description = "绑定设备")
    public ServerResponseEntity<?> setSnDevice(@ParameterObject CdnDeviceRelease cdnDeviceRelease) {
        deviceService.setSnDevice(cdnDeviceRelease);
        return ServerResponseEntity.success();
    }
    /**
     * 设备实际收益
     * @return
     */
    @GetMapping("/selectSnDeviceIncome" )
    @Operation(summary = "设备实际收益" , description = "设备实际收益")
    public ServerResponseEntity<IPage<DeviceIncomeParam>> selectSnDeviceIncome(PageParam<DeviceIncomeParam> page,@ParameterObject DeviceIncomeParam param) {
        return ServerResponseEntity.success(deviceService.selectSnDeviceIncome(page,param));
    }
    /**
     * 托管设备撤销
     * @param sn
     * @return
     */
    @GetMapping("/setDeviceRepeal" )
    @Operation(summary = "SN托管设备撤销" , description = "托管设备撤销")
    public ServerResponseEntity<?> setDeviceRepeal(@ParameterObject String sn) {
        return ServerResponseEntity.success(deviceService.setDeviceRepeal(sn));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/DeviceProfitLogController.java
New file
@@ -0,0 +1,22 @@
package com.yami.shop.platform.controller;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.DeviceProfitLogService;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/deviceIncome")
@Tag(name = "设备收益日志接口")
public class DeviceProfitLogController {
    @Autowired
    private DeviceProfitLogService deviceProfitLogService;
    @PostMapping("/excute")
    public ServerResponseEntity<Integer> excute(){
        return deviceProfitLogService.excute();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FileController.java
New file
@@ -0,0 +1,155 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.AttachFile;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.config.ShopConfig;
import com.yami.shop.service.AttachFileService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * 文件上传 controller
 * @author lgh
 *
 */
@RestController
@RequestMapping("/admin/file")
@Tag(name = "文件上传")
public class FileController {
    @Autowired
    private AttachFileService attachFileService;
    @Autowired
    private ShopConfig shopConfig;
    @PostMapping("/upload/element")
    @Operation(summary = "视频上传" , description = "视频上传")
    public ServerResponseEntity<Long> uploadElementFile(@RequestParam("file") MultipartFile file) throws IOException{
        if(file.isEmpty()){
            return ServerResponseEntity.success();
        }
        AttachFile attachFile = new AttachFile();
        //视频上传
        attachFile.setType(2);
        attachFile.setShopId(Constant.PLATFORM_SHOP_ID);
        attachFile.setUploadTime(new Date());
        Long fileId = attachFileService.mpUploadFile(file.getBytes(),file.getOriginalFilename(),attachFile);
        return ServerResponseEntity.success(fileId);
    }
    /**
     * 上传聊天时的文件
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping("/upload/imFile")
    @Operation(summary = "图片上传(返回图片名)" , description = "图片上传(返回图片名)")
    public ServerResponseEntity<String> uploadImFile(@RequestParam("file") MultipartFile file) throws IOException{
        if(file.isEmpty()){
            return ServerResponseEntity.success();
        }
        AttachFile attachFile = new AttachFile();
        attachFile.setFileType(FileUtil.extName(file.getOriginalFilename()));
        attachFile.setFileName(FileUtil.mainName(file.getOriginalFilename()));
        attachFile.setType(1);
        attachFile.setShopId(Constant.PLATFORM_SHOP_ID);
        attachFile.setUploadTime(new Date());
        String fileName = attachFileService.uploadImFile(file.getBytes(),file.getOriginalFilename());
        return ServerResponseEntity.success(fileName);
    }
    @PostMapping("/upload/img")
    @Operation(summary = "图片上传(返回文件id)" , description = "图片上传(返回文件id)")
    public ServerResponseEntity<Long> uploadImg(@RequestParam("file") MultipartFile file) throws IOException{
        if(file.isEmpty()){
            return ServerResponseEntity.success();
        }
        AttachFile attachFile = new AttachFile();
        String extName = FileUtil.extName(file.getOriginalFilename());
        attachFile.setFileType(extName);
        attachFile.setFileName(FileUtil.mainName(file.getOriginalFilename()));
        attachFile.setType(1);
        attachFile.setShopId(Constant.PLATFORM_SHOP_ID);
        attachFile.setUploadTime(new Date());
        Long fileId = attachFileService.uploadImg(file.getBytes(),attachFile,extName);
        return ServerResponseEntity.success(fileId);
    }
    @PostMapping("/upload/tinymceEditor")
    @Operation(summary = "上传文件(返回文件名)" , description = "上传文件(返回文件名)")
    public ServerResponseEntity<String> uploadTinymceEditorImages(@RequestParam("editorFile") MultipartFile editorFile) throws IOException{
        String fileName =  attachFileService.uploadFile(editorFile.getBytes(),editorFile.getOriginalFilename());
        return ServerResponseEntity.success(shopConfig.getDomain().getResourcesDomainName() + "/" + fileName);
    }
    @GetMapping("/attachFilePage")
    @Operation(summary = "分页获取文件" , description = "分页获取文件")
    public ServerResponseEntity<IPage<AttachFile>> attachFilePage(PageParam<AttachFile> page, @ParameterObject AttachFile attachFile) {
        attachFile.setShopId(Constant.PLATFORM_SHOP_ID);
        IPage<AttachFile> attachFilePage = attachFileService.getPage(page,attachFile);
        return ServerResponseEntity.success(attachFilePage);
    }
    @DeleteMapping("/deleteFile/{fileId}")
    @Operation(summary = "删除文件" , description = "删除文件")
    @Parameter(name = "fileId", description = "文件id" )
    public ServerResponseEntity<Void> deleteFile(@PathVariable("fileId") Long fileId){
        AttachFile attachFile = attachFileService.getById(fileId);
        attachFileService.deleteFile(attachFile.getFilePath());
        return ServerResponseEntity.success();
    }
    @PutMapping("/updateFile")
    @Operation(summary = "更改图片名称" , description = "更改图片名称")
    public ServerResponseEntity<Boolean> updateFile(@RequestBody  AttachFile attachFile) {
        if (Objects.isNull(attachFile.getFileName())){
            // 图片名称不能为空
            throw new YamiShopBindException("yami.img.name.exist");
        }
        attachFile.setShopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success(attachFileService.updateFile(attachFile));
    }
    @DeleteMapping("/deleteByIds")
    @Operation(summary = "根据文件id列表批量删除文件记录" , description = "根据文件id列表批量删除文件记录")
    @Parameter(name = "ids", description = "文件id列表" )
    public ServerResponseEntity<Void> deleteByIds(@RequestBody List<Long> ids) {
        Long shopId = Constant.PLATFORM_SHOP_ID;
        attachFileService.deleteByIdsAndShopId(ids, shopId);
        return ServerResponseEntity.success();
    }
    @PutMapping("/batchMove")
    @Operation(summary = "根据文件id列表与分组id批量移动文件" , description = "根据文件id列表与分组id批量移动文件")
    public ServerResponseEntity<Void> batchMove(@RequestBody  AttachFile attachFile) {
        Long shopId = Constant.PLATFORM_SHOP_ID;
        attachFileService.batchMoveByShopIdAndIdsAndGroupId(shopId, attachFile);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowAnalysisController.java
New file
@@ -0,0 +1,107 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.dto.FlowAnalysisDto;
import com.yami.shop.bean.dto.SystemDto;
import com.yami.shop.bean.param.FlowAnalysisParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.FlowUserAnalysisExcelService;
import com.yami.shop.service.FlowUserAnalysisService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author lgh on 2018/11/26.
 */
@RestController
@RequestMapping("/platform/flowAnalysis")
@Tag(name = "流量概况")
public class FlowAnalysisController {
    @Autowired
    private FlowUserAnalysisService flowUserAnalysisService;
    @Autowired
    private FlowUserAnalysisExcelService flowUserAnalysisExcelService;
    @GetMapping("/getAnalysisData")
    @Operation(summary = "流量总览" , description = "流量总览")
    public ServerResponseEntity<FlowAnalysisDto> getAnalysisData(@ParameterObject FlowAnalysisParam flowAnalysisParam) {
        //获取开始和结束时间
        flowAnalysisParam.setTime(1);
        FlowAnalysisDto flowAnalysisDto = flowUserAnalysisService.getFlowAnalysisData(flowAnalysisParam);
        return ServerResponseEntity.success(flowAnalysisDto);
    }
    @GetMapping("/analysisDataExport")
    @PreAuthorize("@pms.hasPermission('flow:data:export')")
    @Operation(summary = "导出流量总览" , description = "导出流量总览")
    public void analysisDataExport(@ParameterObject FlowAnalysisParam flowAnalysisParam, HttpServletResponse response) {
        //获取开始和结束时间
        flowAnalysisParam.setTime(1);
        flowUserAnalysisExcelService.analysisDataExport(flowAnalysisParam, response);
    }
    @GetMapping("/flowTrend")
    @Operation(summary = "流量趋势" , description = "流量趋势")
    public ServerResponseEntity<List<FlowAnalysisDto>> flowTrend(@ParameterObject FlowAnalysisParam flowAnalysisParam) {
        // 获取开始和结束时间
        flowAnalysisParam.setTime(2);
        List<FlowAnalysisDto> flowAnalysisDtoList = flowUserAnalysisService.flowTrend(flowAnalysisParam);
        return ServerResponseEntity.success(flowAnalysisDtoList);
    }
    @GetMapping("/flowTrendExport")
    @PreAuthorize("@pms.hasPermission('flow:trend:export')")
    @Operation(summary = "导出流量趋势" , description = "导出流量趋势")
    public void flowTrendExport(@ParameterObject FlowAnalysisParam flowAnalysisParam, HttpServletResponse response) {
        //获取开始和结束时间
        flowAnalysisParam.setTime(2);
        flowUserAnalysisExcelService.flowTrendExport(flowAnalysisParam, response);
    }
    @GetMapping("/flowSour")
    @Operation(summary = "成交转换" , description = "成交转换")
    public ServerResponseEntity<List<FlowAnalysisDto>> flowSour(@ParameterObject FlowAnalysisParam flowAnalysisParam) {
        flowAnalysisParam.setTime(1);
        List<FlowAnalysisDto> flowAnalysisDtoList = flowUserAnalysisService.flowSour(flowAnalysisParam);
        return ServerResponseEntity.success(flowAnalysisDtoList);
    }
    @GetMapping("/flowSourExport")
    @PreAuthorize("@pms.hasPermission('flow:sour:export')")
    @Operation(summary = "导出成交转换" , description = "导出成交转换")
    public void flowSourExport(@ParameterObject FlowAnalysisParam flowAnalysisParam, HttpServletResponse response) {
        //获取开始和结束时间
        flowAnalysisParam.setTime(1);
        flowUserAnalysisExcelService.flowSour(flowAnalysisParam, response);
    }
    @GetMapping("/systemTypeNums")
    @Operation(summary = "系统访客数量" , description = "系统访客数量")
    public ServerResponseEntity<SystemDto> systemTypeNums(@ParameterObject FlowAnalysisParam flowAnalysisParam) {
        flowAnalysisParam.setTime(1);
        SystemDto systemDto = flowUserAnalysisService.systemTypeNums(flowAnalysisParam);
        return ServerResponseEntity.success(systemDto);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowCustomerAnalysisController.java
New file
@@ -0,0 +1,112 @@
package com.yami.shop.platform.controller;
import com.yami.shop.bean.dto.flow.CustomerRetainedDTO;
import com.yami.shop.bean.dto.flow.MemberReqDTO;
import com.yami.shop.bean.vo.flow.*;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.CustomerAnalysisService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * 顾客分析接口
 * @author
 */
@Tag(name = "顾客分析接口")
@RestController("flowCustomerAnalysisController")
@RequestMapping("/platform/flowCustomerAnalysis")
public class FlowCustomerAnalysisController {
    @Autowired
    private CustomerAnalysisService customerAnalysisService;
    /**
     * 会员分析,会员概况
     */
    @Operation(summary = "会员分析,会员概况" , description = "会员分析,会员概况")
    @GetMapping("/getMemberSurvey")
    public ServerResponseEntity<MemberSurveyRespVO> getMemberSurvey(@ParameterObject MemberReqDTO param) {
        MemberSurveyRespVO memberSurveyRespVO = customerAnalysisService.getMemberSurvey(param);
        return ServerResponseEntity.success(memberSurveyRespVO);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 会员分析,会员人数趋势/ 会员占比趋势
     */
    @Operation(summary = "会员分析,会员人数趋势/ 会员占比趋势" , description = "会员分析,会员人数趋势/ 会员占比趋势")
    @GetMapping("/getMemberTrend")
    public ServerResponseEntity<List<MemberTrendRespVO>> getMemberTrend(@ParameterObject MemberReqDTO param) {
        List<MemberTrendRespVO> resList = customerAnalysisService.getMemberTrend(param);
        return ServerResponseEntity.success(resList);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 导出会员分析,会员人数趋势
     */
    @Operation(summary = "导出导出会员分析,会员人数趋势/ 会员占比趋势" , description = "导出会员分析,会员人数趋势/ 会员占比趋势")
    @GetMapping("/memberTrendExport")
    @PreAuthorize("@pms.hasPermission('member:analysis:export')")
    public void memberTrendExport(@ParameterObject MemberReqDTO param, HttpServletResponse response) {
        customerAnalysisService.memberTrendExport(param, response);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 会员分析,会员贡献价值分析
     */
    @Operation(summary = "会员分析,会员贡献价值分析" , description = "会员分析,会员贡献价值分析")
    @GetMapping("/getMemberVontributeValue")
    public ServerResponseEntity<MemberContributeRespVO> getMemberContributeValue(@ParameterObject MemberReqDTO param) {
        MemberContributeRespVO contributeRespVO = customerAnalysisService.getMemberContributeValue(param);
        return ServerResponseEntity.success(contributeRespVO);
    }
    /**
     * // bbc平台/b2c商家 接口
     * 会员分析,新老会员成交分析
     */
    @GetMapping("/getMemberDeal")
    @Operation(summary = "会员分析,新老会员成交分析" , description = "会员分析,新老会员成交分析")
    public ServerResponseEntity<MemberDealRespVO> getMemberDeal(@ParameterObject MemberReqDTO param) {
        MemberDealRespVO respParam = customerAnalysisService.getMemberDeal(param);
        return ServerResponseEntity.success(respParam);
    }
    /**
     * 客户分析,客户留存分析
     */
    @Operation(summary = "客户分析-客户留存分析" , description = "客户分析,客户留存分析,不做周留存数据")
    @GetMapping("/getCustomerRetained")
    public ServerResponseEntity<List<CustomerRetainVO>> getCustomerRetained(@ParameterObject CustomerRetainedDTO customerRetainedDTO) {
        Integer dateType = customerRetainedDTO.getDateType();
        Integer dateRetainType = customerRetainedDTO.getDateRetainType();
        List<CustomerRetainVO> respList = new ArrayList<>();
        if (Objects.equals(1,dateType) && !Objects.equals(1,dateRetainType)) {
            // 最近一月,月留存。此时不显示数据
            return ServerResponseEntity.success(respList);
        }
        Integer retainType = customerRetainedDTO.getRetainType();
        respList = customerAnalysisService.getTradeRetained(customerRetainedDTO);
//        if (Objects.equals(1,retainType)) {
//            // 访问留存
//            respList = customerAnalysisService.getVisitRetained(customerRetainedDTO);
//        } else {
//            // 成交留存
//        }
        return ServerResponseEntity.success(respList);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowPageAnalysisController.java
New file
@@ -0,0 +1,43 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.PageAnalysisDto;
import com.yami.shop.bean.param.FlowAnalysisParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.FlowPageAnalysisService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author lgh on 2018/11/26.
 */
@RestController
@RequestMapping("/platform/flowPageAnalysis")
@Tag(name = "流量页面分析")
public class FlowPageAnalysisController {
    @Autowired
    private FlowPageAnalysisService flowPageAnalysisService;
    @GetMapping("/page")
    @Operation(summary = "分页获取页面统计数据" , description = "分页获取页面统计数据")
    public ServerResponseEntity<IPage<PageAnalysisDto>> page(PageParam<PageAnalysisDto> page, @ParameterObject FlowAnalysisParam flowAnalysisParam) {
        return ServerResponseEntity.success(flowPageAnalysisService.getPageOrProdAnalysis(page, flowAnalysisParam));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowRouteAnalysisController.java
New file
@@ -0,0 +1,44 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.dto.SankeyDto;
import com.yami.shop.bean.param.FlowRouteAnalysisParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.FlowRouteAnalysisService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author lgh on 2018/11/26.
 */
@RestController
@RequestMapping("/platform/flowRouteAnalysis")
@Tag(name = "用户访问路径")
public class FlowRouteAnalysisController {
    @Autowired
    private FlowRouteAnalysisService flowRouteAnalysisService;
    @GetMapping("/getRoutData")
    @Operation(summary = "分页获取用户访问路径数据" , description = "分页获取用户访问路径数据")
    public ServerResponseEntity<FlowRouteAnalysisParam> getRoutData(@ParameterObject FlowRouteAnalysisParam flowRouteAnalysisParam) {
        flowRouteAnalysisParam.setTime();
        SankeyDto sankeyDto = flowRouteAnalysisService.getRoutData(flowRouteAnalysisParam);
        flowRouteAnalysisParam.setSankeyDto(sankeyDto);
        return ServerResponseEntity.success(flowRouteAnalysisParam);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FlowUserAnalysisController.java
New file
@@ -0,0 +1,88 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.date.DateUtil;
import com.yami.shop.bean.dto.FlowAnalysisDto;
import com.yami.shop.bean.param.FlowAnalysisParam;
import com.yami.shop.bean.param.FlowUserAnalysisParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.platform.config.FlowUserAnalysisType;
import com.yami.shop.service.FlowUserAnalysisExcelService;
import com.yami.shop.service.FlowUserAnalysisService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Objects;
/**
 * @author lgh on 2018/11/26.
 */
@RestController
@RequestMapping("/platform/flowUserAnalysis")
@Tag(name = "用户数据分析")
public class FlowUserAnalysisController {
    @Autowired
    private FlowUserAnalysisService flowUserAnalysisService;
    @Autowired
    private FlowUserAnalysisExcelService flowUserAnalysisExcelService;
    @GetMapping("/getUserAnalysisData")
    @Operation(summary = "获取会员分析数据" , description = "获取会员分析数据")
    public ServerResponseEntity<FlowUserAnalysisParam> getUserAnalysisData(@ParameterObject FlowUserAnalysisParam flowUserAnalysisParam) {
        handleTime(flowUserAnalysisParam);
        flowUserAnalysisService.getUserAnalysisData(flowUserAnalysisParam);
        return ServerResponseEntity.success(flowUserAnalysisParam);
    }
    @GetMapping("/userAnalysisDataExport")
    @PreAuthorize("@pms.hasPermission('user:analysis:data:export')")
    @Operation(summary = "导出会员分析数据-地图" , description = "导出会员分析数据-地图")
    public void userAnalysisDataExport(@ParameterObject FlowUserAnalysisParam flowUserAnalysisParam, HttpServletResponse response) {
        handleTime(flowUserAnalysisParam);
        flowUserAnalysisExcelService.userAnalysisDataExport(flowUserAnalysisParam, response);
    }
    private void handleTime(FlowUserAnalysisParam flowUserAnalysisParam) {
        Integer type = flowUserAnalysisParam.getType();
        if (!Objects.equals(type,FlowUserAnalysisType.CUSTOM.value())){
            int day = 0;
            if (type == 1){
                day = -7;
            }else {
                day = -30;
            }
            Date endTime = DateUtil.beginOfDay(new Date());
            flowUserAnalysisParam.setEndTime(endTime);
            flowUserAnalysisParam.setStartTime(DateUtil.offsetDay(endTime,day));
            flowUserAnalysisParam.setStart(flowUserAnalysisParam.getStartTime().getTime());
            flowUserAnalysisParam.setEnd(DateUtil.offsetDay(endTime,-1).getTime());
        }else {
            if (Objects.isNull(flowUserAnalysisParam.getEnd()) || Objects.isNull(flowUserAnalysisParam.getStart())){
                flowUserAnalysisParam.setEndTime(DateUtil.endOfDay(new Date()));
                flowUserAnalysisParam.setStartTime(DateUtil.beginOfDay(new Date()));
            }else {
                flowUserAnalysisParam.setEndTime(DateUtil.endOfDay(new Date(flowUserAnalysisParam.getEnd())));
                flowUserAnalysisParam.setStartTime(new Date(flowUserAnalysisParam.getStart()));
            }
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/FormController.java
New file
@@ -0,0 +1,152 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
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.Form;
import com.yami.shop.bean.model.FormItem;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.FormService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * @author lgh on 2018/11/26.
 */
@RestController
@RequestMapping("/platform/form")
@Tag(name = "数据报表")
public class FormController {
    @Autowired
    private FormService formService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('platform:form:page')")
    @Operation(summary = "分页获取报表" , description = "分页获取报表")
    public ServerResponseEntity<IPage<Form>> page(@ParameterObject Form form, PageParam<Form> page) {
        IPage<Form> formPage = formService.page(page, new LambdaQueryWrapper<Form>()
                .eq(Form::getShopId,Constant.PLATFORM_SHOP_ID)
                .like(StrUtil.isNotBlank(form.getFormName()),Form::getFormName,form.getFormName())
                .eq(Objects.nonNull(form.getTimeType()),Form::getTimeType,form.getTimeType())
                .orderByAsc(Form::getSeq)
                .orderByDesc(Form::getUpdateTime)
        );
        return ServerResponseEntity.success(formPage);
    }
    @GetMapping("/info/{formId}")
    @PreAuthorize("@pms.hasPermission('platform:form:info')")
    @Operation(summary = "获取信息" , description = "获取信息")
    @Parameter(name = "formId", description = "报表id" )
    public ServerResponseEntity<Form> info(@PathVariable("formId") Long formId) {
        Form form = formService.getById(formId);
        return ServerResponseEntity.success(form);
    }
    @PostMapping
    @PreAuthorize("@pms.hasPermission('platform:form:save')")
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@RequestBody @Valid Form form) {
        Date date = new Date();
        form.setCreateTime(date);
        form.setUpdateTime(date);
        if (!form.getRecommendForm()){
            Long shopId = Constant.PLATFORM_SHOP_ID;
            form.setShopId(shopId);
        }
        formService.save(form);
        //如果是推荐报表,则清除缓存
        if (form.getRecommendForm()){
            formService.removeCache();
        }
        return ServerResponseEntity.success();
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:form:update')")
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Void> update(@RequestBody @Valid Form form) {
        if (!form.getRecommendForm()){
            Form formDb = formService.getById(form.getFormId());
            if (!Objects.equals(formDb.getShopId(),Constant.PLATFORM_SHOP_ID)){
                // 该报表数据有误,请刷新后重新输入
                throw new YamiShopBindException("yami.form.data.error");
            }
        }
        form.setUpdateTime(new Date());
        formService.saveOrUpdate(form);
        //如果是推荐报表,则清除缓存
        if (form.getRecommendForm()){
            formService.removeCache();
        }
        return ServerResponseEntity.success();
    }
    @DeleteMapping("/{formId}")
    @PreAuthorize("@pms.hasPermission('platform:form:delete')")
    @Operation(summary = "删除报表" , description = "删除报表")
    @Parameter(name = "formId", description = "报表id" )
    public ServerResponseEntity<Void> delete(@PathVariable("formId") Long formId) {
        Form formDb = formService.getById(formId);
        formService.removeById(formId);
        //如果是推荐报表,则清除缓存
        if (Objects.isNull(formDb.getShopId())){
            formService.removeCache();
        }
        return ServerResponseEntity.success();
    }
    @GetMapping("/getFormItem")
    @Operation(summary = "获取报表项列表" , description = "获取报表项列表")
    @Parameter(name = "type", description = "1:平台端  2:商家端" )
    public ServerResponseEntity<List<FormItem>> getFormItem(@RequestParam("type")Integer type) {
        List<FormItem> formItemEnumList = formService.getFormItem(type, I18nMessage.getDbLang());
        return ServerResponseEntity.success(formItemEnumList);
    }
    @GetMapping("/formExcel")
    @PreAuthorize("@pms.hasPermission('platform:form:excel')")
    @Operation(summary = "生成报表" , description = "生成报表")
    public void formExcel(@RequestParam("formId") Long formId, HttpServletResponse response) {
        formService.formExcel(formId,response);
    }
    @GetMapping("/getRecommendFormPage")
    @Operation(summary = "分页获取推荐报表" , description = "分页获取推荐报表")
    public ServerResponseEntity<IPage<Form>> getRecommendFormPage(@ParameterObject Form form, PageParam<Form> page) {
        IPage<Form> formPage = formService.getRecommendFormPage(page,form);
        return ServerResponseEntity.success(formPage);
    }
    @GetMapping("/getRecommendFormList")
    @Operation(summary = "获取推荐报表" , description = "获取推荐报表")
    public ServerResponseEntity<List<Form>> getRecommendFormList() {
        List<Form> formList = formService.getRecommendFormList();
        return ServerResponseEntity.success(formList);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/HotSearchController.java
New file
@@ -0,0 +1,99 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
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.HotSearch;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.HotSearchService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
/**
 *
 * @author lgh on 2019/03/27.
 */
@RestController
@RequestMapping("/platform/hotSearch")
@Tag(name = "热搜")
public class HotSearchController {
    @Autowired
    private HotSearchService hotSearchService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('platform:hotSearch:page')")
    @Operation(summary = "分页获取热搜" , description = "分页获取热搜")
    public ServerResponseEntity<IPage<HotSearch>> page(@ParameterObject HotSearch hotSearch, PageParam<HotSearch> page){
        IPage<HotSearch> hotSearchs = hotSearchService.page(page,new LambdaQueryWrapper<HotSearch>()
                .eq(HotSearch::getShopId, Constant.PLATFORM_SHOP_ID)
                .like(StrUtil.isNotBlank(hotSearch.getContent()), HotSearch::getContent,hotSearch.getContent())
                .like(StrUtil.isNotBlank(hotSearch.getTitle()), HotSearch::getTitle,hotSearch.getTitle())
                .eq(hotSearch.getStatus()!=null, HotSearch::getStatus,hotSearch.getStatus())
                .orderByAsc(HotSearch::getSeq)
        );
        return ServerResponseEntity.success(hotSearchs);
    }
    @GetMapping("/info/{id}")
    @PreAuthorize("@pms.hasPermission('platform:hotSearch:info')")
    @Operation(summary = "获取信息" , description = "获取信息")
    @Parameter(name = "id", description = "热搜id" )
    public ServerResponseEntity<HotSearch> info(@PathVariable("id") Long id){
        HotSearch hotSearch = hotSearchService.getById(id);
        return ServerResponseEntity.success(hotSearch);
    }
    @PostMapping
    @PreAuthorize("@pms.hasPermission('platform:hotSearch:save')")
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@RequestBody @Valid HotSearch hotSearch){
        hotSearch.setRecDate(new Date());
        hotSearch.setShopId(Constant.PLATFORM_SHOP_ID);
        hotSearchService.save(hotSearch);
        //清除缓存
        hotSearchService.removeHotSearchDtoCacheByshopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:hotSearch:update')")
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Void> update(@RequestBody @Valid HotSearch hotSearch){
        hotSearchService.updateById(hotSearch);
        //清除缓存
        hotSearchService.removeHotSearchDtoCacheByshopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success();
    }
    @DeleteMapping
    @PreAuthorize("@pms.hasPermission('platform:hotSearch:delete')")
    @Operation(summary = "删除" , description = "删除")
    @Parameter(name = "ids", description = "热搜id列表" )
    public ServerResponseEntity<Void> delete(@RequestBody List<Long> ids){
        hotSearchService.removeByIds(ids);
        //清除缓存
        hotSearchService.removeHotSearchDtoCacheByshopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/IndexImgController.java
New file
@@ -0,0 +1,126 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.AttachFileService;
import com.yami.shop.service.IndexImgService;
import com.yami.shop.service.ProductService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.Objects;
/**
 * @author lgh on 2018/11/26.
 */
@RestController
@RequestMapping("/platform/indexImg")
@Tag(name = "轮播图")
public class IndexImgController {
    @Autowired
    private IndexImgService indexImgService;
    @Autowired
    private ProductService productService;
    @Autowired
    private AttachFileService attachFileService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('platform:indexImg:page')")
    @Operation(summary = "分页获取" , description = "分页获取")
    public ServerResponseEntity<IPage<IndexImg>> page(@ParameterObject IndexImg indexImg, PageParam<IndexImg> page) {
        IPage<IndexImg> indexImgPage = indexImgService.page(page,
                new LambdaQueryWrapper<IndexImg>()
                        .eq(indexImg.getStatus() != null,IndexImg::getStatus,indexImg.getStatus())
                        .eq(IndexImg::getShopId, Constant.PLATFORM_SHOP_ID)
                        .eq(!Objects.isNull(indexImg.getImgType()),IndexImg::getImgType,indexImg.getImgType())
                        .orderByAsc(IndexImg::getSeq)
                        .orderByDesc(IndexImg::getStatus)
                        .orderByAsc(IndexImg::getImgType));
        return ServerResponseEntity.success(indexImgPage);
    }
    @GetMapping("/info/{imgId}")
    @PreAuthorize("@pms.hasPermission('platform:indexImg:info')")
    @Operation(summary = "获取信息" , description = "获取信息")
    @Parameter(name = "imgId", description = "轮播图id" )
    public ServerResponseEntity<IndexImg> info(@PathVariable("imgId") Long imgId) {
        Long shopId = Constant.PLATFORM_SHOP_ID;
        IndexImg indexImg = indexImgService.getOne(new LambdaQueryWrapper<IndexImg>().eq(IndexImg::getShopId, shopId).eq(IndexImg::getImgId, imgId));
        if (Objects.nonNull(indexImg.getRelation())) {
            Product product = productService.getProductByProdId(indexImg.getRelation(), I18nMessage.getDbLang());
            if (product !=null) {
                indexImg.setPic(product.getPic());
                indexImg.setProdName(product.getProdName());
            }
        }
        return ServerResponseEntity.success(indexImg);
    }
    @PostMapping
    @PreAuthorize("@pms.hasPermission('platform:indexImg:save')")
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@RequestBody @Valid IndexImg indexImg) {
        Long shopId = Constant.PLATFORM_SHOP_ID;
        indexImg.setShopId(shopId);
        indexImg.setUploadTime(new Date());
        int count = indexImgService.count(new LambdaQueryWrapper<IndexImg>()
                .eq(IndexImg::getImgType, indexImg.getImgType())
                .eq(IndexImg::getShopId, shopId)
        );
        if (count >= Constant.MAX_INDEX_IMG_NUM) {
            // 该平台的轮播图已达到最大数量,不能再进行新增操作
            throw new YamiShopBindException("yami.index.img.reached.limit");
        }
        indexImgService.save(indexImg);
        indexImgService.removeIndexImgCacheByShopId(shopId);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:indexImg:update')")
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Void> update(@RequestBody @Valid IndexImg indexImg) {
        indexImgService.saveOrUpdate(indexImg);
        // 移除缓存
        indexImgService.removeIndexImgCacheByShopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success();
    }
    @DeleteMapping
    @PreAuthorize("@pms.hasPermission('platform:indexImg:delete')")
    @Operation(summary = "删除" , description = "删除")
    @Parameter(name = "ids", description = "轮播图id" )
    public ServerResponseEntity<Void> delete(@RequestBody Long[] ids) {
        indexImgService.deleteIndexImgsByIds(ids, Constant.PLATFORM_SHOP_ID);
        // 移除缓存
        indexImgService.removeIndexImgCacheByShopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/MarsBlackLogController.java
New file
@@ -0,0 +1,44 @@
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.MarsBlackLog;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.MarsBlackLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author LGH
 */
@RestController
@RequestMapping("/platform/blacklog")
@Tag(name = "黑名单日志接口")
@AllArgsConstructor
public class MarsBlackLogController {
    private final MarsBlackLogService marsBlackLogService;
    @GetMapping("/page")
    @Operation(summary = "分页查询黑名单日志列表信息")
    public ServerResponseEntity<IPage<MarsBlackLog>> getMarsTranOrderPage(PageParam<MarsBlackLog> page, MarsBlackLog marsBlackLog) {
        IPage<MarsBlackLog> userInvoicePage = marsBlackLogService.getMarsTranOrderPage(page,marsBlackLog);
        return ServerResponseEntity.success(userInvoicePage);
    }
    @GetMapping("/selectUserBlack")
    @Operation(summary = "查询用户黑名单")
    public ServerResponseEntity<List<MarsBlackLog>> selectUserBlack(@Parameter(name = "page", description = "用户Id") String userId) {
        List<MarsBlackLog> list =marsBlackLogService.selectUserBlack(userId);
        return  ServerResponseEntity.success(list);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/MarsHandCardController.java
New file
@@ -0,0 +1,96 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.MarsHandCard;
import com.yami.shop.bean.vo.MarsHandCardVo;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.MarsHandCardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
 * @author LGH
 */
@RestController
@RequestMapping("/mars/handCard")
@Tag(name = "刮刮卡接口")
@AllArgsConstructor
public class MarsHandCardController {
    @Resource
    private MarsHandCardService marsHandCardService;
    @GetMapping("/page")
    @Operation(summary = "分页查询刮刮卡列表信息")
    public ServerResponseEntity<IPage<MarsHandCard>> getMarsHandCardPage(PageParam<MarsHandCard> page,MarsHandCard card) {
        IPage<MarsHandCard> userInvoicePage = marsHandCardService.getMarsHandCardPage(page,card );
        return ServerResponseEntity.success(userInvoicePage);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "通过刮刮卡id查询刮刮卡信息")
    @Parameter(name = "id", description = "刮刮卡id" , required = true)
    public ServerResponseEntity<MarsHandCard> getMarsHandCardById(@PathVariable("id") Long id) {
        MarsHandCard card = marsHandCardService.getById(id);
        return ServerResponseEntity.success(card);
    }
    @PostMapping("/getMarsHandCards")
    @Operation(summary = "获取刮刮卡信息")
    public ServerResponseEntity<List<MarsHandCard>> getMarsHandCards(@RequestBody  MarsHandCard card) {
        List<MarsHandCard> list = marsHandCardService.getMarsHandCards(card);
        return ServerResponseEntity.success(list);
    }
    @PostMapping("/saveMarsHandCard")
    @Operation(summary = "新增刮刮卡信息")
    public ServerResponseEntity<String> saveMarsHandCard(@RequestBody MarsHandCardVo card) {
        Integer num =  marsHandCardService.saveMarsHandCards(card);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
    @PostMapping("/updateMarsHandCard")
    @Operation(summary = "修改刮刮卡信息")
    public ServerResponseEntity<String> updateMarsHandCard(@RequestBody  MarsHandCard card) {
        Integer num = marsHandCardService.updateMarsHandCard(card);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
    @GetMapping("/delete/{id}")
    @Operation(summary = "通过刮刮卡id删除流水信息")
    @Parameter(name = "id", description = "寄售流水id" , required = true)
    public ServerResponseEntity<String> deleteMarsHandCardById(@PathVariable("id") Long id) {
        Integer num = marsHandCardService.deleteMarsHandCard(id);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/MarsTranOrderController.java
New file
@@ -0,0 +1,60 @@
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.MarsTranOrder;
import com.yami.shop.bean.param.MarsTranOrderParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.MarsTranOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author LGH
 */
@RestController
@RequestMapping("/platform/tranOrder")
@Tag(name = "寄售异常订单接口")
@AllArgsConstructor
public class MarsTranOrderController {
    private final MarsTranOrderService marsTranOrderService;
    @GetMapping("/page")
    @Operation(summary = "分页查询寄售异常订单列表信息")
    public ServerResponseEntity<IPage<MarsTranOrder>> getMarsTranOrderPage(PageParam<MarsTranOrder> page, MarsTranOrder marsTranOrder) {
        IPage<MarsTranOrder> userInvoicePage = marsTranOrderService.getMarsTranOrderPage(page,marsTranOrder );
        return ServerResponseEntity.success(userInvoicePage);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "通过寄售(交易明细表)id查询寄售(交易明细表)信息")
    @Parameter(name = "id", description = "寄售(交易明细表)id" , required = true)
    public ServerResponseEntity<MarsTranOrder> getMarsTranOrderById(@PathVariable("id") Long id) {
        MarsTranOrder card = marsTranOrderService.getById(id);
        return ServerResponseEntity.success(card);
    }
    @GetMapping("/settleDisputes")
    @Operation(summary = "争议订单处理")
    public ServerResponseEntity<?> settleDisputes(MarsTranOrderParam marsTranOrderParam) {
        return marsTranOrderService.settleDisputes(marsTranOrderParam);
    }
    @GetMapping("/removeBlack")
    @Operation(summary = "解除黑名单")
    public ServerResponseEntity<?> removeBlack(@Parameter(name = "page", description = "申请用户Id") String userId) {
        int num =marsTranOrderService.removeBlack(userId);
        if(num>0){
            return ServerResponseEntity.success();
        } else
        return ServerResponseEntity.showFailMsg("解除黑名单失败!");
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/NoticeController.java
New file
@@ -0,0 +1,126 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.img.gif.NeuQuant;
import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yami.shop.bean.app.dto.NoticeDto;
import com.yami.shop.bean.enums.NoticeType;
import com.yami.shop.bean.model.Notice;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.enums.StatusEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.NoticeService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * 公告管理
 *
 * @author hzm
 * @date
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/notice")
@Tag(name = "公告")
public class NoticeController {
    @Autowired
    private  NoticeService noticeService;
    @Autowired
    private MapperFacade mapperFacade;
    @GetMapping("/page")
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<NoticeDto>> getNoticePage(Page<NoticeDto> page, @ParameterObject Notice notice) {
        notice.setShopId(Constant.PLATFORM_SHOP_ID);
        notice.setAccountId(String.valueOf(Constant.PLATFORM_SHOP_ID));
        Page<NoticeDto> noticeDtoPage = noticeService.pageNotice(page, notice);
        return ServerResponseEntity.success(noticeDtoPage);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "公告信息" , description = "公告信息")
    @Parameter(name = "id", description = "公告id" )
    public ServerResponseEntity<Notice> getById(@PathVariable("id") Long id) {
        return ServerResponseEntity.success(noticeService.getInfoById(id));
    }
    @SysLog("新增公告管理")
    @PostMapping
    @PreAuthorize("@pms.hasPermission('platform:notice:save')")
    @Operation(summary = "新增公告" , description = "新增公告")
    public ServerResponseEntity<Void> save(@RequestBody @Valid Notice notice) {
        notice.setShopId(Constant.PLATFORM_SHOP_ID);
        if (notice.getStatus() == 1) {
            notice.setPublishTime(new Date());
        }
        notice.setUpdateTime(new Date());
        noticeService.save(notice);
        noticeService.removeTopNoticeListCacheByShopId(Constant.PLATFORM_SHOP_ID);
        noticeService.removeNoticeCacheById(notice.getId());
        return ServerResponseEntity.success();
    }
    @SysLog("修改公告管理")
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:notice:update')")
    @Operation(summary = "修改公告" , description = "修改公告")
    public ServerResponseEntity<Void> updateById(@RequestBody @Valid NoticeDto noticeDto) {
        Notice oldNotice = noticeService.getById(noticeDto.getId());
        Notice notice = mapperFacade.map(noticeDto, Notice.class);
        noticeDto.setShopId(Constant.PLATFORM_SHOP_ID);
        if (oldNotice.getStatus() == 0 && notice.getStatus() == 1) {
            notice.setPublishTime(new Date());
        }
        notice.setUpdateTime(new Date());
        noticeService.updateById(notice);
        noticeService.removeTopNoticeListCacheByShopId(Constant.PLATFORM_SHOP_ID);
        noticeService.removeNoticeCacheById(notice.getId());
        return ServerResponseEntity.success();
    }
    @SysLog("删除公告管理")
    @DeleteMapping("/{id}")
    @PreAuthorize("@pms.hasPermission('platform:notice:delete')")
    @Operation(summary = "删除公告" , description = "删除公告")
    @Parameter(name = "id", description = "公告id" )
    public ServerResponseEntity<Void> removeById(@PathVariable Long id) {
        noticeService.removeByIdAndShopId(id,Constant.PLATFORM_SHOP_ID);
        noticeService.removeTopNoticeListCacheByShopId(Constant.PLATFORM_SHOP_ID);
        noticeService.removeNoticeCacheById(id);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/NotifyLogController.java
New file
@@ -0,0 +1,70 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.PhoneUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.NotifyLog;
import com.yami.shop.bean.param.OrderParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.NotifyLogService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
/**
 *
 *
 * @author lhd
 * @date 2020-08-10 15:20:53
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/platform/notifyLog" )
@Tag(name = "消息记录")
public class NotifyLogController {
    @Value("${yami.expose.operation.auth:}")
    private Boolean permission;
    private final NotifyLogService notifyLogService;
    @GetMapping("/page" )
//    @PreAuthorize("@pms.hasPermission('platform:notifyLog:list')" )
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<NotifyLog>> getNotifyLogPage(PageParam<NotifyLog> page, @ParameterObject OrderParam orderParam) {
        IPage<NotifyLog> notifyLogPage = notifyLogService.pageByParam(page, orderParam);
        if (BooleanUtil.isFalse(permission)){
            for (NotifyLog record : notifyLogPage.getRecords()) {
                if (Objects.nonNull(record.getUserMobile())) {
                    record.setUserMobile(PhoneUtil.hideBetween(record.getUserMobile()).toString());
                }
            }
        }
        return ServerResponseEntity.success(notifyLogPage);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/NotifyTemplateController.java
New file
@@ -0,0 +1,143 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.collection.CollUtil;
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.enums.RemindType;
import com.yami.shop.bean.enums.SendType;
import com.yami.shop.bean.model.NotifyTemplate;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.NotifyTemplateService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.*;
/**
 *
 *
 * @author lhd
 * @date 2020-07-01 16:13:08
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/notifyTemplate")
@Tag(name = "消息模板")
public class NotifyTemplateController {
    private final NotifyTemplateService notifyTemplateService;
    @GetMapping("/page" )
    @PreAuthorize("@pms.hasPermission('platform:notifyTemplate:page')" )
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<NotifyTemplate>> getNotifyTemplatePage(PageParam<NotifyTemplate> page, @ParameterObject NotifyTemplate notifyTemplate) {
        IPage<NotifyTemplate> templatePage = notifyTemplateService.page(page, new LambdaQueryWrapper<NotifyTemplate>()
                .eq(Objects.nonNull(notifyTemplate.getSendType()),NotifyTemplate::getSendType,notifyTemplate.getSendType())
                .eq(Objects.nonNull(notifyTemplate.getMsgType()), NotifyTemplate::getMsgType, notifyTemplate.getMsgType())
                .ne(NotifyTemplate::getSendType, SendType.CUSTOMIZE.getValue())
                .orderByDesc(NotifyTemplate::getStatus).orderByDesc(NotifyTemplate::getCreateTime));
        if(CollectionUtils.isEmpty(templatePage.getRecords())){
            return ServerResponseEntity.success();
        }
        for (NotifyTemplate  template: templatePage.getRecords()) {
            List<Integer> templateList = getTemplateList(template.getTemplateTypes());
            for (Integer type : templateList) {
                template.setSms(Objects.equals(type,RemindType.SMS.value()) || template.getSms());
                template.setSub(Objects.equals(type,RemindType.MP.value()) || template.getSub());
                template.setApp(Objects.equals(type,RemindType.MINI.value()) || template.getApp());
            }
            template.setTemplateTypeList(templateList);
        }
        return ServerResponseEntity.success(templatePage);
    }
    private List<Integer> getTemplateList(String templateTypes) {
        String[] templateTypeList = templateTypes.split(StrUtil.COMMA);
        List<Integer> templates = new ArrayList<>();
        for (String templateStr : templateTypeList) {
            if (StrUtil.isBlank(templateStr)) {
                continue;
            }
            templates.add(Integer.valueOf(templateStr));
        }
        return templates;
    }
    @GetMapping("/info/{templateId}" )
    @Operation(summary = "查询模板信息" , description = "查询模板信息")
    @Parameter(name = "templateId", description = "模板id" )
    public ServerResponseEntity<NotifyTemplate> getById(@PathVariable("templateId") Long templateId) {
        NotifyTemplate template = notifyTemplateService.getById(templateId);
        template.setTemplateTypeList(getTemplateList(template.getTemplateTypes()));
        return ServerResponseEntity.success(template);
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:notifyTemplate:update')" )
    @Operation(summary = "修改模板" , description = "修改模板")
    public ServerResponseEntity<Boolean> updateById(@RequestBody @Valid NotifyTemplate notifyTemplate) {
        List<NotifyTemplate> notifyTemplates = notifyTemplateService.list(new LambdaQueryWrapper<NotifyTemplate>()
                .eq(NotifyTemplate::getSendType, notifyTemplate.getSendType()).ne(NotifyTemplate::getTemplateId,notifyTemplate.getTemplateId()));
        if(CollectionUtils.isNotEmpty(notifyTemplates)){
            // 已经存在当前消息类型的短信,请去进行修改操作
            throw new YamiShopBindException("yami.select.notify.type.check");
        }
        if (CollUtil.isNotEmpty(notifyTemplate.getTemplateTypeList())) {
            notifyTemplate.setTemplateTypes(arrayChangeList(notifyTemplate.getTemplateTypeList()));
        }
        notifyTemplate.setMsgType(null);
        notifyTemplate.setSendType(null);
        return ServerResponseEntity.success(notifyTemplateService.updateById(notifyTemplate));
    }
    private String getRepeatValue(List<Integer> templateTypeList,List<Integer> shopTemplateTypeList){
        ArrayList<Integer> repeatValue = new ArrayList<>();
        for (Integer integer : templateTypeList) {
            if (shopTemplateTypeList.contains(integer)){
                repeatValue.add(integer);
            }
        }
        String s = repeatValue.toString().replace(" ","");
        return s.substring(1,s.length()-1);
    }
    private String arrayChangeList(List<Integer> templateTypeList) {
        StringBuilder templateTypes = new StringBuilder();
        for (Integer templateType : templateTypeList) {
            templateTypes.append(templateType);
            templateTypes.append(StrUtil.COMMA);
        }
        templateTypes.deleteCharAt(templateTypes.length() - 1);
        return templateTypes.toString();
    }
    @DeleteMapping("/{templateId}" )
    @PreAuthorize("@pms.hasPermission('platform:notifyTemplate:updateSts')" )
    @Operation(summary = "状态变更" , description = "状态变更")
    @Parameter(name = "templateId", description = "模板id" )
    public ServerResponseEntity<Boolean> removeById(@PathVariable Long templateId) {
        NotifyTemplate template = notifyTemplateService.getById(templateId);
        template.setStatus(template.getStatus() == 1? 0:1);
        return ServerResponseEntity.success(notifyTemplateService.updateById(template));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderController.java
New file
@@ -0,0 +1,107 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
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.OrderParam;
import com.yami.shop.bean.param.OrderPayParam;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
/**
 * @author lgh on 2018/09/15.
 */
@RestController
@RequestMapping("/platform/order")
@AllArgsConstructor
@Tag(name = "订单")
public class OrderController {
    private final OrderService orderService;
    private final OrderItemService orderItemService;
    private final UserAddrOrderService userAddrOrderService;
    private final StatisticsOrderService statisticsOrderService;
    private final OrderExcelService orderExcelService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('platform:order:page')")
    @Operation(summary = "分页获取" , description = "分页获取")
    public ServerResponseEntity<IPage<Order>> page(@ParameterObject OrderParam orderParam, PageParam<Order> page) {
        orderParam.setLang(I18nMessage.getDbLang());
        IPage<Order> orderPage = orderService.pageOrdersDetailByOrderParam(page, orderParam);
        return ServerResponseEntity.success(orderPage);
    }
    @GetMapping("/orderPayByShopId")
    @Operation(summary = "根据商家id获取支付信息" , description = "根据商家id获取支付信息")
    @Parameters(value = {
            @Parameter(name = "startTime", description = "开始时间" ),
            @Parameter(name = "endTime", description = "结束时间" )
    })
    public ServerResponseEntity<OrderPayParam> orderPayByShopId(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@RequestParam("startTime") Date startTime,
                                                          @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@RequestParam("endTime") Date endTime) {
        OrderPayParam actualTotal = statisticsOrderService.getPayUserCountByshopId(null,startTime,endTime);
        return ServerResponseEntity.success(actualTotal);
    }
    @GetMapping("/orderInfo/{orderNumber}")
    @PreAuthorize("@pms.hasPermission('platform:order:info')")
    @Operation(summary = "获取信息" , description = "获取信息")
    @Parameter(name = "orderNumber", description = "订单编号" )
    public ServerResponseEntity<Order> info(@PathVariable("orderNumber") String orderNumber) {
        Order order = orderService.getOne(new LambdaUpdateWrapper<Order>().eq(Order::getOrderNumber, orderNumber));
        if (order == null) {
            // 未找到所在的订单
            throw new YamiShopBindException("yami.order.no.exist");
        }
        UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId());
        order.setUserAddrOrder(userAddrOrder);
        List<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber, I18nMessage.getDbLang());
        order.setOrderItems(orderItems);
        return ServerResponseEntity.success(order);
    }
    @GetMapping("/soldExcel")
    @PreAuthorize("@pms.hasPermission('platform:order:exportExcel')")
    @Operation(summary = "导出已销售订单" , description = "导出已销售订单")
    public void soldExcel(@ParameterObject OrderParam orderParam, HttpServletResponse response) {
        orderExcelService.soldExcel(orderParam,response);
    }
    @GetMapping("/getOrderByUserId")
    @Operation(summary = "分页获取用户订单列表" , description = "分页获取用户订单列表")
    @Parameter(name = "userId", description = "用户id" )
    public ServerResponseEntity<IPage<Order>> getOrderByUserId(PageParam<Order> page, String userId){
        IPage<Order> pages = orderService.pageByUserId(page,userId);
        return ServerResponseEntity.success(pages);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderItemController.java
New file
@@ -0,0 +1,46 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.vo.OrderDetailVO;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.OrderItemService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author Pineapple
 * @date 2021/6/9 9:19
 */
@RestController("platformOrderItemController")
@RequestMapping("/platform/order_item")
@Tag(name = "platform-订单项信息")
public class OrderItemController {
    @Autowired
    private OrderItemService orderItemService;
    @GetMapping("/get_order_detail")
    @Operation(summary = "查询订单项、退款详情" , description = "根据id查询")
    @Parameters(value = {
            @Parameter(name = "orderNumber", description = "订单编号" ),
            @Parameter(name = "refundSn", description = "退款编号" )
    })
    public ServerResponseEntity<OrderDetailVO> getOrderItemDetail(String orderNumber, String refundSn){
        OrderDetailVO orderDetailVO = orderItemService.listDetailByParam(orderNumber, refundSn);
        return ServerResponseEntity.success(orderDetailVO);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderRefundController.java
New file
@@ -0,0 +1,68 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.OrderRefundDto;
import com.yami.shop.bean.model.OrderRefund;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.OrderRefundService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author yami
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/orderRefund")
@Tag(name = "订单退款")
public class OrderRefundController {
    private final OrderRefundService orderRefundService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('platform:orderRefund:page')")
    @Operation(summary = "分页查询" , description = "分页查询")
    @Parameters(value = {
            @Parameter(name = "startTime", description = "开始时间" ),
            @Parameter(name = "endTime", description = "结束时间" )
    })
    public ServerResponseEntity<IPage<OrderRefundDto>> getOrderRefundPage(PageParam<OrderRefundDto> page, @ParameterObject OrderRefundDto orderRefundDto,
                                                                    @RequestParam(required = false) String startTime,
                                                                    @RequestParam(required = false) String endTime) {
        IPage<OrderRefundDto> resp = orderRefundService.getPage(page, orderRefundDto, startTime, endTime, 1);
        return ServerResponseEntity.success(resp);
    }
    @GetMapping("/info")
    @PreAuthorize("@pms.hasPermission('platform:orderRefund:info')")
    @Operation(summary = "获取订单退款信息" , description = "获取订单退款信息")
    @Parameters(value = {
            @Parameter(name = "refundId", description = "退款id" ),
            @Parameter(name = "shopId", description = "店铺id" )
    })
    public ServerResponseEntity<OrderRefund> info(@RequestParam("refundId") Long refundId, @RequestParam("shopId") Long shopId) {
        OrderRefund orderRefund = orderRefundService.getOrderRefundById(refundId, shopId);
        return ServerResponseEntity.success(orderRefund);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/PlatformNotifyController.java
New file
@@ -0,0 +1,98 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.enums.SendType;
import com.yami.shop.bean.model.NotifyTemplate;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.NotifyTemplateService;
import com.yami.shop.user.common.service.UserTagService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
/**
 *
 *
 * @author lhd
 * @date 2020-07-01 16:13:08
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/sendTagNotify")
@Tag(name = "平台消息模板")
public class PlatformNotifyController {
    private final NotifyTemplateService notifyTemplateService;
    private final UserTagService userTagService;
    @GetMapping("/page" )
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<NotifyTemplate>> getNotifyTemplatePage(PageParam<NotifyTemplate> page, @ParameterObject NotifyTemplate notifyTemplate) {
        IPage<NotifyTemplate> templatePage = notifyTemplateService.pageTagNotify(page);
        return ServerResponseEntity.success(templatePage);
    }
    @GetMapping("/info/{templateId}" )
    @Operation(summary = "获取模板信息" , description = "获取模板信息")
    @Parameter(name = "templateId", description = "模板id" )
    public ServerResponseEntity<NotifyTemplate> getById(@PathVariable("templateId") Long templateId) {
        NotifyTemplate template = notifyTemplateService.getInfoById(templateId);
        return ServerResponseEntity.success(template);
    }
    @SysLog("新增" )
    @PostMapping
    @PreAuthorize("@pms.hasPermission('platform:notifyTemplate:save')" )
    @Operation(summary = "新增" , description = "新增")
    public ServerResponseEntity<Boolean> save(@RequestBody @Valid NotifyTemplate notifyTemplate) {
        if(CollectionUtils.isEmpty(notifyTemplate.getSelTagIds())){
            throw new YamiShopBindException("yami.notify.tag.msg");
        }
        notifyTemplate.setCreateTime(new Date());
        notifyTemplate.setStatus(1);
        notifyTemplate.setSendType(SendType.CUSTOMIZE.getValue());
        // 站内消息1
        notifyTemplate.setTemplateTypes("3");
        notifyTemplateService.saveTagNotify(notifyTemplate);
        return ServerResponseEntity.success();
    }
    @SysLog("修改" )
    @PutMapping
    @PreAuthorize("@pms.hasPermission('platform:notifyTemplate:update')" )
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Boolean> updateById(@RequestBody @Valid NotifyTemplate notifyTemplate) {
        notifyTemplateService.updateInfoById(notifyTemplate);
        return ServerResponseEntity.success(true);
    }
    @DeleteMapping("/{templateId}" )
    @Operation(summary = "删除消息模板" , description = "删除消息模板")
    @Parameter(name = "templateId", description = "模板id" )
    public ServerResponseEntity<Boolean> deleteUserTag(@PathVariable Long templateId) {
        notifyTemplateService.deleteTemplateInfoById(templateId);
        return ServerResponseEntity.success(true);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ProdAnalysisController.java
New file
@@ -0,0 +1,159 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.*;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.coupon.common.service.CouponUserService;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * 商品分析接口
 * @author
 */
@Tag(name = "商品分析接口")
@RestController
@RequestMapping("/platform/prodAnalysis")
@AllArgsConstructor
public class ProdAnalysisController {
    private final ProductService productService;
    private final ProductExcelService productExcelService;
    private final CouponUserService couponUserService;
    private final FlowProductAnalysisService flowProductAnalysisService;
    /**
     * 获取商品概况
     */
    @GetMapping("/getProdSurvey")
    @Operation(summary = "获取商品概况" , description = "获取商品概况")
    public ServerResponseEntity<ProdAnalysisParam> getProdSurvey(@ParameterObject ProdAnalysisSurveyParam param) {
        ProdAnalysisParam analysis = flowProductAnalysisService.getProdSurvey(param);
        return ServerResponseEntity.success(analysis);
    }
    /**
     * 导出商品概况
     */
    @GetMapping("/prodSurveyExport")
    @Operation(summary = "导出商品概况" , description = "导出商品概况")
    public void prodSurveyExport(HttpServletResponse response, @ParameterObject ProdAnalysisSurveyParam param) {
        flowProductAnalysisService.prodSurveyExport(response, param);
    }
    /**
     * 获取商品趋势分析
     */
    @GetMapping("/getProdTrendAnalysis")
    @Operation(summary = "获取商品趋势分析" , description = "获取商品趋势分析")
    public ServerResponseEntity<List<ProdAnalysisDataParam>> getProdTrendAnalysis(@ParameterObject ProdAnalysisSurveyParam param) {
        List<ProdAnalysisDataParam> trendAnalysisa = productService.getProdTrendAnalysis(param);
        return ServerResponseEntity.success(trendAnalysisa);
    }
    /**
     * 支付金额TOP
     * 访客数TOP
     */
    @GetMapping("/getPayAmountTop")
    @Operation(summary = "支付金额TOP、访客数TOP" , description = "支付金额TOP、访客数TOP")
    public ServerResponseEntity<VisitorAndPayTopParam> getPayAmountTop(PageParam<OrderItem> page, @ParameterObject ProdAnalysisSurveyParam param) {
        param.setShopId(null);
        VisitorAndPayTopParam visitorAndPayTopParam = flowProductAnalysisService.getPayAmountTop(page, param);
        return ServerResponseEntity.success(visitorAndPayTopParam);
    }
    /**
     * 导出支付金额TOP
     * 导出访客数TOP
     */
    @GetMapping("/payAmountTopExport")
    @PreAuthorize("@pms.hasPermission('flow:pay:visit:top')")
    @Operation(summary = "导出支付金额TOP、访客数TOP" , description = "导出支付金额TOP、访客数TOP")
    public void payAmountTopExport(HttpServletResponse response, @ParameterObject ProdAnalysisSurveyParam param) {
        param.setShopId(null);
        flowProductAnalysisService.payAmountTopExport(response, param);
    }
    /**
     * 获取商品效果分析数据
     */
    @GetMapping("/getProdEffect")
    @Operation(summary = "获取商品效果分析数据" , description = "获取商品效果分析数据")
    public ServerResponseEntity<IPage<ProdEffectRespParam>> getProdEffect(PageParam<Product> page, @ParameterObject ProdEffectParam param) {
        if (Objects.equals(1,param.getGroup())) {
            IPage<ProdEffectRespParam> map = new PageParam<>();
            map.setCurrent(page.getCurrent());
            map.setSize(page.getSize());
            map.setPages(page.getPages());
            map.setRecords(new ArrayList<>());
            return ServerResponseEntity.success(map);
        }
        IPage<ProdEffectRespParam> resPage = productService.pageProdEffect(page,param,I18nMessage.getDbLang(), false);
        return ServerResponseEntity.success(resPage);
    }
    /**
     * 导出商品洞察数据
     */
    @GetMapping("/prodEffectExport")
    @PreAuthorize("@pms.hasPermission('flow:prod:effect')")
    @Operation(summary = "导出商品洞察数据" , description = "导出商品洞察数据")
    public void prodEffectExport(HttpServletResponse response, @ParameterObject ProdEffectParam param) {
        productExcelService.prodEffectExport(response,param,I18nMessage.getDbLang());
    }
    /**
     * 单个商品的趋势分析
     */
    @GetMapping("/getSingleProdTrend")
    @Operation(summary = "获取商品效果分析数据" , description = "获取商品效果分析数据")
    public ServerResponseEntity<List<ProdSingleTrendParam>> getSingleProdTrend(Long prodId, @ParameterObject ProdEffectParam param) {
        List<ProdSingleTrendParam> resList = productService.getSingleProdTrend(prodId,param);
        return ServerResponseEntity.success(resList);
    }
    /**
     * 卡券分析,卡券昨日关键指标
     */
    @Operation(summary = "卡券分析,卡券昨日关键指标" , description = "卡券分析,卡券昨日关键指标")
    @GetMapping("/getCouponAnalysis")
    public ServerResponseEntity<List<CouponAnalysisParam>> getCouponAnalysis(@ParameterObject ProdEffectParam param) {
        // 统计近7天的数据
        List<CouponAnalysisParam> resList = couponUserService.getCouponAnalysis(param);
        return ServerResponseEntity.success(resList);
    }
    @GetMapping("/getCouponAnalysisParamByDate")
    @Operation(summary = "根据日期获得优惠券详情信息" , description = "根据日期获得优惠券详情信息")
    public ServerResponseEntity<IPage<CouponAnalysisParam>> getCouponAnalysisParamByDate(PageParam<CouponAnalysisParam> page, @ParameterObject ProdEffectParam param){
        return ServerResponseEntity.success(couponUserService.getCouponAnalysisParamByDate(page,param));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ProductController.java
New file
@@ -0,0 +1,277 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.enums.*;
import com.yami.shop.bean.event.*;
import com.yami.shop.bean.model.OfflineHandleEvent;
import com.yami.shop.bean.model.ProdParameter;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.model.Sku;
import com.yami.shop.bean.param.NotifyTemplateParam;
import com.yami.shop.bean.param.OfflineHandleEventAuditParam;
import com.yami.shop.bean.param.ProductExportParam;
import com.yami.shop.bean.param.ProductParam;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.delivery.common.model.SameCity;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 商品列表、商品发布controller
 *
 * @author lgh
 */
@RestController
@RequestMapping("/prod/prod")
@AllArgsConstructor
@Tag(name = "商品")
public class ProductController {
    private final SkuService skuService;
    private final BasketService basketService;
    private final ProductService productService;
    private final ProductExcelService productExcelService;
    private final ApplicationEventPublisher eventPublisher;
    private final ApplicationContext applicationContext;
    private final OfflineHandleEventService offlineHandleEventService;
    private final SupplierProdService supplierProdService;
    private final ProdParameterService prodParameterService;
    private final SeckillService seckillService;
    @GetMapping("/seckills")
    @Operation(summary = "分页获取秒杀商品信息")
    public ServerResponseEntity<IPage<Product>> seckills(@ParameterObject ProductParam product, PageParam<Product> page){
        product.setLang(I18nMessage.getLang());
        IPage<Product> productIPage = seckillService.pageSeckillNormalProd(page, product);
        return ServerResponseEntity.success(productIPage);
    }
    /**
     * 处理下活动商品的价格
     *
     * @param product
     * @param products
     */
    private void processActivityProdPrice(ProductParam product, List<Product> products) {
        Map<Integer, List<Product>> prodMap = products.stream().collect(Collectors.groupingBy(Product::getProdType));
        if (prodMap.containsKey(ProdType.PROD_TYPE_SECKILL.value())) {
            applicationContext.publishEvent(new ProcessActivityProdPriceEvent(product, prodMap.get(ProdType.PROD_TYPE_SECKILL.value())));
        }
        if (prodMap.containsKey(ProdType.PROD_TYPE_GROUP.value())) {
            applicationContext.publishEvent(new ProcessActivityProdPriceEvent(product, prodMap.get(ProdType.PROD_TYPE_GROUP.value())));
        }
    }
    @GetMapping("/info/{prodId}")
    @PreAuthorize("@pms.hasPermission('prod:prod:info')")
    @Operation(summary = "获取商品信息" , description = "获取商品信息")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<Product> info(@PathVariable("prodId") Long prodId) {
        Product prod = productService.getProductById(prodId);
        List<Sku> skuList = skuService.listSkuAndSkuStock(prodId, I18nMessage.getDbLang());
        prod.setSkuList(skuList);
//        // 获取语言列表
//        List<ProdLang> prodLangList = prodLangService.list(new LambdaQueryWrapper<ProdLang>().eq(ProdLang::getProdId, prodId));
//        prod.setProdLangList(prodLangList);
        List<ProdParameter> prodParameters = prodParameterService.listParameter(prodId, I18nMessage.getDbLang());
        prod.setProdParameterList(prodParameters);
        return ServerResponseEntity.success(prod);
    }
    @DeleteMapping("/{prodId}")
    @PreAuthorize("@pms.hasPermission('prod:prod:update')")
    @Operation(summary = "平台删除商品" , description = "平台删除商品")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<Void> delete(@PathVariable("prodId") Long prodId) {
        this.checkBeforeDeleteProduct(prodId);
        Product dbProduct = productService.getProductByProdId(prodId, I18nMessage.getDbLang());
        List<Sku> dbSkus = skuService.listByProdId(dbProduct.getProdId(), I18nMessage.getDbLang());
        List<Long> supplierIds = supplierProdService.listSupplierIdByProdId(prodId);
        // 没用到,又不知道干嘛的,先注释掉
//        List<Long> takeStockIds = takeStockProdService.listInventoryByProdId(prodId);
        // 删除商品
        productService.removeProductByProdId(prodId);
        //清除供应商商品列表缓存
        if (CollectionUtils.isNotEmpty(supplierIds)) {
            for (Long supplierId : supplierIds) {
                supplierProdService.removeCacheBySupplierId(supplierId);
            }
        }
        // 商品状态改变时的发送事件,让活动下线
        applicationContext.publishEvent(new ProdChangeStatusEvent(dbProduct, ProdStatusEnums.DELETE.getValue()));
        //清除商品缓存
        productService.removeProdCacheByProdId(prodId);
        //清除sku缓存
        for (Sku sku : dbSkus) {
            skuService.removeSkuCacheBySkuId(sku.getSkuId(), sku.getProdId());
        }
        List<String> userIds = basketService.listUserIdByProdId(prodId);
        //清除购物车缓存
        basketService.removeCacheByUserIds(userIds);
        // 删除商品时,改变分销设置,团购订单处理。。。
        applicationContext.publishEvent(new ProdChangeEvent(dbProduct));
        eventPublisher.publishEvent(new EsProductUpdateEvent(prodId, null, EsOperationType.DELETE));
        return ServerResponseEntity.success();
    }
    @GetMapping("/listProdByIdsAndType")
    @Operation(summary = "获取商品信息列表" , description = "获取商品信息列表")
    public ServerResponseEntity<List<Product>> listProdByIdsAndType(@ParameterObject ProductParam product) {
        product.setLang(I18nMessage.getDbLang());
        List<Product> products = productService.listProdByIdsAndType(product);
        processActivityProdPrice(product, products);
        return ServerResponseEntity.success(products);
    }
    @PutMapping("/waterSoldNum")
    @Operation(summary = "更新注水销量" , description = "更新注水销量")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<Void> updateWaterSoldNum(Integer waterSoldNum, Long prodId) {
        if (waterSoldNum == null) {
            waterSoldNum = 0;
        }
        productService.updateWaterSaleNum(waterSoldNum, prodId);
        eventPublisher.publishEvent(new EsProductUpdateEvent(prodId, null, EsOperationType.UPDATE_SOLD_NUM));
        return ServerResponseEntity.success();
    }
    @PostMapping("/offline")
    @PreAuthorize("@pms.hasPermission('prod:prod:update')")
    @Operation(summary = "下线商品" , description = "下线商品")
    public ServerResponseEntity<Void> offline(@RequestBody OfflineHandleEvent offlineHandleEvent) {
        Product dbProduct = productService.getProductByProdId(offlineHandleEvent.getHandleId(), I18nMessage.getDbLang());
        if (dbProduct == null) {
            // 未找到刚商品的信息
            throw new YamiShopBindException("yami.product.not.exist");
        }
        Long sysUserId = SecurityUtils.getSysUser().getUserId();
        productService.offline(offlineHandleEvent.getHandleId(), offlineHandleEvent.getOfflineReason(), sysUserId);
        // 商品状态改变时的发送事件,让活动下线
        applicationContext.publishEvent(new ProdChangeStatusEvent(dbProduct, ProdStatusEnums.PLATFORM_OFFLINE.getValue()));
        List<String> userIds = basketService.listUserIdByProdId(dbProduct.getProdId());
        //清除购物车缓存
        basketService.removeCacheByUserIds(userIds);
        // 移除缓存
        productService.removeProdCacheByProdId(dbProduct.getProdId());
        eventPublisher.publishEvent(new EsProductUpdateEvent(dbProduct.getProdId(), null, EsOperationType.UPDATE));
        //发送商品下架提醒给商家
        NotifyTemplateParam shopParam = new NotifyTemplateParam();
        shopParam.setShopId(dbProduct.getShopId());
        shopParam.setProdId(offlineHandleEvent.getHandleId());
        shopParam.setProdName(dbProduct.getProdName());
        shopParam.setSendType(SendType.PRODUCT_OFFLINE.getValue());
        applicationContext.publishEvent(new SendMessageEvent(shopParam));
        return ServerResponseEntity.success();
    }
    @GetMapping("/getOfflineHandleEventByProdId/{prodId}")
    @Operation(summary = "获取最新下线商品的事件" , description = "获取最新下线商品的事件")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<OfflineHandleEvent> getOfflineHandleEventByProdId(@PathVariable Long prodId) {
        OfflineHandleEvent offlineHandleEvent = offlineHandleEventService.getProcessingEventByHandleTypeAndHandleId(OfflineHandleEventType.PROD.getValue(), prodId);
        return ServerResponseEntity.success(offlineHandleEvent);
    }
    @PostMapping("/prodOfflineAudit")
    @Operation(summary = "审核违规下架的商品" , description = "审核违规下架的商品")
    public ServerResponseEntity<Void> prodOfflineAudit(@RequestBody OfflineHandleEventAuditParam offlineHandleEventAuditParam) {
        Long userId = SecurityUtils.getSysUser().getUserId();
        productService.prodAudit(offlineHandleEventAuditParam, userId);
        // 移除缓存
        productService.removeProdCacheByProdId(offlineHandleEventAuditParam.getHandleId());
        eventPublisher.publishEvent(new EsProductUpdateEvent(offlineHandleEventAuditParam.getHandleId(), null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
    @PostMapping("/auditProd")
    @Operation(summary = "审核待审核的商品" , description = "商品审核开关打开后,新发布的或要上架的商品处于的待审核状态")
    public ServerResponseEntity<Void> auditProd(@RequestBody OfflineHandleEvent offlineHandleEvent) {
        Long prodId = offlineHandleEvent.getHandleId();
        Product dbProduct = productService.getProductByProdId(prodId, I18nMessage.getDbLang());
        if (Objects.isNull(dbProduct)) {
            // 未找到刚商品的信息
            throw new YamiShopBindException("yami.product.not.exist");
        }
        if (!Objects.equals(dbProduct.getStatus(), ProdStatusEnums.AUDIT.getValue())) {
            // 商品状态已改变,请刷新页面
            throw new YamiShopBindException("yami.prod.status.change");
        }
        offlineHandleEvent.setHandlerId(SecurityUtils.getSysUser().getUserId());
        productService.handleAuditProd(dbProduct, offlineHandleEvent);
        // 移除缓存
        productService.removeProdCacheByProdId(dbProduct.getProdId());
        eventPublisher.publishEvent(new EsProductUpdateEvent(dbProduct.getProdId(), null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
    @PutMapping("/toTop/{id}")
    @Operation(summary = "置顶商品" , description = "置顶商品")
    @Parameter(name = "id", description = "商品id" )
    public ServerResponseEntity<Void> removeById(@PathVariable Long id) {
        Product product = productService.getProductByProdId(id, I18nMessage.getDbLang());
        if (!Objects.equals(product.getStatus(), ProdStatusEnums.NORMAL.getValue())) {
            // 只能置顶已上架的商品
            throw new YamiShopBindException("yami.prod.set.top.check");
        }
        product.setIsTop(Objects.equals(product.getIsTop(), 0) ? 1 : 0);
        productService.updateById(product);
        // 移除缓存
        productService.removeProdCacheByProdId(id);
        eventPublisher.publishEvent(new EsProductUpdateEvent(product.getProdId(), null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
    private void checkBeforeDeleteProduct(Long prodId) {
        GetComboProdCountEvent getComboProdCountEvent = new GetComboProdCountEvent();
        getComboProdCountEvent.setProdId(prodId);
        applicationContext.publishEvent(getComboProdCountEvent);
        if (getComboProdCountEvent.getCount() > 0) {
            //参加以下活动的商品不能被删除:优惠套餐
            throw new YamiShopBindException("yami.combo.prod.not.delete");
        }
        GetGiveawayProdCountEvent getGiveawayProdCountEvent = new GetGiveawayProdCountEvent();
        getGiveawayProdCountEvent.setProdId(prodId);
        applicationContext.publishEvent(getGiveawayProdCountEvent);
        if (getGiveawayProdCountEvent.getCount() > 0) {
            //参加以下活动的商品不能被删除:赠品
            throw new YamiShopBindException("yami.giveaway.prod.not.delete");
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/RevenueOverviewController.java
New file
@@ -0,0 +1,68 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.FinanceDetailsDto;
import com.yami.shop.bean.dto.RevenueOverviewDto;
import com.yami.shop.bean.param.FinanceDetailsParam;
import com.yami.shop.bean.param.RevenueOverviewParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.RevenueOverviewService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.text.ParseException;
/**
 * 财务管理—营收概况
 *
 * @author SJL
 * @date 2020-08-17
 */
@RestController
@RequestMapping("/platform/financialManagement")
@Tag(name = "营收概况")
public class RevenueOverviewController {
    @Autowired
    private RevenueOverviewService revenueOverviewService;
    @GetMapping("/getIncomeProfile")
    @Operation(summary = "获取商家和日期的收入金额和退款金额" , description = "获取商家和日期的收入金额和退款金额")
    public ServerResponseEntity<RevenueOverviewDto> getIncomeProfile(@ParameterObject RevenueOverviewParam param) throws ParseException {
        RevenueOverviewDto result = revenueOverviewService.getData(param);
        return ServerResponseEntity.success(result);
    }
    @GetMapping("/getFinanceDetail")
    @Operation(summary = "分页获取财务明细" , description = "分页获取财务明细")
    public ServerResponseEntity<IPage<FinanceDetailsDto>> getFinanceDetails(PageParam<FinanceDetailsDto> page, @ParameterObject FinanceDetailsParam param) {
        IPage<FinanceDetailsDto> result = revenueOverviewService.getPageDetail(page, param);
        return ServerResponseEntity.success(result);
    }
    @GetMapping("/getFinanceDetailForm")
    @Operation(summary = "导出报表" , description = "导出报表")
    @PreAuthorize("@pms.hasPermission('finance:detail:excel')")
    public void getFinanceDetailForm(@ParameterObject FinanceDetailsParam param, HttpServletResponse response) {
        revenueOverviewService.excelFianceDetail(param, response);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ScoreProductController.java
New file
@@ -0,0 +1,221 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.enums.*;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.bean.model.OfflineHandleEvent;
import com.yami.shop.bean.model.ProdLang;
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.bean.param.ProductScoreParam;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.delivery.common.model.Transport;
import com.yami.shop.delivery.common.service.TransportService;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import ma.glasnost.orika.MapperFacade;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * 商品列表、商品发布controller
 *
 * @author lgh
 */
@RestController
@RequestMapping("/platform/scoreProduct")
@AllArgsConstructor
@Tag(name = "积分商品")
public class ScoreProductController {
    private final ProductService productService;
    private final SkuService skuService;
    private final ProdLangService prodLangService;
    private final BasketService basketService;
    private final ApplicationEventPublisher eventPublisher;
    private final OfflineHandleEventService offlineHandleEventService;
    private final MapperFacade mapperFacade;
    private final TransportService transportService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('score:prod:page')")
    @Operation(summary = "分页获取积分商品信息" , description = "分页获取积分商品信息")
    public ServerResponseEntity<IPage<Product>> page(@ParameterObject ProductParam product, PageParam<Product> page) {
        product.setLang(I18nMessage.getDbLang());
        product.setShopId(Constant.PLATFORM_SHOP_ID);
        product.setProdType(ProdType.PROD_TYPE_SCORE.value());
        IPage<Product> products = productService.pageByLang(page,product);
        return ServerResponseEntity.success(products);
    }
    @GetMapping("/info/{prodId}")
    @PreAuthorize("@pms.hasPermission('score:prod:info')")
    @Operation(summary = "获取信息" , description = "获取信息")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<Product> info(@PathVariable("prodId") Long prodId) {
        Product prod = productService.getProductByProdId(prodId, I18nMessage.getDbLang());
        List<Sku> skuList = skuService.listSkuAndSkuStock(prodId, I18nMessage.getDbLang());
        prod.setSkuList(skuList);
        // 获取语言列表
        List<ProdLang> prodLangList = prodLangService.list(new LambdaQueryWrapper<ProdLang>().eq(ProdLang::getProdId, prodId));
        prod.setProdLangList(prodLangList);
        return ServerResponseEntity.success(prod);
    }
    @DeleteMapping("/{prodId}")
    @PreAuthorize("@pms.hasPermission('score:prod:delete')")
    @Operation(summary = "删除积分商品" , description = "删除积分商品")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<Void> delete(@PathVariable("prodId") Long prodId) {
        Product dbProduct = productService.getProductByProdId(prodId, I18nMessage.getDbLang());
        if (Objects.equals(dbProduct.getStatus(), ProdStatusEnums.DELETE.getValue())) {
            // 该商品已经被删除
            throw new YamiShopBindException("yami.product.already.deleted");
        }
        List<Sku> dbSkus = skuService.listByProdId(dbProduct.getProdId(), I18nMessage.getDbLang());
        // 删除商品
        productService.removeProductByProdId(prodId);
        // 清除缓存
        productService.removeProdCacheByProdId(prodId);
        for (Sku sku : dbSkus) {
            skuService.removeSkuCacheBySkuId(sku.getSkuId(), sku.getProdId());
        }
        List<String> userIds = basketService.listUserIdByProdId(prodId);
        //清除购物车缓存
        basketService.removeCacheByUserIds(userIds);
        eventPublisher.publishEvent(new EsProductUpdateEvent(prodId, null, EsOperationType.DELETE));
        return ServerResponseEntity.success();
    }
    @GetMapping("/getOfflineHandleEventByProdId/{prodId}")
    @Operation(summary = "获取最新下线商品的事件" , description = "获取最新下线商品的事件")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<OfflineHandleEvent> getOfflineHandleEventByProdId(@PathVariable Long prodId) {
        OfflineHandleEvent offlineHandleEvent = offlineHandleEventService.getProcessingEventByHandleTypeAndHandleId(OfflineHandleEventType.PROD.getValue(), prodId);
        return ServerResponseEntity.success(offlineHandleEvent);
    }
    @PutMapping("/prodStatus")
    @Operation(summary = "更新商品状态" , description = "更新商品状态")
    public ServerResponseEntity<Void> shopStatus(@RequestBody ProductParam productParam) {
        Long prodId = productParam.getProdId();
        Integer prodStatus = productParam.getStatus();
        Product dbProduct = productService.getProductByProdId(prodId, I18nMessage.getDbLang());
        if (!(Objects.equals(dbProduct.getStatus(), ProdStatusEnums.NORMAL.getValue())
                || Objects.equals(dbProduct.getStatus(), ProdStatusEnums.SHOP_OFFLINE.getValue()))) {
            // 商品不在正常状态,修改失败
            throw new YamiShopBindException("yami.product.on.normal");
        }
        Product product = new Product();
        product.setProdId(prodId);
        product.setStatus(prodStatus);
        if (prodStatus == 1) {
            product.setPutawayTime(new Date());
        }
        dbProduct.setStatus(prodStatus);
        // 商品状态改变时的发送事件,让活动下线
//        applicationContext.publishEvent(new ProdChangeStatusEvent(dbProduct, dbProduct.getStatus()));
        productService.updateById(product);
        List<String> userIds = basketService.listUserIdByProdId(prodId);
        productService.removeProdCacheByProdId(prodId);
        //清除购物车缓存
        basketService.removeCacheByUserIds(userIds);
        eventPublisher.publishEvent(new EsProductUpdateEvent(prodId, null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
    @PostMapping
    @PreAuthorize("@pms.hasPermission('score:prod:save')")
    @Operation(summary = "保存积分商品" , description = "保存积分商品")
    public ServerResponseEntity<Long> save(@Valid @RequestBody ProductScoreParam productScoreParam) {
        checkParam(productScoreParam);
        ProductParam productParam = mapperFacade.map(productScoreParam,ProductParam.class);
        productParam.setShopId(Constant.PLATFORM_SHOP_ID);
        //积分商品类型
        productParam.setProdType(ProdType.PROD_TYPE_SCORE.value());
        productService.saveProduct(productParam);
        eventPublisher.publishEvent(new EsProductUpdateEvent(productParam.getProdId(), null, EsOperationType.SAVE));
        return ServerResponseEntity.success(productParam.getProdId());
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('score:prod:update')")
    @Operation(summary = "修改积分商品" , description = "修改积分商品")
    public ServerResponseEntity<String> update(@Valid @RequestBody ProductScoreParam productScoreParam) {
        checkParam(productScoreParam);
        Product dbProduct = productService.getProductInfo(productScoreParam.getProdId(), I18nMessage.getDbLang());
        if (Objects.equals(dbProduct.getStatus(), ProdStatusEnums.DELETE.getValue())) {
            // 该商品已经被删除
            throw new YamiShopBindException("yami.product.already.deleted");
        }
        List<String> userIds = basketService.listUserIdByProdId(productScoreParam.getProdId());
        List<Sku> dbSkus = skuService.listByProdId(dbProduct.getProdId(), I18nMessage.getDbLang());
        dbProduct.setSkuList(dbSkus);
        productScoreParam.setProdType(ProdType.PROD_TYPE_SCORE.value());
        ProductParam productParam = mapperFacade.map(productScoreParam,ProductParam.class);
        productParam.setShopId(Constant.PLATFORM_SHOP_ID);
        productService.updateProduct(productParam, dbProduct);
        productService.removeProdCacheByProdId(productParam.getProdId());
        //清除缓存
        for (Sku sku : dbSkus) {
            skuService.removeSkuCacheBySkuId(sku.getSkuId(), sku.getProdId());
        }
        //清除购物车缓存
        basketService.removeCacheByUserIds(userIds);
        eventPublisher.publishEvent(new EsProductUpdateEvent(dbProduct.getProdId(), null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
    private void checkParam(ProductScoreParam productScoreParam) {
        //运费模板
        Transport transport = transportService.getTransportAndAllItems(productScoreParam.getDeliveryTemplateId());
        if (Objects.isNull(transport) && !DeliveryTemplateType.isUnifiedTemplate(productScoreParam.getDeliveryTemplateId())) {
            // 产品运费模板不存在
            throw new YamiShopBindException("yami.prod.transport.not.exist");
        }
        boolean isAllUnUse = true;
        List<Sku> skuList = productScoreParam.getSkuList();
        for (Sku sku : skuList) {
            if (sku.getStatus() == 1) {
                isAllUnUse = false;
            }
        }
        if (isAllUnUse) {
            // 至少要启用一种商品规格
            throw new YamiShopBindException("yami.product.enable.sku");
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopAuditingController.java
New file
@@ -0,0 +1,198 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.app.param.ShopAuditingParam;
import com.yami.shop.bean.dto.ShopAuditingInfoDto;
import com.yami.shop.bean.enums.ShopStatus;
import com.yami.shop.bean.model.ShopAuditing;
import com.yami.shop.bean.model.ShopDetail;
import com.yami.shop.bean.param.AuditingInfoParam;
import com.yami.shop.bean.param.ShopTypeParam;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.security.common.manager.PasswordManager;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.ShopAuditingService;
import com.yami.shop.service.ShopDetailService;
import com.yami.shop.sys.common.model.ShopEmployee;
import com.yami.shop.sys.common.service.ShopEmployeeService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Objects;
/**
 * 商家审核信息
 *
 * @author Dwl
 * @date 2019-09-19 14:02:57
 */
@RestController
@AllArgsConstructor
@RequestMapping("/shop/shopAuditing")
@Tag(name = "店铺审核信息")
public class ShopAuditingController {
    private final ShopAuditingService shopAuditingService;
    private final ShopDetailService shopDetailService;
    private final ShopEmployeeService shopEmployeeService;
    private final PasswordEncoder passwordEncoder;
    private final PasswordManager passwordManager;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('shop:shopAuditing:page')")
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<ShopAuditingInfoDto>> getShopAuditingPage(PageParam<ShopAuditingInfoDto> page, @ParameterObject AuditingInfoParam auditingInfoParam) {
        return ServerResponseEntity.success(shopAuditingService.auditingInfoList(page, auditingInfoParam));
    }
    @GetMapping("/shopDetail/{shopId}")
    @PreAuthorize("@pms.hasPermission('shop:shopAuditing:info')")
    @Operation(summary = "查询详情信息" , description = "查询详情信息")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<ShopDetail> auditingDetail(@PathVariable Long shopId) {
        ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(shopId);
        return ServerResponseEntity.success(shopDetail);
    }
    @GetMapping("/{shopId}")
    @Operation(summary = "根据店铺id查询审核信息" , description = "根据店铺id查询审核信息")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<ShopAuditing> getShopAuditing(@PathVariable Long shopId) {
        ShopAuditing shopAuditing = shopAuditingService.getOne(new LambdaQueryWrapper<ShopAuditing>().eq(ShopAuditing::getShopId, shopId));
        ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(shopId);
        if (Objects.isNull(shopAuditing)){
            shopAuditing = new ShopAuditing();
        }
        if (Objects.equals(shopDetail.getShopStatus(), ShopStatus.OFFLINE.value())) {
            shopAuditing.setStatus(2);
        } else if (Objects.equals(shopDetail.getShopStatus(), ShopStatus.OFFLINE_AUDIT.value())) {
            shopAuditing.setStatus(0);
        }
        return ServerResponseEntity.success(shopAuditing);
    }
    @SysLog("审核商家信息")
    @PutMapping("/audit")
    @PreAuthorize("@pms.hasPermission('shop:shopAuditing:audit')")
    @Operation(summary = "审核信息(审核商家)" , description = "审核信息(审核商家)")
    public ServerResponseEntity<Void> audit(@Valid @RequestBody ShopAuditingParam shopAuditingParam) {
        String limitKey = "AUDIT_"+shopAuditingParam.getShopId();
        Long incr = RedisUtil.incr(limitKey, 1);
        if(incr==1){
            RedisUtil.expire(limitKey,1);
        }else{
            throw  new YamiShopBindException("已提交审核请求");
        }
        shopAuditingParam.setAuditorId(SecurityUtils.getSysUser().getUserId());
        shopDetailService.audit(shopAuditingParam);
        shopDetailService.removeShopDetailCacheByShopId(shopAuditingParam.getShopId());
        shopEmployeeService.update(Wrappers.<ShopEmployee>lambdaUpdate().set(ShopEmployee::getStatus, 1).eq(ShopEmployee::getShopId, shopAuditingParam.getShopId()));
        return ServerResponseEntity.success();
    }
    @PostMapping
    @Operation(summary = "新建店铺" , description = "新建店铺")
    public ServerResponseEntity<ShopDetail> insertDetail(@RequestBody ShopDetail shopDetail) {
        // 判断是账号是否已存在
        int count = shopEmployeeService.checkUserName(shopDetail.getShopId(), shopDetail.getMobile());
        shopDetail.setPassword(passwordEncoder.encode(passwordManager.decryptPassword(shopDetail.getPassword())));
        shopDetailService.insertDetail(shopDetail,count);
        return ServerResponseEntity.success(shopDetail);
    }
    @GetMapping("/checkMobile")
    @Operation(summary = "校验账号" , description = "校验账号")
    @Parameters(value = {
            @Parameter(name = "mobile", description = "账号" ),
            @Parameter(name = "shopId", description = "店铺id" ),
    })
    public ServerResponseEntity<Boolean> checkMobile(@RequestParam("mobile") String mobile,@RequestParam("shopId")Long shopId) {
        boolean isTrue = true;
        if (Objects.isNull(shopId)){
            shopId = 0L;
        }
        int count = shopDetailService.checkMobile(mobile,Long.valueOf(shopId));
        // 加多了一个表,并且为了适配,保证数据的唯一性,需要同时验证两个表的数据
        count = count + shopEmployeeService.checkUserName(shopId, mobile);
        if (count > 0){
            isTrue = false;
        }
        return ServerResponseEntity.success(isTrue);
    }
    @GetMapping("/checkUsername")
    @Operation(summary = "检查用户名是否可用" , description = "检查用户名是否可用")
    @Parameters(value = {
            @Parameter(name = "username", description = "用户名" ),
            @Parameter(name = "shopId", description = "店铺id" ),
    })
    public ServerResponseEntity<Boolean> checkUsername(@RequestParam("username") String username, @RequestParam("shopId") Long shopId) {
        if (StringUtils.isBlank(username)) {
            return ServerResponseEntity.success(Boolean.FALSE);
        }
        int count = 0;
        if (Objects.isNull(shopId)) {
            // shopId为空,判断用户名是否重复
            count = shopEmployeeService.count(Wrappers.lambdaQuery(ShopEmployee.class)
                    .eq(ShopEmployee::getUsername, username)
            );
        } else {
            // 更改店铺商家用户名时,判断username是否重复
            count = shopEmployeeService.count(Wrappers.lambdaQuery(ShopEmployee.class)
                    .eq(ShopEmployee::getUsername, username)
                    .ne(ShopEmployee::getShopId, shopId)
            );
        }
        return ServerResponseEntity.success(count == 0);
    }
    @PutMapping("/updatePasswordOrMobile")
    @Operation(summary = "重置密码或者修改账号" , description = "重置密码或者修改账号")
    public ServerResponseEntity<Boolean> updatePasswordOrMobile(@RequestBody ShopDetail shopDetail) {
        // 判断是账号是否已存在
        String decryptPassword = passwordManager.decryptPassword(shopDetail.getPassword());
        int count = shopEmployeeService.checkUserName(shopDetail.getShopId(), shopDetail.getMobile());
        if (Objects.nonNull(decryptPassword)) {
            //前端传过来密码如果为空,就不set
            shopDetail.setPassword(passwordEncoder.encode(decryptPassword));
        }
        shopDetailService.updatePasswordOrMobile(shopDetail.getShopId(),decryptPassword,shopDetail.getMobile(),count);
        return ServerResponseEntity.success();
    }
    @PutMapping("/updateShopType")
    @Operation(summary = "修改店铺类型" , description = "修改店铺类型")
    public ServerResponseEntity<Boolean> updateShopType(@RequestBody ShopTypeParam shopTypeParam) {
        shopDetailService.batchUpdateShopType(shopTypeParam);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopBankCardController.java
New file
@@ -0,0 +1,46 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.model.ShopBankCard;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.ShopBankCardService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @Author lth
 * @Date 2021/8/11 15:04
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/shopBankCard" )
@Tag(name = "银行卡相关接口")
public class ShopBankCardController {
    private final ShopBankCardService shopBankCardService;
    @GetMapping("/listByShopId")
    @Operation(summary = "根据店铺id批量获取银行卡信息")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<List<ShopBankCard>> getShopBankCardList(@RequestParam(value = "shopId") Long shopId) {
        List<ShopBankCard> list = shopBankCardService.list(new LambdaQueryWrapper<ShopBankCard>().eq(ShopBankCard::getShopId, shopId).eq(ShopBankCard::getStatus,1));
        return ServerResponseEntity.success(list);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopCompanyController.java
New file
@@ -0,0 +1,65 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.enums.AuditStatus;
import com.yami.shop.bean.model.ShopCompany;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.ShopCompanyService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Objects;
/**
 * @Author lth
 * @Date 2021/8/4 14:44
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/shopCompany")
@Tag(name = "商家工商信息")
public class ShopCompanyController {
    private final ShopCompanyService shopCompanyService;
    @GetMapping
    @Operation(summary = "根据店铺id获取店铺工商信息" , description = "根据店铺id获取店铺工商信息")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<ShopCompany> getShopCompanyByShopId(@RequestParam("shopId") Long shopId) {
        ShopCompany shopCompany = shopCompanyService.getOne(Wrappers.lambdaQuery(ShopCompany.class).eq(ShopCompany::getShopId, shopId).eq(ShopCompany::getStatus, AuditStatus.SUCCESSAUDIT.value()));
        if (Objects.isNull(shopCompany)) {
            shopCompany = new ShopCompany();
        }
        return ServerResponseEntity.success(shopCompany);
    }
    @PutMapping
    @Operation(summary = "更新店铺工商信息" , description = "更新店铺工商信息")
    public ServerResponseEntity<Void> editShopCompany(@RequestBody @Valid ShopCompany shopCompany) {
        shopCompanyService.updateByShopId(shopCompany);
        return ServerResponseEntity.success();
    }
    @GetMapping("/checkCreditCode")
    @Operation(summary = "检查统一信用码是否已存在" , description = "检查统一信用码是否已存在")
    @Parameter(name = "creditCode", description = "信用码" )
    public ServerResponseEntity<Boolean> checkCreditCode(@RequestParam(value = "creditCode") String creditCode) {
         int count = shopCompanyService.count(Wrappers.lambdaQuery(ShopCompany.class)
                .eq(ShopCompany::getCreditCode, creditCode).eq(ShopCompany::getStatus, AuditStatus.SUCCESSAUDIT.value())
        );
        return ServerResponseEntity.success(count > 0);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopDetailController.java
New file
@@ -0,0 +1,236 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Maps;
import com.yami.shop.bean.app.dto.ShopHeadInfoDto;
import com.yami.shop.bean.app.param.SendSmsParam;
import com.yami.shop.bean.dto.ShopCreateInfoDTO;
import com.yami.shop.bean.dto.ShopSigningInfoDTO;
import com.yami.shop.bean.enums.*;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.bean.model.OfflineHandleEvent;
import com.yami.shop.bean.model.ShopDetail;
import com.yami.shop.bean.param.AuditingInfoParam;
import com.yami.shop.bean.param.OfflineHandleEventAuditParam;
import com.yami.shop.bean.param.ShopSearchParam;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.PrincipalUtil;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.OfflineHandleEventService;
import com.yami.shop.service.ShopDetailService;
import com.yami.shop.service.SmsLogService;
import com.yami.shop.sys.common.model.ShopEmployee;
import com.yami.shop.sys.common.service.ShopEmployeeService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Objects;
/**
 * 商家详细信息
 * @author yami
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/platform/shopDetail")
@Tag(name = "店铺基本信息相关接口")
public class ShopDetailController {
    @Value("${yami.expose.operation.auth:}")
    private Boolean permission;
    private final ShopDetailService shopDetailService;
    private final OfflineHandleEventService offlineHandleEventService;
    private final ApplicationEventPublisher eventPublisher;
    private final SmsLogService smsLogService;
    private final ShopEmployeeService shopEmployeeService;
    @GetMapping("/info")
    @Operation(summary = "根据店铺id获取店铺基本信息" , description = "根据店铺id获取店铺基本信息")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<ShopDetail> getInfo(@RequestParam Long shopId) {
        ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(shopId);
        ShopEmployee shopEmployee = shopEmployeeService.getOne(Wrappers.lambdaQuery(ShopEmployee.class).eq(ShopEmployee::getShopId, shopId).eq(ShopEmployee::getType, PositionType.ADMIN.value()));
        if (Objects.nonNull(shopEmployee)) {
            shopDetail.setMerchantAccount(shopEmployee.getUsername());
            shopDetail.setAccountStatus(shopEmployee.getStatus());
            if (BooleanUtil.isFalse(permission)) {
                if (StrUtil.isNotBlank(shopDetail.getTel())){
                    shopDetail.setTel(PhoneUtil.hideBetween(shopDetail.getTel()).toString());
                }
                if (PrincipalUtil.isMobile(shopDetail.getMerchantAccount())) {
                    shopDetail.setMerchantAccount(PhoneUtil.hideBetween(shopDetail.getMerchantAccount()).toString());
                }
            }
        }
        return ServerResponseEntity.success(shopDetail);
    }
    @GetMapping("/getMerchantInfo")
    @Operation(summary = "根据店铺id获取店铺商家账号信息" , description = "根据店铺id获取店铺商家账号信息")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<ShopEmployee> getMerchantInfo(@RequestParam Long shopId) {
        ShopEmployee shopEmployee = shopEmployeeService.getMerchantInfoByShopId(shopId);
        return ServerResponseEntity.success(shopEmployee);
    }
    @PutMapping("/updateMerchantInfo")
    @Operation(summary = "更新店铺商家账号信息" , description = "更新店铺商家账号信息")
    public ServerResponseEntity<Void> updateMerchantInfo(@RequestBody ShopEmployee shopEmployee) {
        if (Objects.equals(shopEmployee.getShopId(), Constant.MAIN_SHOP) && BooleanUtil.isFalse(permission)) {
            throw new YamiShopBindException("yami.no.auth");
        }
        shopEmployeeService.updateMerchantInfo(shopEmployee);
        return ServerResponseEntity.success();
    }
    @PostMapping("/createShop")
    @Operation(summary = "新建店铺" , description = "新建店铺")
    public ServerResponseEntity<Void> createShop(@RequestBody @Valid ShopCreateInfoDTO shopCreateInfoDTO) {
        Long userId = SecurityUtils.getSysUser().getUserId();
        shopDetailService.platformCreateShop(shopCreateInfoDTO, userId);
        return ServerResponseEntity.success();
    }
    @PostMapping("/sendCode")
    @Operation(summary = "发送申请开店验证码" , description = "发送申请开店验证码")
    public ServerResponseEntity<Void> sendCode(@Valid @RequestBody SendSmsParam sendSmsParam) {
        int count = shopEmployeeService.count(Wrappers.lambdaQuery(ShopEmployee.class).eq(ShopEmployee::getMobile, sendSmsParam.getMobile()));
        if (count > 0) {
            // 手机号已存在
            throw new YamiShopBindException("yami.phone.number.already.exists");
        }
        smsLogService.sendSms(SendType.VALID, null, sendSmsParam.getMobile(), Maps.newHashMap());
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "编辑店铺基本信息" , description = "编辑店铺基本信息")
    public ServerResponseEntity<Void> editShop(@RequestBody ShopDetail shopDetail) {
        if (Objects.isNull(shopDetail.getShopId())) {
            throw new YamiShopBindException("店铺id不能为空");
        }
        shopDetailService.updateShopDetail(shopDetail);
        eventPublisher.publishEvent(new EsProductUpdateEvent(shopDetail.getShopId(), null, EsOperationType.UPDATE_BY_SHOP_ID));
        return ServerResponseEntity.success();
    }
    @PutMapping("/updateSigningInfo")
    @Operation(summary = "更新店铺签约信息" , description = "更新店铺签约信息")
    public ServerResponseEntity<Void> updateSigningInfo(@RequestBody @Valid ShopSigningInfoDTO shopSigningInfoDTO) {
        if (Objects.isNull(shopSigningInfoDTO.getShopId())) {
            throw new YamiShopBindException("店铺id不能为空");
        }
        shopDetailService.updateSigningInfo(shopSigningInfoDTO);
        return ServerResponseEntity.success();
    }
    @GetMapping("/getOfflineHandleEventByShopId/{shopId}")
    @PreAuthorize("@pms.hasPermission('shop:shopAuditing:info')")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<OfflineHandleEvent> getOfflineHandleEventByShopId(@PathVariable("shopId") Long shopId) {
        OfflineHandleEvent offlineHandleEvent = offlineHandleEventService.getProcessingEventByHandleTypeAndHandleId(OfflineHandleEventType.SHOP.getValue(), shopId);
        return ServerResponseEntity.success(offlineHandleEvent);
    }
    /**
     * 下线店铺
     */
    @PostMapping("/offline")
    @PreAuthorize("@pms.hasPermission('shop:shopAuditing:audit')")
    public ServerResponseEntity<Void> offline(@RequestBody OfflineHandleEvent offlineHandleEvent) {
        Long sysUserId = SecurityUtils.getSysUser().getUserId();
        ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(offlineHandleEvent.getHandleId());
        if (shopDetail == null) {
            // 未找到该店铺信息
            throw new YamiShopBindException("yami.store.not.exist");
        }
        if (!Objects.equals(shopDetail.getShopStatus(), ShopStatus.OPEN.value()) && !Objects.equals(shopDetail.getShopStatus(), ShopStatus.STOP.value())) {
            // 店铺不处于营业或停业状态,不能进行下线
            throw new YamiShopBindException("yami.store.offline.check");
        }
        shopDetailService.offline(shopDetail, offlineHandleEvent.getOfflineReason(), sysUserId);
        eventPublisher.publishEvent(new EsProductUpdateEvent(shopDetail.getShopId(), null, EsOperationType.UPDATE_BY_SHOP_ID));
        return ServerResponseEntity.success();
    }
    /**
     * 店铺审核重新开店
     */
    @PostMapping("/auditShop")
    @PreAuthorize("@pms.hasPermission('shop:shopAuditing:audit')")
    @Operation(summary = "店铺违规审核" , description = "店铺违规审核")
    public ServerResponseEntity<Void> auditOfflineShop(@RequestBody OfflineHandleEventAuditParam offlineHandleEventAuditParam) {
        Long sysUserId = SecurityUtils.getSysUser().getUserId();
        shopDetailService.auditOfflineShop(offlineHandleEventAuditParam, sysUserId);
        return ServerResponseEntity.success();
    }
    @GetMapping("/checkShopName")
    @Operation(summary = "检查店铺名称是否已存在" , description = "检查店铺名称是否已存在")
    public ServerResponseEntity<Boolean> checkShopName(@RequestParam(value = "shopName") String shopName) {
        int count = shopDetailService.count(Wrappers.lambdaQuery(ShopDetail.class)
                .eq(ShopDetail::getShopName, shopName)
                .ne(ShopDetail::getShopStatus, ShopStatus.NOTOPEN.value())
        );
        return ServerResponseEntity.success(count > 0);
    }
    @GetMapping("/exportShop")
    @Operation(summary = "导出店铺列表" , description = "导出店铺列表")
    public void exportShop(@ParameterObject AuditingInfoParam auditingInfoParam, HttpServletResponse response) {
        shopDetailService.exportShop(auditingInfoParam, response);
    }
    private String generateKey(Map<String, String> values) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
            byte[] bytes = digest.digest(values.toString().getBytes(StandardCharsets.UTF_8));
            return String.format("%032x", new BigInteger(1, bytes));
        } catch (NoSuchAlgorithmException nsae) {
            throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).", nsae);
        }
    }
    @GetMapping("/searchShops")
    @Operation(summary = "搜索店铺" , description = "根据店铺名称搜索店铺")
    public ServerResponseEntity<IPage<ShopHeadInfoDto>> searchShops(PageParam<ShopDetail> page, @ParameterObject ShopSearchParam shopSearchParam) {
        IPage<ShopHeadInfoDto> shopPage = shopDetailService.renovationShopPage(page, shopSearchParam);
        return ServerResponseEntity.success(shopPage);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopRenovationController.java
New file
@@ -0,0 +1,106 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.enums.RenovationType;
import com.yami.shop.bean.model.ShopRenovation;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.ShopRenovationService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Objects;
/**
 * 店铺装修信息
 *
 * @author lhd
 * @date 2021-01-05 11:03:38
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/shopRenovation" )
@Tag(name = "店铺装修页面接口")
public class ShopRenovationController {
    private final ShopRenovationService shopRenovationService;
    @GetMapping("/pagePC" )
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:pagePC')")
    @Operation(summary = "PC端分页获取店铺装修信息" , description = "PC端分页获取店铺装修信息")
    public ServerResponseEntity<IPage<ShopRenovation>> getShopRenovationPagePC(PageParam<ShopRenovation> page, @ParameterObject ShopRenovation shopRenovation) {
        return ServerResponseEntity.success(shopRenovationService.page(page, new LambdaQueryWrapper<ShopRenovation>()
                .eq(ShopRenovation::getShopId,Constant.PLATFORM_SHOP_ID)
                .eq(Objects.nonNull(shopRenovation.getRenovationType()), ShopRenovation::getRenovationType, shopRenovation.getRenovationType())
                .like(Objects.nonNull(shopRenovation.getName()), ShopRenovation::getName, shopRenovation.getName())
                .eq(ShopRenovation::getRenovationType,shopRenovation.getRenovationType())
                .orderByDesc(ShopRenovation::getHomeStatus, ShopRenovation::getCreateTime))
        );
    }
    @GetMapping("/pageMove" )
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:pageMove')")
    @Operation(summary = "移动端分页获取店铺装修信息" , description = "移动端分页获取店铺装修信息")
    public ServerResponseEntity<IPage<ShopRenovation>> getShopRenovationPageMove(PageParam<ShopRenovation> page, @ParameterObject ShopRenovation shopRenovation) {
        return ServerResponseEntity.success(shopRenovationService.page(page, new LambdaQueryWrapper<ShopRenovation>()
                .eq(ShopRenovation::getShopId,Constant.PLATFORM_SHOP_ID)
                .eq(Objects.nonNull(shopRenovation.getRenovationType()), ShopRenovation::getRenovationType, shopRenovation.getRenovationType())
                .like(Objects.nonNull(shopRenovation.getName()), ShopRenovation::getName, shopRenovation.getName())
                .eq(ShopRenovation::getRenovationType,shopRenovation.getRenovationType())
                .orderByDesc(ShopRenovation::getHomeStatus, ShopRenovation::getCreateTime)
        ));
    }
    @GetMapping("/info/{renovationId}" )
    @Operation(summary = "查询店铺装修信息" , description = "查询店铺装修信息")
    @Parameter(name = "renovationId", description = "店铺装修id" )
    public ServerResponseEntity<ShopRenovation> getById(@PathVariable("renovationId") Long renovationId) {
        return ServerResponseEntity.success(shopRenovationService.getById(renovationId));
    }
    @SysLog("新增店铺装修信息" )
    @PostMapping("/savePC")
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:savePC')")
    @Operation(summary = "PC端新增店铺装修信息" , description = "PC端新增店铺装修信息")
    public ServerResponseEntity<Long> savePC(@RequestBody @Valid ShopRenovation shopRenovation) {
        shopRenovation.setShopId(Constant.PLATFORM_SHOP_ID);
        if (Objects.isNull(shopRenovation.getRenovationType())) {
            shopRenovation.setRenovationType(RenovationType.H5.value());
        }
        shopRenovationService.save(shopRenovation);
        return ServerResponseEntity.success(shopRenovation.getRenovationId());
    }
    @SysLog("新增店铺装修信息" )
    @PostMapping("/saveMove")
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:saveMove')")
    @Operation(summary = "移动端新增店铺装修信息" , description = "移动端新增店铺装修信息")
    public ServerResponseEntity<Long> saveMove(@RequestBody @Valid ShopRenovation shopRenovation) {
        shopRenovation.setShopId(Constant.PLATFORM_SHOP_ID);
        if (Objects.isNull(shopRenovation.getRenovationType())) {
            shopRenovation.setRenovationType(RenovationType.H5.value());
        }
        shopRenovationService.save(shopRenovation);
        return ServerResponseEntity.success(shopRenovation.getRenovationId());
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopRenovationUpDelController.java
New file
@@ -0,0 +1,137 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.model.ShopRenovation;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.ShopRenovationService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Objects;
/**
 * 店铺装修信息
 *
 * @author lhd
 * @date 2021-01-05 11:03:38
 */
@RestController
@RequestMapping("/platform/shopRenovation/operate" )
@ConditionalOnProperty(prefix = "yami", name = "expose.operation.auth", havingValue = "true", matchIfMissing = true)
@Tag(name = "店铺页面-修改、删除、设为主页")
public class ShopRenovationUpDelController {
    @Autowired
    private ShopRenovationService shopRenovationService;
    @SysLog("修改店铺装修信息" )
    @PutMapping("/updatePC")
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:updatePC')")
    @Operation(summary = "PC端修改店铺装修页面信息" , description = "PC端修改店铺装修页面信息")
    public ServerResponseEntity<Boolean> updatePCById(@RequestBody @Valid ShopRenovation shopRenovation) {
        ShopRenovation shopRenovationDb = shopRenovationService.getById(shopRenovation.getRenovationId());
        if(!Objects.equals(shopRenovationDb.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopRenovationService.checkShopRenovation(shopRenovation);
        shopRenovationService.removeCache(shopRenovationDb.getShopId(), shopRenovationDb.getRenovationType(), shopRenovationDb.getRenovationId());
        return ServerResponseEntity.success(shopRenovationService.updateById(shopRenovation));
    }
    @SysLog("修改店铺装修信息" )
    @PutMapping("/updateMove")
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:updateMove')")
    @Operation(summary = "移动端修改店铺装修页面信息" , description = "移动端修改店铺装修页面信息")
    public ServerResponseEntity<Boolean> updateMoveById(@RequestBody @Valid ShopRenovation shopRenovation) {
        ShopRenovation shopRenovationDb = shopRenovationService.getById(shopRenovation.getRenovationId());
        if(!Objects.equals(shopRenovationDb.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopRenovationService.checkShopRenovation(shopRenovation);
        shopRenovationService.removeCache(shopRenovationDb.getShopId(), shopRenovationDb.getRenovationType(), shopRenovationDb.getRenovationId());
        return ServerResponseEntity.success(shopRenovationService.updateById(shopRenovation));
    }
    @SysLog("删除店铺装修信息" )
    @DeleteMapping("/deletePC/{renovationId}" )
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:deletePC')")
    @Operation(summary = "PC端删除店铺装修页面信息" , description = "PC端删除店铺装修页面信息")
    @Parameter(name = "renovationId", description = "店铺装修id" )
    public ServerResponseEntity<Boolean> removePCById(@PathVariable("renovationId") Long renovationId) {
        ShopRenovation shopRenovation = shopRenovationService.getById(renovationId);
        if(!Objects.equals(shopRenovation.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopRenovationService.removeCache(shopRenovation.getShopId(), shopRenovation.getRenovationType(), shopRenovation.getRenovationId());
        return ServerResponseEntity.success(shopRenovationService.removeById(renovationId));
    }
    @SysLog("删除店铺装修信息" )
    @DeleteMapping("/deleteMove/{renovationId}" )
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:deleteMove')")
    @Operation(summary = "移动端删除店铺装修页面信息" , description = "移动端删除店铺装修页面信息")
    @Parameter(name = "renovationId", description = "店铺装修id" )
    public ServerResponseEntity<Boolean> removeMoveById(@PathVariable("renovationId") Long renovationId) {
        ShopRenovation shopRenovation = shopRenovationService.getById(renovationId);
        if(!Objects.equals(shopRenovation.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopRenovationService.removeCache(shopRenovation.getShopId(), shopRenovation.getRenovationType(), shopRenovation.getRenovationId());
        return ServerResponseEntity.success(shopRenovationService.removeById(renovationId));
    }
    @PutMapping("/updateHomePagePC/{id}" )
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:updateHomePagePC')")
    @Operation(summary = "PC端修改主页标识" , description = "PC端修改主页标识")
    @Parameter(name = "id", description = "店铺装修id" )
    public ServerResponseEntity<Void> updateHomePagePC(@PathVariable("id") Long id) {
        ShopRenovation shopRenovation = shopRenovationService.getById(id);
        if(!Objects.equals(shopRenovation.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopRenovationService.updateToHomePage(shopRenovation);
        shopRenovationService.removeCache(shopRenovation.getShopId(), shopRenovation.getRenovationType(), shopRenovation.getRenovationId());
        return ServerResponseEntity.success();
    }
    @PutMapping("/updateHomePageMove/{id}" )
    @PreAuthorize("@pms.hasPermission('platform:shopRenovation:updateHomePageMove')")
    @Operation(summary = "移动端修改主页标识" , description = "移动端修改主页标识")
    @Parameter(name = "id", description = "店铺装修id" )
    public ServerResponseEntity<Void> updateHomePageMove(@PathVariable("id") Long id) {
        ShopRenovation shopRenovation = shopRenovationService.getById(id);
        if(!Objects.equals(shopRenovation.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopRenovationService.updateToHomePage(shopRenovation);
        shopRenovationService.removeCache(shopRenovation.getShopId(), shopRenovation.getRenovationType(), shopRenovation.getRenovationId());
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopTemplateController.java
New file
@@ -0,0 +1,132 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.enums.TemplateType;
import com.yami.shop.bean.model.ShopTemplate;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.ShopTemplateService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.Objects;
/**
 *
 *
 * @author LGH
 * @date 2022-07-29 16:54:02
 */
@RestController
@RequestMapping("/platform/shopTemplate")
@Tag(name = "店铺装修模板接口")
public class ShopTemplateController {
    @Autowired
    private ShopTemplateService shopTemplateService;
    @GetMapping("/pagePC")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:pagePC')")
    @Operation(summary = "PC端分页查询店铺模板信息" , description = "PC端分页查询店铺模板信息")
    public ServerResponseEntity<IPage<ShopTemplate>> getShopTemplatePagePC(PageParam<ShopTemplate> page, @ParameterObject ShopTemplate shopTemplate) {
        return ServerResponseEntity.success(shopTemplateService.page(page, new LambdaQueryWrapper<ShopTemplate>()
                .eq(ShopTemplate::getShopId, Constant.PLATFORM_SHOP_ID)
                .eq(Objects.nonNull(shopTemplate.getType()), ShopTemplate::getType, shopTemplate.getType())
                .eq(ShopTemplate::getType,shopTemplate.getType())
                .orderByDesc(ShopTemplate::getUpdateTime)
        ));
    }
    @GetMapping("/pageMove")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:pageMove')")
    @Operation(summary = "移动端分页查询店铺模板信息" , description = "移动端分页查询店铺模板信息")
    public ServerResponseEntity<IPage<ShopTemplate>> getShopTemplatePageMove(PageParam<ShopTemplate> page, @ParameterObject ShopTemplate shopTemplate) {
        return ServerResponseEntity.success(shopTemplateService.page(page, new LambdaQueryWrapper<ShopTemplate>()
                .eq(ShopTemplate::getShopId, Constant.PLATFORM_SHOP_ID)
                .eq(Objects.nonNull(shopTemplate.getType()), ShopTemplate::getType, shopTemplate.getType())
                .eq(ShopTemplate::getType,shopTemplate.getType())
                .orderByDesc(ShopTemplate::getUpdateTime)
        ));
    }
    /**
     * 通过id查询店铺模板信息
     * @param templateId id
     * @return 单个数据
     */
    @GetMapping("/info/{templateId}" )
    @Operation(summary = "通过id查询店铺装修模板信息" , description = "通过id查询店铺装修模板信息")
    public ServerResponseEntity<ShopTemplate> getById(@PathVariable("templateId") Long templateId) {
        return ServerResponseEntity.success(shopTemplateService.getById(templateId));
    }
    @SysLog("新增店铺模板信息" )
    @PostMapping("/savePC")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:savePC')")
    @Operation(summary = "PC端新增店铺模板信息" , description = "PC端新增店铺模板信息")
    public ServerResponseEntity<Long> savePC(@RequestBody @Valid ShopTemplate shopTemplate) {
        shopTemplate.setShopId(Constant.PLATFORM_SHOP_ID);
        if (Objects.isNull(shopTemplate.getType())) {
            shopTemplate.setType(TemplateType.PC.value());
        }
        shopTemplateService.save(shopTemplate);
        return ServerResponseEntity.success(shopTemplate.getTemplateId());
    }
    @SysLog("新增店铺模板信息" )
    @PostMapping("/saveMove")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:saveMove')")
    @Operation(summary = "移动端新增店铺模板信息" , description = "移动端新增店铺模板信息")
    public ServerResponseEntity<Long> saveMove(@RequestBody @Valid ShopTemplate shopTemplate) {
        shopTemplate.setShopId(Constant.PLATFORM_SHOP_ID);
        if (Objects.isNull(shopTemplate.getType())) {
            shopTemplate.setType(TemplateType.H5.value());
        }
        shopTemplateService.save(shopTemplate);
        return ServerResponseEntity.success(shopTemplate.getTemplateId());
    }
    @PostMapping("/copyPC/{templateId}")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:copyPC')")
    @Operation(summary = "PC端复制模板信息" , description = "PC端复制模板信息")
    public ServerResponseEntity<Long> copyTemplatePC(@PathVariable Long templateId){
        ShopTemplate shopTemplate = shopTemplateService.getById(templateId);
        shopTemplate.setTemplateId(null);
        shopTemplate.setName(shopTemplate.getName()+"副本");
        shopTemplate.setCreateTime(new Date());
        shopTemplate.setUpdateTime(new Date());
        shopTemplateService.save(shopTemplate);
        return ServerResponseEntity.success(shopTemplate.getTemplateId());
    }
    @PostMapping("/copyMove/{templateId}")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:copyMove')")
    @Operation(summary = "PC端复制模板信息" , description = "PC端复制模板信息")
    public ServerResponseEntity<Long> copyTemplateMove(@PathVariable Long templateId){
        ShopTemplate shopTemplate = shopTemplateService.getById(templateId);
        shopTemplate.setTemplateId(null);
        shopTemplate.setName(shopTemplate.getName()+"副本");
        shopTemplate.setCreateTime(new Date());
        shopTemplate.setUpdateTime(new Date());
        shopTemplateService.save(shopTemplate);
        return ServerResponseEntity.success(shopTemplate.getTemplateId());
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopTemplateUpDelController.java
New file
@@ -0,0 +1,107 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.util.BooleanUtil;
import com.yami.shop.bean.model.ShopRenovation;
import com.yami.shop.bean.model.ShopTemplate;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.ShopTemplateService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.Objects;
/**
 *
 *
 * @author LGH
 * @date 2022-07-29 16:54:02
 */
@RestController
@RequestMapping("/platform/shopTemplate")
//@ConditionalOnProperty(prefix = "yami", name = "expose.operation.auth", havingValue = "true", matchIfMissing = true)
@Tag(name = "店铺模板-修改、删除、设为主页")
public class ShopTemplateUpDelController {
    @Value("${yami.expose.operation.auth:}")
    private Boolean permission;
    @Autowired
    private ShopTemplateService shopTemplateService;
    @SysLog("修改店铺模板信息" )
    @PutMapping("/updatePC")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:updatePC')")
    @Operation(summary = "PC端修改店铺模板信息" , description = "PC端修改店铺模板信息")
    public ServerResponseEntity<Boolean> updatePCById(@RequestBody @Valid ShopTemplate shopTemplate) {
        ShopTemplate shopTemplateDb = shopTemplateService.getById(shopTemplate.getTemplateId());
        if(BooleanUtil.isFalse(permission) || !Objects.equals(shopTemplateDb.getShopId() , Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopTemplate.setUpdateTime(new Date());
        return ServerResponseEntity.success(shopTemplateService.updateById(shopTemplate));
    }
    @SysLog("修改店铺模板信息" )
    @PutMapping("/updateMove")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:updateMove')")
    @Operation(summary = "移动端修改店铺模板信息" , description = "移动端修改店铺模板信息")
    public ServerResponseEntity<Boolean> updateMoveById(@RequestBody @Valid ShopTemplate shopTemplate) {
        ShopTemplate shopTemplateDb = shopTemplateService.getById(shopTemplate.getTemplateId());
        if(BooleanUtil.isFalse(permission) || !Objects.equals(shopTemplateDb.getShopId() , Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        shopTemplate.setUpdateTime(new Date());
        return ServerResponseEntity.success(shopTemplateService.updateById(shopTemplate));
    }
    @SysLog("删除店铺模板信息" )
    @DeleteMapping("/deletePC/{templateId}")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:deletePC')")
    @Operation(summary = "PC端通过id删除店铺模板信息" , description = "PC端通过id删除店铺模板信息")
    @Parameter(name = "templateId", description = "店铺模板id" )
    public ServerResponseEntity<Boolean> removePCById(@PathVariable Long templateId) {
        ShopTemplate shopTemplate = shopTemplateService.getById(templateId);
        if(BooleanUtil.isFalse(permission) || !Objects.equals(shopTemplate.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        return ServerResponseEntity.success(shopTemplateService.removeById(templateId));
    }
    @SysLog("删除店铺模板信息" )
    @DeleteMapping("/deleteMove/{templateId}")
    @PreAuthorize("@pms.hasPermission('platform:shopTemplate:deleteMove')")
    @Operation(summary = "移动端通过id删除店铺模板信息" , description = "移动端通过id删除店铺模板信息")
    @Parameter(name = "templateId", description = "店铺模板id" )
    public ServerResponseEntity<Boolean> removeMoveById(@PathVariable Long templateId) {
        ShopTemplate shopTemplate = shopTemplateService.getById(templateId);
        if(BooleanUtil.isFalse(permission) || !Objects.equals(shopTemplate.getShopId() ,Constant.PLATFORM_SHOP_ID)){
            // 没有权限进行操作
            throw new YamiShopBindException("yami.no.auth");
        }
        return ServerResponseEntity.success(shopTemplateService.removeById(templateId));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopWalletController.java
New file
@@ -0,0 +1,97 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.ShopWallet;
import com.yami.shop.bean.model.ShopWalletLog;
import com.yami.shop.bean.param.CustomerReqParam;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.ShopWalletLogService;
import com.yami.shop.service.ShopWalletService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
/**
 * 商家钱包信息
 *
 * @author LGH
 * @date 2019-09-29 11:10:12
 */
@RestController
@AllArgsConstructor
@RequestMapping("/shop/shopWallet" )
@Tag(name = "商家钱包")
public class ShopWalletController {
    private final ShopWalletService shopWalletService;
    private final ShopWalletLogService shopWalletLogService;
    @GetMapping("/page" )
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<ShopWallet>> getShopWalletPage(PageParam<ShopWallet> page, @ParameterObject ShopWallet shopWallet) {
        return ServerResponseEntity.success(shopWalletService.pageShopWallet(page, shopWallet));
    }
    @GetMapping("/getShopWallet")
    @Operation(summary = "查看店铺钱包信息" , description = "根据店铺id查看店铺钱包信息")
    public ServerResponseEntity<ShopWallet> getShopWalletVoByShopId(){
        ShopWallet shopWallet = shopWalletService.getShopWalletByShopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success(shopWallet);
    }
    @GetMapping("/pagePlatformInfo")
    @Operation(summary = "分页查询平台收入明细" , description = "分页查询平台收入明细")
    public ServerResponseEntity<IPage<ShopWalletLog>> getShopWalletLogPage(PageParam<ShopWalletLog> page, @ParameterObject ShopWalletLog shopWalletLog){
        shopWalletLog.setShopId(Constant.PLATFORM_SHOP_ID);
        IPage<ShopWalletLog> shopWalletLogPage = shopWalletLogService.pageByParam(page, shopWalletLog);
        return ServerResponseEntity.success(shopWalletLogPage);
    }
    @GetMapping("/getAllShopWallet")
    @Operation(summary = "查看店铺钱包总信息")
    public ServerResponseEntity<ShopWallet> getAllShopWalletVoByShopId(@ParameterObject CustomerReqParam customerReqParam) {
        ShopWallet shopWallet = shopWalletService.getAllShop(customerReqParam);
        return ServerResponseEntity.success(shopWallet);
    }
    @GetMapping("/pageShopWalletByTime")
    @Operation(summary = "分页查看店铺钱包总信息")
    public ServerResponseEntity<IPage<ShopWallet>> pageShopWalletByTime(PageParam<ShopWallet> page, @ParameterObject CustomerReqParam customerReqParam) {
        IPage<ShopWallet> shopWalletPage = shopWalletService.pageShopWalletByTime(page, customerReqParam);
        return ServerResponseEntity.success(shopWalletPage);
    }
    @GetMapping("/pageAllShop")
    @Operation(summary = "查看所有店铺的日志")
    public ServerResponseEntity<IPage<ShopWalletLog>> getAllShopWalletLogPage(PageParam<ShopWallet> page, @ParameterObject ShopWalletLog shopWalletLog) {
        IPage<ShopWalletLog> shopWalletLogPage = shopWalletLogService.pageAllShop(page, shopWalletLog);
        return ServerResponseEntity.success(shopWalletLogPage);
    }
    @GetMapping("/getShopWalletLogForm")
    @Operation(summary = "导出报表" , description = "导出店铺结算报表")
    @PreAuthorize("@pms.hasPermission('shop:wallet:excel')")
    public void getShopWalletLogForm(@ParameterObject CustomerReqParam customerReqParam, HttpServletResponse response) {
        shopWalletLogService.excelShopWalletLog(customerReqParam, response);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ShopWithdrawCashController.java
New file
@@ -0,0 +1,102 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.ShopWithdrawCashConfigDto;
import com.yami.shop.bean.model.ShopDetail;
import com.yami.shop.bean.model.ShopWithdrawCash;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.ShopDetailService;
import com.yami.shop.service.ShopWithdrawCashService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
/**
 * 商家提现申请信息
 *
 * @author LGH
 * @date 2019-09-29 11:10:12
 */
@RestController
@AllArgsConstructor
@RequestMapping("/shop/shopWithdrawCash")
@Tag(name = "商家提现申请信息")
public class ShopWithdrawCashController {
    private final ShopWithdrawCashService shopWithdrawCashService;
    private final ShopDetailService shopDetailService;
    @GetMapping("/page")
    @Operation(summary = "分页查询" , description = "分页查询")
    public ServerResponseEntity<IPage<ShopWithdrawCash>> getShopWithdrawCashPage(PageParam<ShopWithdrawCash> page, @ParameterObject ShopWithdrawCash shopWithdrawCash) {
        return ServerResponseEntity.success(shopWithdrawCashService.pageShopWithdrawCash(page, shopWithdrawCash));
    }
    @GetMapping("/exportShopWithdrawCash")
    @Operation(summary = "导出商家提现信息" , description = "导出商家提现信息")
    public void exportShopWithdrawCash(@ParameterObject ShopWithdrawCash shopWithdrawCash, HttpServletResponse response) {
        shopWithdrawCashService.exportShopWithdrawCash(shopWithdrawCash, response);
    }
    @GetMapping("/info/{cashId}")
    @Operation(summary = "查询商家提现申请信息" , description = "查询商家提现申请信息")
    @Parameter(name = "cashId", description = "商家提现申请id" )
    public ServerResponseEntity<ShopWithdrawCash> getById(@PathVariable("cashId") Long cashId) {
        return ServerResponseEntity.success(shopWithdrawCashService.getById(cashId));
    }
    @SysLog("审核商家提现信息")
    @PutMapping("/audit")
    @PreAuthorize("@pms.hasPermission('shop:shopWithdrawCash:audit')")
    @Operation(summary = "审核信息" , description = "审核信息")
    public ServerResponseEntity<Void> audit(@RequestBody ShopWithdrawCash shopWithdrawCash) {
        ShopWithdrawCash dbShopWithdrawCash = shopWithdrawCashService.getById(shopWithdrawCash.getCashId());
        if (dbShopWithdrawCash == null) {
            // 未找到申请信息
            throw new YamiShopBindException("yami.store.apply.no.exist");
        }
        ShopDetail shopdetail = shopDetailService.getShopDetailByShopId(dbShopWithdrawCash.getShopId());
        if (shopdetail == null) {
            // 未找到该店铺信息
            throw new YamiShopBindException("yami.store.not.exist");
        }
        shopWithdrawCash.setCreateTime(null);
        shopWithdrawCashService.auditWithdrawCash(shopWithdrawCash.getCashId(), shopWithdrawCash, SecurityUtils.getSysUser().getUserId(), null);
        // 减少冻结金额
        return ServerResponseEntity.success();
    }
    @PostMapping("/save")
    @Operation(summary = "设置提现金额" , description = "设置提现金额")
    public ServerResponseEntity<Void> saveWithdrawCashConfig(@RequestBody ShopWithdrawCashConfigDto shopWithdrawCashDto) {
        shopWithdrawCashService.saveConfig(shopWithdrawCashDto);
        return ServerResponseEntity.success();
    }
    @GetMapping("/getWithdrawCash")
    public ServerResponseEntity<ShopWithdrawCashConfigDto> getWithdrawCashConfig(){
        return ServerResponseEntity.success(shopWithdrawCashService.getConfig());
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/SigningAuditingController.java
New file
@@ -0,0 +1,112 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.dto.BrandShopDTO;
import com.yami.shop.bean.model.*;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.platform.util.SecurityUtils;
import com.yami.shop.service.SigningAuditingService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * @Author lth
 * @Date 2021/8/19 13:51
 */
@RestController
@RequestMapping("/platform/signingAuditing")
@Tag(name = "签约信息相关接口")
public class SigningAuditingController {
    @Autowired
    private SigningAuditingService signingAuditingService;
    @GetMapping("/listApplySigningCategory")
    @Operation(summary = "获取可以签约的平台分类列表(已经签约的平台分类不会返回)" , description = "获取可以签约的平台分类列表(已经签约的平台分类不会返回)")
    @Parameter(name = "shopId", description = "店铺id" )
    public ServerResponseEntity<List<Category>> listApplySigningCategory(@RequestParam("shopId") Long shopId) {
        List<Category> categoryList = signingAuditingService.listApplySigningCategory(shopId, I18nMessage.getLang());
        return ServerResponseEntity.success(categoryList);
    }
    @GetMapping("/listApplySigningBrand")
    @Operation(summary = "获取可以签约的平台品牌列表(已经签约的平台品牌不会返回)" , description = "获取可以签约的平台品牌列表(已经签约的平台品牌不会返回)")
    public ServerResponseEntity<List<Brand>> listApplySigningBrand(@ParameterObject Brand brand) {
        Long shopId = brand.getShopId();
        if (Objects.isNull(shopId)) {
            return ServerResponseEntity.success(new ArrayList<>());
        }
        brand.setShopId(null);
        List<Brand> brandList = signingAuditingService.listApplySigningBrand(shopId, brand, I18nMessage.getLang());
        return ServerResponseEntity.success(brandList);
    }
    @GetMapping("/page")
    @Operation(summary = "分页获取待审核的签约信息" , description = "分页获取待审核的签约信息")
    public ServerResponseEntity<IPage<SigningAuditing>> page(PageParam<SigningAuditing> page, @ParameterObject SigningAuditing signingAuditing) {
        IPage<SigningAuditing> signingAuditingPage = signingAuditingService.pageSigningAuditing(page, signingAuditing);
        return ServerResponseEntity.success(signingAuditingPage);
    }
    @PutMapping("/audit")
    @Operation(summary = "审核签约信息" , description = "审核签约信息")
    public ServerResponseEntity<Void> audit(@Valid @RequestBody SigningAuditing signingAuditing) {
        signingAuditing.setAuditorId(SecurityUtils.getSysUser().getUserId());
        signingAuditingService.audit(signingAuditing);
        return ServerResponseEntity.success();
    }
    @PutMapping("/updateCategoryRate")
    @Operation(summary = "更新店铺签约分类自定义扣率" , description = "更新店铺签约分类自定义扣率")
    @Parameters(value = {
            @Parameter(name = "categoryShopId", description = "店铺签约分类id" ),
            @Parameter(name = "rate", description = "扣率" )
    })
    public ServerResponseEntity<Void> updateCategoryRate(@RequestParam("categoryShopId") Long categoryShopId, @RequestParam("rate") Double rate) {
        signingAuditingService.updateCategoryRate(categoryShopId, rate);
        return ServerResponseEntity.success();
    }
    @PostMapping("/addSigningCategory")
    @Operation(summary = "增加签约分类" , description = "增加签约分类")
    @Parameters(value = {
            @Parameter(name = "categoryShopList", description = "店铺签约分类id列表" ),
            @Parameter(name = "shopId", description = "店铺id" )
    })
    public ServerResponseEntity<Void> addSigningCategory(@RequestBody List<CategoryShop> categoryShopList, @RequestParam("shopId") Long shopId) {
        signingAuditingService.addSigningCategory(categoryShopList, shopId);
        return ServerResponseEntity.success();
    }
    @PostMapping("/addSigningBrand")
    @Operation(summary = "增加签约品牌" , description = "增加签约品牌")
    @Parameters(value = {
            @Parameter(name = "brandShopList", description = "店铺签约品牌id列表" ),
            @Parameter(name = "shopId", description = "店铺id" )
    })
    public ServerResponseEntity<Void> addSigningBrand(@RequestBody List<BrandShopDTO> brandShopList, @RequestParam("shopId") Long shopId) {
        signingAuditingService.addSigningBrand(brandShopList, shopId);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/SkuController.java
New file
@@ -0,0 +1,46 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.model.Sku;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.SkuService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author yami
 */
@RestController
@RequestMapping("/platform/sku")
@AllArgsConstructor
@Tag(name = "商品sku")
public class SkuController {
    private final SkuService skuService;
    @GetMapping("/getAllSkuList")
    @PreAuthorize("@pms.hasPermission('plateform:sku:list')")
    @Operation(summary = "获取指定商品sku列表" , description = "获取指定商品sku列表")
    @Parameter(name = "prodId", description = "商品id" )
    public ServerResponseEntity<List<Sku>> getSkuListByProdId(Long prodId) {
        List<Sku> skus = skuService.listSkuAndSkuStock(prodId, I18nMessage.getDbLang());
        return ServerResponseEntity.success(skus);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/SpecController.java
New file
@@ -0,0 +1,102 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
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.config.Constant;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.ProdPropService;
import com.yami.shop.service.ProdPropValueService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
 * 规格管理
 *
 * @author lgh
 */
@RestController
@RequestMapping("/prod/spec")
@Tag(name = "规格")
public class SpecController {
    @Autowired
    private ProdPropService prodPropService;
    @Autowired
    private ProdPropValueService prodPropValueService;
    @GetMapping("/list")
    @Operation(summary = "获取所有的规格" , description = "获取所有的规格")
    public ServerResponseEntity<List<ProdProp>> list() {
//        List<ProdProp> list = prodPropService.list(new LambdaQueryWrapper<ProdProp>()
//                .eq(ProdProp::getRule, ProdPropRule.SPEC.value()).
//                eq(ProdProp::getShopId, Constant.PLATFORM_SHOP_ID));
        ProdProp prodProp = new ProdProp();
        prodProp.setShopId( Constant.PLATFORM_SHOP_ID);
        prodProp.setRule(ProdPropRule.SPEC.value());
        List<ProdProp> list = prodPropService.listByLang(prodProp);
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/page")
    @Operation(summary = "分页获取" , description = "分页获取")
    public ServerResponseEntity<IPage<ProdProp>> page(@ParameterObject ProdProp prodProp, PageParam<ProdProp> page) {
        prodProp.setRule(ProdPropRule.SPEC.value());
        prodProp.setShopId( Constant.PLATFORM_SHOP_ID);
        IPage<ProdProp> list = prodPropService.pagePropAndValue(prodProp, page);
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/listSpecValue/{specId}")
    @Operation(summary = "根据规格id获取规格值" , description = "根据规格id获取规格值")
    @Parameter(name = "specId", description = "规格id" )
    public ServerResponseEntity<List<ProdPropValue>> listSpecValue(@PathVariable("specId") Long specId) {
//        List<ProdPropValue> list = prodPropValueService.list(new LambdaQueryWrapper<ProdPropValue>().eq(ProdPropValue::getPropId, specId))
        List<ProdPropValue> list = prodPropValueService.propValueListByPropId(specId);
        return ServerResponseEntity.success(list);
    }
    @PostMapping
    @Operation(summary = "保存" , description = "保存")
    public ServerResponseEntity<Void> save(@Valid @RequestBody ProdProp prodProp) {
        prodProp.setRule(ProdPropRule.SPEC.value());
        prodProp.setShopId(Constant.PLATFORM_SHOP_ID);
        prodPropService.saveProdPropAndValues(prodProp);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "修改" , description = "修改")
    public ServerResponseEntity<Void> update(@Valid @RequestBody ProdProp prodProp) {
        prodProp.setRule(ProdPropRule.SPEC.value());
        prodPropService.updateProdPropAndValues(prodProp);
        return ServerResponseEntity.success();
    }
    @DeleteMapping("/{id}")
    @Operation(summary = "删除" , description = "删除")
    @Parameter(name = "id", description = "规格id" )
    public ServerResponseEntity<Void> delete(@PathVariable Long id) {
        prodPropService.deleteProdPropAndValues(id, ProdPropRule.SPEC.value(), Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/StatisticsController.java
New file
@@ -0,0 +1,83 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.yami.shop.bean.vo.statistics.HotStatisticsVO;
import com.yami.shop.bean.vo.statistics.PlatformStatisticsVO;
import com.yami.shop.bean.vo.statistics.TrendStatisticsVO;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.StatisticsService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
/**
 * @author chiley
 * @create 2022/5/16 15:34
 */
@RestController
@RequestMapping("/platform/statistics")
@AllArgsConstructor
@Tag(name = "平台统计信息")
public class StatisticsController {
    private final StatisticsService statisticsService;
    @GetMapping("/getPlatformStatistics")
    @Operation(summary = "获取主页基本信息、今日待办")
    public ServerResponseEntity<PlatformStatisticsVO> getPlatformStatistics() {
        return ServerResponseEntity.success(statisticsService.getPlatformStatistics());
    }
    @GetMapping("/platformRealTimeOverview")
    @Operation(summary = "获取主页实时概括")
    @Parameter(name = "startTime", description = "开始时间" )
    public ServerResponseEntity<PlatformStatisticsVO> platformRealTimeOverview(@DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam("startTime") Date startTime) {
        return ServerResponseEntity.success(statisticsService.platformRealTimeOverview(startTime));
    }
    @GetMapping("/prod")
    @Operation(summary = "获取某段时间内的商品销量排行")
    public ServerResponseEntity<List<HotStatisticsVO>> getHotProds(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("startTime") Date startTime,
                                                             @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("endTime") Date endTime) {
        return ServerResponseEntity.success(statisticsService.loadHotProdByDate(startTime, endTime));
    }
    @GetMapping("/shop")
    @Operation(summary = "获取某段时间内的店铺销量排行")
    public ServerResponseEntity<List<HotStatisticsVO>> getHotShops(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("startTime") Date startTime,
                                                             @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("endTime") Date endTime) {
        return ServerResponseEntity.success(statisticsService.loadHotShopByDate(startTime, endTime));
    }
    @GetMapping("/trendData")
    @Operation(summary = "获取某段时间内的交易数据")
    public ServerResponseEntity<List<TrendStatisticsVO>> getTrendData(@DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam("startTime") Date startTime,
                                                                @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam("endTime") Date endTime) {
        return ServerResponseEntity.success(statisticsService.loadDataTrend(null, startTime, endTime));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/StatisticsOrderController.java
New file
@@ -0,0 +1,107 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.date.DateUtil;
import com.yami.shop.bean.app.dto.OrderCountData;
import com.yami.shop.bean.dto.StatisticsRefundDto;
import com.yami.shop.bean.param.OrderPayParam;
import com.yami.shop.bean.param.StatisticsRefundParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.StatisticsOrderService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author lhd on 2019/10/19.
 */
@RestController
@RequestMapping("/platform/statisticsOrder")
@AllArgsConstructor
@Tag(name = "platform")
public class StatisticsOrderController {
    private final StatisticsOrderService statisticsOrderService;
    @GetMapping("/orderCount")
    @Operation(summary = "查询店铺订单各状态数量" , description = "查询店铺订单各状态数量")
    public ServerResponseEntity<OrderCountData> getOrderCount() {
        OrderCountData orderCountData = statisticsOrderService.getOrderCountOfStatusByShopId(null);
        return ServerResponseEntity.success(orderCountData);
    }
    @GetMapping("/orderPayByShopId")
    @Operation(summary = "通过时间获取支付信息")
    public ServerResponseEntity<OrderPayParam> orderPayByShopId() {
        OrderPayParam actualTotal = statisticsOrderService.getPayUserCountByshopId(null,DateUtil.beginOfDay(DateUtil.date()), DateUtil.endOfDay(DateUtil.date()));
        return ServerResponseEntity.success(actualTotal);
    }
    @GetMapping("/getActualTotalByHour")
    @Operation(summary = "通过24小时分段获取支付金额")
    public ServerResponseEntity<OrderPayParam> getActualTotalByHour() {
        OrderPayParam payList = statisticsOrderService.getActualTotalByHour(
                null,DateUtil.beginOfDay(DateUtil.date()),DateUtil.endOfDay(DateUtil.date()));
        return ServerResponseEntity.success(payList);
    }
    @GetMapping("/getActualTotalByDay")
    @Operation(summary = "通过天数分段获取支付金额")
    public ServerResponseEntity<List<OrderPayParam>> getActualTotalByDay() {
        List<OrderPayParam> payList = statisticsOrderService.getActualTotalByDay(
                null,DateUtil.endOfDay(DateUtil.lastMonth()),DateUtil.endOfDay(DateUtil.date()));
        return ServerResponseEntity.success(payList);
    }
    @GetMapping("/getOrderRefundByTime")
    @Operation(summary = "通过时间获取比率信息")
    public ServerResponseEntity<StatisticsRefundParam> getOrderRefundByTime() {
        StatisticsRefundParam refundParam = statisticsOrderService.getOrderRefundByShopId(
                null,DateUtil.beginOfDay(DateUtil.date()), DateUtil.endOfDay(DateUtil.date()));
        return ServerResponseEntity.success(refundParam);
    }
    @GetMapping("/getOrderRefundDayByTime")
    @Operation(summary = "通过时间获取分段比率信息及退款金额信息")
    public ServerResponseEntity<List<StatisticsRefundParam>> getOrderRefundById() {
        List<StatisticsRefundParam> refundList = statisticsOrderService.getOrderRefundByShopIdAndDay(
                null,DateUtil.endOfDay(DateUtil.lastMonth()),DateUtil.endOfDay(DateUtil.date()));
        return ServerResponseEntity.success(refundList);
    }
    @GetMapping("/getRefundRankingByProd")
    @Operation(summary = "根据商品名生成退款排行")
    public ServerResponseEntity<List<StatisticsRefundParam>> getRefundRankingByProd() {
        /*
         * 如果一个订单有多个商品的,整单退款,用户输入退款金额少于支付金额,
         * 无法确认某个商品的准确退款金额,所以就用整个订单来代替
         * 例如:订单中 商品A 100 元,商品B 8元,共支付108元,整单退款,退款金额为90元,
         * 所以商品的退款金额在这个时候是模糊的,不能准确统计
         */
        List<StatisticsRefundParam> refundList = statisticsOrderService.getRefundRankingByProd(new StatisticsRefundDto());
        return ServerResponseEntity.success(refundList);
    }
    @GetMapping("/getRefundRankingByReason")
    @Operation(summary = "根据退款原因生成退款排行")
    public ServerResponseEntity<List<StatisticsRefundParam>> getRefundRankingByReason() {
        List<StatisticsRefundParam> refundList = statisticsOrderService.getRefundRankingByReason(
                null,DateUtil.endOfDay(DateUtil.lastMonth()),DateUtil.endOfDay(DateUtil.date()));
        return ServerResponseEntity.success(refundList);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/StockChangeReasonController.java
New file
@@ -0,0 +1,104 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.StockChangeReason;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.StockChangeReasonService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
 *
 * 出入库原因
 * @author LGH
 * @date 2021-09-07 16:04:18
 */
@RestController
@AllArgsConstructor
@RequestMapping("/platform/stockChangeReason" )
@Tag(name = "platform")
public class StockChangeReasonController {
    private final StockChangeReasonService stockChangeReasonService;
    @GetMapping("/page" )
    @Operation(summary = "分页获取出入库原因" , description = "分页获取出入库原因")
    public ServerResponseEntity<IPage<StockChangeReason>> getStockChangeReasonPage(PageParam<StockChangeReason> page, @ParameterObject StockChangeReason stockChangeReason) {
        stockChangeReason.setShopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success(stockChangeReasonService.pageByParams(page, stockChangeReason));
    }
    @GetMapping("/list")
    @Schema(description = "获取出入库原因列表" )
    public ServerResponseEntity<List<StockChangeReason>> listResponse(@ParameterObject StockChangeReason stockChangeReason) {
        stockChangeReason.setShopId(Constant.PLATFORM_SHOP_ID);
        return ServerResponseEntity.success(stockChangeReasonService.listByParams(stockChangeReason));
    }
    @GetMapping("/info/{stockChangeReasonId}" )
    @Operation(summary = "根据id查询出入库原因" , description = "根据id查询出入库原因")
    public ServerResponseEntity<StockChangeReason> getById(@PathVariable("stockChangeReasonId") Long stockChangeReasonId) {
        Long shopId = Constant.PLATFORM_SHOP_ID;
        StockChangeReason stockChangeReason = stockChangeReasonService.getOne(Wrappers.lambdaQuery(StockChangeReason.class)
                .eq(StockChangeReason::getStockChangeReasonId, stockChangeReasonId)
                .eq(StockChangeReason::getShopId, shopId)
        );
        stockChangeReason.setShopId(null);
        return ServerResponseEntity.success(stockChangeReason);
    }
    @PostMapping
    @Operation(summary = "新增出入库原因" , description = "新增出入库原因")
    public ServerResponseEntity<Void> save(@RequestBody @Valid StockChangeReason stockChangeReason) {
        stockChangeReason.setShopId(Constant.PLATFORM_SHOP_ID);
        stockChangeReasonService.saveInfo(stockChangeReason);
        return ServerResponseEntity.success();
    }
    @PutMapping
    @Operation(summary = "修改出入库原因" , description = "修改出入库原因")
    public ServerResponseEntity<Void> updateById(@RequestBody @Valid StockChangeReason stockChangeReason) {
        stockChangeReason.setShopId(Constant.PLATFORM_SHOP_ID);
        stockChangeReasonService.updateInfo(stockChangeReason);
        return ServerResponseEntity.success();
    }
    @PutMapping("/changeStatus" )
    @Schema(description = "修改出入库原因状态" )
    @Parameters(value = {
            @Parameter(name = "stockChangeReasonId", description = "出入库原因id" ),
            @Parameter(name = "status", description = "状态,1:启用 0:禁用" )
    })
    public ServerResponseEntity<Void> removeById(@RequestParam("stockChangeReasonId") Long stockChangeReasonId, @RequestParam("status") Integer status) {
        Long shopId = Constant.PLATFORM_SHOP_ID;
        stockChangeReasonService.update(Wrappers.lambdaUpdate(StockChangeReason.class)
                .set(StockChangeReason::getStatus, status)
                .eq(StockChangeReason::getStockChangeReasonId, stockChangeReasonId)
                .eq(StockChangeReason::getShopId, shopId)
        );
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/TaskRuleController.java
New file
@@ -0,0 +1,95 @@
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.task.common.model.Task;
import com.yami.shop.task.common.model.TaskRule;
import com.yami.shop.task.common.service.TaskRuleService;
import com.yami.shop.task.common.service.TaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
 * @author tz_task_user
 */
@RestController
    @RequestMapping("/platform/taskRule")
@Tag(name = "任务规则表接口")
@AllArgsConstructor
public class TaskRuleController {
    @Resource
    private TaskRuleService taskRuleService;
    @Resource
    private TaskService taskService;
    @GetMapping("/page")
    @Operation(summary = "分页查询任务规则表列表信息")
    public ServerResponseEntity<IPage<TaskRule>> getTaskRulePage(PageParam<TaskRule> page, TaskRule taskRule) {
        IPage<TaskRule> resultPage = taskRuleService.getTaskRulePage(page,taskRule );
        return ServerResponseEntity.success(resultPage);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "通过任务规则表id查询任务规则表信息")
    @Parameter(name = "id", description = "任务规则表id" , required = true)
    public ServerResponseEntity<TaskRule> getTaskRuleById(@PathVariable("id") Long id) {
        TaskRule card = taskRuleService.getById(id);
        return ServerResponseEntity.success(card);
    }
    @PostMapping("/saveTaskRule")
    @Operation(summary = "新增任务规则表信息")
    public ServerResponseEntity<String> saveTaskRule(@RequestBody TaskRule taskRule) {
        Integer num = taskRuleService.saveTaskRule(taskRule);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
    @PostMapping("/updateTaskRule")
    @Operation(summary = "修改任务规则表信息")
    public ServerResponseEntity<String> updateTaskRule(@RequestBody TaskRule taskRule) {
        Integer num = taskRuleService.updateTaskRule(taskRule);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
    @DeleteMapping("/{id}")
    @Operation(summary = "删除任务规则信息")
    public ServerResponseEntity<String> deleteTaskRule(@PathVariable("id") Long ruleId) {
        int num =taskRuleService.deleteTaskRule(ruleId);
        if(num>0){
            return ServerResponseEntity.success();
        }
        return ServerResponseEntity.showFailMsg("删除任务规则信息失败!");
    }
    @GetMapping("/selectStatusByTaskId")
    @Operation(summary = "根据任务Id获取任务状态")
    public ServerResponseEntity<List<TaskRule>> selectStatusByTaskId(@RequestParam(value = "taskId") Long taskId) {
        List<TaskRule> list = taskRuleService.selectStatusByTaskId(taskId);
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/selectTasks")
    @Operation(summary = "查询状态在启动状态下的任务列表")
    public ServerResponseEntity<List<Task>> selectTasks(Task task) {
        List<Task> list = taskService.getTaskList(task);
        return ServerResponseEntity.success(list);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/TaskUserController.java
New file
@@ -0,0 +1,82 @@
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.task.common.model.Task;
import com.yami.shop.task.common.model.TaskUser;
import com.yami.shop.task.common.service.TaskService;
import com.yami.shop.task.common.service.TaskUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
 * @author tz_task_user
 */
@RestController
@RequestMapping("/platform/taskUser")
@Tag(name = "用户任务表接口")
@AllArgsConstructor
public class TaskUserController {
    @Resource
    private TaskUserService taskUserService;
    @Resource
    private TaskService taskService;
    @GetMapping("/page")
    @Operation(summary = "分页查询用户任务表列表信息")
    public ServerResponseEntity<IPage<TaskUser>> getTaskUserPage(PageParam<TaskUser> page, TaskUser TaskUser) {
        IPage<TaskUser> resultPage = taskUserService.getTaskUserPage(page,TaskUser );
        return ServerResponseEntity.success(resultPage);
    }
    @GetMapping("/info/{id}")
    @Operation(summary = "通过用户任务表id查询用户任务表信息")
    @Parameter(name = "id", description = "用户任务表id" , required = true)
    public ServerResponseEntity<TaskUser> getTaskUserById(@PathVariable("id") Long id) {
        TaskUser card = taskUserService.getById(id);
        Task task =taskService.getById(card.getTaskId());
        card.setTaskTitle(task.getTaskTitle());
        return ServerResponseEntity.success(card);
    }
    @PostMapping("/saveTaskUser")
    @Operation(summary = "新增用户任务表信息")
    public ServerResponseEntity<String> saveTaskUser(@RequestBody TaskUser taskUser) {
        Integer num = taskUserService.saveTaskUser(taskUser);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
    @PostMapping("/updateTaskUser")
    @Operation(summary = "修改用户任务表信息")
    public ServerResponseEntity<String> updateTaskUser(@RequestBody TaskUser taskUser) {
        Integer num = taskUserService.updateTaskUser(taskUser);
        if(num >0 ){
            return ServerResponseEntity.success(ResponseEnum.OK.getMsg());
        }else{
            return ServerResponseEntity.showFailMsg(ResponseEnum.ERROR.getMsg());
        }
    }
    @GetMapping("/deleteTaskUser/{id}")
    @Operation(summary = "删除用户任务信息")
    public ServerResponseEntity<?> deleteTaskUser(@PathVariable("id") Long id) {
        int num =taskUserService.deleteTaskUser(id);
        if(num>0){
            return ServerResponseEntity.success();
        }
        return ServerResponseEntity.showFailMsg("删除用户任务信息失败!");
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/TestController.java
New file
@@ -0,0 +1,70 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
 * @author yxf on 2022/11/25.
 */
@RestController
@Tag(name = "测试接口-商城代码不要用,给自动化测试用的")
@RequestMapping("/platform/test")
public class TestController {
    @Autowired
    private OrderService orderService;
    @Autowired
    private OrderItemService orderItemService;
    @Autowired
    private ProductService productService;
    @Autowired
    private SkuService skuService;
    @PostMapping("/orderSettlement")
    @Operation(summary = "订单结算" , description = "订单结算")
    public ServerResponseEntity<Void> settlement(@RequestBody Long orderId) {
        Date date = DateUtil.offsetDay(new Date(), -20).toJdkDate();
        Order order = new Order();
        order.setOrderId(orderId);
        order.setUpdateTime(date);
        order.setFinallyTime(date);
        orderService.updateById(order);
        order = orderService.getById(orderId);
        List<OrderItem> orderItems = orderItemService.list(new LambdaQueryWrapper<OrderItem>().eq(OrderItem::getOrderNumber, order.getOrderNumber()));
        order.setOrderItems(orderItems);
        List<Order> orderList = Collections.singletonList(order);
        // 结算
        orderService.orderCommissionSettlement(orderList);
        // 移除缓存
        for (OrderItem orderItem : orderItems) {
            productService.removeProdCacheByProdId(orderItem.getProdId());
            skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
        }
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/UserAddrController.java
New file
@@ -0,0 +1,49 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.model.UserAddr;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.UserAddrService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author lgh on 2018/10/16.
 */
@RestController
@RequestMapping("/platform/userAddr")
@Tag(name = "用户地址")
public class UserAddrController {
    @Autowired
    private UserAddrService userAddrService;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('plateform:user:page')")
    @Operation(summary = "分页获取" , description = "分页获取")
    public ServerResponseEntity<List<UserAddr>> page(@ParameterObject UserAddr userAddr, PageParam<UserAddr> page) {
        List<UserAddr> userAddrPage = userAddrService.list(new LambdaQueryWrapper<UserAddr>()
                .eq(UserAddr::getUserId, userAddr.getUserId()));
        return ServerResponseEntity.success(userAddrPage);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/UserController.java
New file
@@ -0,0 +1,472 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiniu.util.StringUtils;
import com.yami.shop.bean.model.User;
import com.yami.shop.bean.param.*;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.i18n.LanguageEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.Json;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.PoiExcelUtil;
import com.yami.shop.common.util.PrincipalUtil;
import com.yami.shop.coupon.common.service.CouponUserService;
import com.yami.shop.platform.task.AnalysisSalseTask;
import com.yami.shop.security.common.enums.SysTypeEnum;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.security.common.service.AppConnectService;
import com.yami.shop.service.UserService;
import com.yami.shop.user.common.model.UserLevel;
import com.yami.shop.user.common.service.UserLevelService;
import com.yami.shop.user.common.service.UserTagUserService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Sheet;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * @author lgh on 2018/10/16.
 */
@RestController
@RequestMapping("/admin/user")
@RequiredArgsConstructor
@Tag(name = "用户")
public class UserController {
    @Value("${yami.expose.operation.auth:}")
    private Boolean permission;
    @Autowired
    private UserService userService;
    @Autowired
    private AppConnectService appConnectService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private UserTagUserService userTagUserService;
    @Autowired
    private CouponUserService couponUserService;
    @Autowired
    private UserLevelService userLevelService;
    @Autowired
    private MapperFacade mapperFacade;
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('plateform:user:page')")
    @Operation(summary = "分页获取用户基本信息", description = "分页获取用户基本信息")
    public ServerResponseEntity<IPage<User>> page(@ParameterObject User user, PageParam<User> page) {
        IPage<User> userPage = userService.getUserPage(page, user);
        return ServerResponseEntity.success(userPage);
    }
    @GetMapping("/list")
    @Operation(summary = "获取用户列表", description = "获取用户列表")
    public ServerResponseEntity<List<User>> list(@ParameterObject String nameOrPhone, String userIds) {
        if (StringUtils.isNullOrEmpty(userIds)){
            throw new YamiShopBindException("yami.pcdn.device.userId.null");
        }
        List<User> userList = userService.list(nameOrPhone, userIds);
        return ServerResponseEntity.success(userList);
    }
    @GetMapping("/info/{userId}")
    @PreAuthorize("@pms.hasPermission('plateform:user:info')")
    @Operation(summary = "获取信息", description = "获取信息")
    public ServerResponseEntity<UserManagerParam> info(@PathVariable("userId") String userId) {
        UserManagerParam param = userService.getuserInfo(userId);
        List<UserTagParam> userTagParams = userTagUserService.getUserTagsUserByUserId(userId);
        param.setUserTagParam(userTagParams);
        CouponUserParam couponUser = couponUserService.getCouponCountByUserId(userId);
        param.setCouponUserParam(couponUser);
        return ServerResponseEntity.success(param);
    }
    @PutMapping
    @PreAuthorize("@pms.hasPermission('plateform:user:update')")
    @Operation(summary = "修改", description = "修改")
    public ServerResponseEntity<Void> update(@RequestBody User user) {
        user.setModifyTime(new Date());
        user.setNickName(user.getNickName());
        userService.updateById(user);
        tokenStore.deleteAllToken(SysTypeEnum.ORDINARY.value().toString(), user.getUserId());
        return ServerResponseEntity.success();
    }
    @GetMapping("/userPage")
    @Operation(summary = "分页获取用户及扩展信息", description = "分页获取用户及扩展信息")
    public ServerResponseEntity<IPage<UserManagerParam>> userPage(PageParam<User> page, @ParameterObject UserManagerReqParam user) {
        IPage<UserManagerParam> userPage = userService.getUserInfoPage(page, user);
        return ServerResponseEntity.success(userPage);
    }
    @GetMapping("/importUserModel")
    @Operation(summary = "用户导出模板", description = "用户导出模板")
    public void importRetailProdModel(HttpServletResponse response) {
        // 用户导出模板
        userService.downloadUserModel(response);
    }
    @Operation(summary = "导入文件", description = "导入文件")
    @PostMapping("/importExcel")
    @ResponseBody
    @PreAuthorize("@pms.hasPermission('platform:user:import')")
    public ServerResponseEntity importExcel(@RequestParam("excelFile") MultipartFile excelFile) throws Exception {
        if (Objects.isNull(excelFile)) {
            // 网络繁忙,请稍后重试
            throw new YamiShopBindException("yami.network.busy");
        }
        // 需要判断成长值所在等级
        List<UserLevel> list = userLevelService.list();
        List<UserLevelParam> userLevels = mapperFacade.mapAsList(list, UserLevelParam.class);
        UserExcelParam param = userService.parseUserImportFile(excelFile, userLevels);
        if (param.getImmediately()) {
            return ServerResponseEntity.success(param.getMsg());
        }
        Integer registerNum = 0;
        //重复数据的set
        Set<String> repeatPhoneSet = new HashSet<>();
        Set<String> repeatMailSet = new HashSet<>();
        if (param.getSuccess()) {
            // 获取到参数
            List<UserRegisterExcelParam> userList = Json.parseArray(param.getParam(), UserRegisterExcelParam[].class);
            // 批量注册用户
            registerNum = appConnectService.batchRegisterUser(param, userList, repeatPhoneSet, repeatMailSet);
        }
        Integer lang = I18nMessage.getDbLang();
        param.setMsg(getMsg(param, registerNum, repeatPhoneSet, repeatMailSet, lang));
        return ServerResponseEntity.success(param.getMsg());
    }
    @GetMapping("/exportUser")
    @Operation(summary = "导出用户信息", description = "导出用户信息")
    public void exportUser(@ParameterObject UserManagerReqParam user, HttpServletResponse response) {
        // 开始时间
        long start = System.currentTimeMillis();
        PageParam<User> userPageParam = new PageParam<>();
        userPageParam.setCurrent(1);
        userPageParam.setSize(10L);
        IPage<UserManagerParam> userPage = userService.getUserInfoPage(userPageParam, user);
        // 总共有多少条数据
        Long total = userPage.getTotal();
        // 用户有很多,考虑2000条以上数据的导出 一个最多104w 行数据
        Long rowMaxCount = 500000L;
        // 每一次查询条数
        Long eachCount = 1000L;
        // 这里不用PageParam<User> ,为了方便自由调整查询条数
        Page<User> pages = new Page<>();
        pages.setCurrent(1);
        List<UserManagerParam> list = new ArrayList<>();
        // 查询记录数
        //通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getBigWriter();
        //
        String fileName = I18nMessage.getMessage("yami.excel.user.info") + ".xlsx";
        if (total <= rowMaxCount) {
            if (total <= eachCount) {
                pages.setSize(total);
                userPage = userService.getUserInfoPage(pages, user);
                exportExcel(userPage.getRecords(), 1, 1, response, writer);
                // 导出
                PoiExcelUtil.writeExcel(fileName, writer, response);
            } else {
                // 开启多线程查询,每eachCount(100)行数据为一个线程开始
                int pageSize = getPageSize(total, eachCount);
//                ExecutorService execservice = new ThreadPoolExecutor(4,10,200L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(10));
                ExecutorService execservice = Executors.newFixedThreadPool(15);
                try {
                    List<Callable<List<UserManagerParam>>> tasks = new ArrayList<Callable<List<UserManagerParam>>>();
                    for (int i = 1; i <= pageSize; i++) {
                        Page<User> pagesIndex = new Page<>();
                        pagesIndex.setCurrent(i);
                        pagesIndex.setSize(eachCount);
                        Callable<List<UserManagerParam>> task = new AnalysisSalseTask(userService, user, pagesIndex);
                        tasks.add(task);
                    }
                    List<Future<List<UserManagerParam>>> futures = execservice.invokeAll(tasks);
                    if (futures != null && futures.size() > 0) {
                        for (Future<List<UserManagerParam>> future : futures) {
                            list.addAll(future.get());
                        }
                    }
                    execservice.shutdown();
                    long end = System.currentTimeMillis();
                    System.out.println("线程查询数据用时:" + (end - start) + "ms");
                } catch (Exception e) {
                    System.out.println("多线程查询异常");
                }
                userPage.setRecords(list);
                exportExcel(list, 1, 1, response, writer);
                // 导出
                PoiExcelUtil.writeExcel(fileName, writer, response);
                list.clear();
            }
        } else {
            // 分多少个 sheet
            exportBigData(user, response, total, writer, fileName);
        }
    }
    private void exportBigData(UserManagerReqParam user, HttpServletResponse response, Long total, ExcelWriter writer, String fileName) {
        Long rowMaxCount = 500000L;
        List<UserManagerParam> list = new ArrayList<>();
        Long eachCount;
        int pageSize = getPageSize(total, rowMaxCount);
        eachCount = 1000L;
        List<String> filepaths = new ArrayList<>();
        for (int i = 1; i <= pageSize; i++) {
            list.clear();
            int size = getPageSize(rowMaxCount, eachCount);
            try {
                ExecutorService execservice = Executors.newFixedThreadPool(15);
                List<Callable<List<UserManagerParam>>> tasks = new ArrayList<Callable<List<UserManagerParam>>>();
                for (int j = 1; j <= size; j++) {
                    Page<User> pagesIndex = new Page<>();
                    pagesIndex.setSize(eachCount);
                    pagesIndex.setCurrent((i - 1) * size + j);
                    Callable<List<UserManagerParam>> task = new AnalysisSalseTask(userService, user, pagesIndex);
                    tasks.add(task);
                }
                List<Future<List<UserManagerParam>>> futures = execservice.invokeAll(tasks);
                if (futures.size() > 0) {
                    for (Future<List<UserManagerParam>> future : futures) {
                        list.addAll(future.get());
                    }
                }
                tasks.clear();
                execservice.shutdown();
            } catch (Exception e) {
                System.out.println("多线程查询异常");
            }
            // 序号 (i-1) * rowMaxCount + 1
            int rowStart = new BigDecimal(i - 1).multiply(new BigDecimal(rowMaxCount)).add(new BigDecimal(1)).intValue();
            //方法1: 导出到一个临时文件,
            // 然后合并小于50W行的Excel到100W行为一个Excel文件,此处逃过,直接是100W行为一个文件
            // 然后将100W的每一个Excel文件进行压缩
            // 方法2:分多个sheet 导出
            // 此时达到 rowMaxCount 行数据 list 导出到excel
            exportExcel(list, rowStart, i - 1, response, writer);
            list.clear();
        }
        // 导出
        PoiExcelUtil.writeExcel(fileName, writer, response);
    }
    private int getPageSize(Long total, Long eachCount) {
        int pageSize = new BigDecimal(total).divide(new BigDecimal(eachCount), 1).intValue();
        int mod = new BigDecimal(total).divideAndRemainder(new BigDecimal(eachCount))[1].intValue();
        if (mod > 0) {
            pageSize = pageSize + 1;
        }
        return pageSize;
    }
    private String generateKey(Map<String, String> values) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
            byte[] bytes = digest.digest(values.toString().getBytes(StandardCharsets.UTF_8));
            return String.format("%032x", new BigInteger(1, bytes));
        } catch (NoSuchAlgorithmException nsae) {
            throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).", nsae);
        }
    }
    @NotNull
    private String getMsg(UserExcelParam param, Integer registerNum, Set<String> repeatPhoneSet, Set<String> repeatMailSet, Integer lang) {
        String msg;
        if (Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
            String str = CollectionUtils.isNotEmpty(param.getErrorRowInfos()) ? "错误数据信息" + Json.toJsonString(param.getErrorRowInfos()) : "";
            String repeatPhone = CollUtil.isNotEmpty(repeatPhoneSet) ? "重复的手机号" + repeatPhoneSet.toString() + "," : "";
            String repeatMail = CollUtil.isNotEmpty(repeatMailSet) ? "重复的邮箱" + repeatMailSet.toString() + "," : "";
            msg = "检查到" + param.getTotal() + "条用户,正确格式数据有" + param.getSuccessNum() + "条!错误" + param.getErrorNum() + "条用户信息!" +
                    (param.getSuccessNum() - registerNum) + "条数据已存在!" + repeatPhone + repeatMail + "注册成功" + registerNum + "条数据!" + str;
        } else {
            String str = CollectionUtils.isNotEmpty(param.getErrorRowInfos()) ? "Error data information" + Json.toJsonString(param.getErrorRowInfos()) : "";
            String repeatPhone = CollUtil.isNotEmpty(repeatPhoneSet) ? "Duplicate cell phone numbers" + repeatPhoneSet.toString() + "," : "";
            String repeatMail = CollUtil.isNotEmpty(repeatMailSet) ? "Duplicate mailboxes" + repeatMailSet.toString() + "," : "";
            msg = "Checked" + param.getTotal() + "user with" + param.getSuccessNum() + "piece of correctly formatted data! Error" + param.getErrorNum() + "user information!" +
                    (param.getSuccessNum() - registerNum) + "data already exists! " + repeatPhone + repeatMail + "Registration success" + registerNum + "data!" + str;
        }
        return msg;
    }
    /**
     * 商品导出or模板
     *
     * @param list     导出的数据
     * @param rowStart 开始的行数
     * @param pages    一共有多少页
     */
    private void exportExcel(List<UserManagerParam> list, int rowStart, int pages, HttpServletResponse response, ExcelWriter writer) {
        List<String> headerList = getHeadList();
        Sheet sheet = writer.getSheet();
        writer.merge(headerList.size() - 1, I18nMessage.getMessage("yami.excel.user.info"));
        writer.writeRow(headerList);
        for (int i = 0; i < headerList.size(); i++) {
            if (i == 19 || i == 20) {
                sheet.setColumnWidth(i, 30 * 256);
            } else {
                sheet.setColumnWidth(i, 20 * 256);
            }
        }
        // 如果要导出的数据为空,导出一个模板
        if (CollectionUtils.isEmpty(list)) {
            PoiExcelUtil.writeExcel(response, writer);
            return;
        }
        int row = rowStart;
        for (UserManagerParam param : list) {
            int firstRow = row + 1;
            int lastRow = row + 1;
            int col = -1;
            // 序号
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, rowStart++);
            // 用户昵称
            String nickName = Objects.isNull(param.getNickName()) ? "" : BooleanUtil.isFalse(permission) && PrincipalUtil.isMobile(param.getNickName()) ? PhoneUtil.hideBetween(param.getNickName()).toString() : param.getNickName();
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, nickName);
//            // 用户名称
//            String realName = Objects.isNull(param.getRealName())?"":param.getRealName();
//            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col,realName);
            // 联系电话
            String userMobile = Objects.isNull(param.getUserMobile()) ? "" : BooleanUtil.isFalse(permission) ? PhoneUtil.hideBetween(param.getUserMobile()).toString() : param.getUserMobile();
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, userMobile);
            // 会员等级
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getLevelName());
            // 会员类型
            String levelType;
            if (Objects.equals(I18nMessage.getDbLang(), LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
                levelType = param.getLevelType() == 0 ? "普通会员" : "付费会员";
            } else {
                levelType = param.getLevelType() == 0 ? "Ordinary member" : "Paid membership";
            }
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, levelType);
            // 用户积分
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getScore());
            // 状态
            String status;
            if (Objects.equals(I18nMessage.getDbLang(), LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
                status = param.getStatus() == 0 ? "禁用" : "正常";
            } else {
                status = param.getStatus() == 0 ? "Disable" : "Normal";
            }
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, status);
            // 消费金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getConsAmount());
            // 实付金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getActualAmount());
            // 消费次数
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getConsTimes());
            // 平均折扣
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getAverDiscount());
            // 充值金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getRechargeAmount());
            // 充值次数
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getRechargeTimes());
            // 退款金额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getAfterSaleAmount());
            // 退款次数
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getAfterSaleTimes());
            // 累计积分
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getSumScore());
            // 当前余额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getCurrentBalance());
            // 累计余额
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, param.getSumBalance());
            // 注册时间
            String regTime = "";
            if (Objects.nonNull(param.getUserRegtime())) {
                regTime = DateUtil.format(param.getUserRegtime(), "yyyy-MM-dd HH:mm:ss");
            }
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, regTime);
            // 最近消费时间
            String recTime = Objects.nonNull(param.getUserRegtime()) ? DateUtil.format(param.getReConsTime(), "yyyy-MM-dd HH:mm:ss") : "";
            PoiExcelUtil.mergeIfNeed(writer, firstRow, lastRow, ++col, col, recTime);
            row++;
        }
    }
    private List<String> getHeadList() {
        List<String> headerList;
        // ["序号","用户昵称","用户名称","联系电话","会员等级","会员类型","用户积分","状态","消费金额","实付金额",
        // "消费次数","平均折扣","充值金额","充值次数","退款金额","退款次数","累计积分","当前余额","累计余额","注册时间","最近消费时间"]
        String[] header = {
                I18nMessage.getMessage("yami.excel.user.number"),
                I18nMessage.getMessage("yami.excel.user.userNickname"),
//                I18nMessage.getMessage("yami.excel.user.userName"),
                I18nMessage.getMessage("yami.excel.user.contactNumber"),
                I18nMessage.getMessage("yami.excel.user.memberLevel"),
                I18nMessage.getMessage("yami.excel.user.memberType"),
                I18nMessage.getMessage("yami.excel.user.accScore"),
                I18nMessage.getMessage("yami.excel.user.status"),
                I18nMessage.getMessage("yami.excel.user.amountConsum"),
                I18nMessage.getMessage("yami.excel.user.amountActPaid"),
                I18nMessage.getMessage("yami.excel.user.numberConsum"),
                I18nMessage.getMessage("yami.excel.user.aveDiscount"),
                I18nMessage.getMessage("yami.excel.user.rechargeAmount"),
                I18nMessage.getMessage("yami.excel.user.topUpTimes"),
                I18nMessage.getMessage("yami.excel.user.refundAmount"),
                I18nMessage.getMessage("yami.excel.user.numberOfRefund"),
                I18nMessage.getMessage("yami.excel.user.accPoints"),
                I18nMessage.getMessage("yami.excel.user.currBalance"),
                I18nMessage.getMessage("yami.excel.user.accBalance"),
                I18nMessage.getMessage("yami.excel.user.regTime"),
                I18nMessage.getMessage("yami.excel.user.receConsumDate")
        };
        headerList = Arrays.asList(header);
        return headerList;
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/WebConfigController.java
New file
@@ -0,0 +1,95 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.controller;
import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.enums.WebConfigTypeEnum;
import com.yami.shop.bean.model.WebConfig;
import com.yami.shop.common.annotation.SysLog;
import com.yami.shop.common.bean.SysConfig;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.SysConfigService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Objects;
/**
 * 由于权限要求不同,所以不能把功能合并到sysController
 *
 * @author SJL
 * @date 2021-02-20 09:44:42
 * @description: 平台网站配置、商家网站配置、PC配置、H5配置、自提点网站配置
 */
@RestController
@RequestMapping("/sys/webConfig")
@Tag(name = "网站配置")
public class WebConfigController {
    @Autowired
    private SysConfigService sysConfigService;
    @Value("${yami.expose.operation.auth:}")
    private Boolean permission;
    @SysLog("获取配置信息")
    @GetMapping("/info/{key}")
    @Operation(summary = "获取配置信息" , description = "获取配置信息")
    @Parameter(name = "key", description = "参数名" )
    public ServerResponseEntity<String> info(@PathVariable("key") String key) {
        return ServerResponseEntity.success(sysConfigService.getValue(key));
    }
    @SysLog("保存配置")
    @PostMapping("/save")
    @Operation(summary = "保存配置" , description = "保存配置")
    @PreAuthorize("@pms.hasPermission('sys:webConfig:save')")
    public ServerResponseEntity<Void> save(@RequestBody @Valid SysConfig sysConfig) {
        if (BooleanUtil.isFalse(permission)) {
            return ServerResponseEntity.fail(ResponseEnum.NOT_FOUND);
        }
        String paramValue = sysConfig.getParamValue();
        String paramKey = sysConfig.getParamKey();
        if (Objects.isNull(paramKey) || Objects.isNull(paramValue)) {
            throw new YamiShopBindException("参数不完整,请准确填写后重试");
        }
        int count = sysConfigService.count(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getParamKey, paramKey));
        if (count > 0) {
            sysConfigService.updateValueByKey(paramKey, paramValue);
        } else {
            sysConfigService.save(sysConfig);
            sysConfigService.removeSysConfig(paramKey);
        }
        return ServerResponseEntity.success();
    }
    /**
     * 获取当前激活的后台网站配置
     *
     * @return
     */
    @GetMapping("/getActivity")
    public ServerResponseEntity<WebConfig> getActivityWebConfig() {
        WebConfig webConfig  = sysConfigService.getSysConfigObject(WebConfigTypeEnum.PLATFROM.value(), WebConfig.class);
        webConfig.setParamKey(WebConfigTypeEnum.PLATFROM.value());
        return ServerResponseEntity.success(webConfig);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/C2cRealNameAuditController.java
New file
@@ -0,0 +1,76 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.C2cRealName;
import com.yami.shop.bean.model.CdnRealName;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.C2cRealNameService;
import com.yami.shop.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/c2c/realName")
@Tag(name = "C2c实名认证接口")
public class C2cRealNameAuditController {
    @Autowired
    private C2cRealNameService c2cRealNameService;
    @Autowired
    private UserService userService;
    @GetMapping("/getList")
    @Operation(summary = "根据条件分页查询实名认证列表", description = "根据条件分页查询实名认证列表")
    public ServerResponseEntity<IPage<C2cRealName>> page(PageParam<C2cRealName> page, @ParameterObject C2cRealName c2cRealName) {
        return ServerResponseEntity.success(c2cRealNameService.getC2cRealNamePage(page, c2cRealName));
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/info/{id}" )
    @Operation(summary = "通过id查询" , description = "通过id查询")
    public ServerResponseEntity<C2cRealName> getById(@PathVariable("id") Long id) {
        return ServerResponseEntity.success(c2cRealNameService.getById(id));
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/infoByUserId/{id}" )
    @Operation(summary = "通过用户id查询" , description = "通过用户id查询")
    public ServerResponseEntity<C2cRealName> getById(@PathVariable("id") String id) {
        return ServerResponseEntity.success(c2cRealNameService.getOne(Wrappers.lambdaQuery(C2cRealName.class).eq(C2cRealName::getUserId, id)));
    }
    @PostMapping("/edit")
    @Operation(summary = "编辑保存", description = "编辑保存")
    public ServerResponseEntity save(@RequestBody C2cRealName c2cRealName) {
        c2cRealNameService.updateById(c2cRealName);
        return ServerResponseEntity.success();
    }
    @PostMapping("/audit")
    @Operation(summary = "审核", description = "审核")
    public ServerResponseEntity audit(@RequestBody C2cRealName c2cRealName) {
        c2cRealNameService.audit(c2cRealName);
        return ServerResponseEntity.success();
    }
    @PostMapping("/del")
    @Operation(summary = "删除", description = "删除")
    public ServerResponseEntity delete(@RequestParam Long id) {
        c2cRealNameService.removeById(id);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnConfigController.java
New file
@@ -0,0 +1,42 @@
package com.yami.shop.platform.controller.cdn;
import com.yami.shop.bean.CdnConfigWrapper;
import com.yami.shop.bean.model.CdnConfig;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.CdnConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/cdn/config")
@Tag(name = "cdn配置项接口")
public class CdnConfigController {
    @Autowired
    private CdnConfigService cdnConfigService;
    @GetMapping("/getCdnConfig")
    @Operation(summary = "获取cdn配置项", description = "获取cdn配置项")
    @Parameters(
            {@Parameter(name = "group", description = "配置分组 充值:recharge,提现:withdrawal,转账:transferAccounts,划转:convert 支付:pay, 积分:rebate, 托管协议:agreement", required = true),}
    )
    public ServerResponseEntity<List<CdnConfig>> getCdnConfig(@RequestParam String group) {
        return ServerResponseEntity.success(cdnConfigService.getList(group));
    }
    @PostMapping
    @Operation(summary = "保存cdn配置项", description = "保存cdn配置项")
    public ServerResponseEntity getCdnConfig(@RequestBody CdnConfigWrapper wrappers) {
        List<CdnConfig> cdnConfigList = wrappers.getCdnConfigList();
        for (CdnConfig cdnConfig : cdnConfigList){
            cdnConfigService.updateConfig(cdnConfig);
        }
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnRealNameController.java
New file
@@ -0,0 +1,116 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.CdnRealName;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.CdnRealNameService;
import com.yami.shop.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/cdn/realName")
@Tag(name = "Cdn实名认证接口")
public class CdnRealNameController {
    @Autowired
    private CdnRealNameService cdnRealNameService;
    @Autowired
    private UserService userService;
    @GetMapping("/getList")
    @Operation(summary = "根据条件分页查询实名认证列表", description = "根据条件分页查询实名认证列表")
    public ServerResponseEntity<IPage<CdnRealName>> page(PageParam<CdnRealName> page, @ParameterObject CdnRealName cdnRealName) {
        LambdaQueryWrapper<CdnRealName> wrapper = new LambdaQueryWrapper<>(CdnRealName.class);
        if (cdnRealName.getId() != null) wrapper.eq(CdnRealName::getId, cdnRealName.getId());
        if (cdnRealName.getUserId() != null && !cdnRealName.getUserId().equals("")) wrapper.eq(CdnRealName::getUserId, cdnRealName.getUserId());
        if (cdnRealName.getName() != null && !cdnRealName.getName().equals("")) wrapper.like(CdnRealName::getName, cdnRealName.getName());
        if (cdnRealName.getCard() != null && !cdnRealName.getCard().equals("")) wrapper.eq(CdnRealName::getCard, cdnRealName.getCard());
        if (cdnRealName.getBankName() != null && !cdnRealName.getBankName().equals("")) wrapper.like(CdnRealName::getBankName, cdnRealName.getBankName());
        if (cdnRealName.getBankCode() != null && !cdnRealName.getBankCode().equals("")) wrapper.eq(CdnRealName::getBankCode, cdnRealName.getBankCode());
        if (cdnRealName.getBankAddr() != null && !cdnRealName.getBankAddr().equals("")) wrapper.like(CdnRealName::getBankAddr, cdnRealName.getBankAddr());
        if (cdnRealName.getWxName() != null && !cdnRealName.getWxName().equals("")) wrapper.like(CdnRealName::getWxName, cdnRealName.getWxName());
        if (cdnRealName.getAliName() != null && !cdnRealName.getAliName().equals("")) wrapper.like(CdnRealName::getAliName, cdnRealName.getAliName());
        wrapper.orderByDesc(CdnRealName::getCreateTime);
        if (cdnRealName.getId() != null) wrapper.eq(CdnRealName::getId, cdnRealName.getId());
        if (cdnRealName.getMobile() != null && !cdnRealName.getMobile().equals("")){
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserMobile, cdnRealName.getMobile()));
            if (!list.isEmpty()){
                wrapper.in(CdnRealName::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            }else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (cdnRealName.getNickName() != null && !cdnRealName.getNickName().equals("")){
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getNickName, cdnRealName.getNickName()));
            if (!list.isEmpty()){
                wrapper.in(CdnRealName::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            }else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        PageParam<CdnRealName> page1 = cdnRealNameService.page(page, wrapper);
        if (!page1.getRecords().isEmpty()) {
            for (CdnRealName cdnRealName1 : page1.getRecords()) {
                User user = userService.getUserByUserId(cdnRealName1.getUserId());
                if (user != null) {
                    cdnRealName1.setUserName(user.getUserName());
                    cdnRealName1.setNickName(user.getNickName());
                    cdnRealName1.setMobile(user.getUserMobile());
                }
            }
        }
        return ServerResponseEntity.success(page1);
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/info/{id}" )
    @Operation(summary = "通过id查询" , description = "通过id查询")
    public ServerResponseEntity<CdnRealName> getById(@PathVariable("id") Long id) {
        CdnRealName cdnRealName = cdnRealNameService.getById(id);
        User user = userService.getUserByUserId(cdnRealName.getUserId());
        cdnRealName.setUserName(user.getUserName());
        cdnRealName.setNickName(user.getNickName());
        cdnRealName.setMobile(user.getUserMobile());
        return ServerResponseEntity.success(cdnRealName);
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/infoByUserId/{id}" )
    @Operation(summary = "通过用户id查询" , description = "通过用户id查询")
    public ServerResponseEntity<CdnRealName> getById(@PathVariable("id") String id) {
        CdnRealName cdnRealName = cdnRealNameService.getOne(Wrappers.lambdaQuery(CdnRealName.class).eq(CdnRealName::getUserId, id));
        return ServerResponseEntity.success(cdnRealName);
    }
    @PostMapping("/edit")
    @Operation(summary = "编辑保存", description = "编辑保存")
    public ServerResponseEntity save(@RequestBody CdnRealName cdnRelease) {
        cdnRealNameService.updateById(cdnRelease);
        return ServerResponseEntity.success();
    }
    @PostMapping("/del")
    @Operation(summary = "删除", description = "删除")
    public ServerResponseEntity delete(@RequestParam Long id) {
        cdnRealNameService.removeById(id);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserAtlasController.java
New file
@@ -0,0 +1,49 @@
package com.yami.shop.platform.controller.cdn;
import com.yami.shop.bean.vo.CdnUserAtlasVO;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/cdn/userAtlas")
@Tag(name = "cdn会员图谱接口")
public class CdnUserAtlasController {
    @Autowired
    private UserService userService;
    @GetMapping
    @Operation(summary = "获取会员图谱", description = "获取会员图谱")
    public ServerResponseEntity<List<CdnUserAtlasVO>> getDownLevel(@RequestParam("nickName") String nickName,@RequestParam("userId") String userId ,@RequestParam("userMobile") String userMobile) {
        List<CdnUserAtlasVO> list = userService.getLevel(nickName,userMobile,userId);
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/getHeadUser")
    @Operation(summary = "获取用户", description = "获取用户")
    public ServerResponseEntity<List<CdnUserAtlasVO>> getHeadUser(@RequestParam("uid") String uid,@RequestParam("nickName") String nickName,@RequestParam("userId") String userId ,@RequestParam("userMobile") String userMobile) {
        if (userId != null && !userId.equals("") && uid != null && !uid.equals("")){
            userId = "";
        }
        if (nickName != null && !nickName.equals("") && uid != null && !uid.equals("")){
            nickName = "";
        }
        if (userMobile != null && !userMobile.equals("") && uid != null && !uid.equals("")){
            userMobile = "";
        }
        if ((uid == null || uid.equals("")) && (userId == null || userId.equals("")) && (userMobile == null || userMobile.equals("")) && (nickName == null || nickName.equals(""))){
            uid = "0";
        }
        List<CdnUserAtlasVO> list = userService.getNoPidUser(uid,nickName,userId,userMobile);
        return ServerResponseEntity.success(list);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserController.java
New file
@@ -0,0 +1,487 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.*;
import com.yami.shop.bean.param.CdnShiftPidParam;
import com.yami.shop.bean.param.CdnUserParam;
import com.yami.shop.bean.param.CdnWalletInfoParam;
import com.yami.shop.bean.vo.CdnUserAtlasVO;
import com.yami.shop.common.enums.WalletEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.common.enums.SysTypeEnum;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/cdn/user")
@Tag(name = "cdn用户管理")
public class CdnUserController {
    @Autowired
    private UserService userService;
    @Autowired
    private CdnTeamRelationService cdnTeamRelationService;
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @Autowired
    private  UserExtensionService userExtensionService;
    @Autowired
    private CdnFlowService cdnFlowService;
    @Autowired
    private CdnReleaseService cdnReleaseService;
    @Autowired
    private CdnRealNameService cdnRealNameService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private TokenStore tokenStore;
    @GetMapping("/getUserList")
    @Operation(summary = "获取会员列表", description = "获取会员列表")
    public ServerResponseEntity<IPage<User>> page(PageParam<User> page, @ParameterObject User user) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(User.class);
        if (user.getUserId() != null && !user.getUserId().equals("")) wrapper.eq(User::getUserId, user.getUserId());
        if (user.getNickName() != null && !user.getNickName().equals("")) wrapper.like(User::getNickName, user.getNickName());
        if (user.getUserMobile() != null && !user.getUserMobile().equals("")) wrapper.like(User::getUserMobile, user.getUserMobile());
        if (user.getStatus() != null) wrapper.eq(User::getStatus, user.getStatus());
        if (user.getStartRegisterTime() != null && !user.getStartRegisterTime().equals("") && !user.getEndRegisterTime().equals("") && user.getEndRegisterTime() != null) {
            wrapper.between(User::getUserRegtime, user.getStartRegisterTime(), user.getEndRegisterTime());
        }
        if (user.getAgentLevelId() != null) {
            List<CdnTeamRelation> cdnTeamRelations = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getAgentLevelId, user.getAgentLevelId()));
            if (!cdnTeamRelations.isEmpty()) {
                List<String> userIds = cdnTeamRelations.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList());
                if (!userIds.isEmpty()) {
                    wrapper.in(User::getUserId, userIds);
                }else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (user.getCenterLevelId() != null) {
            List<CdnTeamRelation> cdnTeamRelations = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getCenterLevelId, user.getCenterLevelId()));
            if (!cdnTeamRelations.isEmpty()) {
                List<String> userIds = cdnTeamRelations.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList());
                if (!userIds.isEmpty()) {
                    wrapper.in(User::getUserId, userIds);
                }else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (user.getPid() != null && !user.getPid().equals("")) {
            List<CdnTeamRelation> cdnTeamRelations = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getPid, user.getPid()));
            if (!cdnTeamRelations.isEmpty()) {
                List<String> userIds = cdnTeamRelations.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList());
                if (!userIds.isEmpty()) {
                    wrapper.in(User::getUserId, userIds);
                }else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (user.getPNickName() != null && !user.getPNickName().equals("")) {
            List<User> users = userService.list(Wrappers.lambdaQuery(User.class).like(User::getNickName, user.getPNickName()));
            if (!users.isEmpty()) {
                List<CdnTeamRelation> list = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).in(CdnTeamRelation::getPid, users.stream().map(User::getUserId).collect(Collectors.toList())));
                if (!list.isEmpty()) {
                    wrapper.in(User::getUserId, list.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                }else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (user.getPMobile() != null && !user.getPMobile().equals("")) {
            List<User> users = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserMobile, user.getPMobile()));
            if (!users.isEmpty()) {
                List<CdnTeamRelation> list = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).in(CdnTeamRelation::getPid, users.stream().map(User::getUserId).collect(Collectors.toList())));
                if (!list.isEmpty()) {
                    wrapper.in(User::getUserId, list.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                }else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        wrapper.eq(User::getDelStatus, 0);
        wrapper.orderByDesc(User::getUserRegtime);
        PageParam<User> page1 = userService.page(page, wrapper);
        if (!page1.getRecords().isEmpty()) {
            page1.getRecords().forEach(user1 -> {
                User userInfo = cdnTeamRelationService.getInfoByUserId(user1.getUserId());
                user1.setPayPassword(null);
                user1.setLoginPassword(null);
                user1.setPid(userInfo.getPid());
                user1.setMeall(userInfo.getMeall());
                user1.setTeamall(userInfo.getTeamall());
                user1.setAgentLevelId(userInfo.getAgentLevelId());
                user1.setCenterLevelId(userInfo.getCenterLevelId());
                user1.setAgentLevelName(userInfo.getAgentLevelName());
                user1.setCenterLevelName(userInfo.getCenterLevelName());
                user1.setPNickName(userInfo.getPNickName());
                user1.setPMobile(userInfo.getPMobile());
                user1.setTotalRecharge(userInfo.getTotalRecharge());
                user1.setTotalConsumption(userInfo.getTotalConsumption());
                user1.setTotalWithdrawal(userInfo.getTotalWithdrawal());
                CdnRealName cdnRealName = cdnRealNameService.getOne(Wrappers.lambdaQuery(CdnRealName.class).eq(CdnRealName::getUserId, user1.getUserId()));
                if (cdnRealName != null) {
                    user1.setIsRealName(1);
                } else {
                    user1.setIsRealName(0);
                }
            });
        }else {
            return ServerResponseEntity.success(new PageParam<>());
        }
        return ServerResponseEntity.success(page1);
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/info/{id}")
    @Operation(summary = "通过id查询", description = "通过id查询")
    public ServerResponseEntity<User> getById(@PathVariable("id") String id) {
        CdnTeamRelation cdnTeamRelation = cdnTeamRelationService.getOne(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getUserId, id));
        User user = userService.getById(id);
        user.setAgentLevelId(cdnTeamRelation != null ? cdnTeamRelation.getAgentLevelId() : 0);
        user.setCenterLevelId(cdnTeamRelation != null ? cdnTeamRelation.getCenterLevelId() : 0);
        user.setPid(cdnTeamRelation != null ? cdnTeamRelation.getPid() : "");
        User pUser = userService.getById(cdnTeamRelation.getPid());
        if(null != pUser){
            user.setPMobile(pUser.getUserMobile());
            user.setPUserName(pUser.getUserName());
            user.setPNickName(pUser.getNickName());
        }
        return ServerResponseEntity.success(user);
    }
    @PostMapping("/edit")
    @Operation(summary = "编辑保存", description = "编辑保存")
    public ServerResponseEntity save(@RequestBody CdnUserParam cdnUserParam) {
        if (cdnUserParam.getPayPassword().length() > 6) {
            return ServerResponseEntity.showFailMsg("支付密码格式错误");
        }
        String ppw = encryptWithMD5(cdnUserParam.getPayPassword());
        User user = mapperFacade.map(cdnUserParam, User.class);
        user.setPayPassword(ppw);
        user.setLoginPassword(passwordEncoder.encode(cdnUserParam.getLoginPassword()));
        CdnTeamRelation cdnTeamRelation = cdnTeamRelationService.geteamRelationByUserId(user.getUserId());
        if (cdnUserParam.getStatus() == 0) {
            cdnTeamRelation.setMeall(BigDecimal.ZERO); // 个人设备清零,收益清零
            cdnTeamRelation.setDongjing(BigDecimal.ZERO);
            // 设备状态改为撤销
            List<CdnRelease> releases = cdnReleaseService.list(Wrappers.lambdaQuery(CdnRelease.class).eq(CdnRelease::getUserId, user.getUserId()));
            //计算失效的设备数量
            Integer num = 0;
            if (!releases.isEmpty()) {
                for (CdnRelease release : releases) {
                    release.setStatus(2);
                    cdnReleaseService.updateById(release);
                    //删除SN绑定的设备号
                    deviceService.deleteBySn(release.getSn());
                    num += release.getNum();
                }
            }
            //根据userId和数量 减少个人数量和团队数量
            updateTmall(user.getUserId(), num);
        }
        if (!cdnTeamRelation.getPid().equals(cdnUserParam.getPid())) {
            CdnShiftPidParam cdnShiftPidParam = new CdnShiftPidParam();
            cdnShiftPidParam.setUserId(cdnUserParam.getUserId());
            cdnShiftPidParam.setPid(cdnUserParam.getPid());
            cdnTeamRelationService.shiftPid(cdnShiftPidParam);
        } else {
            cdnTeamRelationService.update(cdnTeamRelation, Wrappers.lambdaUpdate(CdnTeamRelation.class).eq(CdnTeamRelation::getUserId, user.getUserId()));
        }
        if (cdnUserParam.getAgentLevelId() != null){
            cdnTeamRelation.setAgentLevelId(cdnUserParam.getAgentLevelId());
        }
        if (cdnUserParam.getCenterLevelId() != null){
            cdnTeamRelation.setCenterLevelId(cdnUserParam.getCenterLevelId());
        }
        if (cdnUserParam.getPid() != null && !cdnUserParam.getPid().equals("")){
            cdnTeamRelation.setPid(cdnUserParam.getPid());
        }
        cdnTeamRelationService.updateById(cdnTeamRelation);
        User userByUserId = userService.getUserByUserId(cdnUserParam.getUserId());
        if(!userByUserId.getUserMobile().equals(cdnUserParam.getUserMobile())){
            if (cdnUserParam.getUserMobile() != null && !"".equals(cdnUserParam.getUserMobile())){
                if (cdnUserParam.getUserMobile().length() != 11){
                    return ServerResponseEntity.showFailMsg("手机号格式错误");
                }
                List<User> mUser = userService.list(Wrappers.lambdaQuery(User.class).eq(User::getUserMobile, cdnUserParam.getUserMobile()));
                if (!mUser.isEmpty()){
                    return ServerResponseEntity.showFailMsg("手机号已存在");
                }
            }
        }
        userService.updateById(user);
        tokenStore.deleteAllToken(SysTypeEnum.ORDINARY.value().toString(), user.getUserId());
        return ServerResponseEntity.success();
    }
    @PostMapping("/modifyTixianStatus")
    @Operation(summary = "修改提现/转账/划转状态", description = "修改提现状态")
    public ServerResponseEntity modifyStatus(@RequestBody User user) {
        userService.update(Wrappers.lambdaUpdate(User.class)
                .eq(User::getUserId, user.getUserId())
                .set(User::getTixian, user.getTixian())
                .set(User::getHuazhuan, user.getHuazhuan())
                .set(User::getZhuanzhang, user.getZhuanzhang()));
        return ServerResponseEntity.success();
    }
    @GetMapping("/lookWallet")
    @Operation(summary = "查看钱包", description = "查看钱包")
    public ServerResponseEntity<CdnWalletInfoParam> lookWallet(@RequestParam String userId) {
        // 查询钱包币种数额
        CdnWalletInfoParam cdnWalletInfoParam = new CdnWalletInfoParam();
        // 根据币种查询钱包相关币种余额
        CdnUserWallet flow = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, userId)
                .eq(CdnUserWallet::getWalletId, WalletEnum.FLOW.value()));
        CdnUserWallet goldCowBean = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, userId)
                .eq(CdnUserWallet::getWalletId, WalletEnum.GOLDCOWBEAN.value()));
        CdnUserWallet luckPool = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, userId)
                .eq(CdnUserWallet::getWalletId, WalletEnum.LUCKPOOL.value()));
        CdnUserWallet integral = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, userId)
                .eq(CdnUserWallet::getWalletId, WalletEnum.INTEGRAL.value()));
        CdnUserWallet credit = cdnUserWalletService.getWalletByUserId(userId,WalletEnum.CREDIT.value());
        CdnUserWallet alloy = cdnUserWalletService.getWalletByUserId(userId,WalletEnum.ALLOY.value());
        BigDecimal b = new BigDecimal(0);
        cdnWalletInfoParam.setFlow(flow != null ? flow.getMoney() : b);
        cdnWalletInfoParam.setGoldCowBean(goldCowBean != null ? goldCowBean.getMoney() : b);
        cdnWalletInfoParam.setLuckPool(luckPool != null ? luckPool.getMoney() : b);
        cdnWalletInfoParam.setIntegral(integral != null ? integral.getMoney() : b);
        cdnWalletInfoParam.setCredit(credit != null ? credit.getMoney() : b);
        cdnWalletInfoParam.setAlloy(alloy != null ? alloy.getMoney() : b);
        cdnWalletInfoParam.setUserId(userId);
        return ServerResponseEntity.success(cdnWalletInfoParam);
    }
    @PostMapping("/edit/userWallet")
    @Operation(summary = "编辑保存用户钱包", description = "编辑保存用户钱包")
    public ServerResponseEntity saveUserWallet(@RequestBody CdnWalletInfoParam cdnWalletInfoParam) {
        // 根据币种查询钱包相关币种余额
        cdnWalletInfoParam.setFlow(cdnWalletInfoParam.getFlow().setScale(2, RoundingMode.HALF_UP));
        cdnWalletInfoParam.setGoldCowBean(cdnWalletInfoParam.getGoldCowBean().setScale(2, RoundingMode.HALF_UP));
        cdnWalletInfoParam.setLuckPool(cdnWalletInfoParam.getLuckPool().setScale(2, RoundingMode.HALF_UP));
        cdnWalletInfoParam.setIntegral(cdnWalletInfoParam.getIntegral().setScale(2, RoundingMode.HALF_UP));
        CdnUserWallet flow = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                .eq(CdnUserWallet::getWalletId, WalletEnum.FLOW.value()));
        CdnUserWallet goldCowBean = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                .eq(CdnUserWallet::getWalletId, WalletEnum.GOLDCOWBEAN.value()));
        CdnUserWallet luckPool = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                .eq(CdnUserWallet::getWalletId, WalletEnum.LUCKPOOL.value()));
        CdnUserWallet integral = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                .eq(CdnUserWallet::getWalletId, WalletEnum.INTEGRAL.value()));
        CdnUserWallet credit = cdnUserWalletService.getWalletByUserId(cdnWalletInfoParam.getUserId(),WalletEnum.CREDIT.value());
        CdnUserWallet alloy = cdnUserWalletService.getWalletByUserId(cdnWalletInfoParam.getUserId(),WalletEnum.ALLOY.value());
        CdnFlow cdnFlow = new CdnFlow();
        cdnFlow.setDate(new Date());
        cdnFlow.setMemo("后台操作钱包余额调整");
        cdnFlow.setUserId(cdnWalletInfoParam.getUserId());
        cdnFlow.setCreateTime(new Date());
        cdnFlow.setFlownameId(4); //后台操作
        if (!cdnWalletInfoParam.getFlow().equals(BigDecimal.ZERO) && !cdnWalletInfoParam.getFlow().equals(flow.getMoney())) {
            BigDecimal money = cdnWalletInfoParam.getFlow().subtract(flow.getMoney());
            cdnFlow.setWalletId(WalletEnum.FLOW.value());
            cdnFlow.setMoney(money);
            cdnFlow.setOldmoney(flow.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWalletService.update(Wrappers.lambdaUpdate(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                    .eq(CdnUserWallet::getWalletId, WalletEnum.FLOW.value())
                    .set(CdnUserWallet::getMoney, cdnWalletInfoParam.getFlow())
                    .set(CdnUserWallet::getUpdateTime, new Date()));
        }
        if (!cdnWalletInfoParam.getGoldCowBean().equals(BigDecimal.ZERO) && !cdnWalletInfoParam.getGoldCowBean().equals(goldCowBean.getMoney())) {
            BigDecimal money = cdnWalletInfoParam.getGoldCowBean().subtract(goldCowBean.getMoney());
            cdnFlow.setWalletId(WalletEnum.GOLDCOWBEAN.value());
            cdnFlow.setMoney(money);
            cdnFlow.setOldmoney(goldCowBean.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWalletService.update(Wrappers.lambdaUpdate(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                    .eq(CdnUserWallet::getWalletId, WalletEnum.GOLDCOWBEAN.value())
                    .set(CdnUserWallet::getMoney, cdnWalletInfoParam.getGoldCowBean())
                    .set(CdnUserWallet::getUpdateTime, new Date()));
        }
        if (!cdnWalletInfoParam.getLuckPool().equals(BigDecimal.ZERO) && !cdnWalletInfoParam.getLuckPool().equals(luckPool.getMoney())) {
            BigDecimal money = cdnWalletInfoParam.getLuckPool().subtract(luckPool.getMoney());
            cdnFlow.setWalletId(WalletEnum.LUCKPOOL.value());
            cdnFlow.setMoney(money);
            cdnFlow.setOldmoney(luckPool.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWalletService.update(Wrappers.lambdaUpdate(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                    .eq(CdnUserWallet::getWalletId, WalletEnum.LUCKPOOL.value())
                    .set(CdnUserWallet::getMoney, cdnWalletInfoParam.getLuckPool())
                    .set(CdnUserWallet::getUpdateTime, new Date()));
        }
        if (!cdnWalletInfoParam.getIntegral().equals(BigDecimal.ZERO) && !cdnWalletInfoParam.getIntegral().equals(integral.getMoney())) {
            BigDecimal money = cdnWalletInfoParam.getIntegral().subtract(integral.getMoney());
            cdnFlow.setWalletId(WalletEnum.INTEGRAL.value());
            cdnFlow.setMoney(money);
            cdnFlow.setOldmoney(integral.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWalletService.update(Wrappers.lambdaUpdate(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                    .eq(CdnUserWallet::getWalletId, WalletEnum.INTEGRAL.value())
                    .set(CdnUserWallet::getMoney, cdnWalletInfoParam.getIntegral())
                    .set(CdnUserWallet::getUpdateTime, new Date()));
        }
        if (!cdnWalletInfoParam.getCredit().equals(BigDecimal.ZERO) && !cdnWalletInfoParam.getCredit().equals(credit.getMoney())) {
            BigDecimal money = cdnWalletInfoParam.getCredit().subtract(credit.getMoney());
            cdnFlow.setWalletId(WalletEnum.CREDIT.value());
            cdnFlow.setMoney(money);
            cdnFlow.setOldmoney(credit.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWalletService.update(Wrappers.lambdaUpdate(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                    .eq(CdnUserWallet::getWalletId, WalletEnum.CREDIT.value())
                    .set(CdnUserWallet::getMoney, cdnWalletInfoParam.getCredit())
                    .set(CdnUserWallet::getUpdateTime, new Date()));
        }
        if (!cdnWalletInfoParam.getAlloy().equals(BigDecimal.ZERO) && !cdnWalletInfoParam.getAlloy().equals(alloy.getMoney())) {
            BigDecimal money = cdnWalletInfoParam.getAlloy().subtract(alloy.getMoney());
            cdnFlow.setWalletId(WalletEnum.ALLOY.value());
            cdnFlow.setMoney(money);
            cdnFlow.setOldmoney(alloy.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWalletService.update(Wrappers.lambdaUpdate(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, cdnWalletInfoParam.getUserId())
                    .eq(CdnUserWallet::getWalletId, WalletEnum.ALLOY.value())
                    .set(CdnUserWallet::getMoney, cdnWalletInfoParam.getAlloy())
                    .set(CdnUserWallet::getUpdateTime, new Date()));
        }
        return ServerResponseEntity.success();
    }
    @GetMapping("/getDownLevel")
    @Operation(summary = "获取下级用户", description = "获取下级用户")
    public ServerResponseEntity<List<CdnUserAtlasVO>> getDownLevel(@RequestParam String userId) {
        List<CdnUserAtlasVO> list = userService.getDownLevel(userId);
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/getUpLevel")
    @Operation(summary = "获取上级用户", description = "获取上级用户")
    public ServerResponseEntity<List<CdnUserAtlasVO>> getUpLevel(@RequestParam String userId) {
        List<CdnUserAtlasVO> list = userService.getUpLevel(userId);
        return ServerResponseEntity.success(list);
    }
    /**
     * 释放后减少个人业绩和团队业绩
     * @param userId
     * @param num
     */
    public void updateTmall(String userId, Integer num) {
        CdnTeamRelation cdnTeamRelation = cdnTeamRelationService.geteamRelationByUserId(userId);
        if (cdnTeamRelation != null && StringUtils.isNotEmpty(cdnTeamRelation.getPids() )) {
            //减去个人业绩
            cdnTeamRelation.setMeall(cdnTeamRelation.getMeall().subtract(BigDecimal.valueOf(num)));
            cdnTeamRelationService.updateById(cdnTeamRelation);
            String[] pids = cdnTeamRelation.getPids().split(",");
            for (String pid : pids) {
                if (StringUtils.isNotBlank(pid)) {
                    CdnTeamRelation cdnTeamRelation1 = cdnTeamRelationService.geteamRelationByUserId(pid);
                    cdnTeamRelation1.setTeamall(cdnTeamRelation1.getTeamall().subtract(BigDecimal.valueOf(num)));
                    cdnTeamRelationService.updateById(cdnTeamRelation1);
                }
            }
        }
    }
    @GetMapping("/delUser/{userId}")
    @Operation(summary = "删除用户", description = "删除用户")
    public ServerResponseEntity delFlow(@PathVariable("userId") String userId) {
        List<CdnTeamRelation> list = cdnTeamRelationService.getTeamRelationListSort(userId);
        if(list.size() > 0){
            return ServerResponseEntity.showFailMsg("该会员存在下级用户,无法删除!");
        }
        CdnTeamRelation cdnTeamRelation1 = cdnTeamRelationService.geteamRelationByUserId(userId);
        if(cdnTeamRelation1.getMeall().compareTo(BigDecimal.ZERO) >0 || cdnTeamRelation1.getTeamall().compareTo(BigDecimal.ZERO) >0){
            return ServerResponseEntity.showFailMsg("该会员存在cdn托管设备业绩,无法删除!");
        }
        if(cdnTeamRelation1.getMeallOwn().compareTo(BigDecimal.ZERO) >0 || cdnTeamRelation1.getTeamallOwn().compareTo(BigDecimal.ZERO) >0){
            return ServerResponseEntity.showFailMsg("该会员存在cdn自主设备业绩,无法删除!");
        }
        //User user = userService.getById(userId);
        userService.removeById(userId);
        userExtensionService.remove(new LambdaQueryWrapper<UserExtension>().eq(UserExtension::getUserId,userId));
        cdnTeamRelationService.removeById(cdnTeamRelation1.getId());
        cdnUserWalletService.remove(new LambdaQueryWrapper<CdnUserWallet>().eq(CdnUserWallet::getUserId,userId));
        return ServerResponseEntity.success();
    }
    // 支付密码加密
    public static String encryptWithMD5(String password) {
        try {
            // 创建一个MD5的消息摘要对象
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 将密码转换为字节数组并进行MD5摘要
            byte[] messageDigest = md.digest(password.getBytes());
            // 将摘要转换为16进制字符串
            BigInteger no = new BigInteger(1, messageDigest);
            String hashtext = no.toString(16);
            // 补齐长度到32位
            while (hashtext.length() < 32) {
                hashtext = "0" + hashtext;
            }
            return hashtext;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 hashing algorithm not found", e);
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserFlowController.java
New file
@@ -0,0 +1,208 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.*;
import com.yami.shop.bean.param.CdnFlowParam;
import com.yami.shop.bean.vo.UserVO;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/cdn/flow")
@Tag(name = "cdn用户流水")
public class CdnUserFlowController {
    @Autowired
    private CdnFlowService cdnFlowService;
    @Autowired
    private UserService userService;
    @Autowired
    private CdnFlownameService cdnFlownameService;
    @Autowired
    private CdnTeamRelationService cdnTeamRelationService;
    @Autowired
    private CdnWalletService cdnWalletService;
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @Autowired
    private DeviceService deviceService;
    @GetMapping("/getUserFlowList")
    @Operation(summary = "根据条件分页查询会员流水列表", description = "根据条件分页查询会员流水列表")
    public ServerResponseEntity<CdnFlowParam> page(PageParam<CdnFlow> page, @ParameterObject CdnFlow cdnFlow) {
        LambdaQueryWrapper<CdnFlow> wrapper = new LambdaQueryWrapper<>(CdnFlow.class);
        if (cdnFlow.getId() != null) wrapper.eq(CdnFlow::getId, cdnFlow.getId());
        if (cdnFlow.getUserId() != null && !cdnFlow.getUserId().equals(""))
            wrapper.eq(CdnFlow::getUserId, cdnFlow.getUserId());
        if (cdnFlow.getWalletId() != null) wrapper.eq(CdnFlow::getWalletId, cdnFlow.getWalletId());
        if (cdnFlow.getXmoney() != null || cdnFlow.getDmoney() != null)
            wrapper.between(CdnFlow::getMoney, cdnFlow.getXmoney(), cdnFlow.getDmoney());
        if (cdnFlow.getMemo() != null && !cdnFlow.getMemo().equals(""))
            wrapper.like(CdnFlow::getMemo, cdnFlow.getMemo());
        if (cdnFlow.getStartCreateTime() != null && !cdnFlow.getStartCreateTime().equals("") && !cdnFlow.getEndCreateTime().equals("") && cdnFlow.getEndCreateTime() != null) {
            wrapper.between(CdnFlow::getCreateTime, cdnFlow.getStartCreateTime(), cdnFlow.getEndCreateTime());
        }
        if (cdnFlow.getMobile() != null && !cdnFlow.getMobile().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserMobile, cdnFlow.getMobile()));
            if (!list.isEmpty()) {
                wrapper.in(CdnFlow::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnFlowParam());
            }
        }
        if (cdnFlow.getFlownameIdList() != null && !cdnFlow.getFlownameIdList().equals("")) {
            String flownameIdList = cdnFlow.getFlownameIdList();
            String[] split = flownameIdList.split(",");
            wrapper.in(CdnFlow::getFlownameId, split);
        }
        wrapper.eq(CdnFlow::getDelStatus, 0);
        wrapper.orderByDesc(CdnFlow::getCreateTime);
        PageParam<CdnFlow> page1 = cdnFlowService.page(page, wrapper);
        if (!page1.getRecords().isEmpty()) {
            for (CdnFlow cdnFlow1 : page1.getRecords()) {
                CdnUserWallet info = userService.getInfo(cdnFlow1.getUserId(), cdnFlow1.getWalletId());
                if (info != null) {
                    cdnFlow1.setPid(info.getPid());
                    cdnFlow1.setPNickName(info.getPNickName());
                    cdnFlow1.setPMobile(info.getPMobile());
                    cdnFlow1.setUserName(info.getUserName());
                    cdnFlow1.setNickName(info.getNickName());
                    cdnFlow1.setMobile(info.getMobile());
                    cdnFlow1.setWalletName(info.getWalletName());
                }
            }
        }
        CdnFlowParam cdnFlowParam = new CdnFlowParam();
        cdnFlowParam.setCdnFlowPageParam(page1);
        if(cdnFlow.getWalletId() != null && cdnFlow.getMemo() != null && !cdnFlow.getMemo().equals("")) {
            List<CdnFlow> list = cdnFlowService.list(wrapper);
            // 统计
            if (!list.isEmpty()) {
                BigDecimal count = list.stream().map(CdnFlow::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
                cdnFlowParam.setCount(count);
            }
        }else{
            cdnFlowParam.setCount(BigDecimal.ZERO);
        }
        return ServerResponseEntity.success(cdnFlowParam);
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/info/{id}")
    @Operation(summary = "通过id查询", description = "通过id查询")
    public ServerResponseEntity<CdnFlow> getById(@PathVariable("id") Long id) {
        CdnFlow cdnFlow = cdnFlowService.getById(id);
        User user = userService.getUserByUserId(cdnFlow.getUserId());
        cdnFlow.setUserName(user.getUserName());
        cdnFlow.setNickName(user.getNickName());
        cdnFlow.setMobile(user.getUserMobile());
        return ServerResponseEntity.success(cdnFlow);
    }
    @PostMapping("/edit")
    @Operation(summary = "编辑保存", description = "编辑保存")
    @Transactional(rollbackFor = Exception.class)
    public ServerResponseEntity save(@RequestBody CdnFlow cdnFlow) throws ParseException {
//        if (cdnFlow.getMoney().compareTo(BigDecimal.ZERO) <= 0) {
//            return ServerResponseEntity.showFailMsg("金额必须大于0");
//        }
        Device device = deviceService.list(Wrappers.lambdaQuery(Device.class).eq(Device::getDeviceName, cdnFlow.getDeviceName())).get(0);
        if (device == null) {
            return ServerResponseEntity.showFailMsg("设备不存在");
        }
        CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class).eq(CdnUserWallet::getUserId, cdnFlow.getUserId()).eq(CdnUserWallet::getWalletId, cdnFlow.getWalletId()));
        if (cdnUserWallet == null){
            return ServerResponseEntity.showFailMsg("用户不存在");
        }
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        List<CdnFlow> cdnFlowList = cdnFlowService.list(Wrappers.lambdaQuery(CdnFlow.class)
                .eq(CdnFlow::getUserId,device.getUserId())
                .eq(CdnFlow::getBizSource,device.getId())
                .like(CdnFlow::getDate,cdnFlow.getDate())
                .orderByDesc(CdnFlow::getUpdateTime));
        if (!cdnFlowList.isEmpty()) {
            CdnFlow flow = cdnFlowList.get(0);
            // 插入流水
            cdnFlow.setId(flow.getId());
            cdnFlow.setFlownameId(49);
            cdnFlow.setMoney(cdnFlow.getMoney().add(flow.getMoney()));
            cdnFlow.setMemo("后台操作设备收益");
            cdnFlow.setBizData(3L);
            cdnFlow.setOldmoney(cdnUserWallet.getMoney());
            cdnFlow.setBizType("AI淘金");
            cdnFlow.setUpdateTime(new Date());
            cdnFlow.setDate(simpleDateFormat.parse(cdnFlow.getCreateTimeStr()));
            cdnFlowService.updateById(cdnFlow);
        }else{
            // 插入流水
            cdnFlow.setFlownameId(49);
            cdnFlow.setMemo("后台操作设备收益");
            cdnFlow.setBizData(3L);
            cdnFlow.setOldmoney(cdnUserWallet.getMoney());
            cdnFlow.setBizType("AI淘金");
            cdnFlow.setCreateTime(new Date());
            cdnFlow.setDate(simpleDateFormat.parse(cdnFlow.getCreateTimeStr()));
            cdnFlow.setBizSource(device.getId());
            cdnFlowService.save(cdnFlow);
        }
        // 修改钱包
        cdnUserWallet.setMoney(cdnUserWallet.getMoney().add(cdnFlow.getMoney()));
        cdnUserWalletService.updateById(cdnUserWallet);
        return ServerResponseEntity.success();
    }
    @GetMapping("/getFlowNameList")
    @Operation(summary = "获取流水名称列表", description = "获取流水名称列表")
    public ServerResponseEntity<List<CdnFlowname>> getFlowNameList() {
        return ServerResponseEntity.success(cdnFlownameService.list());
    }
    @GetMapping("/delFlow/{id}")
    @Operation(summary = "删除流水", description = "删除流水")
    public ServerResponseEntity delFlow(@PathVariable("id") Long id) {
        CdnFlow cdnFlow = cdnFlowService.getById(id);
        cdnFlow.setDelStatus(1);
        cdnFlowService.updateById(cdnFlow);
        return ServerResponseEntity.success();
    }
    // 获取用户列表
    @GetMapping("/getUserList")
    @Operation(summary = "获取用户列表", description = "获取用户列表")
    public ServerResponseEntity<List<UserVO>> getUserList() {
        List<User> list = userService.list(Wrappers.lambdaQuery(User.class).eq(User::getStatus, 1));
        return ServerResponseEntity.success(list.stream().map(user -> {
            UserVO userVO = new UserVO();
            userVO.setUserId(user.getUserId());
            userVO.setNickName(user.getNickName());
            userVO.setUserMobile(user.getUserMobile());
            userVO.setPic(user.getPic());
            userVO.setStatus(user.getStatus());
            return userVO;
       }).collect(Collectors.toList()));
    }
    @GetMapping("/getDeviceList")
    @Operation(summary = "获取设备列表", description = "获取设备列表")
    public ServerResponseEntity<List<Device>> getDeviceList() {
        return ServerResponseEntity.success(deviceService.list());
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserLevelController.java
New file
@@ -0,0 +1,93 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.CdnAgentLevel;
import com.yami.shop.bean.model.CdnCenterLevel;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.CdnAgentLevelService;
import com.yami.shop.service.CdnCenterLevelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/cdn/userLevel")
@Tag(name = "Cdn会员等级接口")
public class CdnUserLevelController {
    @Autowired
    private CdnCenterLevelService cdnCenterLevelService;
    @Autowired
    private CdnAgentLevelService cdnAgentLevelService;
    @GetMapping("/center")
    @Operation(summary = "查询加权等级列表",description = "查询加权等级列表")
    public ServerResponseEntity<IPage<CdnCenterLevel>> page(PageParam<CdnCenterLevel> page, @ParameterObject CdnCenterLevel centerLevel) {
        LambdaQueryWrapper<CdnCenterLevel> wrapper = new LambdaQueryWrapper<>(CdnCenterLevel.class);
        if (centerLevel.getId() != null) wrapper.eq(CdnCenterLevel::getId, centerLevel.getId());
        PageParam<CdnCenterLevel> page1 = cdnCenterLevelService.page(page, wrapper);
        return ServerResponseEntity.success(page1);
    }
    @GetMapping("/agent")
    @Operation(summary = "查询会员等级列表",description = "查询会员等级列表")
    public ServerResponseEntity<IPage<CdnAgentLevel>> page(PageParam<CdnAgentLevel> page, @ParameterObject CdnAgentLevel cdnAgentLevel) {
        LambdaQueryWrapper<CdnAgentLevel> wrapper = new LambdaQueryWrapper<>(CdnAgentLevel.class);
        if (cdnAgentLevel.getId() != null) wrapper.eq(CdnAgentLevel::getId, cdnAgentLevel.getId());
        PageParam<CdnAgentLevel> page1 = cdnAgentLevelService.page(page, wrapper);
        return ServerResponseEntity.success(page1);
    }
    @GetMapping("/center/info/{id}")
    @Operation(summary = "根据id获取加权详情" , description = "根据id获取加权详情")
    public ServerResponseEntity<CdnCenterLevel> centerInfo(@PathVariable("id") Long id) {
        CdnCenterLevel cdnCenterLevel = cdnCenterLevelService.getById(id);
        return ServerResponseEntity.success(cdnCenterLevel);
    }
    @GetMapping("/agent/info/{id}")
    @Operation(summary = "根据id获取代理详情" , description = "根据id获取代理详情")
    public ServerResponseEntity<CdnAgentLevel> agentInfo(@PathVariable("id") Long id) {
        CdnAgentLevel cdnAgentLevel = cdnAgentLevelService.getById(id);
        return ServerResponseEntity.success(cdnAgentLevel);
    }
    @PostMapping("/center")
    @Operation(summary = "保存加权信息" , description = "保存加权信息")
    public ServerResponseEntity<Void> save(@RequestBody CdnCenterLevel cdnCenterLevel) {
        cdnCenterLevelService.save(cdnCenterLevel);
        return ServerResponseEntity.success();
    }
    @PostMapping("/agent")
    @Operation(summary = "保存代理信息" , description = "保存代理信息")
    public ServerResponseEntity<Void> save(@RequestBody CdnAgentLevel cdnAgentLevel) {
        cdnAgentLevelService.save(cdnAgentLevel);
        return ServerResponseEntity.success();
    }
    @PutMapping("/center")
    @Operation(summary = "修改加权信息" , description = "修改加权信息")
    public ServerResponseEntity<Void> update(@RequestBody CdnCenterLevel cdnCenterLevel) {
        cdnCenterLevelService.updateById(cdnCenterLevel);
        return ServerResponseEntity.success();
    }
    @PutMapping("/agent")
    @Operation(summary = "修改代理信息" , description = "修改代理信息")
    public ServerResponseEntity<Void> update(@RequestBody CdnAgentLevel cdnAgentLevel) {
        cdnAgentLevelService.updateById(cdnAgentLevel);
        return ServerResponseEntity.success();
    }
    @DeleteMapping("/center/{id}")
    @Operation(summary = "删除加权信息" , description = "删除加权信息")
    public ServerResponseEntity<Void> deleteCenter(@PathVariable Integer id) {
        cdnCenterLevelService.removeById(id);
        return ServerResponseEntity.success();
    }
    @DeleteMapping("/agent/{id}")
    @Operation(summary = "删除代理信息" , description = "删除代理信息")
    public ServerResponseEntity<Void> deleteAgent(@PathVariable Integer id) {
        cdnAgentLevelService.removeById(id);
        return ServerResponseEntity.success();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserWalletController.java
New file
@@ -0,0 +1,128 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.CdnTeamRelation;
import com.yami.shop.bean.model.CdnUserWallet;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.CdnTeamRelationService;
import com.yami.shop.service.CdnUserWalletService;
import com.yami.shop.service.CdnWalletService;
import com.yami.shop.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/cdn/Wallet")
@Tag(name = "cdn用户钱包")
public class CdnUserWalletController {
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @Autowired
    private UserService userService;
    @Autowired
    private CdnWalletService cdnWalletService;
    @Autowired
    private CdnTeamRelationService cdnTeamRelationService;
    @GetMapping("/getUserWalletList")
    @Operation(summary = "根据条件分页查询会员钱包列表123", description = "根据条件分页查询会员钱包列表")
    public ServerResponseEntity<IPage<CdnUserWallet>> page(PageParam<CdnUserWallet> page, @ParameterObject CdnUserWallet cdnUserWallet) {
        LambdaQueryWrapper<CdnUserWallet> wrapper = new LambdaQueryWrapper<>(CdnUserWallet.class);
        if (cdnUserWallet.getUserId() != null && !cdnUserWallet.getUserId().equals(""))
            wrapper.eq(CdnUserWallet::getUserId, cdnUserWallet.getUserId());
        if (cdnUserWallet.getWalletId() != null) wrapper.eq(CdnUserWallet::getWalletId, cdnUserWallet.getWalletId());
        if (cdnUserWallet.getXmoney() != null || cdnUserWallet.getDmoney() != null)
            wrapper.between(CdnUserWallet::getMoney, cdnUserWallet.getXmoney(), cdnUserWallet.getDmoney());
        if (cdnUserWallet.getCreateTimeStr() != null && !cdnUserWallet.getCreateTimeStr().equals(""))
            wrapper.like(CdnUserWallet::getCreateTime, cdnUserWallet.getCreateTimeStr());
        if (cdnUserWallet.getUpdateTimeStr() != null && !cdnUserWallet.getUpdateTimeStr().equals(""))
            wrapper.like(CdnUserWallet::getUpdateTime, cdnUserWallet.getUpdateTimeStr());
        if (cdnUserWallet.getUserName() != null && !cdnUserWallet.getUserName().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserName, cdnUserWallet.getUserName()));
            if (!list.isEmpty()) {
                wrapper.in(CdnUserWallet::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (cdnUserWallet.getNickName() != null && !cdnUserWallet.getNickName().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getNickName, cdnUserWallet.getNickName()));
            if (!list.isEmpty()) {
                wrapper.in(CdnUserWallet::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (cdnUserWallet.getMobile() != null && !cdnUserWallet.getMobile().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserMobile, cdnUserWallet.getMobile()));
            if (!list.isEmpty()) {
                wrapper.in(CdnUserWallet::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (cdnUserWallet.getPid() != null && !cdnUserWallet.getPid().equals("")) {
            List<CdnTeamRelation> users = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getPid, cdnUserWallet.getPid()));
            if (!users.isEmpty()) {
                wrapper.in(CdnUserWallet::getUserId, users.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (cdnUserWallet.getPNickName() != null && !cdnUserWallet.getPNickName().equals("")) {
            List<User> users = userService.list(Wrappers.lambdaQuery(User.class).like(User::getNickName, cdnUserWallet.getPNickName()));
            if (!users.isEmpty()) {
                List<CdnTeamRelation> list = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).in(CdnTeamRelation::getPid, users.stream().map(User::getUserId).collect(Collectors.toList())));
                if (!list.isEmpty()) {
                    wrapper.in(CdnUserWallet::getUserId, list.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                } else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        if (cdnUserWallet.getPMobile() != null && !cdnUserWallet.getPMobile().equals("")) {
            User users = userService.getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserMobile, cdnUserWallet.getPMobile()));
            if (users != null) {
                List<CdnTeamRelation> list = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getPid, users.getUserId()));
                if (!list.isEmpty()) {
                    wrapper.in(CdnUserWallet::getUserId, list.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                } else {
                    return ServerResponseEntity.success(new PageParam<>());
                }
            } else {
                return ServerResponseEntity.success(new PageParam<>());
            }
        }
        wrapper.orderByDesc(CdnUserWallet::getCreateTime);
        PageParam<CdnUserWallet> page1 = cdnUserWalletService.page(page, wrapper);
        if (!page1.getRecords().isEmpty()) {
            for (CdnUserWallet cdnUserWallet1 : page1.getRecords()) {
                CdnUserWallet info = userService.getInfo(cdnUserWallet1.getUserId(), cdnUserWallet1.getWalletId());
                if (info != null) {
                    cdnUserWallet1.setPid(info.getPid());
                    cdnUserWallet1.setPNickName(info.getPNickName());
                    cdnUserWallet1.setPMobile(info.getPMobile());
                    cdnUserWallet1.setUserName(info.getUserName());
                    cdnUserWallet1.setNickName(info.getNickName());
                    cdnUserWallet1.setMobile(info.getMobile());
                    cdnUserWallet1.setWalletName(info.getWalletName());
                }
            }
        }
        return ServerResponseEntity.success(page1);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnUserWithdrawalController.java
New file
@@ -0,0 +1,306 @@
package com.yami.shop.multishop.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yami.shop.bean.model.*;
import com.yami.shop.bean.param.CdnUserWithdrawalParam;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/cdn/Withdrawal")
@Tag(name = "cdn提现")
public class CdnUserWithdrawalController {
    @Autowired
    private CdnWithdrawalService cdnWithdrawalService;
    @Autowired
    private UserService userService;
    @Autowired
    private CdnUserBankService cdnUserBankService;
    @Autowired
    private CdnWalletService cdnWalletService;
    @Autowired
    private CdnTeamRelationService cdnTeamRelationService;
    @GetMapping("/getWithdrawalList")
    @Operation(summary = "根据条件分页查询提现列表", description = "根据条件分页查询提现列表")
    public ServerResponseEntity<CdnUserWithdrawalParam> page(PageParam<CdnWithdrawal> page, @ParameterObject CdnWithdrawal cdnWithdrawal) {
        LambdaQueryWrapper<CdnWithdrawal> wrapper = new LambdaQueryWrapper<>(CdnWithdrawal.class);
        if (cdnWithdrawal.getUserId() != null && !cdnWithdrawal.getUserId().equals("")) {
            wrapper.eq(CdnWithdrawal::getUserId, cdnWithdrawal.getUserId());
        }
        if (cdnWithdrawal.getTeam() != null && !cdnWithdrawal.getTeam().equals("")) {
            List<CdnTeamRelation> cdnTeamRelation = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).like(CdnTeamRelation::getPids, "," + cdnWithdrawal.getTeam() + ","));
            if (!cdnTeamRelation.isEmpty()) {
                List<String> collect = new ArrayList<>();
                collect.add(cdnWithdrawal.getTeam());
                collect.addAll(cdnTeamRelation.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                wrapper.in(CdnWithdrawal::getUserId,collect);
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getWalletId() != null) wrapper.eq(CdnWithdrawal::getWalletId, cdnWithdrawal.getWalletId());
        if (cdnWithdrawal.getUserBankId() != null)
            wrapper.eq(CdnWithdrawal::getUserBankId, cdnWithdrawal.getUserBankId());
        if (cdnWithdrawal.getXmoney() != null || cdnWithdrawal.getDmoney() != null)
            wrapper.between(CdnWithdrawal::getMoney, cdnWithdrawal.getXmoney(), cdnWithdrawal.getDmoney());
        if (cdnWithdrawal.getXsxf() != null || cdnWithdrawal.getDsxf() != null)
            wrapper.between(CdnWithdrawal::getSxf, cdnWithdrawal.getXsxf(), cdnWithdrawal.getDsxf());
        if (cdnWithdrawal.getXpoint() != null || cdnWithdrawal.getDpoint() != null)
            wrapper.between(CdnWithdrawal::getPoint, cdnWithdrawal.getXpoint(), cdnWithdrawal.getDpoint());
        if (cdnWithdrawal.getStatus() != null) wrapper.eq(CdnWithdrawal::getStatus, cdnWithdrawal.getStatus());
        if (cdnWithdrawal.getStartCreateTime() != null && !cdnWithdrawal.getStartCreateTime().equals("") && !cdnWithdrawal.getEndCreateTime().equals("") && cdnWithdrawal.getEndCreateTime() != null) {
            wrapper.between(CdnWithdrawal::getCreateTime, cdnWithdrawal.getStartCreateTime(), cdnWithdrawal.getEndCreateTime());
        }
        if (cdnWithdrawal.getStartUpdateTime() != null && !cdnWithdrawal.getStartUpdateTime().equals("") && !cdnWithdrawal.getEndUpdateTime().equals("") && cdnWithdrawal.getEndUpdateTime() != null) {
            wrapper.between(CdnWithdrawal::getUpdateTime, cdnWithdrawal.getStartUpdateTime(), cdnWithdrawal.getEndUpdateTime());
        }
        wrapper.orderByDesc(CdnWithdrawal::getCreateTime);
        if (cdnWithdrawal.getUserName() != null && !cdnWithdrawal.getUserName().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserName, cdnWithdrawal.getUserName()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getNickName() != null && !cdnWithdrawal.getNickName().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getNickName, cdnWithdrawal.getNickName()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
//        if (cdnWithdrawal.getRealName() != null && !cdnWithdrawal.getRealName().equals("")) {
//            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getRealName, cdnWithdrawal.getRealName()));
//            if (!list.isEmpty()) {
//                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
//            } else {
//                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
//            }
//        }
        if (cdnWithdrawal.getMobile() != null && !cdnWithdrawal.getMobile().equals("")) {
            List<User> list = userService.list(Wrappers.lambdaQuery(User.class).like(User::getUserMobile, cdnWithdrawal.getMobile()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(User::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getBank() != null && !cdnWithdrawal.getBank().equals("")) {
            List<CdnUserBank> list = cdnUserBankService.list(Wrappers.lambdaQuery(CdnUserBank.class).like(CdnUserBank::getBank, cdnWithdrawal.getBank()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(CdnUserBank::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getRealName() != null && !cdnWithdrawal.getRealName().equals("")) {
            List<CdnUserBank> list = cdnUserBankService.list(Wrappers.lambdaQuery(CdnUserBank.class).like(CdnUserBank::getName, cdnWithdrawal.getRealName()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(CdnUserBank::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getType() != null && !cdnWithdrawal.getType().equals("")) {
            List<CdnUserBank> list = cdnUserBankService.list(Wrappers.lambdaQuery(CdnUserBank.class).eq(CdnUserBank::getType, cdnWithdrawal.getType()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserBankId, list.stream().map(CdnUserBank::getId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getBankCode() != null && !cdnWithdrawal.getBankCode().equals("")) {
            List<CdnUserBank> list = cdnUserBankService.list(Wrappers.lambdaQuery(CdnUserBank.class).like(CdnUserBank::getBankcode, cdnWithdrawal.getBankCode()));
            if (!list.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, list.stream().map(CdnUserBank::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getPid() != null && !cdnWithdrawal.getPid().equals("")) {
            List<CdnTeamRelation> cdnTeamRelation = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getPid, cdnWithdrawal.getPid()));
            if (!cdnTeamRelation.isEmpty()) {
                wrapper.in(CdnWithdrawal::getUserId, cdnTeamRelation.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
            } else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getPMobile() != null && !cdnWithdrawal.getPMobile().equals("")) {
            User user = userService.getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserMobile, cdnWithdrawal.getPMobile()));
            if (user != null) {
                List<CdnTeamRelation> cdnTeamRelation = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).eq(CdnTeamRelation::getPid, user.getUserId()));
                if (!cdnTeamRelation.isEmpty()) {
                    wrapper.in(CdnWithdrawal::getUserId, cdnTeamRelation.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                }else {
                    return ServerResponseEntity.success(new CdnUserWithdrawalParam());
                }
            }else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        if (cdnWithdrawal.getPNickName() != null && !cdnWithdrawal.getPNickName().equals("")) {
            List<User> users = userService.list(Wrappers.lambdaQuery(User.class).like(User::getNickName, cdnWithdrawal.getPNickName()));
            if (!users.isEmpty()) {
                List<CdnTeamRelation> list = cdnTeamRelationService.list(Wrappers.lambdaQuery(CdnTeamRelation.class).in(CdnTeamRelation::getPid, users.stream().map(User::getUserId).collect(Collectors.toList())));
                if (!list.isEmpty()) {
                    wrapper.in(CdnWithdrawal::getUserId, list.stream().map(CdnTeamRelation::getUserId).collect(Collectors.toList()));
                }else {
                    return ServerResponseEntity.success(new CdnUserWithdrawalParam());
                }
            }else {
                return ServerResponseEntity.success(new CdnUserWithdrawalParam());
            }
        }
        wrapper.orderByDesc(CdnWithdrawal::getCreateTime);
        List<CdnWithdrawal> lists = cdnWithdrawalService.list(wrapper);
        List<CdnWithdrawal> equalUserIdRecords = lists.stream()
                .filter(cdnWithdrawal2 -> cdnWithdrawal2.getUserId().equals(cdnWithdrawal.getTeam()))
                .collect(Collectors.toList());
        List<CdnWithdrawal> otherRecords = lists.stream()
                .filter(cdnWithdrawal2 -> !cdnWithdrawal2.getUserId().equals(cdnWithdrawal.getTeam()))
                .collect(Collectors.toList());
        // 合并两个列表
        List<CdnWithdrawal> sortedRecords = new ArrayList<>(equalUserIdRecords);
        sortedRecords.addAll(otherRecords);
        // 如果需要更新原始列表
        lists.clear();
        lists.addAll(sortedRecords);
        // 创建分页对象
        PageParam<CdnWithdrawal> page1 = new PageParam<>();
        page1.setSize(page.getSize());
        page1.setCurrent(page.getCurrent());
        if (!lists.isEmpty()) {
            // 设置分页对象的总记录数
            page1.setTotal(lists.size());
            // 设置分页对象的当前页数据
            int start = (int) ((page1.getCurrent() - 1) * page1.getSize());
            int end = (int) Math.min(start + page1.getSize(), sortedRecords.size());
            List<CdnWithdrawal> pageRecords = sortedRecords.subList(start, end);
            // 更新分页对象中的记录
            page1.setRecords(pageRecords);
//        PageParam<CdnWithdrawal> page1 = cdnWithdrawalService.page(page, wrapper);
//        if (!page1.getRecords().isEmpty()) {
            List<CdnWithdrawal> records = page1.getRecords();
            for (CdnWithdrawal cdnWithdrawal1 : records) {
                CdnUserWallet info = userService.getInfo(cdnWithdrawal1.getUserId(), cdnWithdrawal1.getWalletId());
                if (info != null) {
                    cdnWithdrawal1.setNickName(info.getNickName());
                    cdnWithdrawal1.setMobile(info.getMobile());
                    cdnWithdrawal1.setWalletName(info.getWalletName());
                }
                CdnUserBank cdnUserBank = cdnUserBankService.getById(cdnWithdrawal1.getUserBankId());
                if (cdnUserBank != null) {
                    cdnWithdrawal1.setBankCode(cdnUserBank.getBankcode());
                    cdnWithdrawal1.setBank(cdnUserBank.getBank());
                    cdnWithdrawal1.setType(cdnUserBank.getType());
                    cdnWithdrawal1.setRealName(cdnUserBank.getName());
                    cdnWithdrawal1.setImage(cdnUserBank.getImage());
                }
            }
        }
        CdnUserWithdrawalParam cdnUserWithdrawalParam = new CdnUserWithdrawalParam();
        cdnUserWithdrawalParam.setCdnWithdrawalPageParam(page1);
        List<CdnWithdrawal> list = cdnWithdrawalService.list(wrapper);
        // 统计
        if (!list.isEmpty()) {
            BigDecimal count = list.stream().map(CdnWithdrawal::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
            cdnUserWithdrawalParam.setCount(count);
        }
        BigDecimal applying = list.stream()
                .filter(c -> c != null && c.getStatus() != null && c.getStatus().equals(0))
                .map(CdnWithdrawal::getMoney)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        cdnUserWithdrawalParam.setApplying(applying);
        BigDecimal checked = list.stream()
                .filter(c -> c != null && c.getStatus() != null && c.getStatus().equals(1))
                .map(CdnWithdrawal::getMoney)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        cdnUserWithdrawalParam.setChecked(checked);
        BigDecimal refuse = list.stream()
                .filter(c -> c != null && c.getStatus() != null && c.getStatus().equals(2))
                .map(CdnWithdrawal::getMoney)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        cdnUserWithdrawalParam.setRefuse(refuse);
        return ServerResponseEntity.success(cdnUserWithdrawalParam);
    }
    /**
     * 通过id查询
     * @param id id
     * @return 单个数据
     */
    @GetMapping("/info/{id}")
    @Operation(summary = "通过id查询", description = "通过id查询")
    public ServerResponseEntity<CdnWithdrawal> getById(@PathVariable("id") Long id) {
        CdnWithdrawal cdnWithdrawal = cdnWithdrawalService.getById(id);
        return ServerResponseEntity.success(cdnWithdrawal);
    }
    @PostMapping("/edit")
    @Operation(summary = "编辑保存", description = "编辑保存")
    public ServerResponseEntity save(@RequestBody CdnWithdrawal cdnWithdrawal) {
        CdnWithdrawal cdnWithdrawal1 = cdnWithdrawalService.getById(cdnWithdrawal.getId());
        if (cdnWithdrawal.getStatus() == 1) {
            if (cdnWithdrawal1.getStatus() == 1){
                return ServerResponseEntity.showFailMsg("已通过,不能重复通过!");
            }
            return cdnWithdrawalService.doWithdrawal(cdnWithdrawal);
        } else if (cdnWithdrawal.getStatus() == 2) {
            if (cdnWithdrawal1.getStatus() == 2){
                return ServerResponseEntity.showFailMsg("已拒绝,不能重复拒绝!");
            }
            return cdnWithdrawalService.returnWithdrawal(cdnWithdrawal);
        }
        return ServerResponseEntity.success();
    }
    @PostMapping("/saveBatch")
    @Operation(summary = "批量保存", description = "批量保存")
    public ServerResponseEntity saveBatch(@RequestBody List<CdnWithdrawal> cdnWithdrawals) {
        for (CdnWithdrawal cdnWithdrawal : cdnWithdrawals){
            if (cdnWithdrawal == null){
                continue;
            }
            cdnWithdrawal.setStatus(1);
            cdnWithdrawalService.doWithdrawal(cdnWithdrawal);
        }
        return ServerResponseEntity.success();
    }
    @GetMapping("/exportWithdrawal")
    @Schema(description = "导出提现列表")
    public void prodExport(HttpServletResponse response, @ParameterObject CdnWithdrawal cdnWithdrawal) {
        cdnWithdrawalService.export(response, cdnWithdrawal);
    }
    @GetMapping("/exportWithdrawals")
    @Schema(description = "导出提现列表")
    public void prodExports(HttpServletResponse response, @ParameterObject CdnWithdrawal cdnWithdrawal) {
        cdnWithdrawalService.exports(response, cdnWithdrawal);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/cdn/CdnWalletController.java
New file
@@ -0,0 +1,60 @@
package com.yami.shop.platform.controller.cdn;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.CdnAgentLevel;
import com.yami.shop.bean.model.CdnCenterLevel;
import com.yami.shop.bean.model.CdnFlowname;
import com.yami.shop.bean.model.CdnWallet;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.service.CdnAgentLevelService;
import com.yami.shop.service.CdnCenterLevelService;
import com.yami.shop.service.CdnFlownameService;
import com.yami.shop.service.CdnWalletService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/cdn/typeName")
@Tag(name = "cdn获取类型字典列表")
public class CdnWalletController {
    @Autowired
    private CdnWalletService cdnWalletService;
    @Autowired
    private CdnAgentLevelService cdnAgentLevelService;
    @Autowired
    private CdnCenterLevelService cdnCenterLevelService;
    @Autowired
    private CdnFlownameService cdnFlownameService;
    @GetMapping("/getWalletList")
    @Operation(summary = "获取币种列表", description = "获取币种列表")
    public ServerResponseEntity<List<CdnWallet>> getWalletList() {
        List<CdnWallet> list = cdnWalletService.list(Wrappers.lambdaQuery(CdnWallet.class).eq(CdnWallet::getStatus, "1"));
        return ServerResponseEntity.success(list);
    }
    @GetMapping("/getAgentList")
    @Operation(summary = "获取代理列表", description = "获取代理列表")
    public ServerResponseEntity<List<CdnAgentLevel>> getAgentList() {
        return ServerResponseEntity.success(cdnAgentLevelService.list(Wrappers.lambdaQuery()));
    }
    @GetMapping("/getCenterList")
    @Operation(summary = "获取加权列表", description = "获取加权列表")
    public ServerResponseEntity<List<CdnCenterLevel>> getCenterList() {
        return ServerResponseEntity.success(cdnCenterLevelService.list(Wrappers.lambdaQuery()));
    }
    @GetMapping("/getFlowTypeList")
    @Operation(summary = "获取流水类型列表", description = "获取流水类型列表")
    public ServerResponseEntity<List<CdnFlowname>> getFlowTypeList() {
        return ServerResponseEntity.success(cdnFlownameService.list(Wrappers.lambdaQuery()));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/test/TestTaskController.java
New file
@@ -0,0 +1,277 @@
package com.yami.shop.platform.controller.test;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alipay.api.AlipayApiException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.model.CdnFlow;
import com.yami.shop.bean.model.CdnUserWallet;
import com.yami.shop.bean.model.MarsAdverIncome;
import com.yami.shop.bean.model.User;
import com.yami.shop.bean.pay.TransferDto;
import com.yami.shop.common.constants.DictConstant;
import com.yami.shop.common.enums.WalletEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.manager.impl.TransferManager;
import com.yami.shop.service.CdnFlowService;
import com.yami.shop.service.CdnUserWalletService;
import com.yami.shop.service.MarsAdverIncomeService;
import com.yami.shop.service.UserService;
import com.yami.shop.task.common.model.TaskProvideRecord;
import com.yami.shop.task.common.model.TaskProvideUser;
import com.yami.shop.task.common.service.TaskProvideRecordService;
import com.yami.shop.task.common.service.TaskProvideUserService;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
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 java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Random;
@RequestMapping("/platform/test")
@RestController
@AllArgsConstructor
public class TestTaskController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private TaskProvideRecordService taskProvideRecordService;
    @Autowired
    private TaskProvideUserService taskProvideUserService;
    @Autowired
    private UserService userService;
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @Autowired
    private CdnFlowService cdnFlowService;
    @Autowired
    private MarsAdverIncomeService marsAdverIncomeService;
    private final TransferManager transferManager;
    @RequestMapping("/statusModify")
    public ServerResponseEntity cancelPointRelease() {
        Date now = new Date();
        logger.info("任务状态修改开始。。。" + DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        // 查询已激活的任务释放
        List<TaskProvideUser> taskProvideUserList = taskProvideUserService.list(Wrappers.lambdaQuery(TaskProvideUser.class).gt(TaskProvideUser::getStatus, 0).lt(TaskProvideUser::getStatus, 4));
        for (TaskProvideUser taskProvideUser : taskProvideUserList) {
            switch (taskProvideUser.getStatus()) {
                case 1:
                    taskProvideUser.setStatus(2);
                    Random random = new Random();
                    // 生成0到1之间的随机小数
                    double randomFloat = random.nextDouble();
                    // 将随机小数映射到1到20之间
                    double randomFloat1To20 = randomFloat * 19 + 1;
                    System.out.println(randomFloat1To20);
                    // 以50%的概率添加负号
                    if (random.nextBoolean()) {
                        randomFloat1To20 = -randomFloat1To20;
                    }
                    // 使用String.format()保留两位小数
                    String formattedString = String.format("%.2f", randomFloat1To20);
                    // 如果你确实需要double类型,但注意这会丢失不必要的零
                    double formattedDouble = Double.parseDouble(formattedString);
                    taskProvideUser.setProcess(50 + formattedDouble);
                    taskProvideUser.setProcess(50 + randomFloat1To20);
                    taskProvideUserService.updateById(taskProvideUser);
                    break;
                case 2:
                    taskProvideUser.setStatus(3);
                    taskProvideUser.setProcess(100.00);
                    taskProvideUserService.updateById(taskProvideUser);
                    break;
                default:
                    break;
            }
        }
        return ServerResponseEntity.success();
    }
    @RequestMapping("/init")
    public ServerResponseEntity cancelGeneral() {
        Date now = new Date();
        logger.info("任务提供释放初始化开始。。。" + DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        // 查询任务提供释放记录。开始和结束时间在当前时间段内
//        Date date = DateUtil.parse(DateUtil.format(new Date(), "yyyy-MM-dd"), "yyyy-MM-dd");
        // 将Date对象格式化为字符串
        String formattedDate = DateUtil.format(now, "yyyy-MM-dd");
        List<TaskProvideRecord> taskProvideRecordList = taskProvideRecordService.list(Wrappers.lambdaQuery(TaskProvideRecord.class)
                .le(TaskProvideRecord::getStartTime,formattedDate)
                .ge(TaskProvideRecord::getEndTime, formattedDate));
        // 遍历任务提供记录
        for (TaskProvideRecord taskProvideRecord : taskProvideRecordList) {
            // 查询所有激活任务大厅的用户
            List<User> users = userService.list(Wrappers.lambdaQuery(User.class).eq(User::getActiveStatus, 1));
            // 遍历初始化用户
            for (User user : users) {
                // 查询用户的金牛豆释放总额
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class).eq(CdnUserWallet::getUserId, user.getUserId()).eq(CdnUserWallet::getWalletId, WalletEnum.LUCKPOOL.value()));
                BigDecimal releaseCount = cdnUserWallet.getReleaseCount() != null ? cdnUserWallet.getReleaseCount() : BigDecimal.ZERO;
                if (releaseCount.compareTo(BigDecimal.ZERO) > 0) {
                    // 创建任务提供用户
                    TaskProvideUser taskProvideUser = new TaskProvideUser();
                    taskProvideUser.setUserId(user.getUserId());
                    taskProvideUser.setProvideRecordId(taskProvideRecord.getProvideRecordId());
                    taskProvideUser.setDateTime(formattedDate + "");
                    taskProvideUser.setCreateTime(new Date());
                    taskProvideUser.setTaskId(taskProvideRecord.getTaskId());
                    taskProvideUser.setStatus(0);
                    taskProvideUser.setProcess(0.00);
                    taskProvideUser.setStartTime(new Date());
                    taskProvideUser.setReleaseCount(cdnUserWallet.getReleaseCount());
                    // 根据释放百分比算出本次释放的幸运池数量
                    BigDecimal release = cdnUserWallet.getReleaseCount().multiply(taskProvideRecord.getReleaseRate());
                    Random random = new Random();
                    // 生成0到1之间的随机小数(不包括1)
                    double randomFloat = random.nextDouble();
                    // 以50%的概率添加负号
                    if (random.nextBoolean()) {
                        randomFloat = -randomFloat;
                    }
                    // 使用String.format()保留两位小数
                    String formattedString = String.format("%.2f", randomFloat);
                    // 如果你确实需要double类型,但注意这会丢失不必要的零
                    double formattedDouble = Double.parseDouble(formattedString);
                    release = release.add(BigDecimal.valueOf(formattedDouble));
                    taskProvideUser.setReleases(release);
                    taskProvideUserService.save(taskProvideUser);
                }
            }
        }
        logger.info("任务提供释放初始化结束。。。" + DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        return ServerResponseEntity.success();
    }
    @RequestMapping("/releaseMoney")
    public ServerResponseEntity releaseMoney() {
        try {
            logger.info("AI+自主掘金分润计算开始。。。" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            List<TaskProvideUser> list = taskProvideUserService.list(new LambdaQueryWrapper<TaskProvideUser>().eq(TaskProvideUser::getStatus, 3));
            for (TaskProvideUser taskProvideUser : list) {
                if(ObjectUtil.isNotEmpty(taskProvideUser.getReleases())){
                    if(taskProvideUser.getReleases().compareTo(BigDecimal.ZERO) > 0){
                        setFlowLog(taskProvideUser.getUserId(),taskProvideUser.getUserId(),taskProvideUser.getReleases(), DictConstant.WALLET_ID_10,"AI+自主掘金分润",DictConstant.FLOWNAME_ID_50);
                        taskProvideUser.setStatus(4);
                        taskProvideUserService.updateById(taskProvideUser);
                        MarsAdverIncome marsAdverIncome = MarsAdverIncome.builder()
                                .userId(taskProvideUser.getUserId())
                                .money(taskProvideUser.getReleases())
                                .date(new Date())
                                .build();
                        marsAdverIncomeService.save(marsAdverIncome);
                    }
                }
            }
            logger.info("AI+自主掘金分润计算结束。。。" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        }catch (Exception e) {
            logger.error("AI+自主掘金分润计算失败", e);
        }
        return ServerResponseEntity.success();
    }
    /**
     *
     * @param userId 受益人
     * @param userIds 贡献人
     * @param money 收益金额
     * @param walletId 钱包类型
     * @param desc 收益描述
     * @param flownameId 流水类型
     */
    private void  setFlowLog(String userId, String userIds, BigDecimal money, Integer walletId, String desc, Integer flownameId){
        CdnFlow cdnFlow = new CdnFlow();
        cdnFlow.setUserIds(userIds);
        cdnFlow.setUserId(userId);
        cdnFlow.setFlownameId(flownameId);
        cdnFlow.setWalletId(walletId);
        //钱包更改
        CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,walletId));
        if(cdnUserWallet.getMoney().compareTo(BigDecimal.ZERO) == 0){
            return;
        }
        if(money.compareTo(BigDecimal.ZERO) <= 0){//幸运池已经释放完毕
            return;
        }else{
            if(money.compareTo(cdnUserWallet.getMoney()) >= 0){
                cdnFlow.setMoney(cdnUserWallet.getMoney().negate());
            }else{
                cdnFlow.setMoney(money.negate());
            }
        }
        cdnFlow.setMemo(desc+":释放用户"+userIds);
        cdnFlow.setCreateTime(new Date());
        cdnFlow.setDate(new Date());
        cdnFlow.setOldmoney(cdnUserWallet.getMoney());
        cdnFlowService.save(cdnFlow);
        cdnUserWallet.setUserId(userId);
        cdnUserWallet.setWalletId(walletId);
        cdnUserWallet.setMoney(cdnUserWallet.getMoney().add(cdnFlow.getMoney()));
        cdnUserWallet.setUpdateTime(new Date());
        cdnUserWallet.setReleaseCount(cdnUserWallet.getReleaseCount().add(cdnFlow.getMoney()));
        cdnUserWalletService.updateById(cdnUserWallet);
        //释放完进入流量
        CdnFlow cdnFlows = new CdnFlow();
        BeanUtils.copyProperties(cdnFlow, cdnFlows);
        cdnFlows.setMoney(cdnFlow.getMoney().negate());
        cdnFlows.setWalletId(DictConstant.WALLET_ID_8);
        cdnFlows.setFlownameId(flownameId);
        CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,DictConstant.WALLET_ID_8));
        if(null == cdnUserWallets){
            return;
        }
        cdnFlows.setOldmoney(cdnUserWallets.getMoney() == null?BigDecimal.ZERO:cdnUserWallets.getMoney());
        cdnFlows.setMemo(desc+"-->流量:相关用户"+userIds);
        cdnFlowService.save(cdnFlows);
        //钱包更改
        cdnUserWallets.setUserId(userId);
        cdnUserWallets.setWalletId(DictConstant.WALLET_ID_8);
        cdnUserWallets.setMoney(cdnUserWallets.getMoney()== null?cdnFlow.getMoney().negate():cdnUserWallets.getMoney().add(cdnFlow.getMoney().negate()));
        cdnUserWallets.setUpdateTime(new Date());
        cdnUserWalletService.updateById(cdnUserWallets);
    }
    @PostMapping("/test/transfer")
    public ServerResponseEntity<?> test(@RequestBody TransferDto transferDto){
        transferDto.setOutBizNo("20241012112121");
//        transferDto.setTransAmount("0.2");
        transferDto.setOrderTitle("测试转账");
//        transferDto.setIdentity("3673245879@qq.com");
        transferDto.setIdentityType("ALIPAY_LOGON_ID");
//        transferDto.setName("上海门也科技股份有限公司");
        try {
            return transferManager.aliTransfer(transferDto);
        } catch (AlipayApiException e) {
            throw new RuntimeException(e);
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/AnalysisSalseTask.java
New file
@@ -0,0 +1,44 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yami.shop.bean.model.User;
import com.yami.shop.bean.param.UserManagerParam;
import com.yami.shop.bean.param.UserManagerReqParam;
import com.yami.shop.service.UserService;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.concurrent.Callable;
/**
 * @author yami
 */
@Slf4j
public class AnalysisSalseTask implements Callable<List<UserManagerParam>> {
    private UserService userService;
    private UserManagerReqParam user;
    private Page<User> pages;
    public AnalysisSalseTask(UserService userService, UserManagerReqParam user, Page<User> pages) {
        this.userService = userService;
        this.user = user;
        this.pages = pages;
    }
    @Override
    public List<UserManagerParam> call() throws Exception {
        IPage<UserManagerParam> userPage =  userService.getUserInfoPage(pages,user);
        return userPage.getRecords();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/CpsShopOrderTask.java
New file
@@ -0,0 +1,377 @@
package com.yami.shop.platform.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.*;
import com.yami.shop.common.config.BigSellerConfig;
import com.yami.shop.cps.commom.client.DtkApiClient;
import com.yami.shop.cps.commom.request.putstorage.DtkNewestGoodsRequest;
import com.yami.shop.cps.commom.request.putstorage.DtkPullGoodsByTimeRequest;
import com.yami.shop.cps.commom.request.putstorage.DtkStaleGoodsByTimeRequest;
import com.yami.shop.cps.commom.response.base.DtkApiResponse;
import com.yami.shop.cps.commom.response.base.DtkPageResponse;
import com.yami.shop.cps.commom.response.mastertool.DtkGetOrderDetailsResultsItemsResponse;
import com.yami.shop.cps.commom.response.mastertool.DtkGetOrderDetailsResultsResponse;
import com.yami.shop.cps.commom.response.mastertool.DtkJdOrderQueryV2Response;
import com.yami.shop.cps.commom.response.mastertool.MtOrderResponse;
import com.yami.shop.cps.commom.response.putstorage.DtkGoodsListItemResponse;
import com.yami.shop.cps.commom.response.putstorage.DtkNewestGoodsResponse;
import com.yami.shop.cps.commom.response.putstorage.DtkStaleGoodsByTimeResponse;
import com.yami.shop.cps.commom.service.*;
import com.yami.shop.cps.commom.utils.DTKUtils;
import com.yami.shop.cps.commom.utils.MTUtils;
import com.yami.shop.cps.commom.vo.*;
import com.yami.shop.service.CpsConfigService;
import com.yami.shop.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * cps商品订单定时任务
 */
@Component
public class CpsShopOrderTask {
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
     @Autowired
     private CpsConfigService cpsConfigService;
     @Autowired
     private CpsShopService cpsShopService;
     @Autowired
     private CpsOrderService cpsOrderService;
     @Autowired
     private CpsJdOrderService cpsJdOrderService;
     @Autowired
     private UserService userService;
     @Autowired
     private CpsMtActitvityService cpsMtActitvityService;
     @Autowired
     private CpsMtOrderService cpsMtOrderService;
     @Autowired
     private CpsDdOrderService cpsDdOrderService;
     @Autowired
     private CpsJtkOrderService cpsJtkOrderService;
     /**
      * 淘宝商品定时拉取 1小时执行一次
      */
     @XxlJob("pushGoodByTime")
     public void pushGoodByTime(String startTime,String endTime,String pageId) {
          LocalDateTime currentDateTime = LocalDateTime.now();
          //计算开始时间和结束时间
          if(StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)){
               //初次计算时间
               DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
               // 计算前1小时时间
               LocalDateTime hourAgo = currentDateTime.minusHours(1);
               endTime =  currentDateTime.format(formatter)+":00";
               startTime= hourAgo.format(formatter)+":01";
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          TaoBaoVo taoBaoVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_TAOBAO_CONFIG, TaoBaoVo.class);
          DtkApiClient client = DtkApiClient.getInstance(taoBaoVo.getAppKey(),taoBaoVo.getAppSecret());
          DtkPullGoodsByTimeRequest request = new DtkPullGoodsByTimeRequest();
          request.setVersion("v1.2.3");
          if(StringUtils.isNotBlank(pageId)){
               request.setPageId(pageId);
          }else{
               request.setPageId("1");
          }
          request.setPageSize(100);
          DtkApiResponse<DtkPageResponse<DtkGoodsListItemResponse>> execute = client.execute(request);
          //创建实体类
          List<DtkGoodsListItemResponse> list = execute.getData().getList();
          List<CpsShop> cpsShopList =  new ArrayList<>();
          for (DtkGoodsListItemResponse shop : list) {
               CpsShop cpsShop = new CpsShop();
               BeanUtils.copyProperties(shop, cpsShop);
               cpsShop.setShopId(shop.getId());
               cpsShop.setConnect(shop.getDesc());
               cpsShopList.add(cpsShop);
          }
          int count = cpsShopService.insertCpsShops(cpsShopList);
          if(count == 100){
               pushGoodByTime(startTime,endTime,execute.getData().getPageId());
          }
     }
     /**
      * 失效淘宝商品定时处理 5分钟执行一次
      */
     @XxlJob("getStateGoodByTime")
     public void getStateGoodByTime(String startTime,String endTime,String pageId) {
          LocalDateTime currentDateTime = LocalDateTime.now();
          //计算开始时间和结束时间
          if(StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)){
               //初次计算时间
               DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
               // 计算前20分钟时间
               LocalDateTime halfHourAgo = currentDateTime.minus(Duration.ofMinutes(5));
               endTime =  currentDateTime.format(formatter)+":00";
               startTime= halfHourAgo.format(formatter)+":01";
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          TaoBaoVo taoBaoVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_TAOBAO_CONFIG, TaoBaoVo.class);
          DtkApiClient client = DtkApiClient.getInstance(taoBaoVo.getAppKey(),taoBaoVo.getAppSecret());
          DtkStaleGoodsByTimeRequest request = new DtkStaleGoodsByTimeRequest();
          request.setVersion("v1.0.1");
          if(StringUtils.isNotBlank(pageId)){
               request.setPageId(pageId);
          }else{
               request.setPageId("1");
          }
          request.setPageSize(100);
          DtkApiResponse<DtkPageResponse<DtkStaleGoodsByTimeResponse>> execute = client.execute(request);
          //创建实体类
          List<DtkStaleGoodsByTimeResponse> list = execute.getData().getList();
          for (DtkStaleGoodsByTimeResponse shop : list) {
               cpsShopService.deleteCpsShopAndValues((long)shop.getId());
          }
          if(list.size() == 100){
               getStateGoodByTime(startTime,endTime,execute.getData().getPageId());
          }
     }
     /**
      * 淘宝商品定时更新 2分钟执行一次
      */
     @XxlJob("getNewestGoods")
     public void getNewestGoods(String startTime,String endTime,String pageId) {
          LocalDateTime currentDateTime = LocalDateTime.now();
          //计算开始时间和结束时间
          if(StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)){
               //初次计算时间
               DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
               // 计算前20分钟时间
               LocalDateTime halfHourAgo = currentDateTime.minus(Duration.ofMinutes(2));
               endTime =  currentDateTime.format(formatter)+":00";
               startTime= halfHourAgo.format(formatter)+":01";
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          TaoBaoVo taoBaoVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_TAOBAO_CONFIG, TaoBaoVo.class);
          DtkApiClient client = DtkApiClient.getInstance(taoBaoVo.getAppKey(),taoBaoVo.getAppSecret());
          DtkNewestGoodsRequest request = new DtkNewestGoodsRequest();
          request.setVersion("v1.2.0");
          if(StringUtils.isNotBlank(pageId)){
               request.setPageId(pageId);
          }else{
               request.setPageId("1");
          }
          request.setPageSize(100);
          DtkApiResponse<DtkPageResponse<DtkNewestGoodsResponse>> execute = client.execute(request);
          //创建实体类
          List<DtkNewestGoodsResponse> list = execute.getData().getList();
          for (DtkNewestGoodsResponse shop : list) {
               CpsShop cpsShop = cpsShopService.getBaseMapper().selectOne(new LambdaQueryWrapper<CpsShop>().eq(CpsShop::getShopId, shop.getId()));
               BeanUtils.copyProperties(shop, cpsShop);
               cpsShopService.updateById(cpsShop);
          }
          if(list.size() == 100){
               getNewestGoods(startTime,endTime,execute.getData().getPageId());
          }
     }
     /**
      * 淘宝订单定时获取 20分钟执行一次
      */
     @XxlJob("getOrderDetails")
     public void getOrderDetails(String startTime,String endTime) {
          LocalDateTime currentDateTime = LocalDateTime.now();
          //计算开始时间和结束时间
          if(StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)){
               //初次计算时间
               DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
               // 计算前20分钟时间
               LocalDateTime halfHourAgo = currentDateTime.minus(Duration.ofMinutes(20));
               endTime =  currentDateTime.format(formatter)+":00";
               startTime= halfHourAgo.format(formatter)+":01";
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          TaoBaoVo taoBaoVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_TAOBAO_CONFIG, TaoBaoVo.class);
          DtkGetOrderDetailsResultsResponse result = DTKUtils.getTBOrderList(startTime,endTime,1,taoBaoVo);
          if(result != null) {
               //插入cps订单表
               List<DtkGetOrderDetailsResultsItemsResponse> list = result.getPublisherOrderDto();
               for (DtkGetOrderDetailsResultsItemsResponse item : list){
                    //存在的话 更新订单 不存在
                    CpsOrder order = cpsOrderService.select(item.getTradeParentId());
                    User user =userService.getBaseMapper().selectOne(new LambdaQueryWrapper<User>().eq(User::getSpecialId, item.getSpecialId()));
                    if(order != null){
                         BeanUtils.copyProperties(item, order);
                         order.setUserId(user.getUserId());
                         cpsOrderService.updateById(order);
                    }else{
                         CpsOrder cpsOrder = new CpsOrder();
                         BeanUtils.copyProperties(item, cpsOrder);
                         cpsOrder.setUserId(user.getUserId());
                         cpsOrderService.insert(cpsOrder);
                    }
               }
               //批量插入订单
               if(result.getPublisherOrderDto().size() >= 20){
                    getOrderDetails(startTime,endTime);
               }
          }
     }
     /**
      * 京东订单定时获取 20分钟执行一次
      */
     @XxlJob("getJdOrderDetails")
     public void getJdOrderDetails(String startTime,String endTime) {
          LocalDateTime currentDateTime = LocalDateTime.now();
          //计算开始时间和结束时间
          if(StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)){
               //初次计算时间
               DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
               // 计算前20分钟时间
               LocalDateTime halfHourAgo = currentDateTime.minus(Duration.ofMinutes(20));
               endTime =  currentDateTime.format(formatter)+":00";
               startTime= halfHourAgo.format(formatter)+":01";
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          JingDongVo jingDongVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_JINGDONG_CONFIG, JingDongVo.class);
          DtkJdOrderQueryV2Response result = DTKUtils.getJDOrderList(startTime,endTime,1,jingDongVo);
          if(result != null) {
               //插入cps京东订单表
               List<DtkJdOrderQueryV2Response.OrderRow> list = result.getList();
               for (DtkJdOrderQueryV2Response.OrderRow item : list){
                    //存在的话 更新订单 不存在
                    CpsJdOrder order = cpsJdOrderService.select(item.getId());
                    User user =userService.getBaseMapper().selectOne(new LambdaQueryWrapper<User>().eq(User::getUserCode, item.getPositionId()));
                    if(order != null){
                         BeanUtils.copyProperties(item, order);
                         order.setUserId(user.getUserId());
                         cpsJdOrderService.updateById(order);
                    }else {
                         CpsJdOrder cpsJdOrder = new CpsJdOrder();
                         BeanUtils.copyProperties(item, cpsJdOrder);
                         cpsJdOrder.setImageUrl(item.getGoodsInfo().getImageUrl());
                         cpsJdOrder.setShopName(item.getGoodsInfo().getShopName());
                         cpsJdOrder.setUserId(user.getUserId());
                         cpsJdOrderService.insert(cpsJdOrder);
                    }
               }
               //批量插入订单
               if(result.getHasMore() == 1){
                    getJdOrderDetails(startTime,endTime);
               }
          }
     }
     /**
      * 美团订单定时获取 2分钟执行一次
      */
     @XxlJob("getMtOrderDetails")
     public void getMtOrderDetails(String startTime,String endTime) {
          long timestamp13Bit = System.currentTimeMillis();
          //计算开始时间和结束时间
          if(StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)){
               // 计算前20分钟时间
               endTime =  timestamp13Bit / 1000 +"";
               startTime= (timestamp13Bit - 2 * 60 * 1000)/1000 +"";
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          MtlmVo mtlmVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_MTLM_CONFIG, MtlmVo.class);
          List<CpsMtActitvity> list =  cpsMtActitvityService.getMtActivityList();
          for (CpsMtActitvity item : list){
               MtOrderResponse response = MTUtils.getMtOrderList(startTime, endTime, item, mtlmVo);
               List<CpsMtOrder> cpsMtOrders = response.getMtOrderList();
               for (CpsMtOrder cpsMtOrder:cpsMtOrders){
                    User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getUserCode, cpsMtOrder.getSid()));
                    CpsMtOrder order = cpsMtOrderService.getBaseMapper().selectOne(new LambdaQueryWrapper<CpsMtOrder>().eq(CpsMtOrder::getOrderid, cpsMtOrder.getOrderid()));
                    if(order == null){
                         BeanUtils.copyProperties(cpsMtOrder, order);
                         order.setUserId(user.getUserId());
                         cpsMtOrderService.insert(order);
                    }else {
                         BeanUtils.copyProperties(cpsMtOrder, order);
                         order.setUserId(user.getUserId());
                         cpsMtOrderService.updateById(order);
                    }
               }
          }
     }
     /**
      * 拼多多订单定时获取 2分钟执行一次
      */
     @XxlJob("getPddOrderDetails")
     public void getPddOrderDetails() throws Exception {
          //计算开始时间和结束时间
          SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          Date nowdate=new Date();
          long c=nowdate.getTime()-1000*60*2;//1000*60*1 是1分钟
          Date olddate=new Date(c);
          String startTime = sdf.format(nowdate);
          String endTime = sdf.format(olddate);
          PddVo pddVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_PDD_CONFIG, PddVo.class);
          cpsDdOrderService.savePddOrderList(startTime,endTime,1,pddVo);
     }
     /**
      * 聚推客订单定时获取 2分钟执行一次
      */
     @XxlJob("getJtkOrderDetails")
     public void getJtkOrderDetails(Date startTime,Date endTime,int page) throws Exception {
          if(page < 1){
               page = 1;
          }
          //计算开始时间和结束时间
          if(startTime == null || endTime == null){
               endTime=new Date();
               long c=endTime.getTime()-1000*60*2;//1000*60*1 是1分钟
               startTime=new Date(c);
          }else{
               startTime = startTime;
               endTime = endTime;
          }
          JtkVo jtkVo = cpsConfigService.getCpsConfigObject(BigSellerConfig.CPS_JTK_CONFIG, JtkVo.class);
          int total =cpsJtkOrderService.saveJtkOrderList(startTime,endTime,page,jtkVo);
          if(total == 100){
               page++;
               getJtkOrderDetails(startTime,endTime,page);
          }
     }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/EnterprisePayTask.java
New file
@@ -0,0 +1,86 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import cn.hutool.core.util.StrUtil;
import com.github.binarywang.wxpay.bean.entpay.EntPayQueryResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.EnterprisePay;
import com.yami.shop.config.WxConfig;
import com.yami.shop.service.EnterprisePayService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * 微信要关掉这个接口,所以这个接口要废弃了
 * @author yami
 */
@Slf4j
@Component("enterprisePay")
@AllArgsConstructor
@Deprecated
public class EnterprisePayTask {
    private final EnterprisePayService enterprisePayService;
    private final WxConfig wxConfig;
    /**
     * 企业支付
     */
    @XxlJob("sendEnterprisePay")
    public void sendEnterprisePay() {
        log.info("开始执行发送企业付款任务》》》》》》》》》》》》》》》》》》》》》");
        List<EnterprisePay> enterprisePayList = enterprisePayService.listApplyEnterprisePay();
        enterprisePayService.sendEnterprisePay(enterprisePayList);
        log.info("结束执行发送企业付款任务》》》》》》》》》》》》》》》》》》》》》");
    }
    /**
     * 查询支付情况
     */
    @XxlJob("queryAndUpdateEntPay")
    public void queryAndUpdateEntPay() {
        log.info("开始查询企业付款任务》》》》》》》》》》》》》》》》》》》》》》》");
        List<EnterprisePay> enterprisePayList = enterprisePayService.listApplyEnterprisePay();
        if (CollectionUtils.isNotEmpty(enterprisePayList)) {
            for (EnterprisePay enterprisePay : enterprisePayList) {
                try {
                    EntPayQueryResult entPayQueryResult = wxConfig.getEntPayService().queryEntPay(String.valueOf(enterprisePay.getEntPayOrderNo()));
                    // PROCESSING:处理中
                    if (StrUtil.equalsIgnoreCase(entPayQueryResult.getStatus(), "PROCESSING")) {
                    }
                    // SUCCESS: 转账成功
                    if (StrUtil.equalsIgnoreCase(entPayQueryResult.getStatus(), "SUCCESS")) {
                        enterprisePayService.paySuccess(enterprisePay);
                    }
                    // FAILED: 转账失败
                    if (StrUtil.equalsIgnoreCase(entPayQueryResult.getStatus(), "FAILED")) {
                        enterprisePayService.payFailed(enterprisePay);
                        log.error(entPayQueryResult.getReason());
                    }
                } catch (WxPayException e) {
                    log.error("WxPayException:", e);
                }
            }
        }
        log.info("结束查询企业付款任务》》》》》》》》》》》》》》》》》》》》》》》");
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/FlowAnalysisTask.java
New file
@@ -0,0 +1,53 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.service.FlowLogService;
import com.yami.shop.service.FlowService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author yami
 */
@Component
public class FlowAnalysisTask {
    @Autowired
    private FlowLogService flowLogService;
    @Autowired
    private FlowService flowService;
    /**
     * 根据设置的时间,将缓存中的记录插入到数据库
     */
    @XxlJob("insertFlowAnalysisLog")
    public void insertFlowAnalysisLog(){
        if (RedisUtil.hasKey(Constant.FLOW_ANALYSIS_LOG)){
            flowLogService.insertBatch();
        }
    }
    /**
     * 统计记录数据,储存到对应的数据表中
     */
    @XxlJob("statisticalFlowData")
    public void statisticalFlowData(){
        //更新数据再统计
        insertFlowAnalysisLog();
        flowService.statisticalData();
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/FossickTask.java
New file
@@ -0,0 +1,140 @@
package com.yami.shop.platform.task;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.CdnFlow;
import com.yami.shop.bean.model.CdnUserWallet;
import com.yami.shop.bean.model.MarsAdverIncome;
import com.yami.shop.common.constants.DictConstant;
import com.yami.shop.service.CdnFlowService;
import com.yami.shop.service.CdnUserWalletService;
import com.yami.shop.service.MarsAdverIncomeService;
import com.yami.shop.task.common.model.TaskProvideUser;
import com.yami.shop.task.common.service.TaskProvideUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Component
public class FossickTask {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private TaskProvideUserService taskProvideUserService;
    @Autowired
    private CdnFlowService cdnFlowService;
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @Autowired
    private MarsAdverIncomeService marsAdverIncomeService;
    /**
     * AI+自主掘金分润
     */
    @XxlJob("fossickCancel")
    public void fossickCancel() {
        try {
            logger.info("AI+自主掘金分润计算开始。。。" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            List<TaskProvideUser> list = taskProvideUserService.list(new LambdaQueryWrapper<TaskProvideUser>().eq(TaskProvideUser::getStatus, 3));
            for (TaskProvideUser taskProvideUser : list) {
                if(ObjectUtil.isNotEmpty(taskProvideUser.getReleases())){
                    if(taskProvideUser.getReleases().compareTo(BigDecimal.ZERO) > 0){
                        setFlowLog(taskProvideUser.getUserId(),taskProvideUser.getUserId(),taskProvideUser.getReleases(), DictConstant.WALLET_ID_10,"AI+自主掘金分润",DictConstant.FLOWNAME_ID_50);
                        taskProvideUser.setStatus(4);
                        taskProvideUserService.updateById(taskProvideUser);
                        MarsAdverIncome marsAdverIncome = MarsAdverIncome.builder()
                                .userId(taskProvideUser.getUserId())
                                .money(taskProvideUser.getReleases())
                                .date(new Date())
                                .build();
                        marsAdverIncomeService.save(marsAdverIncome);
                    }
                }
             }
            logger.info("AI+自主掘金分润计算结束。。。" + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        }catch (Exception e) {
            logger.error("AI+自主掘金分润计算失败", e);
        }
    }
    /**
     *
     * @param userId 受益人
     * @param userIds 贡献人
     * @param money 收益金额
     * @param walletId 钱包类型
     * @param desc 收益描述
     * @param flownameId 流水类型
     */
    private void  setFlowLog(String userId, String userIds, BigDecimal money, Integer walletId, String desc, Integer flownameId){
        CdnFlow cdnFlow = new CdnFlow();
        cdnFlow.setUserIds(userIds);
        cdnFlow.setUserId(userId);
        cdnFlow.setFlownameId(flownameId);
        cdnFlow.setWalletId(walletId);
        //钱包更改
        CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,walletId));
        if(cdnUserWallet.getMoney().compareTo(BigDecimal.ZERO) == 0){
            return;
        }
        if(money.compareTo(BigDecimal.ZERO) <= 0){//幸运池已经释放完毕
            return;
        }else{
            if(money.compareTo(cdnUserWallet.getMoney()) >= 0){
                cdnFlow.setMoney(cdnUserWallet.getMoney().negate());
            }else{
                cdnFlow.setMoney(money.negate());
            }
        }
        cdnFlow.setMemo(desc+":释放用户"+userIds);
        cdnFlow.setCreateTime(new Date());
        cdnFlow.setDate(new Date());
        cdnFlow.setOldmoney(cdnUserWallet.getMoney());
        cdnFlowService.save(cdnFlow);
        cdnUserWallet.setUserId(userId);
        cdnUserWallet.setWalletId(walletId);
        cdnUserWallet.setMoney(cdnUserWallet.getMoney().add(cdnFlow.getMoney()));
        cdnUserWallet.setUpdateTime(new Date());
        cdnUserWallet.setReleaseCount(cdnUserWallet.getReleaseCount().add(cdnFlow.getMoney()));
        cdnUserWalletService.updateById(cdnUserWallet);
        //释放完进入流量
        CdnFlow cdnFlows = new CdnFlow();
        BeanUtils.copyProperties(cdnFlow, cdnFlows);
        cdnFlows.setMoney(cdnFlow.getMoney().negate());
        cdnFlows.setWalletId(DictConstant.WALLET_ID_8);
        cdnFlows.setFlownameId(flownameId);
        CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,DictConstant.WALLET_ID_8));
        if(null == cdnUserWallets){
            return;
        }
        cdnFlows.setOldmoney(cdnUserWallets.getMoney() == null?BigDecimal.ZERO:cdnUserWallets.getMoney());
        cdnFlows.setMemo(desc+"-->流量:相关用户"+userIds);
        cdnFlowService.save(cdnFlows);
        //钱包更改
        cdnUserWallets.setUserId(userId);
        cdnUserWallets.setWalletId(DictConstant.WALLET_ID_8);
        cdnUserWallets.setMoney(cdnUserWallets.getMoney()== null?cdnFlow.getMoney().negate():cdnUserWallets.getMoney().add(cdnFlow.getMoney().negate()));
        cdnUserWallets.setUpdateTime(new Date());
        cdnUserWalletService.updateById(cdnUserWallets);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/NotifyTask.java
New file
@@ -0,0 +1,51 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import cn.hutool.core.collection.CollectionUtil;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.NotifyLog;
import com.yami.shop.service.NotifyLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * @author yami
 */
@Component
public class NotifyTask {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private NotifyLogService notifyLogService;
    @XxlJob("sendMessage")
    public void sendMessage(){
        logger.info("获取还未进行消息推送的通知。。。");
        // 获取还未进行消息推送的通知
        List<NotifyLog> logList = notifyLogService.listUnSendMsgList();
        if (CollectionUtil.isEmpty(logList)) {
            return;
        }
        // 修改成已发送
        logList.forEach(notifyLog -> notifyLog.setStatus(1));
        notifyLogService.updateBatchById(logList);
        // 推送消息
        notifyLogService.sendMessage(logList);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/OrderRefundTask.java
New file
@@ -0,0 +1,100 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.dto.OrderRefundDto;
import com.yami.shop.bean.enums.ReturnMoneyStsType;
import com.yami.shop.bean.enums.SendType;
import com.yami.shop.bean.model.OrderRefund;
import com.yami.shop.common.config.Constant;
import com.yami.shop.service.NotifyTemplateService;
import com.yami.shop.service.OrderRefundService;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
 * @author yami
 */
@Slf4j
@Component
public class OrderRefundTask {
    @Autowired
    private OrderRefundService orderRefundService;
    @Autowired
    private NotifyTemplateService notifyTemplateService;
    @Autowired
    private MapperFacade mapperFacade;
    /**
     * 取消申请超时的订单,无论该超时订单处于任何状态
     */
    @XxlJob("cancelWhenTimeOut")
    public void cancelWhenTimeOut() {
        log.info("==============订单退款超时处理开始===================");
        // 设定时间值
        Date date = DateUtil.offsetDay(new Date(), -Constant.MAX_REFUND_APPLY_TIME);
        // 获取待处理的退款订单
        List<OrderRefund> orderRefundList = orderRefundService.list(new LambdaQueryWrapper<OrderRefund>()
                .in(OrderRefund::getReturnMoneySts, Arrays.asList(ReturnMoneyStsType.APPLY.value(),ReturnMoneyStsType.PROCESSING.value(),ReturnMoneyStsType.CONSIGNMENT.value(),ReturnMoneyStsType.RECEIVE.value()))
                .lt(OrderRefund::getApplyTime, date));
        if (CollectionUtils.isNotEmpty(orderRefundList)) {
            orderRefundService.cancelWhenTimeOut(orderRefundList);
        }
        log.info("==============订单退款超时处理结束===================");
    }
    /**
     * 退款临近超时提醒,每12小时执行发送一次的提醒
     */
    @XxlJob("pressRefundOrder")
    public void pressRefundOrder(){
        log.info("==============订单退款超时提醒开始===================");
        // 临时超时时间为 最大申请时间 - 12小时
        Integer overTime = Constant.MAX_REFUND_APPLY_TIME * 24;
        Date date = DateUtil.offsetHour(new Date(), Constant.MAX_REFUND_HOUR - overTime);
        Date overDate = DateUtil.offsetDay(new Date(), -Constant.MAX_REFUND_APPLY_TIME);
        // 获取临近超时的退款订单,大于超时时间,小于临时时间
        List<OrderRefund> orderRefundList = orderRefundService.list(new LambdaQueryWrapper<OrderRefund>()
                .in(OrderRefund::getReturnMoneySts, Arrays.asList(ReturnMoneyStsType.APPLY.value(),ReturnMoneyStsType.PROCESSING.value(),ReturnMoneyStsType.CONSIGNMENT.value(),ReturnMoneyStsType.RECEIVE.value()))
                .gt(OrderRefund::getApplyTime,overDate)
                .lt(OrderRefund::getApplyTime, date));
        if (CollectionUtils.isNotEmpty(orderRefundList)) {
            List<OrderRefundDto> orderRefundDtos = mapperFacade.mapAsList(orderRefundList, OrderRefundDto.class);
            orderRefundDtos = orderRefundDtos.stream().filter(distinctByKey(OrderRefundDto::getUserId)).collect(Collectors.toList());
            for (OrderRefundDto orderRefundDto : orderRefundDtos) {
                notifyTemplateService.sendNotifyByRefund(orderRefundDto,SendType.REFUND_OUT_TIME);
            }
        }
        log.info("==============退款临近超时提醒结束===================");
    }
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/OrderTask.java
New file
@@ -0,0 +1,199 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.bo.PayInfoResultBO;
import com.yami.shop.bean.enums.OrderStatus;
import com.yami.shop.bean.enums.PayStatus;
import com.yami.shop.bean.enums.SendType;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.bean.model.OrderSettlement;
import com.yami.shop.bean.model.PayInfo;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.enums.PayType;
import com.yami.shop.manager.impl.PayManager;
import com.yami.shop.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
 * @author yami
 */
@Component
public class OrderTask {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private OrderService orderService;
    @Autowired
    private NotifyTemplateService notifyTemplateService;
    @Autowired
    private ProductService productService;
    @Autowired
    private SkuService skuService;
    @Autowired
    private PayInfoService payInfoService;
    @Autowired
    private OrderSettlementService orderSettlementService;
    @Autowired
    private PayManager payManager;
    @Autowired
    private NotifyLogService notifyLogService;
    @XxlJob("cancelOrder")
    public void cancelOrder() {
        Date now = new Date();
        logger.info("取消超时未支付订单。。。");
        // 获取30分钟之前未支付的订单
        List<Order> orders = orderService.listUnRefundOrderAndOrderItems(OrderStatus.UNPAY.value(), DateUtil.offsetMinute(now, -30));
        if (CollectionUtil.isEmpty(orders)) {
            return;
        }
        List<Order> cancelOrderList = this.checkOrders(orders);
        orderService.cancelOrders(cancelOrderList);
        // 移除缓存
        this.removeCache(cancelOrderList);
    }
    /**
     * 确认收货15天后执行订单结算
     */
    @XxlJob("orderCommissionSettlement")
    public void orderCommissionSettlement() {
        logger.info("开始执行订单结算任务》》》》》》》》》》》》》》》》》》》》》");
        Date now = new Date();
        // 确认收货15天的订单,进行结算(更新时间为15天前的订单)
        List<Order> orders = orderService.listPendingSettlementOrders(OrderStatus.SUCCESS.value(), DateUtil.beginOfDay(DateUtil.offsetDay(now, -Constant.DISTRIBUTION_SETTLEMENT_TIME)));
        if (CollectionUtil.isEmpty(orders)) {
            return;
        }
        orderService.orderCommissionSettlement(orders);
        // 移除缓存
        this.removeCache(orders);
        logger.info("结束执行订单结算任务》》》》》》》》》》》》》》》》》》》》》");
    }
    /**
     * 订单催付提醒,每1分钟执行发送一次订单未支付的提醒
     */
    @XxlJob("pressPayOrder")
    public void pressPayOrder() {
        Date now = new Date();
        logger.info("执行订单催付提醒");
        // 获取15分钟之前未支付的订单
        List<Order> orders = orderService.listUnRefundOrderAndOrderItems(OrderStatus.UNPAY.value(), DateUtil.offsetMinute(now, -15));
        if (CollectionUtil.isEmpty(orders)) {
            return;
        }
        // 消息推送-订单催付提醒
        // 对相同用户id进行去重
        orders = orders.stream().filter(distinctByKey(Order::getUserId)).collect(Collectors.toList());
        for (Order order : orders) {
            Integer num = notifyLogService.countMsgNum(order.getOrderNumber());
            if (num < 1) {
                //没发过催付消息才会发送
                notifyTemplateService.sendNotifyOfDelivery(order, null, SendType.PRESS_PAY);
            }
        }
    }
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
    /**
     * 确认收货
     */
    @XxlJob("confirmOrder")
    public void confirmOrder() {
        Date now = new Date();
        logger.info("系统自动确认收货订单。。。");
        // 获取15天之前等待确认收货的订单
        List<Order> orders = orderService.listUnRefundOrderAndOrderItems(OrderStatus.CONSIGNMENT.value(), DateUtil.offsetDay(now, -15));
        if (CollectionUtil.isEmpty(orders)) {
            return;
        }
        orderService.receiptOrder(orders);
        // 移除缓存
        this.removeCache(orders);
    }
    /**
     * 查询订单,去除已支付的订单
     *
     * @param orders
     */
    private List<Order> checkOrders(List<Order> orders) {
        List<String> orderNumbers = orders.stream().map(Order::getOrderNumber).collect(Collectors.toList());
        // 收集未支付的订单
        List<Order> orderList = new ArrayList<>();
        //获取订单对应的订单结算数据
        List<OrderSettlement> orderSettlementList = orderSettlementService.list(new LambdaQueryWrapper<OrderSettlement>()
                .in(OrderSettlement::getOrderNumber, orderNumbers));
        Map<String, OrderSettlement> orderSettlementMap = orderSettlementList.stream()
                .collect(Collectors.toMap(OrderSettlement::getOrderNumber, orderSettlement -> orderSettlement));
        for (Order order : orders) {
            OrderSettlement orderSettlement = orderSettlementMap.get(order.getOrderNumber());
            if (Objects.isNull(orderSettlement) || Objects.isNull(orderSettlement.getPayType()) || Objects.isNull(orderSettlement.getPayNo())) {
                orderList.add(order);
                continue;
            }
            String bizPayNo = null;
            if (Objects.nonNull(orderSettlement.getPayNo())) {
                PayInfo payInfo = payInfoService.getOne(Wrappers.lambdaQuery(PayInfo.class).eq(PayInfo::getPayNo, orderSettlement.getPayNo()));
                bizPayNo = payInfo.getBizPayNo();
            }
            PayInfoResultBO payInfoResultBO = payManager.getPayInfo(PayType.instance(orderSettlement.getPayType()), orderSettlement.getPayNo(), bizPayNo);
            if (!payInfoResultBO.getIsPaySuccess()) {
                // 根据内部订单号更新order settlement
                PayInfo payInfo = payInfoService.getOne(new LambdaQueryWrapper<PayInfo>().eq(PayInfo::getPayNo, orderSettlement.getPayNo()));
                if (Objects.equals(payInfo.getPayStatus(), PayStatus.UNPAY.value())) {
                    payInfoService.noticeOrder(payInfoResultBO, payInfo);
                }
            } else {
                orderList.add(order);
            }
        }
        return orderList;
    }
    /**
     * 移除缓存
     */
    private void removeCache(List<Order> orders) {
        for (Order order : orders) {
            List<OrderItem> orderItems = order.getOrderItems();
            for (OrderItem orderItem : orderItems) {
                productService.removeProdCacheByProdId(orderItem.getProdId());
                skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
            }
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/ProdTask.java
New file
@@ -0,0 +1,128 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.bo.ProductBO;
import com.yami.shop.bean.enums.EsOperationType;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.dao.OrderMapper;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.service.ProductService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author yami
 */
@Component
public class ProdTask {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private ProductService productService;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private SearchProductService searchProductService;
    @Autowired
    private ApplicationEventPublisher eventPublisher;;
    @XxlJob("recoveryPreSaleProd")
    public void recoveryPreSaleProd(){
        logger.info("过了预售时间的商品,恢复成普通商品状态。。。");
        // 获取30分钟之前未支付的订单
        List<Product> products = productService.recoveryPreSaleProd();
        if(CollectionUtils.isEmpty(products)){
            return;
        }
        for (Product product : products) {
            //清除缓存
            productService.removeProdCacheByProdId(product.getProdId());
        }
    }
    @XxlJob("offlineExpireVirtualProd")
    public void offlineExpireVirtualProd(){
        logger.info("过了核销时间的虚拟商品,进行下架操作。");
        // 获取30分钟之前未支付的订单
        List<Product> products = productService.handleExpireVirtualProd();
        if(CollectionUtils.isEmpty(products)){
            return;
        }
        List<Long> ids = new ArrayList<>();
        for (Product product : products) {
            //清除缓存
            productService.removeProdCacheByProdId(product.getProdId());
            ids.add(product.getProdId());
        }
        applicationContext.publishEvent(new EsProductUpdateEvent(null, ids, EsOperationType.UPDATE_BATCH));
    }
    /**
     * 校验商品数量是否完整
     * 商品数据是否为最新的:根据商品更新时间判断
     */
    @XxlJob("verifyProdStockAndSold")
    public void verifySpuStockAndSold(){
        // 获取6分钟内提交订单中的商品列表
        List<Long> prodIds = orderMapper.listProdIdByTime(DateUtil.offsetDay(new Date(), -6));
        if (CollUtil.isEmpty(prodIds)) {
            return;
        }
        // 从es中查询商品的库存和销量数据
        EsProductParam productParam = new EsProductParam();
        productParam.setProdIds(prodIds);
        productParam.setFetchSource(new String[]{"prodId", "totalStocks", "soldNum", "actualSoldNum"});
        List<ProductSearchVO> esProdList = searchProductService.listSpuByProdIds(productParam);
        Map<Long, ProductSearchVO> prodMap = esProdList.stream().collect(Collectors.toMap(ProductSearchVO::getProdId, p -> p));
        // 从mysql中查询商品的库存和销量数据
        List<ProductBO> prodList = productService.listProdStockAndSold(prodIds);
        List<Long> updateList = new ArrayList<>();
        //比较es和mysql的商品数据
        for (ProductBO productBO : prodList) {
            ProductSearchVO productSearchVO = prodMap.get(productBO.getProdId());
            if (Objects.isNull(productSearchVO)) {
                continue;
            }
            boolean change = !Objects.equals(productBO.getTotalStocks(),  productSearchVO.getTotalStocks()) ||
                    !Objects.equals(productBO.getActualSoldNum() + productBO.getWaterSoldNum(), productSearchVO.getSoldNum()) ||
                    !Objects.equals(productBO.getActualSoldNum(), productSearchVO.getActualSoldNum());
            if (change) {
                updateList.add(productBO.getProdId());
            }
        }
        // 数据不一致的商品,更新es中的数据
        if (CollUtil.isNotEmpty(updateList)) {
            eventPublisher.publishEvent(new EsProductUpdateEvent(null, updateList, EsOperationType.UPDATE_SOLD_NUM_BATCH));
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/ShopTask.java
New file
@@ -0,0 +1,40 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.platform.task;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.service.ShopDetailService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * @Author lth
 * @Date 2021/8/9 14:45
 */
@Slf4j
@Component
@AllArgsConstructor
public class ShopTask {
    private final ShopDetailService shopDetailService;
    /**
     * 根据签约时间改变店铺状态
     */
    @XxlJob("changeShopStatusByContractTime")
    public void changeShopStatusByContractTime() {
        log.info("根据签约时间改变店铺状态");
        Date now = new Date();
        shopDetailService.changeShopStatusByContractTime(now);
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/TaskReleaseTask.java
New file
@@ -0,0 +1,140 @@
package com.yami.shop.platform.task;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.CdnUserWallet;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.enums.WalletEnum;
import com.yami.shop.service.CdnUserWalletService;
import com.yami.shop.service.UserService;
import com.yami.shop.task.common.model.TaskProvideRecord;
import com.yami.shop.task.common.model.TaskProvideUser;
import com.yami.shop.task.common.service.TaskProvideRecordService;
import com.yami.shop.task.common.service.TaskProvideUserService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Random;
@Slf4j
@Component
public class TaskReleaseTask {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private TaskProvideRecordService taskProvideRecordService;
    @Autowired
    private TaskProvideUserService taskProvideUserService;
    @Autowired
    private UserService userService;
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @XxlJob("cancelTaskRelease")
    public void cancelRelease() {
        Date now = new Date();
        logger.info("任务状态修改开始。。。" + DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        // 查询已激活的任务释放
        List<TaskProvideUser> taskProvideUserList = taskProvideUserService.list(Wrappers.lambdaQuery(TaskProvideUser.class).gt(TaskProvideUser::getStatus, 0).lt(TaskProvideUser::getStatus, 4));
        for (TaskProvideUser taskProvideUser : taskProvideUserList) {
            switch (taskProvideUser.getStatus()) {
                case 1:
                    taskProvideUser.setStatus(2);
                    Random random = new Random();
                    // 生成0到1之间的随机小数
                    double randomFloat = random.nextDouble();
                    // 将随机小数映射到1到20之间
                    double randomFloat1To20 = randomFloat * 19 + 1;
                    System.out.println(randomFloat1To20);
                    // 以50%的概率添加负号
                    if (random.nextBoolean()) {
                        randomFloat1To20 = -randomFloat1To20;
                    }
                    // 使用String.format()保留两位小数
                    String formattedString = String.format("%.2f", randomFloat1To20);
                    // 如果你确实需要double类型,但注意这会丢失不必要的零
                    double formattedDouble = Double.parseDouble(formattedString);
                    taskProvideUser.setProcess(50 + formattedDouble);
                    taskProvideUserService.updateById(taskProvideUser);
                    break;
                case 2:
                    taskProvideUser.setStatus(3);
                    taskProvideUser.setProcess(100.00);
                    taskProvideUserService.updateById(taskProvideUser);
                    break;
                default:
                    break;
                }
        }
    }
    @XxlJob("cancelTaskUserGeneral")
    public void cancelGeneral() {
        Date now = new Date();
        logger.info("任务提供释放初始化开始。。。" + DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        // 查询任务提供释放记录。开始和结束时间在当前时间段内
        String formattedDate = DateUtil.format(now, "yyyy-MM-dd");
        List<TaskProvideRecord> taskProvideRecordList = taskProvideRecordService.list(Wrappers.lambdaQuery(TaskProvideRecord.class)
                .le(TaskProvideRecord::getStartTime,formattedDate)
                .ge(TaskProvideRecord::getEndTime, formattedDate));
        // 遍历任务提供记录
        for (TaskProvideRecord taskProvideRecord : taskProvideRecordList) {
            // 查询所有激活任务大厅的用户
            List<User> users = userService.list(Wrappers.lambdaQuery(User.class).eq(User::getActiveStatus, 1));
            // 遍历初始化用户
            for (User user : users) {
                // 查询用户的金牛豆释放总额
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class).eq(CdnUserWallet::getUserId, user.getUserId()).eq(CdnUserWallet::getWalletId, WalletEnum.LUCKPOOL.value()));
                BigDecimal releaseCount = cdnUserWallet.getReleaseCount() != null ? cdnUserWallet.getReleaseCount() : BigDecimal.ZERO;
                if (releaseCount.compareTo(BigDecimal.ZERO) > 0) {
                    // 创建任务提供用户
                    TaskProvideUser taskProvideUser = new TaskProvideUser();
                    taskProvideUser.setUserId(user.getUserId());
                    taskProvideUser.setProvideRecordId(taskProvideRecord.getProvideRecordId());
                    taskProvideUser.setDateTime(formattedDate + "");
                    taskProvideUser.setCreateTime(new Date());
                    taskProvideUser.setTaskId(taskProvideRecord.getTaskId());
                    taskProvideUser.setStatus(0);
                    taskProvideUser.setProcess(0.00);
                    taskProvideUser.setStartTime(new Date());
                    taskProvideUser.setReleaseCount(cdnUserWallet.getReleaseCount());
                    // 根据释放百分比算出本次释放的幸运池数量
                    BigDecimal release = cdnUserWallet.getReleaseCount().multiply(taskProvideRecord.getReleaseRate());
                    Random random = new Random();
                    // 生成0到1之间的随机小数(不包括1)
                    double randomFloat = random.nextDouble();
                    // 以50%的概率添加负号
                    if (random.nextBoolean()) {
                        randomFloat = -randomFloat;
                    }
                    // 使用String.format()保留两位小数
                    String formattedString = String.format("%.2f", randomFloat);
                    // 如果你确实需要double类型,但注意这会丢失不必要的零
                    double formattedDouble = Double.parseDouble(formattedString);
                    release = release.add(BigDecimal.valueOf(formattedDouble));
                    taskProvideUser.setReleases(release);
                    taskProvideUserService.save(taskProvideUser);
                }
            }
        }
        logger.info("任务提供释放初始化结束。。。" + DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/UserLevelTask.java
New file
@@ -0,0 +1,1132 @@
package com.yami.shop.platform.task;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.*;
import com.yami.shop.common.constants.DictConstant;
import com.yami.shop.common.util.DateUtils;
import com.yami.shop.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Slf4j
@Component
public class UserLevelTask {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private CdnTeamRelationService cdnTeamRelationService;
    @Autowired
    private CdnAgentLevelService cdnAgentLevelService;
    @Autowired
    private AgentLevelService agentLevelService;
    @Autowired
    private CdnFlowService cdnFlowService;
    @Autowired
    private CdnUserWalletService cdnUserWalletService;
    @Autowired
    private CdnReleaseService cdnReleaseService;
    @Autowired
    private CdnConfigService cdnConfigService;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private UserService userService;
    @Autowired
    private MarsAdverIncomeService marsAdverIncomeService;
    @Autowired
    private CdnReleaseTempService cdnReleaseTempService;
    @XxlJob("cancelPointRelease")
    public void cancelPointRelease() {
        Double bpointReleaseBl = Double.valueOf( cdnConfigService.selectValueByName("point_release_bl_hb"));
        Double zpointReleaseBl = Double.valueOf(cdnConfigService.selectValueByName("point_release_bl"));
        cdnReleaseTempService.deleteAll();
        List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>().eq(CdnRelease::getIsAct,2));
        for (CdnRelease release : list) {
            CdnReleaseTemp cdnReleaseTemp = new CdnReleaseTemp();
            cdnReleaseTemp.setId(release.getId());
            if(release.getMoney().compareTo(release.getBalance()) > 0){
                cdnReleaseTemp.setPointRelease(BigDecimal.valueOf(bpointReleaseBl));
            }else{
                cdnReleaseTemp.setPointRelease(BigDecimal.valueOf(zpointReleaseBl));
            }
            cdnReleaseTempService.insert(cdnReleaseTemp);
        }
    }
    /**
     * 用户等级 判定算法(手机卡)
     */
    @XxlJob("cancelUserLevel")
    public void cancelUserLevel() {
        try {
            Date now = new Date();
            logger.info("用户等级调整开始。。。"+ DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
            List<CdnTeamRelation> cdnTeamRelationList = cdnTeamRelationService.list(new LambdaQueryWrapper<CdnTeamRelation>());
            List<AgentLevel> agentLevelList = agentLevelService.getCdnAgentLevelList();
            List<CdnTeamRelation>  lists = new ArrayList<>();
            for (CdnTeamRelation relation : cdnTeamRelationList) {
                for (AgentLevel agentLevel : agentLevelList) {
                    if(relation.getLevel() >= agentLevel.getId()){//等级只能升 不能降
                        continue;
                    }else{
                        //直推人
                        Integer num = 0;//直推会员人数
                        Integer total = 0;//激活账号数
                        List<CdnTeamRelation> cdnTeamRelations = cdnTeamRelationService.getTeamRelationListSort(relation.getUserId());
                        if(cdnTeamRelations.size() >= agentLevel.getInvite()){//判断直推人条件
                            for (CdnTeamRelation c:cdnTeamRelations){//直推会员人数
                                if(c.getLevel() == (agentLevel.getId()-1)){
                                    num++;
                                }
                            }
                            String pids = null;
                            if (StringUtils.isNotBlank(relation.getPids())) {
                                pids = relation.getPids() + relation.getUserId() + ",";
                            } else {
                                pids = "," + relation.getUserId() + ",";
                            }
                            CdnTeamRelation cdns = new CdnTeamRelation();
                            cdns.setPids(pids);
                            List<CdnTeamRelation> cdnTeamList = cdnTeamRelationService.getDividendList(cdns);
                            for (CdnTeamRelation c:cdnTeamList){
                                User d = new User();
                                d.setUserId(c.getUserId());
                                d.setActiveStatus("1");
                                total += userService.getctiveStatusCount(d);
                            }
                            if(num >= agentLevel.getMember() && total  >= agentLevel.getDeviceActive()){
                                relation.setLevel(agentLevel.getId());
                                if("V7".equals(agentLevel.getName())){
                                    User user = userService.getUserByUserId(relation.getUserId());
                                    if(user != null && user.getActiveTime() != null){
                                        if(DateUtils.flagIn30DayDate(new SimpleDateFormat("yyyy-MM-dd").format(user.getActiveTime()))){
                                            user.setActiveStatus("1");
                                            userService.updateById(user);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                lists.add(relation);
            }
            cdnTeamRelationService.updateBatchById(lists);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 帮扶、分享 、平级(CND托管)
     */
    @XxlJob("cancelAssists")
    public void cancelAssists() {
        Date now = new Date();
        logger.info("帮扶费用计算开始。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        //分享比例
        Double share =Double.valueOf(cdnConfigService.selectValueByName("share")) ;
        Double zpointReleaseBl = Double.valueOf(cdnConfigService.selectValueByName("point_release_bl"));
        List<CdnTeamRelation> cdnTeamRelationLists = cdnTeamRelationService.getTeamRelationListSort(null);
        //循环所有完整线 分享、平级、帮扶
        for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationLists){
            //基础比例
            if(cdnTeamRelation.getMeall().compareTo(BigDecimal.ZERO) == 0){
                continue;
            }
            if(cdnTeamRelation.getPids() != null){
                String[] pidsArrays = Arrays.copyOfRange(cdnTeamRelation.getPids().split(StrUtil.COMMA), 1, cdnTeamRelation.getPids().split(StrUtil.COMMA).length);
                CdnAgentLevel cdnPAgentLevel = cdnAgentLevelService.getById(cdnTeamRelation.getAgentLevelId());
                List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>().eq(CdnRelease::getUserId,cdnTeamRelation.getUserId()).eq(CdnRelease::getStatus,1).eq(CdnRelease::getIsAct,2));
                if(list.size() == 0){
                    continue;
                }
                //加速释放比例
                Integer id = 1;
                BigDecimal releases= BigDecimal.ZERO;
                //个人业绩总价钱
                BigDecimal mellTotalPrice = BigDecimal.ZERO;
                for (CdnRelease cdnRelease : list){
                    CdnReleaseTemp cdnReleaseTemp = cdnReleaseTempService.getById(cdnRelease.getId());
                    if(cdnReleaseTemp == null){
                        mellTotalPrice = mellTotalPrice.add(cdnRelease.getMoney().multiply(BigDecimal.valueOf(zpointReleaseBl)));
                    }else{
                        mellTotalPrice = mellTotalPrice.add(cdnRelease.getMoney().multiply(cdnReleaseTemp.getPointRelease()));
                    }
                }
                //从后往前循环遍历
                for (int i = pidsArrays.length - 1; i >= 0; i--) {
                    CdnTeamRelation cdnTeamRelationPids = cdnTeamRelationService.getOne(new LambdaQueryWrapper<CdnTeamRelation>().eq(CdnTeamRelation::getUserId,pidsArrays[i]));
                    CdnAgentLevel cdnPAgentLevels = cdnAgentLevelService.getById(cdnTeamRelationPids.getAgentLevelId());
                    if(i==pidsArrays.length - 1) {//不计算帮扶 算分享
                        BigDecimal fxmoney = mellTotalPrice.multiply(BigDecimal.valueOf(share));
                        if(fxmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0){
                            if(cdnTeamRelation.getMeall().compareTo(BigDecimal.ZERO) > 0){//没有个人设备 无法分享助力
                                if(fxmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0){
                                    if(!cdnTeamRelation.getPid().equals("0")){
                                        setFlowLog(cdnTeamRelation.getPid(),cdnTeamRelation.getUserId(),fxmoney,5,"分享助力",19);
                                    }
                                }
                            }
                        }
                    }
                    if(id < cdnPAgentLevels.getId()){//帮扶
                        BigDecimal pfmoney  = mellTotalPrice.multiply((cdnPAgentLevels.getReleases().subtract(releases)));
                        if(pfmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0){
                            setFlowLog(cdnTeamRelationPids.getUserId(),cdnTeamRelation.getUserId(),pfmoney,5,"帮扶助力",20);
                        }
                        releases = cdnPAgentLevels.getReleases();
                        id = cdnPAgentLevels.getId();
                    }
                }
                //平级奖
                Integer ids = cdnPAgentLevel.getId();
                Integer parallels = 1;
                BigDecimal income = BigDecimal.ZERO;
                BigDecimal releasess= cdnPAgentLevel.getReleases();
                for (int i = pidsArrays.length - 1; i >= 0; i--) {
                    CdnTeamRelation cdnTeamRelationPids = cdnTeamRelationService.getOne(new LambdaQueryWrapper<CdnTeamRelation>().eq(CdnTeamRelation::getUserId,pidsArrays[i]));
                    CdnAgentLevel cdnPAgentLevels = cdnAgentLevelService.getById(cdnTeamRelationPids.getAgentLevelId());
                    if(cdnTeamRelationPids.getAgentLevelId() == ids && cdnTeamRelationPids.getAgentLevelId() > parallels){
                        //平级金额
                        BigDecimal pjmoney = income.multiply(cdnPAgentLevels.getParallels());
                        if(pjmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0){
                            setFlowLog(cdnTeamRelationPids.getUserId(),cdnTeamRelation.getUserId(),pjmoney,5,"管理津贴",21);
                        }
                        ids = cdnTeamRelationPids.getAgentLevelId();
                        parallels = ids;
                    }else{
                        if(cdnTeamRelationPids.getAgentLevelId() > ids){
                            income = mellTotalPrice.multiply((cdnPAgentLevels.getReleases().subtract(releasess)));
                            ids = cdnTeamRelationPids.getAgentLevelId();
                        }
                    }
                }
//                }
            }
        }
    }
    /**
     * 加权分红费用 计算(CND托管)
     */
    @XxlJob("cancelWeightedDividend")
    public void cancelWeightedDividend() throws ParseException {
        Date now = new Date();
        logger.info("加权分红费用计算开始。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat forms = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        LocalDateTime currentDateTime = LocalDateTime.now();
        String nowDateTime = currentDateTime.format(formatter);
        LocalDateTime pastDateTime = currentDateTime.minusHours(24);
        String formattedDateTime = pastDateTime.format(formatter);
        Map<String,BigDecimal> map  = new HashMap<>();
        //加权分红(级差部分)
        List<CdnTeamRelation> cdnTeamRelationList = cdnTeamRelationService.list(new LambdaQueryWrapper<CdnTeamRelation>()
                .gt(CdnTeamRelation::getTodayQuantity,0).orderByDesc(CdnTeamRelation::getSort));
        //循环所有完整线
        for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationList){
            if(StringUtils.isNotBlank(cdnTeamRelation.getPids())){
                if(cdnTeamRelation.getTodayQuantity() == 0){
                    continue;
                }
                //加速释放比例
                Integer id = 1;
                BigDecimal rebate= BigDecimal.ZERO;
                BigDecimal mellTotalPrice = BigDecimal.ZERO;
                List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>()
                        .eq(CdnRelease::getUserId,cdnTeamRelation.getUserId()).eq(CdnRelease::getIsAct,2)
                        .between(CdnRelease::getCreateTime, forms.parse(formattedDateTime), forms.parse(nowDateTime)));
                for (CdnRelease cdnRelease:list){
                    mellTotalPrice = mellTotalPrice.add(cdnRelease.getMoney());
                }
                if(mellTotalPrice.compareTo(BigDecimal.ZERO) == 0){
                    continue;
                }
                String[] pidsArrays = Arrays.copyOfRange(cdnTeamRelation.getPids().split(StrUtil.COMMA), 1, cdnTeamRelation.getPids().split(StrUtil.COMMA).length);
                //从后往前循环遍历
                for (int i = pidsArrays.length - 1; i >= 0; i--) {
                    CdnTeamRelation cdnTeamRelationPids = cdnTeamRelationService.getOne(new LambdaQueryWrapper<CdnTeamRelation>().eq(CdnTeamRelation::getUserId,pidsArrays[i]));
                    if(cdnTeamRelationPids != null){
                        if(cdnTeamRelationPids.getAgentLevelId() < 4) {//市级以下不用算
                            continue;
                        }
                        CdnAgentLevel cdnPAgentLevels = cdnAgentLevelService.getById(cdnTeamRelationPids.getAgentLevelId());
                        if(id < cdnPAgentLevels.getId()) {
                            BigDecimal pfmoney = mellTotalPrice.multiply(cdnPAgentLevels.getRebate().subtract(rebate));
                            if (pfmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0) {
                                if(map.containsKey(cdnTeamRelationPids.getUserId())){
                                    BigDecimal value = map.get(cdnTeamRelationPids.getUserId()).add(pfmoney);
                                    map.put(cdnTeamRelationPids.getUserId(),value);
                                }else{
                                    BigDecimal value = pfmoney;
                                    map.put(cdnTeamRelationPids.getUserId(),value);
                                }
                                id = cdnPAgentLevels.getId();
                                rebate =cdnPAgentLevels.getRebate();
                            }
                        }
                    }
                }
            }
        }
        //大区、省、市
        CdnTeamRelation cdns = new CdnTeamRelation();
        cdns.setAgentLevelId(3);
        List<CdnTeamRelation> cdnTeamRelationLists = cdnTeamRelationService.selectByAgentLevelId(cdns);
        BigDecimal money = cdnTeamRelationService.selectCountMoney();
        if (money == null || money.compareTo(BigDecimal.ZERO) == 0) {
            return;
        }
        //市级集合
        List<CdnTeamRelation> cityTeamRelationList = new ArrayList<>();
        //市级团队业绩之和
        BigDecimal  cityMall = BigDecimal.ZERO;
        //省级集合
        List<CdnTeamRelation> provinceTeamRelationList = new ArrayList<>();
        //省级团队业绩之和
        BigDecimal  provinceMall = BigDecimal.ZERO;
        //大区集合
        List<CdnTeamRelation> regionTeamRelationList = new ArrayList<>();
        //大区团队业绩之和
        BigDecimal  regionMall = BigDecimal.ZERO;
        //循环所有市、省、大区用户
        for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationLists){
            //新增设备
            CdnAgentLevel cdnPAgentLevel = cdnAgentLevelService.getById(cdnTeamRelation.getAgentLevelId());
            //省市 大区 分组
            if(cdnPAgentLevel.getName().indexOf("市级") >= 0){
                cityTeamRelationList.add(cdnTeamRelation);
                cityMall = cityMall.add(cdnTeamRelation.getTeamall());
            }else if(cdnPAgentLevel.getName().indexOf("省级") >= 0){
                provinceTeamRelationList.add(cdnTeamRelation);
                provinceMall=provinceMall.add(cdnTeamRelation.getTeamall());
            }else if(cdnPAgentLevel.getName().indexOf("大区") >= 0){
                regionTeamRelationList.add(cdnTeamRelation);
                regionMall=regionMall.add(cdnTeamRelation.getTeamall());
            }
        }
        //市级代理
        CdnFlow cdnFlow = new CdnFlow();
        if(cityTeamRelationList.size() > 0){
            for (CdnTeamRelation teamRelation : cityTeamRelationList){
                CdnAgentLevel cdnAgentLevels = cdnAgentLevelService.getById(teamRelation.getAgentLevelId());
                BigDecimal price = money.multiply(cdnAgentLevels.getRebates()).multiply(teamRelation.getTeamall()).divide(cityMall,BigDecimal.ROUND_CEILING);
                if(map.containsKey(teamRelation.getUserId())){
                    BigDecimal value = map.get(teamRelation.getUserId()).add(price);
                    map.put(teamRelation.getUserId(),value);
                }else{
                    BigDecimal value = price;
                    map.put(teamRelation.getUserId(),value);
                }
            }
        }
        //省级代理
        if(provinceTeamRelationList.size() > 0){
            for (CdnTeamRelation teamRelation : provinceTeamRelationList){
                CdnAgentLevel cdnAgentLevels = cdnAgentLevelService.getById(teamRelation.getAgentLevelId());
                BigDecimal price = money.multiply(cdnAgentLevels.getRebates()).multiply(teamRelation.getTeamall()).divide(provinceMall,BigDecimal.ROUND_CEILING);
                if(map.containsKey(teamRelation.getUserId())){
                    BigDecimal value = map.get(teamRelation.getUserId()).add(price);
                    map.put(teamRelation.getUserId(),value);
                }else{
                    BigDecimal value = price;
                    map.put(teamRelation.getUserId(),value);
                }
            }
        }
        //大区代理
        if(regionTeamRelationList.size() > 0){
            for (CdnTeamRelation teamRelation : regionTeamRelationList){
                CdnAgentLevel cdnAgentLevels = cdnAgentLevelService.getById(teamRelation.getAgentLevelId());
                BigDecimal price = money.multiply(cdnAgentLevels.getRebates()).multiply(teamRelation.getTeamall()).divide(regionMall,BigDecimal.ROUND_CEILING);
                if(map.containsKey(teamRelation.getUserId())){
                    BigDecimal value = map.get(teamRelation.getUserId()).add(price);
                    map.put(teamRelation.getUserId(),value);
                }else{
                    BigDecimal value = price;
                    map.put(teamRelation.getUserId(),value);
                }
            }
        }
        map.forEach((key, value) -> {
            CdnFlow cdnFlows = new CdnFlow();
            CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId, key)
                    .eq(CdnUserWallet::getWalletId, 6));
            cdnFlows.setUserId(key);
            cdnFlows.setMemo("加权分红");
            cdnFlows.setMoney(value);
            cdnFlows.setOldmoney(cdnUserWallets != null ? cdnUserWallets.getMoney() : BigDecimal.ZERO);
            cdnFlows.setFlownameId(22);
            cdnFlows.setCreateTime(new Date());
            cdnFlows.setWalletId(6);
            cdnFlows.setDate(new Date());
            cdnFlowService.save(cdnFlows);
            cdnUserWallets.setMoney(cdnFlows.getOldmoney().add(cdnFlows.getMoney()));
            cdnUserWallets.setUpdateTime(new Date());
            cdnUserWalletService.updateById(cdnUserWallets);
        });
        cdnTeamRelationService.updateTodayQuantity();
    }
    /**
     * 普通分润(CND托管)
     */
    @XxlJob("cancelDividend")
    public void cancelDividend() {
        Date now = new Date();
        logger.info("普通分润计算费用。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        CdnTeamRelation relation = new CdnTeamRelation();
        relation.setMeall(BigDecimal.ZERO);
        List<CdnTeamRelation> cdnTeamRelationList = cdnTeamRelationService.selectByAgentLevelId(relation);
        Double zpointReleaseBl = Double.valueOf(cdnConfigService.selectValueByName("point_release_bl"));
        for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationList){
            //基础比例
            List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>().eq(CdnRelease::getUserId,cdnTeamRelation.getUserId()).eq(CdnRelease::getStatus,1).eq(CdnRelease::getIsAct,2));
            if(list.size() == 0){
                continue;
            }
            CdnFlow cdnFlow = new CdnFlow();
            cdnFlow.setUserIds(cdnTeamRelation.getUserId());
            cdnFlow.setUserId(cdnTeamRelation.getUserId());
            cdnFlow.setFlownameId(18);
            cdnFlow.setWalletId(5);
            BigDecimal totalPrice = BigDecimal.ZERO;
            for (CdnRelease cdnRelease : list){
                BigDecimal price = BigDecimal.ZERO;
                CdnReleaseTemp cdnReleaseTemp = cdnReleaseTempService.getById(cdnRelease.getId());
                if(cdnReleaseTemp == null){
                    price = cdnRelease.getMoney().multiply(BigDecimal.valueOf(zpointReleaseBl));
                }else{
                    price = cdnRelease.getMoney().multiply(cdnReleaseTemp.getPointRelease());
                }
                CdnRelease cdnReleases = cdnReleaseService.getById(cdnRelease.getId());
                if(cdnReleases.getBalance().compareTo(BigDecimal.ZERO) == 0){//绿积分清空 已释放  直接
                    continue;
                }
                if(cdnRelease.getBalance().compareTo(price) < 0){//剩余率积分小于分润 扣除剩余积分余额
                    cdnRelease.setBalance(BigDecimal.ZERO);
                    cdnRelease.setStatus(0);
                    //updateTmall(cdnRelease.getUserId(),cdnRelease.getNum());
                    //修改个人数量和上级团队数量
                    totalPrice=totalPrice.add(cdnRelease.getBalance());
                }else{//否则正常扣除
                    cdnRelease.setBalance(cdnRelease.getBalance().subtract(price));
                    totalPrice = totalPrice.add(price);
                }
                cdnReleaseService.updateById(cdnRelease);
            }
            cdnFlow.setMemo("普通分润:相关用户"+cdnTeamRelation.getUserId());
            if(totalPrice.compareTo(BigDecimal.ZERO) == 0){
                continue;
            }
            cdnFlow.setMoney(totalPrice.negate());
            cdnFlow.setCreateTime(new Date());
            cdnFlow.setDate(new Date());
            //钱包更改
            CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId,cdnTeamRelation.getUserId())
                    .eq(CdnUserWallet::getWalletId,5));
            cdnFlow.setOldmoney(cdnUserWallet.getMoney());
            cdnFlowService.save(cdnFlow);
            cdnUserWallet.setMoney(cdnFlow.getOldmoney().add(cdnFlow.getMoney()));
            cdnUserWallet.setUpdateTime(new Date());
            cdnUserWalletService.updateById(cdnUserWallet);
            CdnFlow cdnFlows = new CdnFlow();
            BeanUtils.copyProperties(cdnFlow, cdnFlows);
            cdnFlows.setMoney(cdnFlow.getMoney().negate());
            cdnFlows.setWalletId(3);
            cdnFlows.setFlownameId(18);
            CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                    .eq(CdnUserWallet::getUserId,cdnTeamRelation.getUserId())
                    .eq(CdnUserWallet::getWalletId,3));
            cdnFlows.setOldmoney(cdnUserWallets.getMoney() == null ? BigDecimal.ZERO:cdnUserWallets.getMoney());
            cdnFlows.setMemo("普通分润-->余额:相关用户"+cdnTeamRelation.getUserId());
            cdnFlowService.save(cdnFlows);
            cdnUserWallets.setUserId(cdnTeamRelation.getUserId());
            cdnUserWallets.setWalletId(3);
            cdnUserWallets.setMoney(cdnUserWallets.getMoney().add(cdnFlow.getMoney().negate()));
            cdnUserWallets.setUpdateTime(new Date());
            cdnUserWalletService.updateById(cdnUserWallets);
        }
    }
    /**
     * 帮扶、分享 、平级(手机卡)
     */
    @XxlJob("cancelAssist")
    public void cancelAssist() {
        try {
            Date now = new Date();
            logger.info("团队奖励计算开始。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
            //先计算合伙人分红 查询
            BigDecimal companyPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("company_point")));//公司沉淀
            BigDecimal teamPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("team_point")));//团队奖比例
            BigDecimal  partnerPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("partner_point")));//合伙人
            BigDecimal  directPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("share")));//直推奖
            BigDecimal  jcPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("team_range_point")));//团队中的级差比例
            BigDecimal incomeCount = marsAdverIncomeService.selectAllIncome(null);
            if(incomeCount != null){
                //合伙人总数
                List<User> userList = userService.list(new LambdaQueryWrapper<User>().eq(User::getIsPartner,1));
                for (User user:userList){
                    //全天的总收益 *(1-公司沉淀0.3) * 50%(团队奖) * 8%  / 合伙人(查询)的总数
                    BigDecimal money = incomeCount.multiply(BigDecimal.valueOf(1).subtract(companyPoint)).multiply(teamPoint).multiply(partnerPoint).divide(BigDecimal.valueOf(userList.size()),2, RoundingMode.HALF_UP);
                    if(money.compareTo(BigDecimal.valueOf(0.01)) >= 0) {
                        setFlowLogs(user.getUserId(), user.getUserId(), money, DictConstant.WALLET_ID_10, "合伙人分成", DictConstant.FLOWNAME_ID_30);
                    }
                }
            }
            List<CdnTeamRelation> cdnTeamRelationLists = cdnTeamRelationService.getTeamRelationListSort(null);
            Map<Integer,BigDecimal> map  =new HashMap<>();
            for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationLists){
                //查询个人的幸运池 没有直接无收益
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                        .eq(CdnUserWallet::getUserId,cdnTeamRelation.getUserId())
                        .eq(CdnUserWallet::getWalletId,10));
                if(cdnUserWallet.getMoney().compareTo(BigDecimal.ZERO) == 0){
                    continue;
                }
                BigDecimal income = marsAdverIncomeService.selectAllIncome(cdnTeamRelation.getUserId());
                if(income  == null || income.compareTo(BigDecimal.ZERO) == 0){//没有业绩的
                    continue;
                }
                CdnTeamRelation pCdnTeamRelation = cdnTeamRelationService.geteamRelationByUserId(cdnTeamRelation.getPid());
                if(pCdnTeamRelation != null){
                    //直推奖 个人业绩 * (1-公司沉淀0.3) * 50%(团队奖) * 10%
                    BigDecimal ztmoney = income.multiply(BigDecimal.valueOf(1).subtract(companyPoint)).multiply(teamPoint).multiply(directPoint);
                    if(ztmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0) {
                        setFlowLogs(pCdnTeamRelation.getUserId(), cdnTeamRelation.getUserId(), ztmoney, DictConstant.WALLET_ID_10, "直推奖励", DictConstant.FLOWNAME_ID_30);
                    }
                    //循环所有上级用户 计算平级奖 直到1毛为止
                    if(cdnTeamRelation.getPids() != null) {
                        String[] pidsArrays = Arrays.copyOfRange(cdnTeamRelation.getPids().split(StrUtil.COMMA), 1, cdnTeamRelation.getPids().split(StrUtil.COMMA).length);
                        AgentLevel agentLevel = agentLevelService.getById(cdnTeamRelation.getLevel());
                        //加速释放比例
                        Integer id = 0;
                        BigDecimal releases= BigDecimal.ZERO;
                        if(agentLevel != null){
                            releases= agentLevel.getReleases();
                        }
                        Integer ids = 0;
//                    if(agentLevel != null){
//                        ids= agentLevel.getId();
//                    }
                        Integer parallels = 0;
                        // 将各个会员等级的收益全部汇总  个人今日收益 * (1-公司沉淀0.3) * 50%(团队奖) * 10%
                        BigDecimal countPoint = income.multiply(BigDecimal.valueOf(1).subtract(companyPoint)).multiply(teamPoint);
                        BigDecimal djPoint = countPoint.multiply(BigDecimal.valueOf(1).subtract(jcPoint));
                        BigDecimal pjCount = countPoint;
                        for (int i = pidsArrays.length - 1; i >= 0; i--) {
                            BigDecimal childMoeny = BigDecimal.ZERO;
                            CdnTeamRelation cdnTeamRelationPids = cdnTeamRelationService.getOne(new LambdaQueryWrapper<CdnTeamRelation>().eq(CdnTeamRelation::getUserId, pidsArrays[i]));
                            AgentLevel agentLevels = agentLevelService.getById(cdnTeamRelationPids.getLevel());
                            if(agentLevels != null){
                                if(map.containsKey(agentLevels.getId())){
                                    BigDecimal value = map.get(agentLevels.getId()).add(djPoint);
                                    map.put(agentLevels.getId(),value);
                                }else{
                                    BigDecimal value = djPoint;
                                    map.put(agentLevels.getId(),value);
                                }
                                //获取收益
                                if(id < agentLevels.getId()) {
                                    childMoeny = countPoint.multiply(jcPoint).multiply((agentLevels.getReleases().subtract(releases)));
                                    if(childMoeny.compareTo(BigDecimal.valueOf(0.01)) >= 0){
                                        setFlowLogs(cdnTeamRelationPids.getUserId(),cdnTeamRelation.getUserId(),childMoeny,DictConstant.WALLET_ID_10,"团队奖(级差)",DictConstant.FLOWNAME_ID_30);
                                    }
                                    releases = agentLevels.getReleases();
                                    id = agentLevels.getId();
                                }
                                if (cdnTeamRelationPids.getLevel() == ids && cdnTeamRelationPids.getLevel() > parallels && ids != 0) {
                                    //平级奖 本用户收益的10% 贡献给上级
                                    BigDecimal pjmoney = pjCount.multiply(agentLevels.getParallels());
                                    if (pjmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0) {
                                        setFlowLogs(cdnTeamRelationPids.getUserId(), cdnTeamRelation.getUserId(), pjmoney, DictConstant.WALLET_ID_10, "管理津贴", DictConstant.FLOWNAME_ID_30);
                                    }
                                    ids = cdnTeamRelationPids.getLevel();
                                    parallels = ids;
                                    pjCount = pjmoney;
                                } else {
                                    ids = cdnTeamRelationPids.getLevel();
                                }
                            }else{
                                ids = 0;
                            }
                        }
                    }
                }
            }
            //计算团队奖的业绩部分 例如:V1总业绩(是否带团队的收益)
            List<AgentLevel>  list = agentLevelService.list();
            for (AgentLevel agentLevel : list) {
                //根据会员等级计算今日总业绩
                BigDecimal count  = cdnTeamRelationService.selectTMell(agentLevel.getId());
                if(count  == null || count.compareTo(BigDecimal.ZERO) == 0){//没有业绩的
                    continue;
                }
                //循环所有会员(V1会员)
                List<CdnTeamRelation> cdnTeamRelations = cdnTeamRelationService.getTeamRelationListByAgentLevel(agentLevel.getId());
                for (CdnTeamRelation c :cdnTeamRelations){
                    //查询个人的幸运池 没有直接无收益
                    CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                            .eq(CdnUserWallet::getUserId,c.getUserId())
                            .eq(CdnUserWallet::getWalletId,10));
                    if(cdnUserWallet.getMoney().compareTo(BigDecimal.ZERO) == 0){
                        continue;
                    }
                    if(c.getTeamTodayMobile() > 0){
                        BigDecimal pjmoney = map.get(c.getLevel()).multiply(BigDecimal.valueOf(c.getTeamTodayMobile())).divide(count,BigDecimal.ROUND_CEILING);
                        if (pjmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0) {
                            setFlowLogs(c.getUserId(), null, pjmoney, DictConstant.WALLET_ID_10, "团队奖(业绩部分)", DictConstant.FLOWNAME_ID_30);
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
   // @XxlJob("/cancelGeneral")
    public void cancelGeneral() {
        try {
            Date now = new Date();
            logger.info("个人分成计算开始。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
            BigDecimal companyPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("company_point")));//公司沉淀
            BigDecimal mellPoint = BigDecimal.valueOf(Double.valueOf( cdnConfigService.selectValueByName("mell_point")));//个人比例
            List<CdnTeamRelation> cdnTeamRelationLists = cdnTeamRelationService.getTeamRelationListSort(null);
            for (CdnTeamRelation cdnTeamRelation : cdnTeamRelationLists) {
                //查询个人的幸运池 没有直接无收益
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                        .eq(CdnUserWallet::getUserId,cdnTeamRelation.getUserId())
                        .eq(CdnUserWallet::getWalletId,DictConstant.WALLET_ID_10));
                if(cdnUserWallet.getMoney().compareTo(BigDecimal.ZERO) == 0){
                    continue;
                }
                BigDecimal income = marsAdverIncomeService.selectAllIncome(cdnTeamRelation.getUserId());
                if(income != null  && income.compareTo(BigDecimal.ZERO) > 0){
                    setFlowLogs(cdnTeamRelation.getUserId(), cdnTeamRelation.getUserId(), income.multiply(BigDecimal.valueOf(1).subtract(companyPoint)).multiply(mellPoint), DictConstant.WALLET_ID_10, "个人分润", DictConstant.FLOWNAME_ID_30);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     *
     * @param userId 受益人
     * @param userIds 贡献人
     * @param money 收益金额
     * @param walletId 钱包类型
     * @param desc 收益描述
     * @param flownameId 流水类型
     */
    private void  setFlowLog(String userId,String userIds,BigDecimal money,Integer walletId,String desc,Integer flownameId){
        CdnFlow cdnFlow = new CdnFlow();
        cdnFlow.setUserIds(userIds);
        cdnFlow.setUserId(userId);
        cdnFlow.setFlownameId(flownameId);
        cdnFlow.setWalletId(walletId);
        //钱包更改
        CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,walletId));
        if(money.compareTo(BigDecimal.ZERO) <= 0){//绿积分为0 已经释放完毕
            return;
        }else{
            if(money.compareTo(cdnUserWallet.getMoney()) >= 0){//分润超过剩余绿积分 释放最后的绿积分 并将SN改为结束
                cdnFlow.setMoney(cdnUserWallet.getMoney().negate());
                //updateResouce(userId,BigDecimal.ZERO);
            }else{
                cdnFlow.setMoney(money.negate());
                //updateResouce(userId,money);
            }
        }
        cdnFlow.setMemo(desc+":相关用户"+userIds);
        cdnFlow.setCreateTime(new Date());
        cdnFlow.setDate(new Date());
        cdnFlow.setOldmoney(cdnUserWallet.getMoney());
        cdnFlowService.save(cdnFlow);
        cdnUserWallet.setUserId(userId);
        cdnUserWallet.setWalletId(walletId);
        cdnUserWallet.setMoney(cdnUserWallet.getMoney().add(cdnFlow.getMoney()));
        cdnUserWallet.setUpdateTime(new Date());
        cdnUserWalletService.updateById(cdnUserWallet);
        CdnFlow cdnFlows = new CdnFlow();
        BeanUtils.copyProperties(cdnFlow, cdnFlows);
        cdnFlows.setMoney(cdnFlow.getMoney().negate());
        cdnFlows.setWalletId(3);
        cdnFlows.setFlownameId(flownameId);
        CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,3));
        if(null == cdnUserWallets){
            return;
        }
        cdnFlows.setOldmoney(cdnUserWallets.getMoney() == null?BigDecimal.ZERO:cdnUserWallets.getMoney());
        cdnFlows.setMemo(desc+"-->余额:相关用户"+userIds);
        cdnFlowService.save(cdnFlows);
        //钱包更改
        cdnUserWallets.setUserId(userId);
        cdnUserWallets.setWalletId(3);
        cdnUserWallets.setMoney(cdnUserWallets.getMoney()== null?cdnFlow.getMoney().negate():cdnUserWallets.getMoney().add(cdnFlow.getMoney().negate()));
        cdnUserWallets.setUpdateTime(new Date());
        cdnUserWalletService.updateById(cdnUserWallets);
    }
    /**
     *
     * @param userId 受益人
     * @param userIds 贡献人
     * @param money 收益金额
     * @param walletId 钱包类型
     * @param desc 收益描述
     * @param flownameId 流水类型
     */
    private void  setFlowLogs(String userId,String userIds,BigDecimal money,Integer walletId,String desc,Integer flownameId){
        CdnFlow cdnFlow = new CdnFlow();
        cdnFlow.setUserIds(userIds);
        cdnFlow.setUserId(userId);
        cdnFlow.setFlownameId(flownameId);
        cdnFlow.setWalletId(walletId);
        //钱包更改
        CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,walletId));
        if(cdnUserWallet.getMoney().compareTo(BigDecimal.ZERO) == 0){
            return;
        }
        if(money.compareTo(BigDecimal.ZERO) <= 0){//幸运池已经释放完毕
            return;
        }else{
            if(money.compareTo(cdnUserWallet.getMoney()) >= 0){
                cdnFlow.setMoney(cdnUserWallet.getMoney().negate());
            }else{
                cdnFlow.setMoney(money.negate());
            }
        }
        cdnFlow.setMemo(desc+":释放用户"+userIds);
        cdnFlow.setCreateTime(new Date());
        cdnFlow.setDate(new Date());
        cdnFlow.setOldmoney(cdnUserWallet.getMoney());
        cdnFlowService.save(cdnFlow);
        cdnUserWallet.setUserId(userId);
        cdnUserWallet.setWalletId(walletId);
        cdnUserWallet.setMoney(cdnUserWallet.getMoney().add(cdnFlow.getMoney()));
        cdnUserWallet.setUpdateTime(new Date());
        cdnUserWalletService.updateById(cdnUserWallet);
        //释放完进入流量
        CdnFlow cdnFlows = new CdnFlow();
        BeanUtils.copyProperties(cdnFlow, cdnFlows);
        cdnFlows.setMoney(cdnFlow.getMoney().negate());
        cdnFlows.setWalletId(DictConstant.WALLET_ID_8);
        cdnFlows.setFlownameId(flownameId);
        CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                .eq(CdnUserWallet::getUserId,userId)
                .eq(CdnUserWallet::getWalletId,DictConstant.WALLET_ID_8));
        if(null == cdnUserWallets){
            return;
        }
        cdnFlows.setOldmoney(cdnUserWallets.getMoney() == null?BigDecimal.ZERO:cdnUserWallets.getMoney());
        cdnFlows.setMemo(desc+"-->流量:相关用户"+userIds);
        cdnFlowService.save(cdnFlows);
        //钱包更改
        cdnUserWallets.setUserId(userId);
        cdnUserWallets.setWalletId(DictConstant.WALLET_ID_8);
        cdnUserWallets.setMoney(cdnUserWallets.getMoney()== null?cdnFlow.getMoney().negate():cdnUserWallets.getMoney().add(cdnFlow.getMoney().negate()));
        cdnUserWallets.setUpdateTime(new Date());
        cdnUserWalletService.updateById(cdnUserWallets);
    }
    /**
     * 流量分佣
     */
    @XxlJob("cancelFlowPoint")
    public void cancelFlowPoint() {
        Date now = new Date();
        logger.info("流量分佣计算开始。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        List<CdnTeamRelation> cdnTeamRelationList = cdnTeamRelationService.getTeamRelationListSort(null);
        //循环所有完整线
        for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationList){
            if(StringUtils.isNotBlank(cdnTeamRelation.getPids())){
                CdnAgentLevel cdnPAgentLevel = cdnAgentLevelService.getById(cdnTeamRelation.getAgentLevelId());
                //加速释放比例
                Integer id = 1;
                BigDecimal flowPoint= cdnPAgentLevel.getFlowPoint();
                BigDecimal mellTotalPrice = BigDecimal.ZERO;
                List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>().eq(CdnRelease::getUserId,cdnTeamRelation.getUserId()).eq(CdnRelease::getStatus,1).eq(CdnRelease::getIsAct,1));
                if (list.isEmpty()){
                    continue;
                }
                for (CdnRelease cdnRelease:list){
                    List<V1BoxIncome> incomeList = deviceService.getMoneyListBySn(cdnRelease.getSn());
                    if(incomeList.size() >0){
                        for (V1BoxIncome v1BoxIncome:incomeList){
                            mellTotalPrice = mellTotalPrice.add(v1BoxIncome.getAmount());
                            if(v1BoxIncome.getUserAmount() != null){
                                CdnFlow cdnFlow = new CdnFlow();
                                CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                                        .eq(CdnUserWallet::getUserId, cdnTeamRelation.getUserId())
                                        .eq(CdnUserWallet::getWalletId, 6));
                                cdnFlow.setUserId(cdnTeamRelation.getUserId());
                                cdnFlow.setUserIds(cdnTeamRelation.getUserId());
                                cdnFlow.setMemo("流量分佣-->相关用户:" + cdnTeamRelation.getUserId());
                                cdnFlow.setMoney(v1BoxIncome.getUserAmount());
                                cdnFlow.setOldmoney(cdnUserWallets != null ? cdnUserWallets.getMoney() : BigDecimal.ZERO);
                                cdnFlow.setFlownameId(26);
                                cdnFlow.setCreateTime(new Date());
                                cdnFlow.setWalletId(6);
                                cdnFlow.setDate(new Date());
                                cdnFlowService.save(cdnFlow);
                                cdnUserWallets.setWalletId(6);
                                cdnUserWallets.setMoney(cdnUserWallets.getMoney().add(cdnFlow.getMoney()));
                                cdnUserWallets.setCreateTime(new Date());
                                cdnUserWalletService.updateById(cdnUserWallets);
                            }
                        }
                    }
                }
                String[] pidsArrays = Arrays.copyOfRange(cdnTeamRelation.getPids().split(StrUtil.COMMA), 1, cdnTeamRelation.getPids().split(StrUtil.COMMA).length);
                //从后往前循环遍历
                for (int i = pidsArrays.length - 1; i >= 0; i--) {
                    CdnTeamRelation cdnTeamRelationPids = cdnTeamRelationService.getOne(new LambdaQueryWrapper<CdnTeamRelation>().eq(CdnTeamRelation::getUserId,pidsArrays[i]));
                    CdnAgentLevel cdnPAgentLevels = cdnAgentLevelService.getById(cdnTeamRelationPids.getAgentLevelId());
                    if(id < cdnPAgentLevels.getId()) {//
                        String sdMoney = cdnConfigService.selectValueByName("flow_rebate");
                        BigDecimal pfmoney = mellTotalPrice.multiply(BigDecimal.valueOf(Double.parseDouble(sdMoney))).multiply((cdnPAgentLevels.getFlowPoint().subtract(flowPoint)));
                        if (pfmoney.compareTo(BigDecimal.valueOf(0.01)) >= 0) {
                            CdnFlow cdnFlows = new CdnFlow();
                            CdnUserWallet cdnUserWallets = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                                    .eq(CdnUserWallet::getUserId, cdnTeamRelationPids.getUserId())
                                    .eq(CdnUserWallet::getWalletId, 6));
                            cdnFlows.setUserId(cdnTeamRelationPids.getUserId());
                            cdnFlows.setUserIds(cdnTeamRelation.getUserId());
                            cdnFlows.setMemo("流量分佣-->相关用户:" + cdnTeamRelation.getUserId());
                            cdnFlows.setMoney(pfmoney);
                            cdnFlows.setOldmoney(cdnUserWallets != null ? cdnUserWallets.getMoney() : BigDecimal.ZERO);
                            cdnFlows.setFlownameId(26);
                            cdnFlows.setCreateTime(new Date());
                            cdnFlows.setWalletId(6);
                            cdnFlows.setDate(new Date());
                            cdnFlowService.save(cdnFlows);
                            id = cdnPAgentLevels.getId();
                            flowPoint= cdnPAgentLevels.getFlowPoint();
                            cdnUserWallets.setWalletId(6);
                            cdnUserWallets.setMoney(cdnUserWallets.getMoney().add(cdnFlows.getMoney()));
                            cdnUserWallets.setCreateTime(new Date());
                            cdnUserWalletService.updateById(cdnUserWallets);
                        }
                    }
                }
            }
        }
    }
    /**
     * 加权分红(真实设备)
     * 固定部分: 大区代理/省级代理/市区代理分别拿到0.5
     * 计算方式:各自团队业绩/所有代理团队总业绩
     * 极差部分:大区1.5%,省级1%,市区0.5%
     */
    @XxlJob("executeWeightedDividend")
    public void executeWeightedDividend(){
        Date now = new Date();
        logger.info("计算加权分红(自主上机)任务。。。"+DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        //大区、省、市
        CdnTeamRelation cdns = new CdnTeamRelation();
        cdns.setAgentLevelId(3);
        List<CdnTeamRelation> cdnTeamRelationLists = cdnTeamRelationService.selectByAgentLevelId(cdns);
        // 今日总业绩
        List<CdnRelease> cdnReleaseList = cdnReleaseService.list(Wrappers.lambdaQuery(CdnRelease.class)
                .eq(CdnRelease::getStatus, 1)
                .eq(CdnRelease::getIsAct, 1)
                .between(CdnRelease::getCreateTime, DateUtil.beginOfDay(DateUtil.yesterday()), DateUtil.endOfDay(DateUtil.yesterday())));
        if(cdnReleaseList.isEmpty()){
            return;
        }
        BigDecimal countMoney = cdnReleaseList.stream().map(CdnRelease::getMoney).reduce(BigDecimal.ZERO,BigDecimal::add);
        //市级集合
        List<CdnTeamRelation> cityTeamRelationList = new ArrayList<>();
        //市级新增团队业绩之和
        BigDecimal cityMall = BigDecimal.ZERO;
        //省级集合
        List<CdnTeamRelation> provinceTeamRelationList = new ArrayList<>();
        //省级今日新增团队业绩之和
        BigDecimal provinceMall = BigDecimal.ZERO;
        //大区集合
        List<CdnTeamRelation> regionTeamRelationList = new ArrayList<>();
        //大区今日新增团队业绩之和
        BigDecimal regionMall = BigDecimal.ZERO;
        CdnFlow cdnFlow = new CdnFlow();
        //循环所有市、省、大区用户
        for (CdnTeamRelation cdnTeamRelation :cdnTeamRelationLists) {
            if (cdnTeamRelation.getTeamTodayQuantityOwn() == 0){
                continue;
            }
            CdnAgentLevel cdnPAgentLevel = cdnAgentLevelService.getById(cdnTeamRelation.getAgentLevelId());
            //省市 大区 分组
            if (cdnPAgentLevel.getName().indexOf("市级") >= 0) {
                cityTeamRelationList.add(cdnTeamRelation);
                cityMall = cityMall.add(BigDecimal.valueOf(cdnTeamRelation.getTeamTodayQuantityOwn()));
            } else if (cdnPAgentLevel.getName().indexOf("省级") >= 0) {
                provinceTeamRelationList.add(cdnTeamRelation);
                provinceMall = provinceMall.add(BigDecimal.valueOf(cdnTeamRelation.getTeamTodayQuantityOwn()));
            } else if (cdnPAgentLevel.getName().indexOf("大区") >= 0) {
                regionTeamRelationList.add(cdnTeamRelation);
                regionMall = regionMall.add(BigDecimal.valueOf(cdnTeamRelation.getTeamTodayQuantityOwn()));
            }
        }
        // 获取所有今日新增自主设备不为空用户
        List<CdnTeamRelation> cdnTeamRelationList = cdnTeamRelationService.list(new LambdaQueryWrapper<CdnTeamRelation>()
                .gt(CdnTeamRelation::getTodayQuantityOwn,0).orderByDesc(CdnTeamRelation::getSort));
        for (CdnTeamRelation cdnTeamRelation: cdnTeamRelationList){
            // 获取所有上级
            List<CdnTeamRelation> puser = cdnTeamRelationService.getUpLevel(cdnTeamRelation);
            // 比例
            Integer agentLevel = 1;
            BigDecimal rebate = BigDecimal.ZERO;
            // 个人今日新增业绩数额
            List<CdnRelease> todayMallList = cdnReleaseService.list(Wrappers.lambdaQuery(CdnRelease.class)
                    .eq(CdnRelease::getStatus, 1)
                    .eq(CdnRelease::getIsAct, 1)
                    .eq(CdnRelease::getUserId, cdnTeamRelation.getUserId())
                    .between(CdnRelease::getCreateTime, DateUtil.beginOfDay(DateUtil.yesterday()), DateUtil.endOfDay(DateUtil.yesterday())));
            if (todayMallList.isEmpty()) {
                continue;
            }
            BigDecimal todayMallMoney = todayMallList.stream().map(CdnRelease::getMoney).reduce(BigDecimal.ZERO,BigDecimal::add);
            for (CdnTeamRelation p : puser){
                BigDecimal price = BigDecimal.ZERO;
                CdnAgentLevel pAgentLevel = cdnAgentLevelService.getById(p.getAgentLevelId());
                if (pAgentLevel.getId().compareTo(agentLevel) <= 0) {
                    continue;
                }
                if (pAgentLevel.getName().contains("市级") || pAgentLevel.getName().contains("省级") || pAgentLevel.getName().contains("大区")){
                    price = todayMallMoney.multiply(pAgentLevel.getRebateReal().subtract(rebate));
                    agentLevel = pAgentLevel.getId();
                    rebate = pAgentLevel.getRebateReal();
                }
                cdnFlow.setUserId(p.getUserId());
                cdnFlow.setFlownameId(22);
                if(price.compareTo(BigDecimal.ZERO) <= 0){
                    continue;
                }else{
                    cdnFlow.setMoney(price);
                }
                cdnFlow.setMemo("自主上级加权收益(级差部分)-->相关用户:" + cdnTeamRelation.getUserId());
                cdnFlow.setUserIds(cdnTeamRelation.getUserId());
                //红积分
                cdnFlow.setWalletId(6);
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                        .eq(CdnUserWallet::getUserId,cdnTeamRelation.getUserId())
                        .eq(CdnUserWallet::getWalletId,6));
                cdnFlow.setOldmoney(cdnUserWallet != null ? cdnUserWallet.getMoney() : BigDecimal.ZERO);
                cdnFlow.setCreateTime(new Date());
                cdnFlow.setDate(new Date());
                cdnFlowService.save(cdnFlow);
                cdnUserWallet.setMoney(cdnFlow.getOldmoney().add(cdnFlow.getMoney()));
                cdnUserWallet.setUpdateTime(new Date());
                cdnUserWalletService.updateById(cdnUserWallet);
            }
        }
        //省级代理
        if(provinceTeamRelationList.size() > 0 && provinceMall.compareTo(BigDecimal.ZERO) > 0){
            Double rebate = Double.valueOf(cdnConfigService.selectValueByName("province_rebate"));
            BigDecimal provinceMoney = countMoney.multiply(BigDecimal.valueOf(rebate));
            for (CdnTeamRelation teamRelation : provinceTeamRelationList){
                BigDecimal num = BigDecimal.valueOf(teamRelation.getTeamTodayQuantityOwn()).divide(provinceMall,4, RoundingMode.HALF_UP);
                BigDecimal totalPrice = provinceMoney.multiply(num);
                cdnFlow.setUserId(teamRelation.getUserId());
                cdnFlow.setFlownameId(22);
                if(totalPrice.compareTo(BigDecimal.ZERO) <= 0){
                    continue;
                }else{
                    cdnFlow.setMoney(totalPrice);
                }
                cdnFlow.setMemo("自主上级加权收益(固定部分)");
                //红积分
                cdnFlow.setWalletId(6);
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                        .eq(CdnUserWallet::getUserId,teamRelation.getUserId())
                        .eq(CdnUserWallet::getWalletId,6));
                cdnFlow.setOldmoney(cdnUserWallet != null ? cdnUserWallet.getMoney() : BigDecimal.ZERO);
                cdnFlow.setCreateTime(new Date());
                cdnFlow.setDate(new Date());
                cdnFlowService.save(cdnFlow);
                cdnUserWallet.setMoney(cdnFlow.getOldmoney().add(cdnFlow.getMoney()));
                cdnUserWallet.setUpdateTime(new Date());
                cdnUserWalletService.updateById(cdnUserWallet);
            }
        }
        //大区代理
        if(regionTeamRelationList.size() > 0 && regionMall.compareTo(BigDecimal.ZERO) > 0){
            Double rebate = Double.valueOf(cdnConfigService.selectValueByName("region_rebate"));
            BigDecimal regionMoney = countMoney.multiply(BigDecimal.valueOf(rebate));
            for (CdnTeamRelation teamRelation : regionTeamRelationList){
                BigDecimal num = BigDecimal.valueOf(teamRelation.getTeamTodayQuantityOwn()).divide(regionMall,4, RoundingMode.HALF_UP);
                BigDecimal totalPrice = regionMoney.multiply(num);
                cdnFlow.setUserId(teamRelation.getUserId());
                cdnFlow.setFlownameId(22);
                if(totalPrice.compareTo(BigDecimal.ZERO) <= 0){
                    continue;
                }else{
                    cdnFlow.setMoney(totalPrice);
                }
                cdnFlow.setMemo("自主上级加权收益(固定部分)");
                //红积分
                cdnFlow.setWalletId(6);
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                        .eq(CdnUserWallet::getUserId,teamRelation.getUserId())
                        .eq(CdnUserWallet::getWalletId,6));
                cdnFlow.setOldmoney(cdnUserWallet != null ? cdnUserWallet.getMoney() : BigDecimal.ZERO);
                cdnFlow.setCreateTime(new Date());
                cdnFlow.setDate(new Date());
                cdnFlowService.save(cdnFlow);
                cdnUserWallet.setMoney(cdnFlow.getOldmoney().add(cdnFlow.getMoney()));
                cdnUserWallet.setUpdateTime(new Date());
                cdnUserWalletService.updateById(cdnUserWallet);
            }
        }
        //市级代理
        if(cityTeamRelationList.size() > 0 && cityMall.compareTo(BigDecimal.ZERO) > 0){
            Double rebate = Double.valueOf(cdnConfigService.selectValueByName("city_rebate"));
            BigDecimal cityMoney = countMoney.multiply(BigDecimal.valueOf(rebate));
            for (CdnTeamRelation teamRelation : cityTeamRelationList){
                BigDecimal num = BigDecimal.valueOf(teamRelation.getTeamTodayQuantityOwn()).divide(cityMall,4, RoundingMode.HALF_UP);
                BigDecimal totalPrice = cityMoney.multiply(num);
                cdnFlow.setUserId(teamRelation.getUserId());
                cdnFlow.setFlownameId(22);
                if(totalPrice.compareTo(BigDecimal.ZERO) <= 0){
                    continue;
                }else{
                    cdnFlow.setMoney(totalPrice);
                }
                cdnFlow.setMemo("自主上级加权收益(固定部分)");
                //红积分
                cdnFlow.setWalletId(6);
                CdnUserWallet cdnUserWallet = cdnUserWalletService.getOne(Wrappers.lambdaQuery(CdnUserWallet.class)
                        .eq(CdnUserWallet::getUserId,teamRelation.getUserId())
                        .eq(CdnUserWallet::getWalletId,6));
                cdnFlow.setOldmoney(cdnUserWallet != null ? cdnUserWallet.getMoney() : BigDecimal.ZERO);
                cdnFlow.setCreateTime(new Date());
                cdnFlow.setDate(new Date());
                cdnFlowService.save(cdnFlow);
                cdnUserWallet.setMoney(cdnFlow.getOldmoney().add(cdnFlow.getMoney()));
                cdnUserWallet.setUpdateTime(new Date());
                cdnUserWalletService.updateById(cdnUserWallet);
            }
        }
//        cdnTeamRelationService.updateTodayOwnQuantity();
    }
    /**
     * SN释放的时候 先购买的先释放
     * @param userId
     * @param price
     */
    public void updateResouce(String userId,BigDecimal price){
        if(price.compareTo(BigDecimal.ZERO) == 0){
            List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>().eq(CdnRelease::getUserId,userId).eq(CdnRelease::getStatus,1));
            for (CdnRelease cdnRelease :list){
                cdnRelease.setBalance(BigDecimal.valueOf(0.00));
                cdnRelease.setStatus(0);
                cdnReleaseService.updateById(cdnRelease);
                updateTmall(userId,cdnRelease.getNum());
            }
        }else{
            List<CdnRelease> list = cdnReleaseService.list(new LambdaQueryWrapper<CdnRelease>().eq(CdnRelease::getUserId,userId).eq(CdnRelease::getStatus,1).orderByAsc(CdnRelease::getCreateTime));
            for (CdnRelease cdnRelease :list){
                if(cdnRelease.getBalance().compareTo(price) > 0){
                    cdnRelease.setBalance(cdnRelease.getBalance().subtract(price));
                    cdnReleaseService.updateById(cdnRelease);
                    break;
                }else if(cdnRelease.getBalance().compareTo(price) == 0){
                    cdnRelease.setBalance(BigDecimal.valueOf(0.00));
                    cdnRelease.setStatus(0);
                    cdnReleaseService.updateById(cdnRelease);
                    updateTmall(userId,cdnRelease.getNum());
                    break;
                }else{
                    cdnRelease.setBalance(BigDecimal.valueOf(0.00));
                    cdnRelease.setStatus(0);
                    cdnReleaseService.updateById(cdnRelease);
                    updateTmall(userId,cdnRelease.getNum());
                    //updateResouce(userId,price.subtract(cdnRelease.getBalance()));
                }
            }
        }
    }
    /**
     * 释放后减少个人业绩和团队业绩
     * @param userId
     * @param num
     */
    public void updateTmall(String userId,Integer num){
        CdnTeamRelation cdnTeamRelation = cdnTeamRelationService.geteamRelationByUserId(userId);
        if (cdnTeamRelation != null) {
            //减去个人业绩
            cdnTeamRelation.setMeall(cdnTeamRelation.getMeall().subtract(BigDecimal.valueOf(num)));
            cdnTeamRelationService.updateById(cdnTeamRelation);
            if(StringUtils.isNotBlank(cdnTeamRelation.getPids())){
                String[] pids = cdnTeamRelation.getPids().split(",");
                for (String pid : pids) {
                    if (StringUtils.isNotBlank(pid)) {
                        CdnTeamRelation cdnTeamRelation1 = cdnTeamRelationService.geteamRelationByUserId(pid);
                        cdnTeamRelation1.setTeamall(cdnTeamRelation1.getTeamall().subtract(BigDecimal.valueOf(num)));
                        cdnTeamRelationService.updateById(cdnTeamRelation1);
                    }
                }
            }
        }
    }
}
yami-shop-platform/src/main/java/com/yami/shop/platform/task/V1BoxTask.java
New file
@@ -0,0 +1,109 @@
package com.yami.shop.platform.task;
import cn.hutool.core.date.DateUtil;
import com.google.gson.Gson;
import com.yami.shop.bean.model.V1Box;
import com.yami.shop.bean.model.V1BoxIncome;
import com.yami.shop.bean.response.*;
import com.yami.shop.cdn.common.utils.CdnUtils;
import com.yami.shop.cdn.common.vo.CdnVo;
import com.yami.shop.common.config.Constant;
import com.yami.shop.dao.V1BoxIncomeMapper;
import com.yami.shop.service.SysConfigService;
import com.yami.shop.service.V1BoxIncomeService;
import com.yami.shop.service.V1BoxService;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Slf4j
@Component
public class V1BoxTask {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private V1BoxService v1BoxService;
    @Autowired
    private V1BoxIncomeService v1BoxIncomeService;
    @Autowired
    private V1BoxIncomeMapper v1BoxIncomeMapper;
    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private MapperFacade mapperFacade;
    /**
     * 每10分钟拉取盒子列表
     */
//    @Scheduled(cron = "0 * * * * ?")
//    @XxlJob("pullAllUserV1Boxes")
    public void pullV1BoxList() throws IOException {
        Date now = new Date();
        logger.info("拉取盒子列表。。。"+ DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        String value = sysConfigService.getValue(Constant.CDN_CONFIG);
        Gson gson = new Gson();
        CdnVo cdnVo = gson.fromJson(value, CdnVo.class);
        CdnApiResponse<CdnBoxesResponse> boxes = CdnUtils.getBoxesToPost(null, null, null, null, null, null, null, null, null, null, cdnVo);
        if (boxes.getCode() == 200) {
            CdnBoxesResponse data = boxes.getData();
            if (data.getTotal() != null && !data.getTotal().equals("0")) {
                for (CdnBoxesResponse.V1BoxInfo item : data.getBoxes()) {
                    V1Box box = v1BoxService.getById(item.getBoxId());
                    if (box == null) {
                        V1Box v1Box = mapperFacade.map(item, V1Box.class);
                        v1Box.setCreateTime(now);
                        v1BoxService.save(v1Box);
                    } else {
                        V1Box v1Box = mapperFacade.map(item, V1Box.class);
                        v1Box.setUpdateTime(now);
                        v1BoxService.updateById(v1Box);
                    }
                }
            }
            logger.info("拉取盒子列表完成... {}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        }
    }
    /**
     * 每日拉取盒子收益
     */
//    @Scheduled(cron = "0 * * * * ?")
//    @XxlJob("pullYesterdayIncome")
    public void pullV1BoxIncome() throws IOException {
        Date now = new Date();
        logger.info("拉取盒子收益。。。"+ DateUtil.format(now, "yyyy-MM-dd HH:mm:ss"));
        LocalDate today = LocalDate.now();
        LocalDate yesterday = today.minusDays(7);
        String value = sysConfigService.getValue(Constant.CDN_CONFIG);
        Gson gson = new Gson();
        CdnVo cdnVo = gson.fromJson(value, CdnVo.class);
        CdnApiResponse<CdnSingleEarningsResponse> boxIncomeToPost = CdnUtils.getBoxIncomeToPost(null, null, null,yesterday.toString() ,today.toString(), cdnVo);
        if (boxIncomeToPost.getCode() == 200) {
            CdnSingleEarningsResponse data = boxIncomeToPost.getData();
            if (data.getTotal() != null && !data.getTotal().equals("0")) {
                for (CdnSingleEarningsResponse.V1SupplierIncomeV2ResponseItem item : data.getList()) {
                    V1BoxIncome map = new V1BoxIncome();
                    map.setBoxId(item.getBoxId());
                    map.setSupplierBoxId(item.getSupplierBoxId());
                    map.setDate(item.getDate());
                    Double amount = Double.valueOf(item.getAmount());
                    map.setAmount(BigDecimal.valueOf(amount / 100));
                    map.setCreateTime(now);
                    v1BoxIncomeService.save(map);
                }
            }
            logger.info("拉取盒子收益完成... {}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        }
    }
}
yami-shop-platform/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,31 @@
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://192.168.1.119:40006/dev_liuliangniu?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 0
      maximum-pool-size: 20
      idle-timeout: 10000
      auto-commit: true
      connection-test-query: SELECT 1
  redis:
    host: 192.168.1.119
    port: 6379
    database: 10
    password: QpZq2&uX3c
logging:
  config: classpath:logback-spring.xml
elastic:
  address: http://127.0.0.1:9200
  index: product_test
xxl-job:
    accessToken: default_token
    logPath: /data/applogs/xxl-job/jobhandler
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
yami-shop-platform/src/main/resources/application-docker.yml
New file
@@ -0,0 +1,38 @@
server:
  port: 8114
spring:
  datasource:
    url: jdbc:mysql://${MYSQL_HOST:mall4j-mysql}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:yami_bbc}?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    username: ${MYSQL_USERNAME:root}
    password: ${MYSQL_PASSWORD:root}
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 0
      maximum-pool-size: 20
      idle-timeout: 25000
      auto-commit: true
      connection-test-query: select 1
  redis:
    host: ${REDIS_HOST}
    port: ${REDIS_PORT}
    database: ${REDIS_DATABASE:0}
logging:
  config: classpath:logback/logback-docker.xml
elastic:
  address: ${ELASTIC_ADDRESS:http://mall4j-elasticsearch:9200}
xxl-job:
  accessToken: ${XXL_JOB_ACCESS_TOKEN:default_token}
  logPath: ${XXL_JOB_LOG_PATH:/data/applogs/xxl-job/jobhandler}
  admin:
    addresses: ${XXL_JOB_ADDRESS:http://mall4j-job:8080/xxl-job-admin}
management:
  endpoint:
    health:
      probes:
        enabled: true
  endpoints:
    web:
      exposure:
        include: health,startup
yami-shop-platform/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,34 @@
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://192.168.0.174:3306/yami_bbc?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    username: root
    password: ggy6688.
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 0
      maximum-pool-size: 100
      idle-timeout: 10000
      auto-commit: true
      connection-test-query: select 1
  redis:
    host: 192.168.0.171
    port: 6379
    password: 'QpZq2&uX3c'
logging:
  config: classpath:logback-spring.xml
elastic:
  address: http://192.168.0.171:9200
  index: product
image:
  url: https://img.tbt.ink/
xxl-job:
  accessToken: default_token
  logPath: /data/applogs/xxl-job/pcdn/jobhandler
  admin:
    addresses: http://192.168.0.171:8080/xxl-job-admin
yami-shop-platform/src/main/resources/application-test.yml
New file
@@ -0,0 +1,35 @@
server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://192.168.0.174:3306/dev_liuliangniu?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    username: root
    password: ggy6688.
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 0
      maximum-pool-size: 100
      idle-timeout: 10000
      auto-commit: true
      connection-test-query: select 1
  redis:
    host: 192.168.0.171
    port: 6379
    password: 'QpZq2&uX3c'
    database: 12
logging:
  config: classpath:logback-spring-test.xml
elastic:
  address: http://192.168.0.171:9200
  index: product_test
image:
  url: https://img.tbt.ink/
xxl-job:
  accessToken: default_token
  logPath: /data/applogs/xxl-job/pcdn/jobhandler
  admin:
    addresses: http://192.168.0.171:8080/xxl-job-admin-test
yami-shop-platform/src/main/resources/application.yml
New file
@@ -0,0 +1,42 @@
spring:
  # 环境 dev|prod|docker
  profiles:
    active: dev
  #文件上传设置
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
      enabled: true
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER
# mybaits-plus配置
mybatis-plus:
  # MyBatis Mapper所对应的XML文件位置
  mapper-locations: classpath*:/mapper/*Mapper.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      # 主键类型 0:数据库ID自增 1.未定义 2.用户输入 3 id_worker 4.uuid 5.id_worker字符串表示
      id-type: AUTO
      #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
      field-strategy: NOT_NULL
      # 默认数据库表下划线命名
      table-underline: true
  type-aliases-package: com.yami.shop.bean.model
  type-enums-package: com.yami.shop.bean.enums
  #type-handlers-package: com.yami.shop.cdn.handler
  configuration:
    default-enum-type-handler: com.yami.shop.common.handler.EnumTypeHandler
server:
  tomcat:
    basedir: /Users/a123/Documents/UthinkJava/hxsl/temp
# 用于雪花算法生成id
application:
  datacenterId: ${DATACENTER_ID:2}
  workerId: ${WORKER_ID:1}
yami-shop-platform/src/main/resources/banner.txt
New file
@@ -0,0 +1,11 @@
 .----------------.  .----------------.  .----------------.  .----------------.  .----------------.  .----------------.
| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
| | ____    ____ | || |      __      | || |   _____      | || |   _____      | || |   _    _     | || |     _____    | |
| ||_   \  /   _|| || |     /  \     | || |  |_   _|     | || |  |_   _|     | || |  | |  | |    | || |    |_   _|   | |
| |  |   \/   |  | || |    / /\ \    | || |    | |       | || |    | |       | || |  | |__| |_   | || |      | |     | |
| |  | |\  /| |  | || |   / ____ \   | || |    | |   _   | || |    | |   _   | || |  |____   _|  | || |   _  | |     | |
| | _| |_\/_| |_ | || | _/ /    \ \_ | || |   _| |__/ |  | || |   _| |__/ |  | || |      _| |_   | || |  | |_' |     | |
| ||_____||_____|| || ||____|  |____|| || |  |________|  | || |  |________|  | || |     |_____|  | || |  `.___.'     | |
| |              | || |              | || |              | || |              | || |              | || |              | |
| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
 '----------------'  '----------------'  '----------------'  '----------------'  '----------------'  '----------------'
yami-shop-platform/src/main/resources/cert/alipayCertPublicKey_RSA2.crt
New file
@@ -0,0 +1,43 @@
-----BEGIN CERTIFICATE-----
MIIDsjCCApqgAwIBAgIQICQQEIkCIVfQHOHjNW3SLDANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDIgUjEwHhcNMjQxMDEwMDc0OTA3WhcNMjkxMDA5MDc0OTA3WjCBkjELMAkGA1UEBhMCQ04x
LTArBgNVBAoMJOS4iua1t+mXqOS5n+enkeaKgOiCoeS7veaciemZkOWFrOWPuDEPMA0GA1UECwwG
QWxpcGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWP
uC0yMDg4NzQxNjYzNTY4MTMwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAloXaafan
0cDCkCdFFCV+1EBwJ/UYQvxHE2+jIXKngjGdD2n1voOcV3Xk7pe1CM/SjYNjcIrOgwWl2MmFktUR
Nzde8MNFHqhJ2xMUrECpvPQTNWAY4T2eflZmP14cePUPWtHUxnLsZpE/CqdgIJGhHkZijzPe9s9v
ZNyc8oqGqvXSUOho4Z0ajuwt8m5prlHB+Y2IBTOc78bqjGrCrK7H9RsqUCEz47l9lWkVkXn7DRLf
gfUcrCurydyg8tj18sEkYUL8KYjL5zWZ9x1UYzyDgLcmEVlMddImx8/Vrr4oQw8a0KqNqegRhM64
SF0NXix8HKgQLvdK2zhsApjLy6rH3QIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcN
AQELBQADggEBAAM2ihX55ZsV7roXPz0oMc/CWoc7JmCpdriWNxHfyOjMcyKxBn9nJ3Oxr9gcr1wq
bGkW+1oTsikUDBaxvQ+nF5OPRfmY+GDajRDHp0MzeQ7KZzR9sk2r4Ru22kYSx0yMUvyIP+f9sIyw
8WcnWBGD37jibxURGhqLB9srjMWieYtSTDli6Oy/H3yngk/ww7iYmBRA67T4OcEwKEVj0gCSMhpl
hj/Wv8ICk1hUGuNKMUlig+H9EHOogKsJVUCAntyxbIwbYktUcsF7QaWiU5bo1+VnSrgLtI9n3gbA
aFAPyB/kKT6uI9Cymvo3rK5HlbHXJLU6MCgcsvLWqNl9oehznuw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE4jCCAsqgAwIBAgIIYsSr5bKAMl8wDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAU
BgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEw
LwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMy
MjE0MzQxNVoXDTM3MTEyNjE0MzQxNVowgYIxCzAJBgNVBAYTAkNOMRYwFAYDVQQKDA1BbnQgRmlu
YW5jaWFsMSAwHgYDVQQLDBdDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTE5MDcGA1UEAwwwQW50IEZp
bmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBDbGFzcyAyIFIxMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAsLMfYaoRoPRbmDcAfXPCmKf43pWRN5yTXa/KJWO0l+mrgQvs89bA
NEvbDUxlkGwycwtwi5DgBuBgVhLliXu+R9CYgr2dXs8D8Hx/gsggDcyGPLmVrDOnL+dyeauheARZ
fA3du60fwEwwbGcVIpIxPa/4n3IS/ElxQa6DNgqxh8J9Xwh7qMGl0JK9+bALuxf7B541Gr4p0WEN
G8fhgjBV4w4ut9eQLOoa1eddOUSZcy46Z7allwowwgt7b5VFfx/P1iKJ3LzBMgkCK7GZ2kiLrL7R
iqV+h482J7hkJD+ardoc6LnrHO/hIZymDxok+VH9fVeUdQa29IZKrIDVj65THQIDAQABo2MwYTAf
BgNVHSMEGDAWgBRfdLQEwE8HWurlsdsio4dBspzhATAdBgNVHQ4EFgQUSqHkYINtUSAtDPnS8Xoy
oP9p7qEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIB
AIQ8TzFy4bVIVb8+WhHKCkKNPcJe2EZuIcqvRoi727lZTJOfYy/JzLtckyZYfEI8J0lasZ29wkTt
a1IjSo+a6XdhudU4ONVBrL70U8Kzntplw/6TBNbLFpp7taRALjUgbCOk4EoBMbeCL0GiYYsTS0mw
7xdySzmGQku4GTyqutIGPQwKxSj9iSFw1FCZqr4VP4tyXzMUgc52SzagA6i7AyLedd3tbS6lnR5B
L+W9Kx9hwT8L7WANAxQzv/jGldeuSLN8bsTxlOYlsdjmIGu/C9OWblPYGpjQQIRyvs4Cc/mNhrh+
14EQgwuemIIFDLOgcD+iISoN8CqegelNcJndFw1PDN6LkVoiHz9p7jzsge8RKay/QW6C03KNDpWZ
EUCgCUdfHfo8xKeR+LL1cfn24HKJmZt8L/aeRZwZ1jwePXFRVtiXELvgJuM/tJDIFj2KD337iV64
fWcKQ/ydDVGqfDZAdcU4hQdsrPWENwPTQPfVPq2NNLMyIH9+WKx9Ed6/WzeZmIy5ZWpX1TtTolo6
OJXQFeItMAjHxW/ZSZTok5IS3FuRhExturaInnzjYpx50a6kS34c5+c8hYq7sAtZ/CNLZmBnBCFD
aMQqT8xFZJ5uolUaSeXxg7JFY1QsYp5RKvj4SjFwCGKJ2+hPPe9UyyltxOidNtxjaknOCeBHytOr
-----END CERTIFICATE-----
yami-shop-platform/src/main/resources/cert/alipayRootCert.crt
New file
@@ -0,0 +1,88 @@
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----
yami-shop-platform/src/main/resources/cert/appCertPublicKey_2021004183677087.crt
New file
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIEoDCCA4igAwIBAgIQICQQEIcEFUunEjP4U69f4TANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDEgUjEwHhcNMjQxMDEwMDc0OTA3WhcNMjkxMDA5MDc0OTA3WjBoMQswCQYDVQQGEwJDTjEt
MCsGA1UECgwk5LiK5rW36Zeo5Lmf56eR5oqA6IKh5Lu95pyJ6ZmQ5YWs5Y+4MQ8wDQYDVQQLDAZB
bGlwYXkxGTAXBgNVBAMMEDIwODg3NDE2NjM1NjgxMzAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCBrLbho5UUKIKed4JESnNxFhupfxuOV6OGOXgdxF60617NqS5CFmeGnhqJquN1/0wG
cSiA+4Eon6UPzYKu6q1UAt6xTuzyeHiPM01FoGyXhfU0nrmWX/YW9ZWrAEEajozNA1dMWXTYE317
HXcrm4P6Jrc/W9Cv0vz2nsTsX/Qp8z/a8p5YpHihVCcRqmz/Nd/pZ7FCmXqI+8v57CCyA7UhXOVU
X8P6CH+p58wszL2vRUrE6QQtg53uk/pEfOVZVsvVMY+RAt63qvxzdnN9gMmUzqwSBMj/ICipiTv+
GU+s75wfOlrw8AcinI+rjJDb2OBsQVWzzcCkg8Hc1Ja5vcjtAgMBAAGjggEpMIIBJTAfBgNVHSME
GDAWgBRxB+IEYRbk5fJl6zEPyeD0PJrVkTAdBgNVHQ4EFgQUnHWlZek3k5EJNug6uE3995ffZtsw
QAYDVR0gBDkwNzA1BgdggRwBbgEBMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9jYS5hbGlwYXkuY29t
L2Nwcy5wZGYwDgYDVR0PAQH/BAQDAgbAMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jYS5hbGlw
YXkuY29tL2NybDk0LmNybDBgBggrBgEFBQcBAQRUMFIwKAYIKwYBBQUHMAKGHGh0dHA6Ly9jYS5h
bGlwYXkuY29tL2NhNi5jZXIwJgYIKwYBBQUHMAGGGmh0dHA6Ly9jYS5hbGlwYXkuY29tOjgzNDAv
MA0GCSqGSIb3DQEBCwUAA4IBAQCbf27KY3PWP1g1oCecxbIlBAKnjb4Ef0oj3gB+mphlZ/+kHcGl
chehBMWteZVFUt7od2G6RJh5sqF2lZWq9pvV/+7hK7RAqGLByH9Nj0qElO+449qINo1iYkVH+/H8
L8U378w8dQ4xMM0FbY2LzHHSZYE7k3TBAsYJDvPz7IRvVGkTilh4PLIwwyv6+SFR5Wc7C5VzU9FM
cZjMtqxdEEhTVEafdt3MQrYQzB8MTnEMPjJiBafXBg+a7eUHZc7nixjZHAk7bb05JCuKQS4ylkx4
47VuqvYbZWyI2cchddP2fWn0wKHhq1fBKvCYr168BjHMtuOReIrVJ5DwlUi6iYdk
-----END CERTIFICATE-----
yami-shop-platform/src/main/resources/logback-spring-test.xml
New file
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 -->
    <property name="LOG_HOME" value="./logs/test" />
    <logger name="m-shop-mybatis-sql" level="debug"></logger>
    <logger name="com.yami" level="DEBUG"/>
    <!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/hxsl-platfprm-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 生成 error html格式日志开始 -->
    <appender name="HTML" class="ch.qos.logback.core.FileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!--设置日志级别,过滤掉info日志,只输入error日志-->
            <level>ERROR</level>
        </filter>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%p%d%msg%M%F{32}%L</pattern>
            </layout>
        </encoder>
        <file>${LOG_HOME}/hxsl-platfprm-error-log.html</file>
    </appender>
    <!-- 生成 error html格式日志结束 -->
    <!-- 每天生成一个html格式的日志开始 -->
    <appender name="FILE_HTML" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/hxsl-platfprm-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <MaxFileSize>10MB</MaxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%p%d%msg%M%F{32}%L</pattern>
            </layout>
        </encoder>
    </appender>
    <!-- 每天生成一个html格式的日志结束 -->
    <!--myibatis log configure -->
    <logger name="com.apache.ibatis" level="TRACE" />
    <logger name="java.sql.Connection" level="DEBUG" />
    <logger name="java.sql.Statement" level="DEBUG" />
    <logger name="java.sql.PreparedStatement" level="DEBUG" />
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
        <appender-ref ref="HTML" />
        <appender-ref ref="FILE_HTML" />
    </root>
</configuration>
yami-shop-platform/src/main/resources/logback-spring.xml
New file
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 -->
    <property name="LOG_HOME" value="./logs" />
    <logger name="m-shop-mybatis-sql" level="debug"></logger>
    <logger name="com.yami" level="DEBUG"/>
    <!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/hxsl-platfprm-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 生成 error html格式日志开始 -->
    <appender name="HTML" class="ch.qos.logback.core.FileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!--设置日志级别,过滤掉info日志,只输入error日志-->
            <level>ERROR</level>
        </filter>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%p%d%msg%M%F{32}%L</pattern>
            </layout>
        </encoder>
        <file>${LOG_HOME}/hxsl-platfprm-error-log.html</file>
    </appender>
    <!-- 生成 error html格式日志结束 -->
    <!-- 每天生成一个html格式的日志开始 -->
    <appender name="FILE_HTML" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/hxsl-platfprm-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <MaxFileSize>10MB</MaxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%p%d%msg%M%F{32}%L</pattern>
            </layout>
        </encoder>
    </appender>
    <!-- 每天生成一个html格式的日志结束 -->
    <!--myibatis log configure -->
    <logger name="com.apache.ibatis" level="TRACE" />
    <logger name="java.sql.Connection" level="DEBUG" />
    <logger name="java.sql.Statement" level="DEBUG" />
    <logger name="java.sql.PreparedStatement" level="DEBUG" />
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
        <appender-ref ref="HTML" />
        <appender-ref ref="FILE_HTML" />
    </root>
</configuration>
yami-shop-platform/src/main/resources/logback/logback-dev.xml
New file
@@ -0,0 +1,15 @@
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <root level="info">
        <appender-ref ref="CONSOLE" />
    </root>
    <logger name="com.yami.shop" level="debug"/>
    <logger name="springfox.documentation.swagger2" level="off"/>
    <logger name="io.swagger.models.parameters" level="off"/>
    <logger name="springfox.documentation.swagger.readers.operation.OperationImplicitParameterReader" level="off"/>
    <logger name="springfox.documentation.spring.web.readers.operation" level="off"/>
</configuration>
yami-shop-platform/src/main/resources/logback/logback-docker.xml
New file
@@ -0,0 +1,62 @@
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="PROJECT_PATH" value="/opt/projects/yami-b2b2c"/>
    <property name="LOG_FILE_MAX_HISTORY" value="30"/>
    <property name="LOG_FILE_MAX_SIZE" value="50MB"/>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <appender name="DefaultFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <file>${PROJECT_PATH}/log/platform.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${logging.level}</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${PROJECT_PATH}/log/platform/%d{yyyy-MM}/platform-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
            <maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
            <maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
        </rollingPolicy>
    </appender>
    <appender name="ScheduleFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <file>${PROJECT_PATH}/log/schedule.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${logging.level}</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${PROJECT_PATH}/log/schedule/%d{yyyy-MM}/schedule-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
            <maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
            <maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
        </rollingPolicy>
    </appender>
    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DefaultFile" />
    </root>
    <logger name="com.yami.shop.sys.schedule" level="info" additivity="false">
        <appender-ref ref="ScheduleFile" />
    </logger>
    <logger name="com.yami.shop" level="debug"/>
    <logger name="springfox.documentation.swagger2" level="off"/>
    <logger name="io.swagger.models.parameters" level="off"/>
    <logger name="springfox.documentation.swagger.readers.operation.OperationImplicitParameterReader" level="off"/>
    <logger name="springfox.documentation.spring.web.readers.operation" level="off"/>
</configuration>
yami-shop-platform/src/main/resources/logback/logback-prod.xml
New file
@@ -0,0 +1,62 @@
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="PROJECT_PATH" value="/opt/projects/yami-b2b2c"/>
    <property name="LOG_FILE_MAX_HISTORY" value="30"/>
    <property name="LOG_FILE_MAX_SIZE" value="50MB"/>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <appender name="DefaultFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <file>${PROJECT_PATH}/log/platform.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${logging.level}</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${PROJECT_PATH}/log/platform/%d{yyyy-MM}/platform-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
            <maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
            <maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
        </rollingPolicy>
    </appender>
    <appender name="ScheduleFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <file>${PROJECT_PATH}/log/schedule.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${logging.level}</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${PROJECT_PATH}/log/schedule/%d{yyyy-MM}/schedule-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
            <maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
            <maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
        </rollingPolicy>
    </appender>
    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DefaultFile" />
    </root>
    <logger name="com.yami.shop" level="debug"/>
    <logger name="com.yami.shop.sys.schedule" level="info" additivity="false">
        <appender-ref ref="ScheduleFile" />
    </logger>
    <logger name="springfox.documentation.swagger2" level="off"/>
    <logger name="io.swagger.models.parameters" level="off"/>
    <logger name="springfox.documentation.swagger.readers.operation.OperationImplicitParameterReader" level="off"/>
    <logger name="springfox.documentation.spring.web.readers.operation" level="off"/>
</configuration>
yami-shop-search/pom.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-search</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>yami-shop-search-api</module>
        <module>yami-shop-search-multishop</module>
        <module>yami-shop-search-common</module>
        <module>yami-shop-search-platform</module>
    </modules>
</project>
yami-shop-search/yami-shop-search-api/pom.xml
New file
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop-search</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-search-api</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-search-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-search/yami-shop-search-api/src/main/java/com/yami/shop/search/api/config/SwaggerConfiguration.java
New file
@@ -0,0 +1,34 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.api.config;
import lombok.AllArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author Yami
 */
@Configuration("searchSwaggerConfiguration")
@AllArgsConstructor
public class SwaggerConfiguration {
    @Bean
    public GroupedOpenApi searchRestApi() {
        return GroupedOpenApi.builder()
                .group("搜索接口")
                .packagesToScan("com.yami.shop.search.api.controller")
                .pathsToMatch("/**")
                .build();
    }
}
yami-shop-search/yami-shop-search-api/src/main/java/com/yami/shop/search/api/controller/ProductSearchController.java
New file
@@ -0,0 +1,148 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.api.controller;
import cn.hutool.core.collection.CollUtil;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.EsProductActivityInfoEvent;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.EsProductSearchVO;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.common.constants.CacheNames;
import com.yami.shop.common.constants.EsCacheNames;
import com.yami.shop.common.enums.EsRenovationProductSortEnum;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.handler.SensitiveHandler;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.search.common.manager.ProductSearchManager;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.search.common.vo.EsPageVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * 商品搜索
 * @author FrozenWatermelon
 * @date 2020/11/16
 */
@RestController("appSearchSpuController")
@RequestMapping("/search")
@Tag(name = "商品搜索接口")
public class ProductSearchController {
    @Autowired
    private SearchProductService searchProductService;
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    private ProductSearchManager productSearchManager;
    @Autowired
    private SensitiveHandler sensitiveHandler;
    @GetMapping("/page")
    @Operation(summary = "商品搜索接口(仅商品信息)" , description = "商品搜索接口")
    public ServerResponseEntity<EsPageVO<EsProductSearchVO>> page(@Valid EsPageParam pageParam,@ParameterObject EsProductParam productParam) {
        // 敏感词
        if (Objects.nonNull(productParam.getKeyword()) && sensitiveHandler.isSensitive(productParam.getKeyword())){
            throw new YamiShopBindException("yami.sensitive.words.reenter");
        }
        loadSearchProdType(productParam);
        EsPageVO<EsProductSearchVO> esProductSearchVOEsPageVO = simplePage(pageParam, productParam);
        return ServerResponseEntity.success(esProductSearchVOEsPageVO);
    }
    @GetMapping("/renovationPage")
    @Operation(summary = "商品信息列表(装修商品列表)" , description = "商品信息列表(装修商品列表)")
    public ServerResponseEntity<EsPageVO<ProductSearchVO>> renovationPage(@Valid EsPageParam pageParam,@ParameterObject EsProductParam productParam) {
        Long size = null;
        if(Objects.equals(productParam.getShowSpuType(),1)){
            if(Objects.equals(productParam.getEsRenovationSpuSort(), EsRenovationProductSortEnum.CREATE_TIME_ASC.value()) || Objects.equals(productParam.getEsRenovationSpuSort(),EsRenovationProductSortEnum.CREATE_TIME_DESC.value())){
                productParam.setSort(productParam.getEsRenovationSpuSort());
            }else {
                // 获取指定规则的商品ids
                size = getSpuIds(pageParam, productParam);
            }
        }
        EsPageVO<ProductSearchVO> searchPage = searchProductService.renovationPage(pageParam, productParam, size);
        return ServerResponseEntity.success(searchPage);
    }
    private Long getSpuIds(EsPageParam pageParam, EsProductParam productParam) {
        Long shopId = Objects.isNull(productParam.getShopId()) ? 0L :  productParam.getShopId();
        String key = EsCacheNames.RENOVATION_PRODUCT_IDS_CACHE + CacheNames.UNION + shopId +
                CacheNames.UNION_KEY + productParam.getEsTimeRange() + CacheNames.UNION_KEY + productParam.getEsRenovationSpuSort();
        if(!RedisUtil.hasKey(key)){
            productSearchManager.addRenovationSpuCache(key,productParam,shopId, EsCacheNames.RENOVATION_PRODUCT_IDS_CACHE_TIME);
        }
        Long size = RedisUtil.getListSize(key);
        long startNum = (long) (pageParam.getCurrent() - 1) * pageParam.getSize();
        long endNum = (long) pageParam.getCurrent() * pageParam.getSize() - 1;
        endNum = Math.min(endNum,size);
        List prodIds = RedisUtil.getListRange(key, startNum, endNum);
        productParam.setProdIds(prodIds);
        return size;
    }
    private EsPageVO<EsProductSearchVO> simplePage(EsPageParam pageParam, EsProductParam productParam) {
        loadSearchProdType(productParam);
        EsPageVO<EsProductSearchVO> searchPage =  searchProductService.page(pageParam, productParam, Boolean.FALSE);
        loadData(productParam, searchPage.getRecords());
        return searchPage;
    }
    /**
     * 处理搜索数据
     * @param productParam
     * @param list
     */
    private void loadData(EsProductParam productParam, List<EsProductSearchVO> list) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        for (EsProductSearchVO productSearchVO : list) {
            // 如果包含秒杀、团购活动商品,就插入活动信息
            if (containActivityProd(productSearchVO)) {
                eventPublisher.publishEvent(new EsProductActivityInfoEvent(productParam.getProdType(), productSearchVO.getProducts()));
            }
        }
    }
    private boolean containActivityProd(EsProductSearchVO productSearchVO) {
        if (CollUtil.isEmpty(productSearchVO.getProducts())) {
            return Boolean.FALSE;
        }
        long count = productSearchVO.getProducts().stream().filter(product -> Objects.equals(product.getProdType(), ProdType.PROD_TYPE_GROUP.value()) || Objects.equals(product.getProdType(), ProdType.PROD_TYPE_SECKILL.value())).count();
        return count > 0;
    }
    private void loadSearchProdType(EsProductParam productParam) {
        // 用户端没有搜索全部商品需求, 当不指定商品类型时默认为非积分商品和活动商品
        if (Objects.isNull(productParam.getProdType())) {
            List<Integer> prodTypeList = new ArrayList<>();
            prodTypeList.add(ProdType.PROD_TYPE_SCORE.value());
            prodTypeList.add(ProdType.PROD_TYPE_ACTIVE.value());
            productParam.setMustNotProdTypes(prodTypeList);
        }
    }
}
yami-shop-search/yami-shop-search-common/pom.xml
New file
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop-search</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-search-common</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-security-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-service</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/config/ElasticConfig.java
New file
@@ -0,0 +1,79 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.config;
import cn.hutool.core.util.StrUtil;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.protocol.HttpContext;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
 * @author FrozenWatermelon
 * @date 2021/8/4
 */
@Configuration
public class ElasticConfig {
    @Value("${elastic.address}")
    private List<String> addressList;
    @Value("${elastic.password:}")
    private String password;
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        HttpHost[] httpHosts = new HttpHost[addressList.size()];
        for (int i = 0; i < addressList.size(); i++) {
            httpHosts[i] = HttpHost.create(addressList.get(i));
        }
        RestClientBuilder builder = RestClient.builder(httpHosts);
        // 异步httpclient连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(5000);
            requestConfigBuilder.setSocketTimeout(5000);
            requestConfigBuilder.setConnectionRequestTimeout(5000);
            return requestConfigBuilder;
        });
        // 异步httpclient配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            // httpclient连接数配置
            httpClientBuilder.setMaxConnTotal(100);
            httpClientBuilder.setMaxConnPerRoute(100);
            // httpclient保活策略
            httpClientBuilder.setKeepAliveStrategy((httpResponse, httpContext) -> 10000);
            return httpClientBuilder;
        });
        // 设置es密码
        if (StrUtil.isNotBlank(password)) {
            CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", password));
            builder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
                    .setDefaultCredentialsProvider(credentialsProvider));
        }
        return new RestHighLevelClient(builder);
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/config/EsConfig.java
New file
@@ -0,0 +1,34 @@
package com.yami.shop.search.common.config;
import com.yami.shop.search.common.constant.EsIndexEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
 * @Author: zz
 * @Description:
 * @Date: 下午 04:48 2024/10/10 0010
 * @Modified By
 */
@Configuration
public class EsConfig {
    @Value("${elastic.index}")
    private String index;
    private EsIndexEnum esIndexEnum; // 使用枚举类型来存储有效值
    @PostConstruct
    public void init() {
        this.esIndexEnum = EsIndexEnum.PRODUCT;
        this.esIndexEnum.setValue(index);
        System.out.printf("===================" + EsIndexEnum.PRODUCT.value());
    }
    public EsIndexEnum getEsIndexEnum() {
        return this.esIndexEnum;
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/constant/EsConstant.java
New file
@@ -0,0 +1,111 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.constant;
import com.yami.shop.common.config.Constant;
/**
 * 配置缓存名字
 *
 * @author lhd
 * @date 2020/12/30
 */
public interface EsConstant {
    /**
     * 商品
     */
    String PROD_ID = "prodId";
    String PROD_NAME_ZH = "prodNameZh";
    String PROD_NAME_EN = "prodNameEn";
    String BRIEF_ZH = "briefZh";
    String BRIEF_EN = "briefEn";
    String PRICE = "price";
    String ORI_PRICE = "oriPrice";
    String ACTIVITY_PRICE = "activityPrice";
    String ACTIVITY_ORIGINAL_PRICE = "activityOriginalPrice";
    String SCORE_PRICE = "scorePrice";
    String PIC = "pic";
    String IMGS = "imgs";
    String PROD_TYPE = "prodType";
    String MOLD = "mold";
    String PRE_SELL_STATUS = "preSellStatus";
    String SHOP_NAME = "shopName";
    String SHOP_ID = "shopId";
    String SHOP_TYPE = "shopType";
    String STATUS = "status";
    String HAS_STOCK = "hasStock";
    String TOTAL_STOCKS = "totalStocks";
    String SOLD_NUM = "soldNum";
    String ACTUAL_SOLD_NUM = "actualSoldNum";
    String WATER_SOLD_NUM = "waterSoldNum";
    String COMMENT_NUM = "commentNum";
    String POSITIVE_RATING = "positiveRating";
    String DELIVERY_MODE = "deliveryMode";
    String DELIVERIES = "deliveries";
    String CREATE_TIME = "createTime";
    String UPDATE_TIME = "updateTime";
    String PUTAWAY_TIME = "putawayTime";
    String ACTIVITY_START_TIME = "activityStartTime";
    String BRAND = "brand";
    String SEQ = "seq";
    String IS_TOP = "isTop";
    String ACTIVITY_ID = "activityId";
    String PRIMARY_CATEGORY_ID = "primaryCategoryId";
    String SECONDARY_CATEGORY_ID = "secondaryCategoryId";
    String CATEGORY = "category";
    String SHOP_CATEGORY_ID = "shopCategoryId";
    String APP_DISPLAY = "appDisplay";
    /**
     * 品牌
     */
    String BRAND_ID = "brandId";
    String BRAND_IMG = "brandImg";
    String BRAND_NAME_ZH = "brandNameZh";
    String BRAND_NAME_EN = "brandNameEn";
    String BRAND_UNION_ID = BRAND + Constant.PERIOD + BRAND_ID;
    String BRAND_UNION_IMG = BRAND + Constant.PERIOD + BRAND_IMG;
    String BRAND_UNION_NAME_ZH = BRAND + Constant.PERIOD + BRAND_NAME_ZH;
    String BRAND_UNION_NAME_EN = BRAND + Constant.PERIOD + BRAND_NAME_EN;
    /**
     * 分类
     */
    String CATEGORY_ID = "categoryId";
    String CATEGORY_NAME_ZH = "categoryNameZh";
    String CATEGORY_NAME_EN = "categoryNameEn";
    String CATEGORY_UNION_ID = CATEGORY + Constant.PERIOD + CATEGORY_ID;
    String CATEGORY_UNION_NAME_ZH = CATEGORY + Constant.PERIOD + CATEGORY_NAME_ZH;
    String CATEGORY_UNION_NAME_EN = CATEGORY + Constant.PERIOD + CATEGORY_NAME_EN;
    /**
     * 其余字段
     */
    String TOP_HITS_DATA = "top_hits_data";
    String SHOP = "shop";
    String SHOP_CATEGORY = "shopCategory";
    String PROD_LIST = "prodList";
    /**
     * 指定返回字段
     */
    String[] BRAND_INCLUDE = {BRAND_UNION_ID, BRAND_UNION_IMG, BRAND_UNION_NAME_ZH, BRAND_UNION_NAME_EN};
    String[] CATEGORY_INCLUDE = {CATEGORY_UNION_ID, CATEGORY_UNION_NAME_ZH, CATEGORY_UNION_NAME_EN};
    String[] APP_FETCH_SOURCE = {PROD_ID,PROD_NAME_ZH,PROD_NAME_EN,BRIEF_ZH,BRIEF_EN,SHOP_ID,PRICE,PROD_TYPE,ORI_PRICE,ACTIVITY_PRICE,SCORE_PRICE,SOLD_NUM,COMMENT_NUM,POSITIVE_RATING,PIC,IMGS,ACTIVITY_ID,ACTIVITY_ORIGINAL_PRICE,ACTIVITY_START_TIME};
    String[] SHOP_FETCH_SOURCE = {PROD_ID,PROD_NAME_ZH,PROD_NAME_EN,PIC,PRICE,ORI_PRICE,SCORE_PRICE,ACTUAL_SOLD_NUM,TOTAL_STOCKS,SEQ,STATUS,BRIEF_ZH,BRIEF_EN,IS_TOP,DELIVERY_MODE,PROD_TYPE,MOLD};
    String[] PLATFORM_FETCH_SOURCE = {SHOP_ID,SHOP_NAME,PROD_ID,PROD_NAME_ZH,PROD_NAME_EN,PIC,PRICE,ORI_PRICE,SCORE_PRICE,ACTUAL_SOLD_NUM,TOTAL_STOCKS,SEQ,STATUS,BRIEF_ZH,BRIEF_EN,IS_TOP,MOLD,DELIVERY_MODE,WATER_SOLD_NUM,PROD_TYPE};
    String[] SIMPLE_FETCH_SOURCE = {PROD_ID,UPDATE_TIME};
    String[] RENOVATION_FETCH_SOURCE = {PROD_ID,PROD_NAME_ZH,PROD_NAME_EN,PROD_TYPE,STATUS,BRIEF_ZH,BRIEF_EN,SHOP_ID,PRICE,ORI_PRICE,ACTIVITY_PRICE,SCORE_PRICE,SOLD_NUM,PIC,ACTIVITY_ID,ACTIVITY_ORIGINAL_PRICE,SHOP_NAME,TOTAL_STOCKS,ACTIVITY_START_TIME};
    String[] EXCEL_FETCH_SOURCE = {PROD_ID,PROD_NAME_ZH,PROD_NAME_EN,PROD_TYPE,STATUS,BRIEF_ZH,BRIEF_EN,SHOP_NAME};
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/constant/EsIndexEnum.java
New file
@@ -0,0 +1,37 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.constant;
/**
 * es当中的index
 * @author FrozenWatermelon
 * @date 2020/11/12
 */
public enum EsIndexEnum {
    /**
     * 商品
     */
    PRODUCT("product")
    ;
    private String value;
    public String value() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    EsIndexEnum(String value) {
        this.value = value;
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/constant/EsProductSortEnum.java
New file
@@ -0,0 +1,171 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.constant;
import org.elasticsearch.search.sort.SortOrder;
/**
 * 商品排序
 * @author FrozenWatermelon
 * @date 2020/11/17
 */
public enum EsProductSortEnum {
    /**
     * 创建时间正序
     */
    CREATE_TIME_ASC(0, EsConstant.CREATE_TIME, SortOrder.ASC),
    /**
     * 创建时间倒序
     */
    CREATE_TIME_DESC(1, EsConstant.CREATE_TIME, SortOrder.DESC),
    /**
     * 销量倒序
     */
    SALE_NUM_DESC(2, EsConstant.SOLD_NUM, SortOrder.DESC),
    /**
     * 销量正序
     */
    SALE_NUM_ASC(3, EsConstant.SOLD_NUM, SortOrder.ASC),
    /**
     * 商品价格倒序
     */
    PRICE_DESC(4, EsConstant.PRICE, SortOrder.DESC),
    /**
     * 商品价格正序
     */
    PRICE_ASC(5, EsConstant.PRICE, SortOrder.ASC),
    /**
     * 市场价倒序
     */
    MARKET_PRICE_DESC(7, EsConstant.ORI_PRICE, SortOrder.DESC),
    /**
     * 市场价正序
     */
    MARKET_PRICE_ASC(8, EsConstant.ORI_PRICE, SortOrder.ASC),
    /**
     * 商品库存倒序
     */
    STOCK_DESC(10, EsConstant.TOTAL_STOCKS, SortOrder.DESC),
    /**
     * 商品库存正序
     */
    STOCK_ASC(11, EsConstant.TOTAL_STOCKS, SortOrder.ASC),
    /**
     * 商品序号倒序
     */
    SEQ_DESC(12, EsConstant.SEQ, SortOrder.DESC),
    /**
     * 商品序号正序
     */
    SEQ_ASC(13, EsConstant.SEQ, SortOrder.ASC),
    /**
     * 评论数量倒序
     */
    COMMENT_NUM_DESC(14, EsConstant.COMMENT_NUM, SortOrder.DESC),
    /**
     * 评论数量正序
     */
    COMMENT_NUM_ASC(15, EsConstant.COMMENT_NUM, SortOrder.ASC),
    /**
     * 根据置顶状态排序
     */
    IS_TOP_DESC(16, EsConstant.IS_TOP, SortOrder.DESC),
    /**
     * 实际销量倒序
     */
    ACTUAL_SOLD_NUM_DESC(17, EsConstant.ACTUAL_SOLD_NUM, SortOrder.DESC),
    /**
     * 实际销量正序
     */
    ACTUAL_SOLD_NUM_ASC(18, EsConstant.ACTUAL_SOLD_NUM, SortOrder.ASC),
    /**
     * 注水销量倒序
     */
    WATER_SOLD_NUM_DESC(19, EsConstant.WATER_SOLD_NUM, SortOrder.DESC),
    /**
     * 注水销量正序
     */
    WATER_SOLD_NUM_ASC(20, EsConstant.WATER_SOLD_NUM, SortOrder.ASC),
    /**
     * 发布时间倒序
     */
    PUTAWAY_TIME_DESC(21, EsConstant.PUTAWAY_TIME, SortOrder.DESC),
    /**
     * 发布时间正序
     */
    PUTAWAY_TIME_ASC(22, EsConstant.PUTAWAY_TIME, SortOrder.ASC),
    /**
     * 商品ID-倒序
     */
    PROD_ID_DESC(23, EsConstant.PROD_ID, SortOrder.DESC)
    ;
    private final Integer value;
    private final String param;
    private final SortOrder order;
    public Integer value() {
        return value;
    }
    EsProductSortEnum(Integer value, String param, SortOrder order) {
        this.value = value;
        this.param = param;
        this.order = order;
    }
    public String param() {
        return param;
    }
    public SortOrder order() {
        return order;
    }
//    public static Boolean isAsc(EsProductSortEnum esProductSortEnum) {
//        if (Objects.equals(esProductSortEnum.order(), Boolean.TRUE)) {
//            return Boolean.TRUE;
//        }
//        return Boolean.FALSE;
//    }
//    public static Boolean isDesc(EsProductSortEnum esProductSortEnum) {
//        if (Objects.equals(esProductSortEnum.order(), Boolean.FALSE)) {
//            return Boolean.TRUE;
//        }
//        return Boolean.FALSE;
//    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/EsProductUpdateListener.java
New file
@@ -0,0 +1,101 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.listener;
import com.yami.shop.bean.enums.EsOperationType;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.search.common.service.EsProductService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
 * 获取es商品数据事件
 * @author yami
 */
@Component("EsProductUpdateListener")
@AllArgsConstructor
public class EsProductUpdateListener {
    private final EsProductService esProductService;
    @EventListener(EsProductUpdateEvent.class)
    public void defaultEsProductListener(EsProductUpdateEvent event) {
        EsOperationType esOperationType = event.getEsOperationType();
        Long id = event.getId();
        List<Long> ids = event.getIds();
        // 保存
        if (Objects.equals(esOperationType, EsOperationType.SAVE)) {
            esProductService.save(id);
        }
        // 更新
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE)) {
            esProductService.update(id);
        }
        // 删除
        else if (Objects.equals(esOperationType, EsOperationType.DELETE)) {
            esProductService.update(id);
            //esProductService.delete(id);
        }
        // 批量保存
        else if (Objects.equals(esOperationType, EsOperationType.SAVE_BATCH)) {
            esProductService.saveBatch(ids);
        }
        // 批量更新
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_BATCH)) {
            esProductService.updateBatch(ids);
        }
        // 批量删除
        else if (Objects.equals(esOperationType, EsOperationType.DELETE_BATCH)) {
            esProductService.updateBatch(ids);
            //esProductService.deleteBatch(ids);
        }
        // 更新商品评论
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_PROD_COMM)) {
            esProductService.updateProdComm(id);
        }
        // 更新商品销量
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_SOLD_NUM)) {
            esProductService.updateSoldNum(id);
        }
        // 批量更新商品销量
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_SOLD_NUM_BATCH)) {
            esProductService.updateSoldNumBatch(ids);
        }
        // 批量更新商品库存
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_ORDER_STOCK_NUM_BATCH)) {
            esProductService.changeProdStockBatch(event.getProdList());
        }
        // 批量更新下单单商品销量
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_ORDER_SOLD_NUM_BATCH)) {
            esProductService.changeProdSoldBatch(event.getProdList());
        }
        // 根据分类id,更新商品
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_BY_CATEGORY_ID)) {
            esProductService.updateByCategoryId(id);
        }
        // 根据店铺分类id,更新商品
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_BY_SHOP_CATEGORY_ID)) {
            esProductService.updateByShopCategoryId(id);
        }
        // 根据店铺id,更新商品
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_BY_SHOP_ID)) {
            esProductService.updateByShopId(id);
        }
        // 根据拼团活动id,更新商品
        else if (Objects.equals(esOperationType, EsOperationType.UPDATE_BY_GROUP_ID)) {
            esProductService.updateByGroupId(id);
        }
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/ProdChangeListener.java
New file
@@ -0,0 +1,65 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.listener;
import com.yami.shop.bean.event.ProdChangeEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.dao.SupplierProdMapper;
import com.yami.shop.dao.TakeStockProdMapper;
import com.yami.shop.service.ProdCommService;
import com.yami.shop.service.PurchaseProdService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
/**
 * @author Yami
 */
@Slf4j
@Component("multishopProdChangeListener")
@AllArgsConstructor
public class ProdChangeListener {
    private final ProdCommService prodCommService;
    private final SupplierProdMapper supplierProdMapper;
    private final TakeStockProdMapper takeStockProdMapper;
    private final PurchaseProdService purchaseProdService;
    @EventListener(ProdChangeEvent.class)
    @Transactional(rollbackFor = Exception.class)
    public void prodChangeEvent(ProdChangeEvent event) {
        Product product = event.getProduct();
        Long prodId = product.getProdId();
        // 删除评价信息
        Set<Long> prodIds = new HashSet<>();
        prodIds.add(prodId);
        prodCommService.deleteByProdIds(prodIds);
        // 删除供应商商品信息
        supplierProdMapper.deleteByProdId(prodId);
        // 删除盘点商品信息
        takeStockProdMapper.deleteByProdId(prodId);
        // 删除盘点商品信息
        takeStockProdMapper.deleteByProdId(prodId);
        //采购入库
        purchaseProdService.deleteByProdId(prodId, null);
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/ProdChangeStatusListener.java
New file
@@ -0,0 +1,66 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.enums.ProdStatusEnums;
import com.yami.shop.bean.event.ProdChangeStatusEvent;
import com.yami.shop.bean.model.IndexImg;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.order.GeneralActivitiesOrder;
import com.yami.shop.service.IndexImgService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.*;
/**
 * 商品状态修改监听
 * @author LGH
 */
@Component("prodChangeStatus")
@AllArgsConstructor
public class ProdChangeStatusListener {
    private final IndexImgService indexImgService;
    /**
     *
     */
    @EventListener(ProdChangeStatusEvent.class)
    @Order(GeneralActivitiesOrder.DEFAULT)
    public void prodChangeStatusListener(ProdChangeStatusEvent event) {
        Product product = event.getProduct();
        Integer status = event.getStatus();
        // 商品上线,则不进行处理
        // 或者进入待审核状态6也不处理,因为进入该状态要么是新商品,要么是下架商品
        if (Objects.equals(status, ProdStatusEnums.NORMAL.getValue()) || Objects.equals(status,ProdStatusEnums.AUDIT.getValue())) {
            return;
        }
        //移除轮播图中绑定的商品
        List<IndexImg> list = indexImgService.list(new LambdaQueryWrapper<IndexImg>()
                .eq(IndexImg::getType, 0)
                .eq(IndexImg::getRelation, product.getProdId())
        );
        List<Long> ids = new ArrayList<>();
        Set<Long> shopIds = new HashSet<>();
        for (IndexImg indexImg : list) {
            ids.add(indexImg.getImgId());
            shopIds.add(indexImg.getShopId());
        }
        // 更新轮播图数据
        indexImgService.updateImgProd(ids);
        // 清除缓存
        for (Long shopId : shopIds) {
            indexImgService.removeIndexImgCacheByShopId(shopId);
        }
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/listener/SkuDeleteListener.java
New file
@@ -0,0 +1,65 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.listener;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.event.SkuDeleteEvent;
import com.yami.shop.bean.model.StockBillLogItem;
import com.yami.shop.service.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 * @author Yami
 */
@Slf4j
@Component("skuDeleteListener")
@AllArgsConstructor
public class SkuDeleteListener {
    private final PurchaseProdService purchaseProdService;
    private final SupplierProdService supplierProdService;
    private final TakeStockProdService takeStockProdService;
    private final StockBillLogItemService stockBillLogItemService;
    private final StockBillLogService stockBillLogService;
    @EventListener(SkuDeleteEvent.class)
    @Transactional(rollbackFor = Exception.class)
    public void prodChangeEvent(SkuDeleteEvent event) {
        if (CollUtil.isEmpty(event.getSkuIds())) {
            return;
        }
        List<Long> skuIds = event.getSkuIds();
        // 移除采购订单关联sku信息
        purchaseProdService.deleteByProdId(null, skuIds);
        List<Long> supplierIds = supplierProdService.listSupplierIdBySkuIds(skuIds);
        List<Long> takeStockIds = takeStockProdService.listTakeStockIdBySkuIds(skuIds);
        // 删除供应商商品关联sku信息
        supplierProdService.deleteByProdId(null, skuIds);
        // 删除正在盘点的商品的信息
        takeStockProdService.deleteByProdId(null, skuIds);
        // 删除sku出入库信息
        stockBillLogItemService.remove(new LambdaQueryWrapper<StockBillLogItem>().in(StockBillLogItem::getSkuId, skuIds));
        // 清除已经没有商品记录的出入库记录
        stockBillLogService.removeEmptyLog();
        //清除缓存
        for (Long supplierId : supplierIds) {
            supplierProdService.removeCacheBySupplierId(supplierId);
        }
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/manager/ProductSearchManager.java
New file
@@ -0,0 +1,96 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.manager;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.common.constants.EsCacheNames;
import com.yami.shop.common.enums.EsRenovationProductSortEnum;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.search.common.vo.EsPageVO;
import com.yami.shop.service.OrderItemService;
import com.yami.shop.service.ProdCommService;
import ma.glasnost.orika.MapperFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author FrozenWatermelon
 * @date 2020/11/16
 */
@Component
public class ProductSearchManager {
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private ProdCommService prodCommService;
    @Autowired
    private OrderItemService orderItemService;
    @Autowired
    private SearchProductService searchProductService;
    public void addRenovationSpuCache(String key, EsProductParam productSearchDTO, Long shopId, Integer expireTime) {
        if(Objects.isNull(productSearchDTO.getEsTimeRange()) && Objects.isNull(productSearchDTO.getEsRenovationSpuSort())){
            return;
        }
        // 计算时间
        int dayNum = 0;
        switch (productSearchDTO.getEsTimeRange()){
            case 1:
                dayNum = 365;
                break;
            case 2:
                dayNum = 90;
                break;
            case 3:
                dayNum = 30;
                break;
            case 4:
                dayNum = 7;
                break;
            default:
                break;
        }
        List<Long> spuIds;
        // 1.获取根据条件的商品ids
        if(Objects.equals(productSearchDTO.getEsRenovationSpuSort(), EsRenovationProductSortEnum.COMMENT_NUM_DESC.value()) || Objects.equals(productSearchDTO.getEsRenovationSpuSort(),EsRenovationProductSortEnum.COMMENT_NUM_ASC.value())){
            // 获取时间范围内的评论数排序,放入redis
            spuIds = prodCommService.getCommNumRankSpuIdsByShopIdAndTime(key, shopId, dayNum, expireTime,productSearchDTO.getEsRenovationSpuSort());
        }else {
            // 获取时间范围内的销量排序,放入redis
            spuIds = orderItemService.getSoldNumRankByShopIdAndTime(key, shopId, dayNum, expireTime, productSearchDTO.getEsRenovationSpuSort());
        }
        // 2.判断数量是否为1000个,没有就按创建时间排序补充剩余
        if(spuIds.size() >= EsCacheNames.RENOVATION_PRODUCT_IDS_LIMIT){
            return;
        }
        // 不足1000的部分直接查询es数据存入redis
        int surplusNum = EsCacheNames.RENOVATION_PRODUCT_IDS_LIMIT - spuIds.size();
        EsPageParam pageDTO = new EsPageParam();
        pageDTO.setCurrent(1);
        pageDTO.setSize(surplusNum);
        EsProductParam productParam = mapperFacade.map(productSearchDTO, EsProductParam.class);
        productParam.setSpuIdsExclude(spuIds);
        productParam.setShowSpuType(0);
        EsPageVO<ProductSearchVO> searchPage = searchProductService.renovationPage(pageDTO, productParam, null);
        List<Long> esSpuIds = searchPage.getRecords().stream().map(ProductSearchVO::getProdId).collect(Collectors.toList());
        spuIds.addAll(esSpuIds);
        // 放入es缓存
        RedisUtil.setRightPushAll(key,spuIds,expireTime);
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/param/EsPageParam.java
New file
@@ -0,0 +1,152 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.param;
import com.yami.shop.common.util.PrincipalUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
/**
 * @author FrozenWatermelon
 * @date 2020/11/16
 */
public class EsPageParam {
    public static final String ASC = "ASC";
    public static final String DESC = "DESC";
    /**
     * 最大分页大小,如果分页大小大于1000,则用1000作为分页的大小。防止有人直接传入一个较大的数,导致服务器内存溢出宕机
     */
    public static final Integer MAX_PAGE_SIZE = 1000;
    /**
     * 当前页
     */
    @NotNull(message = "current 不能为空")
    @Schema(description = "当前页" , required = true)
    private Integer current;
    @NotNull(message = "pageSize 不能为空")
    @Schema(description = "每页大小" , required = true)
    private Integer size;
    @Schema(description = "排序字段数组,用逗号分割" )
    private String[] columns;
    @Schema(description = "排序字段方式,用逗号分割,ASC正序,DESC倒序" )
    private String[] orders;
    public Integer getCurrent() {
        return current;
    }
    public void setCurrent(Integer current) {
        this.current = current;
    }
    public Integer getSize() {
        return size;
    }
    public void setSize(Integer size) {
        if (size > MAX_PAGE_SIZE) {
            this.size = MAX_PAGE_SIZE;
            return;
        }
        this.size = size;
    }
    public String getOrderBy() {
        return order(this.columns, this.orders);
    }
    public String[] getColumns() {
        return columns;
    }
    public void setColumns(String[] columns) {
        this.columns = columns;
    }
    public String[] getOrders() {
        return orders;
    }
    public void setOrders(String[] orders) {
        this.orders = orders;
    }
    public static String order(String[] columns, String[] orders) {
        if (columns == null || columns.length == 0) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (int x = 0; x < columns.length; x++) {
            String column = columns[x];
            String order;
            if (orders != null && orders.length > x) {
                order = orders[x].toUpperCase();
                if (!(order.equals(ASC) || order.equals(DESC))) {
                    throw new IllegalArgumentException("非法的排序策略:" + column);
                }
            }else {
                order = ASC;
            }
            // 判断列名称的合法性,防止SQL注入。只能是【字母,数字,下划线】
            if (!PrincipalUtil.isField(column)) {
                throw new IllegalArgumentException("非法的排序字段名称:" + column);
            }
            // 驼峰转换为下划线
            column = humpConversionUnderscore(column);
            if (x != 0) {
                stringBuilder.append(", ");
            }
            stringBuilder.append("`").append(column).append("` ").append(order);
        }
        return stringBuilder.toString();
    }
    public static String humpConversionUnderscore(String value) {
        StringBuilder stringBuilder = new StringBuilder();
        char[] chars = value.toCharArray();
        for (char character : chars) {
            if (Character.isUpperCase(character)) {
                stringBuilder.append("_");
                character = Character.toLowerCase(character);
            }
            stringBuilder.append(character);
        }
        return stringBuilder.toString();
    }
    @Override
    public String toString() {
        return "EsPageDTO{" +
                "current=" + current +
                ", size=" + size +
                ", columns=" + Arrays.toString(columns) +
                ", orders=" + Arrays.toString(orders) +
                '}';
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/EsProductService.java
New file
@@ -0,0 +1,114 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.service;
import com.yami.shop.bean.app.vo.SkuVO;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.EsProdUpdateVO;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author Yami
 */
public interface EsProductService {
    /**
     * 保存
     * @param prodId
     */
    void save(Long prodId);
    /**
     * 批量保存
     * @param prodIds
     */
    void saveBatch(List<Long> prodIds);
    /**
     * 更新
     * @param prodId
     */
    void update(Long prodId);
    /**
     * 批量更新
     * @param prodIds
     */
    void updateBatch(List<Long> prodIds);
    /**
     * 更新商品评论
     * @param prodId
     */
    void updateProdComm(Long prodId);
    /**
     * 根据平台分类,更新商品
     * @param categoryId
     */
    void updateByCategoryId(Long categoryId);
    /**
     * 根据店铺分类,更新商品
     * @param shopCategoryId
     */
    void updateByShopCategoryId(Long shopCategoryId);
    /**
     * 更新商品销量
     * @param prodId
     */
    void updateSoldNum(Long prodId);
    /**
     * 批量更新商品销量
     * @param ids
     */
    void updateSoldNumBatch(List<Long> ids);
    /**
     * 根据店铺id, 更新店铺内的商品
     * @param shopId
     */
    void updateByShopId(Long shopId);
    /**
     * 删除
     * @param prodId
     */
    void delete(Long prodId);
    /**
     * 批量删除
     * @param ids
     */
    void deleteBatch(List<Long> ids);
    /**
     * 根据拼团活动id, 更新商品
     * @param id
     */
    void updateByGroupId(Long id);
    /**
     * 改变商品的库存
     * @param prodList
     */
    void changeProdStockBatch(List<EsProdUpdateVO> prodList);
    /**
     * 改变商品的销量
     * @param prodList
     */
    void changeProdSoldBatch(List<EsProdUpdateVO> prodList);
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/SearchProductService.java
New file
@@ -0,0 +1,80 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.service;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.EsProductSearchVO;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.vo.EsPageVO;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author Yami
 */
public interface SearchProductService {
    /**
     * 分页搜索
     * @param pageParam 分页参数
     * @param productParam 筛选参数
     * @param isAgg 是否需要聚合
     * @return 商品列表
     */
    EsPageVO<EsProductSearchVO> page(EsPageParam pageParam, EsProductParam productParam, Boolean isAgg);
    /**
     * 搜索商品扩展数据(分类、品牌等)
     * @param pageParam 分页参数
     * @param productParam 筛选参数
     * @return
     */
    EsProductSearchVO searchExtension(EsPageParam pageParam, EsProductParam productParam);
    /**
     * 根据商品id,获取商品列表
     * @param productParam 筛选参数
     * @return 商品信息列表
     */
    List<ProductSearchVO> listSpuByProdIds(EsProductParam productParam);
    /**
     * 商家、平台端商品管理分页查询
     * @param pageParam 分页参数
     * @param productParam 筛选参数
     * @return 商品信息列表
     */
    EsPageVO<ProductSearchVO> adminPage(EsPageParam pageParam, EsProductParam productParam);
    /**
     * 定时任务校验商品数据查询
     * @param spuIds 商品id列表
     * @return 商品信息列表
     */
    List<ProductSearchVO> simpleList(List<Long> spuIds);
    /**
     * 装修商品分页
     * @param pageParam
     * @param productParam
     * @param size
     * @return
     */
    EsPageVO<ProductSearchVO> renovationPage(EsPageParam pageParam, EsProductParam productParam, Long size);
    /**
     * 导出excel
     * @param response
     * @param productParam
     */
    void export(HttpServletResponse response, EsProductParam productParam);
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/impl/EsProductServiceImpl.java
New file
@@ -0,0 +1,294 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.bo.ProductBO;
import com.yami.shop.bean.enums.EsOperationType;
import com.yami.shop.bean.enums.ProdStatusEnums;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.EsProductEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.vo.EsProdUpdateVO;
import com.yami.shop.dao.ProductMapper;
import com.yami.shop.search.common.service.EsProductService;
import com.yami.shop.search.common.util.EsSearchUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author Yami
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class EsProductServiceImpl implements EsProductService {
    private final ApplicationEventPublisher eventPublisher;
    private final ProductMapper productMapper;
    private final RestHighLevelClient restHighLevelClient;
    @Override
    public void save(Long prodId) {
        ProductBO productBO = getProductBo(prodId);
        if (Objects.equals(productBO.getStatus(), ProdStatusEnums.DELETE.getValue())) {
            return;
        }
        EsSearchUtil.save(productBO.getProdId(), productBO);
    }
    @Override
    public void saveBatch(List<Long> prodIds) {
        List<ProductBO> productBOList = getProductBo(prodIds);
        if (CollUtil.isEmpty(productBOList)) {
            return;
        }
        Map<Long, ProductBO> map = productBOList.stream()
                .filter(productBO -> !Objects.equals(productBO.getStatus(), ProdStatusEnums.DELETE.getValue()))
                .collect(Collectors.toMap(ProductBO::getProdId, p -> p));
        EsSearchUtil.saveBatch(map);
    }
    @Override
    public void update(Long prodId) {
        if (Objects.isNull(prodId)) {
            return;
        }
        ProductBO productBO = getProductBo(prodId);
        if (Objects.equals(productBO.getStatus(), ProdStatusEnums.DELETE.getValue())) {
            delete(prodId);
            return;
        }
        EsSearchUtil.update(productBO);
    }
    @Override
    public void updateBatch(List<Long> prodIds) {
        if (CollUtil.isEmpty(prodIds)) {
            return;
        }
        List<ProductBO> productBOList = getProductBo(prodIds);
        Map<Long, ProductBO> map = new HashMap<>(productBOList.size());
        for (ProductBO productBO : productBOList) {
//            if (Objects.equals(productBO.getStatus(), ProdStatusEnums.DELETE.getValue())) {
//                delete(productBO.getProdId());
//                continue;
//            }
            map.put(productBO.getProdId(), productBO);
        }
        EsSearchUtil.updateBatch(map);
    }
    @Override
    public void updateProdComm(Long prodId) {
        if (Objects.isNull(prodId)) {
            return;
        }
        ProductBO productBO = getProductBo(prodId, EsOperationType.UPDATE_PROD_COMM);
        EsSearchUtil.partialUpdate(productBO);
    }
    @Override
    public void updateByCategoryId(Long categoryId) {
        if (Objects.isNull(categoryId)) {
            return;
        }
        List<Long> ids = productMapper.listProdId(categoryId, null, null);
        updateBatch(ids);
    }
    @Override
    public void updateByShopCategoryId(Long shopCategoryId) {
        if (Objects.isNull(shopCategoryId)) {
            return;
        }
        List<Long> ids = productMapper.listProdId(null, shopCategoryId, null);
        updateBatch(ids);
    }
    @Override
    public void updateSoldNum(Long prodId) {
        if (Objects.isNull(prodId)) {
            return ;
        }
        ProductBO productBO = getProductBo(prodId, EsOperationType.UPDATE_SOLD_NUM);
        EsSearchUtil.partialUpdate(productBO);
    }
    @Override
    public void updateSoldNumBatch(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return ;
        }
        List<ProductBO> productList = getProductBo(ids, EsOperationType.UPDATE_SOLD_NUM);
        for (ProductBO productBO : productList) {
            EsSearchUtil.partialUpdate(productBO);
        }
    }
    @Override
    public void updateByShopId(Long shopId) {
        if (Objects.isNull(shopId)) {
            return;
        }
        List<Long> ids = productMapper.listProdId(null, null, shopId);
        updateBatch(ids);
    }
    @Override
    public void delete(Long prodId) {
        if (Objects.isNull(prodId)) {
            return;
        }
        EsSearchUtil.delete(prodId);
    }
    @Override
    public void deleteBatch(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        EsSearchUtil.deleteBatch(ids);
    }
    @Override
    public void updateByGroupId(Long id) {
        if (Objects.isNull(id)) {
            return;
        }
        List<Product> products = productMapper.selectList(new LambdaQueryWrapper<Product>()
                .eq(Product::getProdType, ProdType.PROD_TYPE_GROUP.value())
                .eq(Product::getActivityId, id));
        List<Long> prodIds = products.stream().map(Product::getProdId).collect(Collectors.toList());
        updateBatch(prodIds);
    }
    @Override
    public void changeProdStockBatch(List<EsProdUpdateVO> prodList) {
        if (CollUtil.isEmpty(prodList)) {
            return;
        }
        Map<Long, Script> map = new HashMap<>(prodList.size());
        for (EsProdUpdateVO esProdUpdateVO : prodList) {
            String code = "-= ";
            if (Objects.equals(esProdUpdateVO.getType(), 1)) {
                code = "+= ";
            }
            Script script = new Script(ScriptType.INLINE,
                    "painless",
                    "ctx._source.totalStocks " + code + esProdUpdateVO.getCount(),
                    Collections.emptyMap());
            map.put(esProdUpdateVO.getProdId(), script);
        }
        EsSearchUtil.updateStockByPainless(map);
    }
    @Override
    public void changeProdSoldBatch(List<EsProdUpdateVO> prodList) {
        if (CollUtil.isEmpty(prodList)) {
            return;
        }
        Map<Long, Script> map = new HashMap<>(prodList.size());
        for (EsProdUpdateVO esProdUpdateVO : prodList) {
            String code = "-= ";
            if (Objects.equals(esProdUpdateVO.getType(), 1)) {
                code = "+= ";
            }
            code = "ctx._source.soldNum " + code + esProdUpdateVO.getCount() + ";" + "ctx._source.actualSoldNum " + code + esProdUpdateVO.getCount();
            Script script = new Script(ScriptType.INLINE,
                    "painless",
                    code,
                    Collections.emptyMap());
            map.put(esProdUpdateVO.getProdId(), script);
        }
        EsSearchUtil.updateStockByPainless(map);
    }
    /**
     * 获取商品信息
     * @param prodId 商品id
     * @return
     */
    private ProductBO getProductBo(Long prodId) {
        if (Objects.isNull(prodId)) {
            return null;
        }
        List<ProductBO> productList = getProductBo(Collections.singletonList(prodId));
        if (CollUtil.isEmpty(productList)) {
            return null;
        }
        return productList.get(0);
    }
    /**
     * 获取商品信息
     * @param prodId 商品id
     * @return
     */
    private ProductBO getProductBo(Long prodId, EsOperationType operationType) {
        if (Objects.isNull(prodId)) {
            return null;
        }
        List<ProductBO> productList = getProductBo(Collections.singletonList(prodId), operationType);
        if (CollUtil.isEmpty(productList)) {
            return null;
        }
        return productList.get(0);
    }
    /**
     * 获取商品信息
     * @param prodIds 商品id
     * @return
     */
    private List<ProductBO> getProductBo(List<Long> prodIds) {
        return listProductBo(prodIds, null);
    }
    /**
     * 获取商品信息
     * @param prodIds 商品id
     * @return
     */
    private List<ProductBO> getProductBo(List<Long> prodIds, EsOperationType operationType) {
        return listProductBo(prodIds, operationType);
    }
    /**
     * 获取商品信息
     * @param prodIds 商品id列表
     * @return
     */
    private List<ProductBO> listProductBo(List<Long> prodIds, EsOperationType operationType) {
        if (CollUtil.isEmpty(prodIds)) {
            return null;
        }
        List<ProductBO> productList = new ArrayList<>();
        try {
            eventPublisher.publishEvent(new EsProductEvent(prodIds, productList, operationType));
        } catch (Exception e) {
            log.error("获取es商品数据异常:{}", e);
        }
        if (CollUtil.isEmpty(productList) || Objects.isNull(productList.get(0))) {
            log.error("商品id为:{}, type:{}的商品数据异常!", prodIds, operationType.value());
            return null;
        }
        return productList;
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/service/impl/SearchProductServiceImpl.java
New file
@@ -0,0 +1,736 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSON;
import com.yami.shop.bean.enums.ProdStatusEnums;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.model.Category;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.param.ProductExportParam;
import com.yami.shop.bean.vo.search.EsProductSearchVO;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.common.enums.StatusEnum;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.i18n.LanguageEnum;
import com.yami.shop.common.util.PoiExcelUtil;
import com.yami.shop.search.common.constant.EsConstant;
import com.yami.shop.search.common.constant.EsIndexEnum;
import com.yami.shop.search.common.constant.EsProductSortEnum;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.search.common.util.EsSearchUtil;
import com.yami.shop.search.common.util.SearchResponseUtil;
import com.yami.shop.search.common.vo.EsPageVO;
import com.yami.shop.service.ProductExcelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * @author Yami
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SearchProductServiceImpl implements SearchProductService {
    private final ProductExcelService productExcelService;
    /**
     * 通过搜索信息分页搜索es数据并聚合返回的信息
     * @param pageParam 分页数据
     * @param productParam 商品搜索条件
     * @return 搜索结果
     */
    @Override
    public EsPageVO<EsProductSearchVO> page(EsPageParam pageParam, EsProductParam productParam, Boolean isAgg) {
        if (Objects.isNull(productParam.getAppDisplay())) {
            productParam.setAppDisplay(Boolean.TRUE);
        }
        SearchResponse response = pageSearchResult(pageParam, productParam, isAgg);
        return buildSearchResult(pageParam,response);
    }
    /**
     * 获取搜索扩展信息
     * @param pageParam 分页数据
     * @param productParam 商品搜索条件
     * @return 搜索结果
     */
    @Override
    public EsProductSearchVO searchExtension(EsPageParam pageParam, EsProductParam productParam) {
        EsProductParam newEsProductParam = new EsProductParam();
        newEsProductParam.setKeyword(productParam.getKeyword());
        newEsProductParam.setPrimaryCategoryId(productParam.getPrimaryCategoryId());
        newEsProductParam.setSecondaryCategoryId(productParam.getSecondaryCategoryId());
        newEsProductParam.setShopCategoryId(productParam.getShopCategoryId());
        pageParam.setSize(0);
        SearchResponse response = pageSearchResult(pageParam, newEsProductParam, Boolean.TRUE);
        return SearchResponseUtil.getProductSearch(response);
    }
    /**
     * 根据spuId列表,获取spu信息
     * @param productParam  商品搜索条件
     * @return spu列表
     */
    @Override
    public List<ProductSearchVO> listSpuByProdIds(EsProductParam productParam) {
        EsPageParam pageParam = new EsPageParam();
        pageParam.setCurrent(1);
        pageParam.setSize(productParam.getProdIds().size());
        SearchResponse response = pageSearchResult(pageParam, productParam, Boolean.FALSE);
        return SearchResponseUtil.buildSpuSearchList(response.getHits());
    }
    @Override
    public EsPageVO<ProductSearchVO> adminPage(EsPageParam pageParam, EsProductParam productParam) {
        EsPageVO<ProductSearchVO> result = new EsPageVO<>();
        SearchResponse response = pageSearchResult(pageParam, productParam, Boolean.FALSE);
        // 商品信息
        result.setRecords(SearchResponseUtil.buildSpuSearchList(response.getHits()));
        // 分页信息
        SearchResponseUtil.buildSearchPage(pageParam, result, response);
        return result;
    }
    @Override
    public List<ProductSearchVO> simpleList(List<Long> spuIds) {
        EsProductParam productParam = new EsProductParam();
        productParam.setFetchSource(EsConstant.SIMPLE_FETCH_SOURCE);
        productParam.setProdIds(spuIds);
        productParam.setGetDelete(true);
        List<ProductSearchVO> list = listSpuByProdIds(productParam);
        return list;
    }
    @Override
    public EsPageVO<ProductSearchVO> renovationPage(EsPageParam pageParam, EsProductParam productParam, Long size) {
        if (Objects.isNull(pageParam.getSize()) || pageParam.getSize() == 0) {
            return new EsPageVO();
        }
        productParam.setFetchSource(EsConstant.RENOVATION_FETCH_SOURCE);
        // 不需要积分商品和活动商品
        if (Objects.isNull(productParam.getProdType())) {
            List<Integer> types = new ArrayList<>();
            types.add(ProdType.PROD_TYPE_ACTIVE.value());
            types.add(ProdType.PROD_TYPE_SCORE.value());
            productParam.setMustNotProdTypes(types);
        }
        // 如果不是搜索指定商品,就只查询可以在用户端显示的商品
        if (CollUtil.isEmpty(productParam.getProdIds())) {
            productParam.setAppDisplay(Boolean.TRUE);
        }
        EsPageVO<ProductSearchVO> searchPage =  this.adminPage(pageParam, productParam);
        long currentTime = System.currentTimeMillis();
        for (ProductSearchVO record : searchPage.getRecords()) {
            // 不是秒杀或者团购商品,或者活动还没开始的商品,就不用处理活动价格
            boolean notHandleActivityPrice = (!Objects.equals(record.getProdType(), ProdType.PROD_TYPE_GROUP.value()) &&
                    !Objects.equals(record.getProdType(), ProdType.PROD_TYPE_SECKILL.value())) ||
                    Objects.isNull(record.getActivityStartTime()) ||
                    record.getActivityStartTime() > currentTime;
            if (notHandleActivityPrice) {
                continue;
            }
            record.setOriPrice(record.getPrice());
            record.setPrice(record.getActivityPrice());
            record.setActivityPrice(null);
            record.setActivityOriginalPrice(null);
        }
        //TODO 以后优化,瀑布流装修商品重新排序
        handleProds(productParam, searchPage,size);
        return searchPage;
    }
    @Override
    public void export(HttpServletResponse response, EsProductParam productParam) {
        productParam.setFetchSource(EsConstant.EXCEL_FETCH_SOURCE);
        productParam.setAppDisplay(Boolean.FALSE);
        ExcelWriter writer = productExcelService.getProdExcelWriter(productParam.getShopId());
        try (Workbook workbook = writer.getWorkbook()) {
            Sheet sheet = writer.getSheet();
            // excel下拉数据列表组装
            productExcelService.dropDownList(productParam.getShopId(), sheet, workbook);
            writerProdToExcel(writer, productParam);
            PoiExcelUtil.writeExcel(response, writer);
        } catch (Exception e) {
            log.error("Exception:", e);
        }
    }
    /**
     * 瀑布流装修商品排序
     * @param productParam
     * @param searchPage
     * @param size
     */
    private void handleProds(EsProductParam productParam, EsPageVO<ProductSearchVO> searchPage, Long size) {
        if(!Objects.equals(productParam.getShowSpuType(),1) || CollectionUtils.isEmpty(productParam.getProdIds())) {
            return;
        }
        searchPage.setTotal(Objects.isNull(size) ? searchPage.getTotal() : size);
        List<ProductSearchVO> productSearchList = new ArrayList<>();
        Map<Long, ProductSearchVO> prodMap = searchPage.getRecords().stream().collect(Collectors.toMap(ProductSearchVO::getProdId, prod -> prod));
        for (Object prodId : productParam.getProdIds()) {
            if(!prodMap.containsKey(Long.valueOf(String.valueOf(prodId)))){
                continue;
            }
            productSearchList.add(prodMap.get(Long.valueOf(String.valueOf(prodId))));
        }
        searchPage.setRecords(productSearchList);
    }
    /**
     * 写入导出的商品数据
     * 使用es的scroll方法来滚动查询es的数据,可以有效的解决大数据容量读取的限制
     * @param writer
     * @param param
     */
    private void writerProdToExcel(ExcelWriter writer, EsProductParam param) {
        int row = 1;
        //设置查询超时时间
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(5L));
        Integer lang = I18nMessage.getDbLang();
        SearchRequest searchRequest = buildScrollSearchRequest(param, scroll, lang);
        // 进行第一次滚动查询
        SearchResponse searchResponse = EsSearchUtil.search(searchRequest);
        // 将商品数据写入excel
        List<ProductExportParam> prodList = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            prodList.add(JSON.parseObject(hit.getSourceAsString(), ProductExportParam.class));
        }
        List<Category> platformCategoryList = productExcelService.listPlatformCategory(param.getShopId());
        Map<Long, String> categoryMap = platformCategoryList.stream().collect(Collectors.toMap(Category::getCategoryId, Category::getCategoryName));
        row = productExcelService.writerProdToExcel(prodList, writer, param.getShopId(), row, categoryMap);
        SearchHits hits= searchResponse.getHits();
        /**
         *在这个位置已经读到了前一千条数据,可以在这先对这一千数据进行处理。下面滚动查询剩下的数据
         */
        //记录要滚动的ID
        String scrollId = searchResponse.getScrollId();
        //滚动查询部分,将从第1001笔数据开始取
        SearchHit[] hitsScroll = hits.getHits();
        while (hitsScroll != null && hitsScroll.length > 0 ) {
            //构造滚动查询条件
            SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
            searchScrollRequest.scroll(scroll);
            searchResponse = EsSearchUtil.scroll(searchScrollRequest);
            scrollId = searchResponse.getScrollId();
            hits = searchResponse.getHits();
            hitsScroll = hits.getHits();
            // 将商品数据写入excel
            prodList.clear();
            for (SearchHit hit : searchResponse.getHits().getHits()) {
                prodList.add(JSON.parseObject(hit.getSourceAsString(), ProductExportParam.class));
            }
            // 将查询的商品数据插入excel中
            row = productExcelService.writerProdToExcel(prodList, writer, param.getShopId(), row, categoryMap);
        }
        //清除滚动,否则影响下次查询
        EsSearchUtil.clearScroll(scrollId);
    }
    /**
     * 构建搜索结果数据
     * @param pageParam
     * @param response
     * @return
     */
    private static EsPageVO<EsProductSearchVO> buildSearchResult(EsPageParam pageParam, SearchResponse response) {
        EsPageVO<EsProductSearchVO> esPageVO = new EsPageVO<>();
        // 1、返回的所有查询到的商品
        List<EsProductSearchVO> productSearchs = new ArrayList<>();
        productSearchs.add(SearchResponseUtil.getProductSearch(response));
        esPageVO.setRecords(productSearchs);
        // 2、分页信息
        SearchResponseUtil.buildSearchPage(pageParam, esPageVO, response);
        return esPageVO;
    }
    /**
     * 通过搜索信息分页搜索es数据的信息
     * @param pageParam 分页数据
     * @param productParam 商品搜索条件
     * @param isAgg true:聚合搜索  false:非聚合搜索  null:非聚合搜索
     * @return 搜索结果
     */
    private SearchResponse pageSearchResult(EsPageParam pageParam, EsProductParam productParam, Boolean isAgg) {
        //1、准备检索请求
        SearchRequest searchRequest = buildSearchRequest(pageParam, productParam, isAgg);
        return EsSearchUtil.search(searchRequest);
    }
    /**
     * 准备滚动检索请求
     *
     * @param param 搜索参数
     * @param lang
     * @return
     */
    private SearchRequest buildScrollSearchRequest(EsProductParam param, Scroll scroll, Integer lang) {
        // 构建bool-query
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 指定返回数组中的字段
        if (ArrayUtil.isNotEmpty(param.getFetchSource())) {
            searchSourceBuilder.fetchSource(param.getFetchSource(), null);
        }
        // 过滤
        filterQueryIfNecessary(param, boolQueryBuilder);
        // 关键字搜索
        keywordSearch(param, boolQueryBuilder, lang);
        // 排序
        sort(param, searchSourceBuilder, boolQueryBuilder, lang);
        //设置最多一次能够取出1000笔数据,从第1001笔数据开始,将开启滚动查询
        //PS:滚动查询也属于这一次查询,只不过因为一次查不完,分多次查
        searchSourceBuilder.size(1000);
        log.debug("构建的DSL语句 {}",searchSourceBuilder.toString());
        SearchRequest searchRequest = new SearchRequest(new String[]{EsIndexEnum.PRODUCT.value()}, searchSourceBuilder);
        //将滚动放入
        searchRequest.scroll(scroll);
        return searchRequest;
    }
    /**
     * 准备检索请求
     * @param pageParam 分页参数
     * @param param 搜索参数
     * @param isAgg true:聚合搜索  false:非聚合搜索  null:非聚合搜索
     * @return
     */
    private SearchRequest buildSearchRequest(EsPageParam pageParam,EsProductParam param, Boolean isAgg) {
        if (Objects.isNull(param.getAppDisplay())) {
            param.setAppDisplay(Boolean.FALSE);
        }
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        Integer lang = I18nMessage.getLang();
        // 指定返回数组中的字段
        if (ArrayUtil.isNotEmpty(param.getFetchSource())) {
            searchSourceBuilder.fetchSource(param.getFetchSource(), null);
        } else {
            searchSourceBuilder.fetchSource(EsConstant.APP_FETCH_SOURCE, null);
        }
        // 构建bool-query
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        // 过滤
        filterQueryIfNecessary(param, boolQueryBuilder);
        // 关键字搜索
        keywordSearch(param, boolQueryBuilder, lang);
        // 排序
        sort(param, searchSourceBuilder, boolQueryBuilder, lang);
        //分页
        if (Objects.nonNull(pageParam)) {
            if (pageParam.getCurrent() <= 0) {
                pageParam.setCurrent(1);
            }
            searchSourceBuilder.from((pageParam.getCurrent()-1)*pageParam.getSize());
            searchSourceBuilder.size(pageParam.getSize());
        }
        // 进行聚合分析
        agg(param, searchSourceBuilder, isAgg);
        log.debug("构建的DSL语句 {}",searchSourceBuilder.toString());
        return new SearchRequest(new String[]{EsIndexEnum.PRODUCT.value()},searchSourceBuilder);
    }
    /**
     * 聚合分析
     */
    private void agg(EsProductParam param, SearchSourceBuilder searchSourceBuilder, Boolean isAgg) {
        // 店铺进行聚合
        if (param.getKeyword() != null && param.getKeyword().length() > 0) {
            searchSourceBuilder.aggregation(AggregationBuilders.terms(EsConstant.SHOP).field(EsConstant.SHOP_ID).size(1));
        }
        if (Objects.isNull(isAgg) || !isAgg) {
            return;
        }
        // 品牌聚合
        searchSourceBuilder.aggregation(EsSearchUtil.nestedAggregation(EsConstant.BRAND, EsConstant.BRAND_UNION_ID, EsConstant.BRAND_ID, EsConstant.BRAND_INCLUDE));
        // 搜索平台商品,按照平台分类信息进行聚合
        if (Objects.isNull(param.getShopId()) && Objects.isNull(param.getCategoryId())) {
            searchSourceBuilder.aggregation(EsSearchUtil.nestedAggregation(EsConstant.CATEGORY, EsConstant.CATEGORY_UNION_ID, EsConstant.CATEGORY_ID, EsConstant.CATEGORY_INCLUDE));
        }
    }
    /**
     * 关键字搜索
     */
    private void keywordSearch(EsProductParam param, BoolQueryBuilder boolQueryBuilder, Integer lang) {
        if (StrUtil.isBlank(param.getKeyword())) {
            return;
        }
        // 创建查询语句 ES中must和should不能同时使用 同时使用should失效 嵌套多个must 将should条件拼接在一个must中即可
        BoolQueryBuilder keywordShouldQuery = QueryBuilders.boolQuery();
        // 提升商品名称搜索的权重
        if (Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
            keywordShouldQuery.should(QueryBuilders.matchQuery(EsConstant.PROD_NAME_ZH, param.getKeyword()).boost(10));
        } else {
            keywordShouldQuery.should(QueryBuilders.matchQuery(EsConstant.PROD_NAME_EN, param.getKeyword()).boost(10));
        }
        if (param.getKeyword().length()>1) {
            // 卖点,不分词
            if (Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
                keywordShouldQuery.should(QueryBuilders.matchPhraseQuery(EsConstant.BRIEF_ZH, param.getKeyword()).boost(2));
            } else {
                keywordShouldQuery.should(QueryBuilders.matchPhraseQuery(EsConstant.BRIEF_EN, param.getKeyword()).boost(2));
            }
            // 店铺名,不分词
            keywordShouldQuery.should(QueryBuilders.matchPhraseQuery(EsConstant.SHOP_NAME, param.getKeyword()));
        }
        boolQueryBuilder.must(keywordShouldQuery);
    }
    /**
     * 进行排序
     */
    private void sort(EsProductParam param, SearchSourceBuilder searchSourceBuilder, BoolQueryBuilder boolQueryBuilder, Integer lang) {
        // 用户端、商家端、平台端自定义排序
        if (Objects.nonNull(param.getSort())) {
            for (EsProductSortEnum enumValue : EsProductSortEnum.values()) {
                if (!Objects.equals(enumValue.value(), param.getSort())) {
                    continue;
                }
                searchSourceBuilder.sort(enumValue.param(), enumValue.order());
            }
            //封装所有的查询条件(没有function score)
            searchSourceBuilder.query(boolQueryBuilder);
            return;
        }
        // 1.关键字排序 -- 没有指定排序规则,且是关键字搜索的查询,统一按关键字优先排序(防止关键字搜素时,关键字的商品没有优先显示)
        // 2.用户端默认排序 -- 如果排序规则设为空,则按照一定的算分规则进行排序,否则按照用户指定排序规则进行排序()
        if (StrUtil.isNotBlank(param.getKeyword()) || param.getAppDisplay()) {
            keywordSort(param, searchSourceBuilder, boolQueryBuilder, lang);
            return;
        }
        if (Objects.equals(param.getShowSpuType(),1)) {
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder).boostMode(CombineFunction.SUM);
            searchSourceBuilder.query(functionScoreQueryBuilder);
            return;
        }
        // 商家、平台默认排序--商品序号 倒序, 创建时间 倒序
        if (Objects.nonNull(param.getShopId())) {
            // 商家端优先显示序号大的商品
            searchSourceBuilder.sort(EsProductSortEnum.SEQ_DESC.param(), EsProductSortEnum.SEQ_DESC.order());
        } else {
            // 平台端优先置顶的商品, 再到销量(实际销量+注水销量)
            searchSourceBuilder.sort(EsProductSortEnum.IS_TOP_DESC.param(), EsProductSortEnum.IS_TOP_DESC.order());
            searchSourceBuilder.sort(EsProductSortEnum.SALE_NUM_DESC.param(), EsProductSortEnum.SALE_NUM_DESC.order());
        }
        searchSourceBuilder.sort(EsProductSortEnum.CREATE_TIME_DESC.param(), EsProductSortEnum.CREATE_TIME_DESC.order());
        searchSourceBuilder.sort(EsProductSortEnum.PROD_ID_DESC.param(), EsProductSortEnum.PROD_ID_DESC.order());
        searchSourceBuilder.query(boolQueryBuilder);
    }
    /**
     * 关键字搜索排序
     * @param param
     * @param searchSourceBuilder
     * @param boolQueryBuilder
     * @param lang
     */
    private void keywordSort(EsProductParam param, SearchSourceBuilder searchSourceBuilder, BoolQueryBuilder boolQueryBuilder, Integer lang) {
        List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
        // 关键字搜索,优先匹配
        if (StrUtil.isNotBlank(param.getKeyword())) {
            FunctionScoreQueryBuilder.FilterFunctionBuilder spuName;
            // 权重调大,防止销量大的商品排在关键词商品的前面
            if (Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
                spuName = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery(EsConstant.PROD_NAME_ZH, param.getKeyword()), ScoreFunctionBuilders.weightFactorFunction(200));
            } else {
                spuName = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery(EsConstant.PROD_NAME_EN, param.getKeyword()), ScoreFunctionBuilders.weightFactorFunction(200));
            }
            filterFunctionBuilders.add(spuName);
        }
        // 用户端默认排序优先使用是否置顶参数
        FunctionScoreQueryBuilder.FilterFunctionBuilder isTop = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery(EsConstant.IS_TOP, 50), ScoreFunctionBuilders.weightFactorFunction(1));
        filterFunctionBuilders.add(isTop);
        // 评论数 log1p
        ScoreFunctionBuilder<FieldValueFactorFunctionBuilder> commentNumScoreFunction = new FieldValueFactorFunctionBuilder(EsConstant.COMMENT_NUM).modifier(FieldValueFactorFunction.Modifier.LOG1P).factor(0.5f);
        filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(commentNumScoreFunction));
        // 销量数 log1p
        ScoreFunctionBuilder<FieldValueFactorFunctionBuilder> saleNumScoreFunction = new FieldValueFactorFunctionBuilder(EsConstant.SOLD_NUM).modifier(FieldValueFactorFunction.Modifier.LOG1P).factor(0.5f);
        filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(saleNumScoreFunction));
        filterFunctionBuilders.toArray();
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, ArrayUtil.toArray(filterFunctionBuilders, FunctionScoreQueryBuilder.FilterFunctionBuilder.class))
                .scoreMode(FunctionScoreQuery.ScoreMode.SUM).boostMode(CombineFunction.SUM);
        // 封装所有的查询条件(带有function score)
        searchSourceBuilder.query(functionScoreQueryBuilder);
    }
    /**
     * 过滤查询条件,如果有必要的话
     * @param param 查询条件
     * @param boolQueryBuilder 组合进boolQueryBuilder
     */
    private void filterQueryIfNecessary(EsProductParam param, BoolQueryBuilder boolQueryBuilder) {
        // 用户端搜索
        if(param.getAppDisplay()) {
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.APP_DISPLAY, param.getAppDisplay()));
        }
        // 商品(状态、库存、商品类型、商品id、商品id列表, 组合商品,配送方式,是否置顶)
        spuFilterQuery(param, boolQueryBuilder);
        // 分类(商家分类,平台一二三级分类)
        categoryFilterQuery(param, boolQueryBuilder);
        // 活动 (活动商品id)
        activityFilterQuery(param, boolQueryBuilder);
        // 商品扩展信息筛选(店铺id、店铺类型、店铺名称、品牌)
        extensionFilterQuery(param, boolQueryBuilder);
        // 范围筛选(价格、销量)
        rangeFilterQuery(param, boolQueryBuilder);
    }
    /**
     * 范围过滤
     * @param param 查询条件
     * @param boolQueryBuilder
     */
    private void rangeFilterQuery(EsProductParam param, BoolQueryBuilder boolQueryBuilder) {
        // 价格区间
        if(param.getMinPrice() != null || param.getMaxPrice() != null){
            boolQueryBuilder.filter(EsSearchUtil.rangeQuery(EsConstant.PRICE,param.getMinPrice(), param.getMaxPrice()));
        }
        // 销量区间
        if(param.getMinSaleNum() != null || param.getMaxSaleNum() != null){
            boolQueryBuilder.filter(EsSearchUtil.rangeQuery(param.getAppDisplay() ? EsConstant.SOLD_NUM : EsConstant.ACTUAL_SOLD_NUM,
                    param.getMinSaleNum(), param.getMaxSaleNum()));
        }
    }
    /**
     * 商品扩展信息过滤
     * @param param 查询条件
     * @param boolQueryBuilder
     */
    private void extensionFilterQuery(EsProductParam param, BoolQueryBuilder boolQueryBuilder) {
        // 店铺id
        if(Objects.nonNull(param.getShopId())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.SHOP_ID, param.getShopId()));
        }
        // 店铺类型
        if(Objects.nonNull(param.getShopType())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.SHOP_TYPE, param.getShopType()));
        }
        // 店铺名称
        if(StrUtil.isNotBlank(param.getShopName())){
            BoolQueryBuilder keywordShouldQuery = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery(EsConstant.SHOP_NAME, param.getShopName()));
            boolQueryBuilder.must(keywordShouldQuery);
        }
        // 品牌
        if(StrUtil.isNotBlank(param.getBrandIds())){
            boolQueryBuilder.filter(EsSearchUtil.nestedQueryByArray(EsConstant.BRAND, EsConstant.BRAND_UNION_ID, param.getBrandIds()));
        }
    }
    /**
     * 商品活动信息过滤
     * @param param 查询条件
     * @param boolQueryBuilder
     */
    private void activityFilterQuery(EsProductParam param, BoolQueryBuilder boolQueryBuilder) {
        // 商品活动Id
        if(Objects.nonNull(param.getActivityId())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.ACTIVITY_ID, param.getActivityId()));
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.PROD_TYPE, param.getProdType()));
        }
        if (Objects.equals(param.getProdType(), ProdType.PROD_TYPE_GROUP.value()) && param.getAppDisplay()){
            boolQueryBuilder.filter(EsSearchUtil.rangeQuery(EsConstant.ACTIVITY_START_TIME, null, System.currentTimeMillis()));
        } else if (Objects.equals(param.getProdType(), ProdType.PROD_TYPE_SECKILL.value()) && param.getAppDisplay()){
            boolQueryBuilder.must(QueryBuilders.existsQuery(EsConstant.ACTIVITY_START_TIME));
        }
    }
    /**
     * 商品信息过滤
     * @param param 查询条件
     * @param boolQueryBuilder
     */
    private void spuFilterQuery(EsProductParam param, BoolQueryBuilder boolQueryBuilder) {
        // spu状态
        List<Integer> statusList = new ArrayList<>();
        if (Objects.nonNull(param.getStatus())) {
            statusList.add(param.getStatus());
        }
        // 装修以及定时任务获取删除商品
        else if (!BooleanUtil.isTrue(param.getGetDelete())) {
            statusList.add(StatusEnum.ENABLE.value());
            statusList.add(StatusEnum.DISABLE.value());
            statusList.add(StatusEnum.OFFLINE.value());
            statusList.add(StatusEnum.WAIT_AUDIT.value());
            statusList.add(ProdStatusEnums.AUDIT.getValue());
        }
        if(CollUtil.isNotEmpty(statusList)) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery(EsConstant.STATUS, statusList));
        }
        // 是否有库存
        if(Objects.nonNull(param.getHasStock())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.HAS_STOCK, BooleanUtil.isTrue(param.getHasStock())));
        }
        // 商品类型
        if(Objects.nonNull(param.getProdType())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.PROD_TYPE, param.getProdType()));
        }
        // 不匹配的商品类型
        if(CollUtil.isNotEmpty(param.getMustNotProdTypes())){
            for (Integer prodType : param.getMustNotProdTypes()) {
                boolQueryBuilder.mustNot(QueryBuilders.termQuery(EsConstant.PROD_TYPE, prodType.toString()));
            }
        }
        // 是否过滤活动商品
        if(Objects.nonNull(param.getIsActive()) && param.getIsActive() == 1){
            boolQueryBuilder.mustNot(QueryBuilders.termQuery(EsConstant.PROD_TYPE, ProdType.PROD_TYPE_ACTIVE.value()));
        }
        // 商品类别
        if(Objects.nonNull(param.getMold())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.MOLD, param.getMold()));
        }
        // spuId
        if(Objects.nonNull(param.getProdId())){
            boolQueryBuilder.filter(QueryBuilders.termsQuery(EsConstant.PROD_ID,param.getProdId().toString()));
        }
        // spuId列表
        else if(CollectionUtil.isNotEmpty(param.getProdIds())){
            boolQueryBuilder.filter(QueryBuilders.termsQuery(EsConstant.PROD_ID,param.getProdIds()));
        }
        // 查询不在该集合中的商品
        if(Objects.nonNull(param.getSpuIdsExclude())){
            boolQueryBuilder.mustNot(QueryBuilders.termsQuery(EsConstant.PROD_ID,param.getSpuIdsExclude()));
        }
        // 配送方式
        if(Objects.nonNull(param.getDeliveryMode())){
            boolQueryBuilder.filter(QueryBuilders.termsQuery(EsConstant.DELIVERIES,param.getDeliveryMode().toString()));
        }
        // 是否置顶
        if(Objects.nonNull(param.getIsTop())){
            boolQueryBuilder.filter(QueryBuilders.termsQuery(EsConstant.IS_TOP,param.getIsTop().toString()));
        }
    }
    /**
     * 商品分类信息过滤
     * @param param
     * @param boolQueryBuilder
     */
    private void categoryFilterQuery(EsProductParam param, BoolQueryBuilder boolQueryBuilder) {
        //商家分类
        if(Objects.nonNull(param.getShopCategoryId())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.SHOP_CATEGORY_ID, param.getShopCategoryId()));
        }
        // 平台一级分类
        if(Objects.nonNull(param.getPrimaryCategoryId())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.PRIMARY_CATEGORY_ID, param.getPrimaryCategoryId()));
        }
        // 查询不在该集合中的商品
        if(Objects.nonNull(param.getNotPrimaryCategoryId())) {
            boolQueryBuilder.mustNot(QueryBuilders.termsQuery(EsConstant.PRIMARY_CATEGORY_ID, param.getNotPrimaryCategoryId().toString()));
        }
        // 平台二级分类
        if(Objects.nonNull(param.getSecondaryCategoryId())){
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstant.SECONDARY_CATEGORY_ID, param.getSecondaryCategoryId()));
        }
        // 平台三级分类
        if(Objects.nonNull(param.getCategoryId())){
            boolQueryBuilder.filter(EsSearchUtil.nestedQuery(EsConstant.CATEGORY, EsConstant.CATEGORY_UNION_ID, param.getCategoryId()));
        }
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/util/EsSearchUtil.java
New file
@@ -0,0 +1,350 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.util;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.yami.shop.bean.app.vo.SkuVO;
import com.yami.shop.bean.bo.ProductBO;
import com.yami.shop.bean.enums.ProdStatusEnums;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.Json;
import com.yami.shop.search.common.constant.EsConstant;
import com.yami.shop.search.common.constant.EsIndexEnum;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.*;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.script.StoredScriptSource;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.TopHitsAggregationBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.*;
/**
 * @author YXF
 * @date 2021/07/29
 */
@Component
public class EsSearchUtil {
    private static final Logger log = LoggerFactory.getLogger(EsSearchUtil.class);
    private static RestHighLevelClient restHighLevelClient;
    /**
     * 正常来说普通方法才是属于实体对象(也就是New出来的对象)的,spring注入是在容器中实例化对象,静态变量是无法注入的
     * 所以需要通过构造方法的方式来进行注入,或者使用@PostConstruct注解
     */
    @Autowired
    public EsSearchUtil(RestHighLevelClient restHighLevelClient) {
        EsSearchUtil.restHighLevelClient = restHighLevelClient;
    }
    public static void save(Long id, ProductBO productBO) {
        if (Objects.isNull(productBO)) {
            return;
        }
        try {
            IndexRequest request = new IndexRequest(EsIndexEnum.PRODUCT.value())
                    .id(String.valueOf(id))
                    .source(Objects.requireNonNull(Json.toJsonString(productBO)), XContentType.JSON);
            IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
            log.info("保存商品:{}", indexResponse.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("保存es信息异常" + e);
        }
    }
    public static void saveBatch(Map<Long, ProductBO> map) {
        if (map.size() == 0) {
            return;
        }
        try {
            BulkRequest request = new BulkRequest();
            for (Long id : map.keySet()) {
                IndexRequest indexRequest = new IndexRequest(EsIndexEnum.PRODUCT.value());
                indexRequest.id(String.valueOf(id));
                indexRequest.source(Objects.requireNonNull(Json.toJsonString(map.get(id))), XContentType.JSON);
                request.add(indexRequest);
            }
            BulkResponse bulkResponse = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
            log.info("批量保存商品:{}", bulkResponse.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("批量保存es信息异常" + e);
        }
    }
    public static void update(ProductBO productBO) {
        delete(productBO.getProdId());
        save(productBO.getProdId(), productBO);
    }
    public static void partialUpdate(ProductBO productBO) {
        if (Objects.equals(productBO.getStatus(), ProdStatusEnums.DELETE.getValue())) {
            delete(productBO.getProdId());
            return;
        }
        if (Objects.isNull(productBO)) {
            return;
        }
        try {
            UpdateRequest request = new UpdateRequest(EsIndexEnum.PRODUCT.value(), String.valueOf(productBO.getProdId()))
                    .doc(Json.toJsonString(productBO), XContentType.JSON);
            UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
            log.info("更新商品:{}", updateResponse.toString());
        } catch (IOException e) {
            log.error("部分更新es信息异常信息:{},商品信息:{}", e, productBO);
            throw new YamiShopBindException("部分更新es信息异常" + e);
        }
    }
    public static void updateBatch(Map<Long, ProductBO> map) {
        deleteBatch(map.keySet());
        saveBatch(map);
    }
    public static void updateStockByPainless(Map<Long, Script> prodMap) {
        BulkRequest request = new BulkRequest();
        prodMap.forEach( (prodId, script) -> {
            UpdateRequest updateRequest = new UpdateRequest(EsIndexEnum.PRODUCT.value(), String.valueOf(prodId));
            updateRequest.script(script);
            request.add(updateRequest);
        });
        try {
            BulkResponse bulkResponse = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
            log.info("painless脚本更新商品" + Json.toJsonString(bulkResponse));
        } catch (IOException e) {
            log.error("更新es商品库存信息异常信息:{},商品信息:{}", e, prodMap);
            throw new YamiShopBindException("更新es商品库存信息异常信息" + e);
        }
    }
    public static void delete(Long id) {
        // 删除数据
        try {
            DeleteRequest request = new DeleteRequest(EsIndexEnum.PRODUCT.value(),String.valueOf(id));
            DeleteResponse deleteResponse = restHighLevelClient.delete(request,RequestOptions.DEFAULT);
            log.info("删除商品:{}", deleteResponse.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("删除es信息异常" + e);
        }
    }
    public static void deleteBatch(Collection<Long> ids) {
        DeleteByQueryRequest request = new DeleteByQueryRequest(EsIndexEnum.PRODUCT.value());
        request.setQuery(new TermsQueryBuilder("prodId", ids));
        try {
            log.info("构建的DSL删除语句 {}", request.toString());
            BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
            log.info("批量删除商品:{}", bulkByScrollResponse.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("批量删除es信息异常:" + e);
        }
    }
    /**
     * 分页检索
     */
    public static SearchResponse search(SearchRequest searchRequest) {
        SearchResponse response = null;
        try {
            //2、执行检索请求
            response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            log.debug("搜索返回结果:" + response.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("搜索服务出了点小差,请稍后再试" + e);
        }
        return response;
    }
    /**
     * 滚动检索
     */
    public static SearchResponse scroll(SearchScrollRequest searchScrollRequest) {
        SearchResponse response = null;
        try {
            //2、执行检索请求
            response = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
            log.debug("滚动搜索返回结果:" + response.toString());
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("滚动搜索服务出了点小差,请稍后再试" + e);
        }
        return response;
    }
    /**
     * 删除滚动
     */
    public static boolean clearScroll(String scrollId) {
        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(scrollId);
        ClearScrollResponse clearScrollResponse = null;
        try {
            //2、执行检索请求
            clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.toString());
            throw new YamiShopBindException("删除滚动服务出了点小差,请稍后再试" + e);
        }
        if (!clearScrollResponse.isSucceeded()) {
            log.error("删除滚动返回结果:" + clearScrollResponse);
            throw new YamiShopBindException("");
        }
        return clearScrollResponse.isSucceeded();
    }
    /**
     *
     */
    public static QueryBuilder termsQueryByArray(String name, String data) {
        List<String> array = StrUtil.split(data, Constant.COMMA);
        return QueryBuilders.termsQuery(name, array);
    }
    /**
     *
     */
    public static QueryBuilder nestedQuery(String nestedName, String name, Long data) {
        return nestedQuery(nestedName, name, data.toString());
    }
    /**
     *
     */
    public static QueryBuilder nestedQuery(String nestedName, String name, String data) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.should(QueryBuilders.termsQuery(name, data));
        return QueryBuilders.nestedQuery(nestedName, boolQuery, ScoreMode.None);
    }
    /**
     *
     */
    public static QueryBuilder nestedQuery(String nestedName, String name, List list) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.should(QueryBuilders.termsQuery(name, list));
        return QueryBuilders.nestedQuery(nestedName, boolQuery, ScoreMode.None);
    }
    /**
     *
     */
    public static QueryBuilder nestedQueryByArray(String nestedName, String name, String data) {
        String[] ids = data.split(Constant.COMMA);
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        for (String brandId : ids) {
            boolQuery.should(QueryBuilders.termsQuery(name, brandId));
        }
        return QueryBuilders.nestedQuery(nestedName,boolQuery, ScoreMode.None);
    }
    /**
     *
     */
    public static RangeQueryBuilder rangeQuery(String name, Long minValue, Long maxValue) {
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(name);
        if (Objects.nonNull(minValue)) {
            rangeQueryBuilder.gte(minValue);
        }
        if (Objects.nonNull(maxValue)) {
            rangeQueryBuilder.lte(maxValue);
        }
        return rangeQueryBuilder;
    }
    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name) {
        return nestedAggregation(nestedName, fieldName, name, null, null);
    }
    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name, String[] fetchSource) {
        return nestedAggregation(nestedName, fieldName, name, fetchSource, null);
    }
    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name, Integer size) {
        return nestedAggregation(nestedName, fieldName, name, null, size);
    }
    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name, String[] fetchSource, Integer size) {
        if (Objects.isNull(size)) {
            size = 1;
        }
        NestedAggregationBuilder nested = AggregationBuilders.nested(nestedName, nestedName);
        TermsAggregationBuilder terms = AggregationBuilders.terms(name).field(fieldName).size(10);
        TopHitsAggregationBuilder topHits = AggregationBuilders
                .topHits(EsConstant.TOP_HITS_DATA)
                .size(size);
        // 指定响应的数据字段
        if (ArrayUtil.isNotEmpty(fetchSource)) {
            topHits.fetchSource(fetchSource, null);
        }
        terms.subAggregation(topHits);
        nested.subAggregation(terms);
        return nested;
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/util/SearchResponseUtil.java
New file
@@ -0,0 +1,225 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.util;
import com.yami.shop.bean.vo.search.*;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.i18n.LanguageEnum;
import com.yami.shop.common.util.Json;
import com.yami.shop.search.common.constant.EsConstant;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.vo.EsPageVO;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.ParsedTopHits;
import org.springframework.stereotype.Component;
import java.util.*;
/**
 * @author YXF
 * @date 2021/07/29
 */
@Component
public class SearchResponseUtil {
    /**
     *
     */
    public static EsProductSearchVO getProductSearch(SearchResponse response) {
        // 1、返回的所有查询到的商品
        EsProductSearchVO esProductSearchVO = new EsProductSearchVO();
        //===============spu列表信息====================//
        esProductSearchVO.setProducts(buildSpuSearchList(response.getHits()));
        //===============聚合信息====================//
        Aggregations aggregations = response.getAggregations();
        if (Objects.nonNull(aggregations)) {
            processingAggregationsData(esProductSearchVO,aggregations);
        }
        return esProductSearchVO;
    }
    /**
     * 从es返回的数据中获取spu列表
     * @param hits es返回的数据
     * @return
     */
    public static List<ProductSearchVO> buildSpuSearchList(SearchHits hits) {
        String spuName = null;
        String sellingPoin = null;
        if (Objects.equals(I18nMessage.getLang(), LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
            spuName = EsConstant.PROD_NAME_ZH;
            sellingPoin = EsConstant.BRIEF_ZH;
        } else {
            spuName = EsConstant.PROD_NAME_EN;
            sellingPoin = EsConstant.BRIEF_EN;
        }
        List<ProductSearchVO> prodList = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            String json = hit.getSourceAsString();
            ProductSearchVO productSearchVO = Json.parseObject(json, ProductSearchVO.class);
            productSearchVO.setProdName(handleAggregationsLang(json, spuName, EsConstant.PROD_NAME_ZH));
            productSearchVO.setBrief(handleAggregationsLang(json, sellingPoin, EsConstant.BRIEF_ZH));
            prodList.add(productSearchVO);
        }
        return prodList;
    }
    /**
     * 处理聚合数据
     * @param aggregations
     */
    private static void processingAggregationsData(EsProductSearchVO esProductSearchVO, Aggregations aggregations) {
        String brandName = null;
        String categoryName = null;
        String attrName = null;
        String attrValueName = null;
        if (Objects.equals(I18nMessage.getLang(), LanguageEnum.LANGUAGE_ZH_CN.getLang())) {
            brandName = EsConstant.BRAND_NAME_ZH;
            categoryName = EsConstant.CATEGORY_NAME_ZH;
        } else {
            brandName = EsConstant.BRAND_NAME_EN;
            categoryName = EsConstant.CATEGORY_NAME_EN;
        }
        //===============品牌信息====================//
        ParsedNested brandNested = aggregations.get(EsConstant.BRAND);
        if (Objects.nonNull(brandNested)) {
            esProductSearchVO.setBrands(new ArrayList<>());
            List<String> brandJson = handleAggregations(brandNested, EsConstant.BRAND_ID);
            for (String json : brandJson) {
                BrandSearchVO brandSearchVO = Json.parseObject(json, BrandSearchVO.class);
                brandSearchVO.setBrandName(handleAggregationsLang(json, brandName, EsConstant.BRAND_NAME_ZH));
                esProductSearchVO.getBrands().add(brandSearchVO);
            }
        }
        //===============分类信息====================//
        loadCategoryAggregationsData(esProductSearchVO, aggregations, categoryName);
        //===============店铺信息====================//
        ParsedLongTerms shopTerms = aggregations.get(EsConstant.SHOP);
        if (Objects.nonNull(shopTerms)) {
            List<? extends Terms.Bucket> shopBuckets = shopTerms.getBuckets();
            for (Terms.Bucket bucket : shopBuckets) {
                esProductSearchVO.setShopInfo(new ShopSearchVO());
                esProductSearchVO.getShopInfo().setShopId(Long.valueOf(bucket.getKey().toString()));
            }
        }
    }
    private static void loadCategoryAggregationsData(EsProductSearchVO esProductSearchVO, Aggregations aggregations, String categoryName) {
        esProductSearchVO.setCategories(new ArrayList<>());
        String categoryId = null;
        ParsedNested categoriesNested = null;
        // 平台分类
        if (Objects.nonNull(aggregations.get(EsConstant.CATEGORY))) {
            categoryId = EsConstant.CATEGORY_ID;
            categoriesNested = aggregations.get(EsConstant.CATEGORY);
        }
        if (Objects.nonNull(categoriesNested)) {
            esProductSearchVO.setCategories(new ArrayList<>());
            List<String> categoryJson = handleAggregations(categoriesNested, categoryId);
            for (String json : categoryJson) {
                CategorySearchVO categorySearchVO = Json.parseObject(json, CategorySearchVO.class);
                categorySearchVO.setName(handleAggregationsLang(json, categoryName, EsConstant.CATEGORY_NAME_ZH));
                esProductSearchVO.getCategories().add(categorySearchVO);
            }
        }
    }
    /**
     * 处理聚合数据
     * @param brandNested
     * @return
     */
    public static List<String> handleAggregations(ParsedNested brandNested, String id) {
        List<String> dataList = new ArrayList<>();
        ParsedLongTerms brandLongTerms = brandNested.getAggregations().get(id);
        List<? extends Terms.Bucket> buckets = brandLongTerms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            ParsedTopHits parsedTopHits = bucket.getAggregations().get(EsConstant.TOP_HITS_DATA);
            for (SearchHit hit : parsedTopHits.getHits().getHits()) {
                String sourceAsString = hit.getSourceAsString();
                dataList.add(sourceAsString);
            }
        }
        return dataList;
    }
    /**
     * 从聚合数据中获取商品列表
     * @param response
     * @return
     */
    public static Map<Long, List<ProductSearchVO>> loadSpuMapByAggregations(SearchResponse response) {
        Aggregations aggregations = response.getAggregations();
        if (Objects.isNull(aggregations.getAsMap())) {
            return new HashMap<>(0);
        }
        ParsedLongTerms shopTerm = aggregations.get(EsConstant.PROD_LIST);
        Map<Long, List<ProductSearchVO>> prodMap = new HashMap<>(10);
        if (Objects.nonNull(shopTerm)) {
            List<? extends Terms.Bucket> buckets = shopTerm.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                Aggregations shopAgg = bucket.getAggregations();
                ParsedTopHits shopHits = shopAgg.get(EsConstant.TOP_HITS_DATA);
                prodMap.put(Long.valueOf(bucket.getKey().toString()), buildSpuSearchList(shopHits.getHits()));
            }
        }
        return prodMap;
    }
    /**
     * 构建分页数据
     * @param pageParam
     * @param esPageVO
     * @param response
     */
    public static void buildSearchPage(EsPageParam pageParam, EsPageVO<?> esPageVO, SearchResponse response) {
        //总记录数
        long total = response.getHits().getTotalHits().value;
        esPageVO.setTotal(total);
        // 总页码
        int totalPages = (int)total % pageParam.getSize() == 0 ?
                (int)total / pageParam.getSize() : ((int)total / pageParam.getSize() + 1);
        esPageVO.setPages(totalPages);
    }
    /**
     * 处理聚合国际化信息
     * @param json 数据
     * @param field 字段
     * @return 对应语言的字段
     */
    private static String handleAggregationsLang(String json, String field, String defaultField) {
        Map<String, Object> map = Json.parseObject(json, Map.class);
        Object object;
        // 找不到指定语言的数据,就查默认语言
        if (Objects.isNull(map.get(field))) {
            object = map.get(defaultField);
        }
        // 获取指定语言的数据
        else {
            object = map.get(field);
        }
        // 没有查到数据
        if (Objects.isNull(object)) {
            return null;
        }
        return object.toString();
    }
}
yami-shop-search/yami-shop-search-common/src/main/java/com/yami/shop/search/common/vo/EsPageVO.java
New file
@@ -0,0 +1,32 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.common.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
 * @author FrozenWatermelon
 * @date 2020/11/16
 */
@Data
public class EsPageVO<T> {
    @Schema(description = "总页数" )
    private Integer pages;
    @Schema(description = "总条目数" )
    private Long total;
    @Schema(description = "结果集" )
    private List<T> records;
}
yami-shop-search/yami-shop-search-multishop/pom.xml
New file
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop-search</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-search-multishop</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-search-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-security-multishop</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-search/yami-shop-search-multishop/src/main/java/com/yami/shop/search/multishop/config/SwaggerConfiguration.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.multishop.config;
import lombok.AllArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author Yami
 */
@Configuration("searchSwaggerConfiguration")
@AllArgsConstructor
public class SwaggerConfiguration {
    @Bean
    public GroupedOpenApi searchRestApi() {
        return GroupedOpenApi.builder()
                .group("搜索接口")
                .packagesToScan("com.yami.shop.search.multishop.controller")
                .pathsToMatch("/**")
                .build();
    }
}
yami-shop-search/yami-shop-search-multishop/src/main/java/com/yami/shop/search/multishop/controller/EsProductController.java
New file
@@ -0,0 +1,106 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.multishop.controller;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.common.constants.CacheNames;
import com.yami.shop.common.constants.EsCacheNames;
import com.yami.shop.common.enums.EsRenovationProductSortEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.search.common.constant.EsConstant;
import com.yami.shop.search.common.manager.ProductSearchManager;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.search.common.vo.EsPageVO;
import com.yami.shop.security.multishop.util.SecurityUtils;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.apache.commons.collections4.CollectionUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * @author lgh
 */
@RestController
@RequestMapping("/admin/search/prod")
@Tag(name = "商家端商品搜索接口")
public class EsProductController {
    @Autowired
    private SearchProductService searchProductService;
    @Autowired
    private ProductSearchManager productSearchManager;
    @GetMapping("/page")
    @Operation(summary = "商品信息列表" , description = "商品信息列表")
    public ServerResponseEntity<EsPageVO<ProductSearchVO>> page(@Valid EsPageParam pageParam, @ParameterObject EsProductParam productParam) {
        Long shopId = SecurityUtils.getShopUser().getShopId();
        productParam.setShopId(shopId);
        productParam.setFetchSource(EsConstant.SHOP_FETCH_SOURCE);
        EsPageVO<ProductSearchVO> searchPage = searchProductService.adminPage(pageParam, productParam);
        return ServerResponseEntity.success(searchPage);
    }
    @GetMapping("/renovationPage")
    @Operation(summary = "商品信息列表(装修商品列表)" , description = "商品信息列表(装修商品列表)")
    public ServerResponseEntity<EsPageVO<ProductSearchVO>> renovationPage(@Valid EsPageParam pageParam, @ParameterObject EsProductParam productParam) {
        productParam.setShopId(SecurityUtils.getShopUser().getShopId());
        Long size = null;
        if(Objects.equals(productParam.getShowSpuType(),1)){
            if(Objects.equals(productParam.getEsRenovationSpuSort(), EsRenovationProductSortEnum.CREATE_TIME_ASC.value()) || Objects.equals(productParam.getEsRenovationSpuSort(),EsRenovationProductSortEnum.CREATE_TIME_DESC.value())){
                productParam.setSort(productParam.getEsRenovationSpuSort());
            }else {
                // 获取指定规则的商品ids
                size = getSpuIds(pageParam, productParam);
            }
        }
        EsPageVO<ProductSearchVO> searchPage = searchProductService.renovationPage(pageParam, productParam, size);
        return ServerResponseEntity.success(searchPage);
    }
    @GetMapping("/prodExport")
    @Operation(summary = "商品导出" , description = "商品导出")
    @PreAuthorize("@pms.hasPermission('prod:prod:exportProd')")
    public void prodExport(HttpServletResponse response, @ParameterObject EsProductParam productParam) {
        productParam.setShopId(SecurityUtils.getShopUser().getShopId());
        searchProductService.export(response, productParam);
    }
    private Long getSpuIds(EsPageParam pageParam, EsProductParam productParam) {
        Long shopId = Objects.isNull(productParam.getShopId()) ? 0L :  productParam.getShopId();
        String key = EsCacheNames.RENOVATION_PRODUCT_IDS_CACHE + CacheNames.UNION + shopId +
                CacheNames.UNION_KEY + productParam.getEsTimeRange() + CacheNames.UNION_KEY + productParam.getEsRenovationSpuSort();
        if(!RedisUtil.hasKey(key)){
            productSearchManager.addRenovationSpuCache(key,productParam,shopId, EsCacheNames.RENOVATION_PRODUCT_IDS_CACHE_TIME);
        }
        Long size = RedisUtil.getListSize(key);
        long startNum = (long) (pageParam.getCurrent() - 1) * pageParam.getSize();
        long endNum = (long) pageParam.getCurrent() * pageParam.getSize() - 1;
        endNum = Math.min(endNum,size);
        List<Long> prodIds = RedisUtil.getListRange(key, startNum, endNum);
        productParam.setProdIds(prodIds);
        return size;
    }
}
yami-shop-search/yami-shop-search-platform/pom.xml
New file
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop-search</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-search-platform</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-search-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-security-platform</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-search/yami-shop-search-platform/src/main/java/com/yami/shop/search/platform/config/SwaggerConfiguration.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.platform.config;
import lombok.AllArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author Yami
 */
@Configuration("searchSwaggerConfiguration")
@AllArgsConstructor
public class SwaggerConfiguration {
    @Bean
    public GroupedOpenApi searchRestApi() {
        return GroupedOpenApi.builder()
                .group("搜索接口")
                .packagesToScan("com.yami.shop.search.platform.controller")
                .pathsToMatch("/**")
                .build();
    }
}
yami-shop-search/yami-shop-search-platform/src/main/java/com/yami/shop/search/platform/controller/EsProdctController.java
New file
@@ -0,0 +1,108 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.platform.controller;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.common.constants.CacheNames;
import com.yami.shop.common.constants.EsCacheNames;
import com.yami.shop.common.enums.EsRenovationProductSortEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.search.common.constant.EsConstant;
import com.yami.shop.search.common.manager.ProductSearchManager;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.search.common.vo.EsPageVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.apache.commons.collections4.CollectionUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
/**
 * @author lgh
 */
@RestController
@RequestMapping("/platform/search/prod")
@Tag(name = "平台端商品搜索接口")
public class EsProdctController {
    @Autowired
    private SearchProductService searchProductService;
    @Autowired
    private ProductSearchManager productSearchManager;
    @GetMapping("/page")
    @Operation(summary = "商品信息列表" , description = "商品信息列表")
    public ServerResponseEntity<EsPageVO<ProductSearchVO>> page(@Valid EsPageParam pageParam, @ParameterObject EsProductParam productParam) {
        productParam.setFetchSource(EsConstant.PLATFORM_FETCH_SOURCE);
        EsPageVO<ProductSearchVO> searchPage =  searchProductService.adminPage(pageParam, productParam);
        return ServerResponseEntity.success(searchPage);
    }
    @GetMapping("/scorePage")
    @Operation(summary = "积分商品信息列表" , description = "积分商品信息列表")
    public ServerResponseEntity<EsPageVO<ProductSearchVO>> scorePage(@Valid EsPageParam pageParam, @ParameterObject EsProductParam productParam) {
        productParam.setFetchSource(EsConstant.SHOP_FETCH_SOURCE);
        EsPageVO<ProductSearchVO> searchPage =  searchProductService.adminPage(pageParam, productParam);
        return ServerResponseEntity.success(searchPage);
    }
    @GetMapping("/renovationPage")
    @Operation(summary = "商品信息列表(装修商品列表)" , description = "商品信息列表(装修商品列表)")
    public ServerResponseEntity<EsPageVO<ProductSearchVO>> renovationPage(@Valid EsPageParam pageParam, @ParameterObject EsProductParam productParam) {
        Long size = null;
        if(Objects.equals(productParam.getShowSpuType(),1)){
            if(Objects.equals(productParam.getEsRenovationSpuSort(), EsRenovationProductSortEnum.CREATE_TIME_ASC.value()) || Objects.equals(productParam.getEsRenovationSpuSort(),EsRenovationProductSortEnum.CREATE_TIME_DESC.value())){
                productParam.setSort(productParam.getEsRenovationSpuSort());
            }else {
                // 获取指定规则的商品ids并返回大小
                size = getSpuIds(pageParam, productParam);
            }
        }
        EsPageVO<ProductSearchVO> searchPage = searchProductService.renovationPage(pageParam, productParam,size);
        return ServerResponseEntity.success(searchPage);
    }
    @GetMapping("/prodExport")
    @Operation(summary = "商品导出" , description = "商品导出")
    @PreAuthorize("@pms.hasPermission('prod:prod:exportProd')")
    public void prodExport(HttpServletResponse response, @ParameterObject EsProductParam productParam) {
        productParam.setShopId(null);
        searchProductService.export(response, productParam);
    }
    private Long getSpuIds(EsPageParam pageParam, EsProductParam productParam) {
        Long shopId = Objects.isNull(productParam.getShopId()) ? 0L :  productParam.getShopId();
        String key = EsCacheNames.RENOVATION_PRODUCT_IDS_CACHE + CacheNames.UNION + shopId +
                CacheNames.UNION_KEY + productParam.getEsTimeRange() + CacheNames.UNION_KEY + productParam.getEsRenovationSpuSort();
        if(!RedisUtil.hasKey(key)){
            productSearchManager.addRenovationSpuCache(key,productParam,shopId, EsCacheNames.RENOVATION_PRODUCT_IDS_CACHE_TIME);
        }
        Long size = RedisUtil.getListSize(key);
        long startNum = (long) (pageParam.getCurrent() - 1) * pageParam.getSize();
        long endNum = (long) pageParam.getCurrent() * pageParam.getSize() - 1;
        endNum = Math.min(endNum,size);
        List prodIds = RedisUtil.getListRange(key, startNum, endNum);
        productParam.setProdIds(prodIds);
        return size;
    }
}
yami-shop-search/yami-shop-search-platform/src/main/java/com/yami/shop/search/platform/task/SearchTask.java
New file
@@ -0,0 +1,259 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.search.platform.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.EsProductParam;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.dao.ProductMapper;
import com.yami.shop.search.common.constant.EsConstant;
import com.yami.shop.search.common.constant.EsIndexEnum;
import com.yami.shop.search.common.param.EsPageParam;
import com.yami.shop.search.common.service.EsProductService;
import com.yami.shop.search.common.service.SearchProductService;
import com.yami.shop.search.common.util.EsSearchUtil;
import com.yami.shop.search.common.vo.EsPageVO;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 商品定时任务
 *
 * @author FrozenWatermelon
 */
@Component
@Slf4j
public class SearchTask {
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private EsProductService esProductService;
    @Autowired
    private SearchProductService searchProductService;
    /**
     * 校验商品数量是否完整
     * 商品数据是否为最新的:根据商品更新时间判断
     */
    @XxlJob("verifySpuData")
    public void verifySpuData(){
        Date currentTime = new Date();
        // 数量相同,不需要更新
        if (verifySpuNum() == 0) {
            return;
        }
        //先更新近3个小时的数据
        DateTime beginTime = DateUtil.beginOfHour(DateUtil.offsetHour(currentTime, -3));
        updateDbDate(beginTime);
        //更新后校验
        if (verifySpuNum() == 0) {
            return;
        }
        // 更新数据库中的所有商品数据, 保证数据库的数据都更新到es
        updateDbDate(null);
        //如果es中的商品数量大于mysql中的商品数量-es有部分已失效的商品数据需要删除
        if (verifySpuNum() < 0) {
            // 滚动查询es中的数据,删除已失效的
            updateEsDateByScroll();
        }
    }
    private void updateDbDate(Date beginTime){
        PageParam page = new PageParam();
        page.setCurrent(1);
        page.setSize(Constant.MAX_PAGE_SIZE);
        List<Long> updateList = new ArrayList<>();
        boolean update = true;
        while (update) {
            PageParam<Product> prodPage = productMapper.selectPage(page, new LambdaQueryWrapper<Product>()
//                    .ne(Product::getStatus, -1)
                    .gt(Objects.nonNull(beginTime), Product::getUpdateTime, beginTime)
            );
            if (Objects.isNull(prodPage) || Objects.isNull(prodPage.getTotal()) || prodPage.getTotal() <= page.getCurrent()){
                update = false;
            }
            List<Product> records = prodPage.getRecords();
            if (CollUtil.isEmpty(records)) {
                break;
            }
            List<Long> spuIds = new ArrayList<>();
            for (Product product : records) {
                spuIds.add(product.getProdId());
            }
            List<ProductSearchVO> productSearchVOList = searchProductService.simpleList(spuIds);
            Map<Long, Long> prodMap = productSearchVOList.stream()
                    .filter(productSearch -> Objects.nonNull(productSearch.getUpdateTime()))
                    .collect(Collectors.toMap(ProductSearchVO::getProdId, ProductSearchVO::getUpdateTime)
            );
            for (Product product : records) {
                boolean isSame = prodMap.containsKey(product.getProdId()) && Objects.equals(product.getUpdateTime().getTime(), prodMap.get(product.getProdId()));
                if (isSame) {
                    continue;
                }
                updateList.add(product.getProdId());
            }
            if (updateList.size() > Constant.MAX_DATA_HANDLE_NUM) {
                esProductService.updateBatch(updateList);
                updateList.clear();
            }
            page.setCurrent(page.getCurrent() + 1);
        }
        if (CollUtil.isNotEmpty(updateList)) {
            esProductService.updateBatch(updateList);
        }
    }
    private void updateEsDateByScroll() {
        // 设置超时时间
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(5L));
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 指定返回数组中的字段
        searchSourceBuilder.fetchSource(new String[]{EsConstant.PROD_ID}, null);
        // 定时任务使用的所以不会有任何条件查询,只是要查出会不会存在其他环境的商品将其删除
        // 设置最多一次能够取出1000笔数据,从第1001笔数据开始,将开启滚动查询
        // PS:滚动查询也属于这一次查询,只不过因为一次查不完,分多次查
        searchSourceBuilder.size(100);
        log.debug("构建的DSL语句 {}", searchSourceBuilder);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsIndexEnum.PRODUCT.value()}, searchSourceBuilder);
        // 将滚动放入
        searchRequest.scroll(scroll);
        // 进行第一次滚动查询
        SearchResponse searchResponse = EsSearchUtil.search(searchRequest);
        // 将商品id写入list
        List<Long> prodIds = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            prodIds.add(Long.parseLong(JSON.parseObject(hit.getSourceAsString()).get("prodId").toString()));
        }
        // 删除不存在商品
        checkAndDelete(prodIds);
        SearchHits hits = searchResponse.getHits();
        // 记录要滚动的ID
        String scrollId = searchResponse.getScrollId();
        // 滚动查询从第1001笔数据开始取
        SearchHit[] hitsScroll = hits.getHits();
        while (hitsScroll != null && hitsScroll.length > 0) {
            //构造滚动查询条件
            SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
            searchScrollRequest.scroll(scroll);
            searchResponse = EsSearchUtil.scroll(searchScrollRequest);
            scrollId = searchResponse.getScrollId();
            hits = searchResponse.getHits();
            hitsScroll = hits.getHits();
            prodIds.clear();
            // 写入id
            for (SearchHit hit : searchResponse.getHits().getHits()) {
                prodIds.add(Long.parseLong(JSON.parseObject(hit.getSourceAsString()).get("prodId").toString()));
            }
            checkAndDelete(prodIds);
        }
        //清除滚动,否则影响下次查询
        EsSearchUtil.clearScroll(scrollId);
    }
    private void checkAndDelete(List<Long> prodIds) {
        if (CollUtil.isEmpty(prodIds)) {
            return;
        }
        List<Long> esProdIds = productMapper.verifySpuExist(prodIds);
        prodIds.removeAll(esProdIds);
        List<Long> deleteList = new ArrayList<>(prodIds);
        esProductService.deleteBatch(deleteList);
        deleteList.clear();
    }
    private void updateEsDate(){
        EsPageParam page = new EsPageParam();
        page.setCurrent(1);
        page.setSize(Constant.MAX_PAGE_SIZE);
        List<Long> deleteList = new ArrayList<>();
        boolean update = true;
        EsProductParam esProductParam = new EsProductParam();
        esProductParam.setFetchSource(new String[]{EsConstant.PROD_ID});
        while (update) {
            EsPageVO<ProductSearchVO> productPage = searchProductService.adminPage(page, esProductParam);
            if (Objects.isNull(productPage) || Objects.isNull(productPage.getPages()) || productPage.getPages() <= page.getCurrent()){
                update = false;
            }
            List<ProductSearchVO> records = productPage.getRecords();
            if (CollUtil.isEmpty(records)) {
                break;
            }
            List<Long> prodIds = records.stream().map(ProductSearchVO::getProdId).collect(Collectors.toList());
            //List<Long> esProdIds = productMapper.verifySpuHasDelete(prodIds);
            List<Long> esProdIds = productMapper.verifySpuExist(prodIds);
            prodIds.removeAll(esProdIds);
            deleteList.addAll(prodIds);
            if (deleteList.size() > Constant.MAX_DATA_HANDLE_NUM) {
                esProductService.deleteBatch(deleteList);
                deleteList.clear();
            }
            page.setCurrent(page.getCurrent() + 1);
        }
        if (CollUtil.isNotEmpty(deleteList)) {
            esProductService.deleteBatch(deleteList);
        }
    }
    /**
     * 校验mysql、es中的商品数据是否一致
     * @return 返回值大于0:数据库有部分数据没同步到es中, 返回值等于0:数据库和es中的商品数据一致, 返回值小于0:es有部分失效的数据未删除
     */
    private Long verifySpuNum() {
        // 查找删除的商品一起存进去
        Integer spuNum = productMapper.selectCount(new LambdaQueryWrapper<>());
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // es默认输出最多一万条,设置trackTotalHits为true,取消限制
        searchSourceBuilder.trackTotalHits(true);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsIndexEnum.PRODUCT.value()}, searchSourceBuilder);
        SearchResponse searchResponse = EsSearchUtil.search(searchRequest);
        Long total = searchResponse.getHits().getTotalHits().value;
        // 数据库商品数量 - es商品数量
        return Long.valueOf(spuNum) - total;
    }
}
yami-shop-seckill/pom.xml
New file
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-seckill</artifactId>
    <description>商城满减模块</description>
    <packaging>pom</packaging>
    <modules>
        <module>yami-shop-seckill-api</module>
        <module>yami-shop-seckill-multishop</module>
        <module>yami-shop-seckill-platform</module>
        <module>yami-shop-seckill-common</module>
    </modules>
</project>
yami-shop-seckill/yami-shop-seckill-api/pom.xml
New file
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop-seckill</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-seckill-api</artifactId>
    <description>商城满减模块接口部分</description>
    <dependencies>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-seckill-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-security-api</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-delivery-api</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/config/SwaggerConfiguration.java
New file
@@ -0,0 +1,36 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.config;
import lombok.AllArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 秒杀的swagger文档
 * @author LGH
 */
@Configuration("seckillSwaggerConfiguration")
@AllArgsConstructor
public class SwaggerConfiguration {
    @Bean
    public GroupedOpenApi seckillRestApi() {
        return GroupedOpenApi.builder()
                .group("秒杀接口")
                .packagesToScan("com.yami.shop.seckill.api.controller")
                .pathsToMatch("/**")
                .build();
    }
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/controller/SeckillOrderController.java
New file
@@ -0,0 +1,299 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.yami.shop.bean.app.dto.*;
import com.yami.shop.bean.app.param.SubmitSeckillOrderParam;
import com.yami.shop.bean.enums.DeliveryType;
import com.yami.shop.bean.enums.OrderType;
import com.yami.shop.bean.enums.ShopStatus;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.model.ShopDetail;
import com.yami.shop.bean.vo.UserDeliveryInfoVO;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.delivery.api.manager.DeliveryOrderManager;
import com.yami.shop.manager.SubmitOrderManager;
import com.yami.shop.manager.impl.ConfirmOrderManager;
import com.yami.shop.manager.impl.ShopCartAdapter;
import com.yami.shop.manager.impl.ShopCartItemAdapter;
import com.yami.shop.seckill.api.param.SeckillOrderParam;
import com.yami.shop.seckill.api.service.SeckillCacheManager;
import com.yami.shop.seckill.common.enums.SeckillEnum;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.model.SeckillSku;
import com.yami.shop.seckill.common.service.SeckillOrderService;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import com.yami.shop.security.api.util.SecurityUtils;
import com.yami.shop.service.OrderService;
import com.yami.shop.service.ProductService;
import com.yami.shop.service.ShopCustomerService;
import com.yami.shop.service.ShopDetailService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
/**
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@RestController
@AllArgsConstructor
@RequestMapping("/p/seckill")
@Tag(name = "秒杀订单接口")
public class SeckillOrderController {
    private final SeckillService seckillService;
    private final SeckillSkuService seckillSkuService;
    private final SeckillOrderService seckillOrderService;
    private final SeckillCacheManager seckillCacheManager;
    private final Snowflake snowflake;
    private final OrderService orderService;
    private final ShopCartAdapter shopCartAdapter;
    private final ShopCartItemAdapter shopCartItemAdapter;
    private final ConfirmOrderManager confirmOrderManager;
    private final DeliveryOrderManager deliveryOrderManager;
    private final SubmitOrderManager submitOrderManager;
    private final ProductService productService;
    private final ShopDetailService shopDetailService;
    private final ShopCustomerService shopCustomerService;
    private static final Executor EXECUTOR = new ThreadPoolExecutor(
            5,
            20,
            5L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1000),
            new ThreadFactoryBuilder()
                    .setNameFormat("seckill-redis-listener-pool-%d").build());
    @Operation(summary = "获取秒杀订单路径")
    @GetMapping(value = "/orderPath")
    @Parameter(name = "prodId", description = "秒杀商品id" , required = true)
    public ServerResponseEntity<String> getOrderPath(Long prodId) {
        String userId = SecurityUtils.getUser().getUserId();
        Seckill seckill = seckillService.getByProdId(prodId);
        if (seckill == null || !Objects.equals(seckill.getStatus(), SeckillEnum.NORMAL.getValue()) || seckill.getEndTime().getTime() < System.currentTimeMillis()) {
            // 秒杀活动已结束
            throw new YamiShopBindException("yami.seckill.has.ended");
        }
        if (seckill.getStartTime().getTime() > System.currentTimeMillis()) {
            // 秒杀活动未开始
            throw new YamiShopBindException("yami.seckill.not.start");
        }
        String orderPath = seckillCacheManager.createOrderPath(userId + prodId);
        return ServerResponseEntity.success(orderPath);
    }
    @Operation(summary = "确认订单")
    @PostMapping(value = "/{orderPath}/confirm")
    @Parameter(name = "orderPath", description = "订单路径" , required = true)
    public ServerResponseEntity<ShopCartOrderMergerDto> confirm(@PathVariable("orderPath") String orderPath, @Valid @RequestBody SeckillOrderParam seckillOrderParam) {
        String userId = SecurityUtils.getUser().getUserId();
        Long prodId = seckillOrderParam.getOrderItem().getProdId();
        seckillCacheManager.checkOrderPath(userId + prodId, orderPath);
        // 秒杀sku(来自缓存),注:千万不要用这里的库存校验
        SeckillSku seckillSku = seckillSkuService.getSeckillSkuById(seckillOrderParam.getSeckillSkuId());
        if (seckillSku == null || !Objects.equals(seckillOrderParam.getOrderItem().getSkuId(), seckillSku.getSkuId())) {
            // 购物车包含无法识别的商品
            throw new YamiShopBindException("yami.shop.cart.prod.unrecognized");
        }
        List<ShopCartItemDto> shopCartItemsDb = shopCartItemAdapter.getShopCartItem(seckillOrderParam.getOrderItem(), seckillSku.getSeckillPrice(), userId, seckillOrderParam.getAddrId());
        // 将要返回给前端的完整的订单信息
        ShopCartOrderMergerDto shopCartOrderMerger = new ShopCartOrderMergerDto();
        shopCartOrderMerger.setDvyType(seckillOrderParam.getDvyType());
        shopCartOrderMerger.setOrderType(OrderType.SECKILL);
        shopCartOrderMerger.setSeckillId(seckillSku.getSeckillId());
        shopCartOrderMerger.setSeckillSkuId(seckillSku.getSeckillSkuId());
        // 筛选过滤掉不同配送的商品
        List<ShopCartItemDto> shopCartItems = confirmOrderManager.filterShopItemsByType(shopCartOrderMerger, shopCartItemsDb);
        // 该商品不满足任何的配送方式
        if (CollectionUtil.isEmpty(shopCartItems)) {
            return ServerResponseEntity.fail(ResponseEnum.ORDER_DELIVERY_NOT_SUPPORTED, shopCartOrderMerger);
        }
        ShopCartItemDto firstShopCartItem = shopCartItems.get(0);
        // 商品类别 0.实物商品 1. 虚拟商品
        int mold = 0;
        if (shopCartItems.stream().filter(shopCartItemDto -> shopCartItemDto.getMold() == 1).count() == shopCartItems.size()) {
            // 订单项中的所有商品都为虚拟商品时,才是虚拟订单
            mold = 1;
        }
        shopCartOrderMerger.setMold(mold);
        // 是否为预售订单
        seckillOrderParam.setPreSellStatus(firstShopCartItem.getPreSellStatus());
        shopCartOrderMerger.setPreSellStatus(firstShopCartItem.getPreSellStatus());
        // 购物车
        List<ShopCartDto> shopCarts = shopCartAdapter.getShopCarts(shopCartItems);
        // 计算运费,获取用户地址,自提信息
        UserDeliveryInfoVO userDeliveryInfo = new UserDeliveryInfoVO();
        if (Objects.equals(mold, 0)) {
            userDeliveryInfo = deliveryOrderManager.calculateAndGetDeliverInfo(userId, seckillOrderParam.getAddrId(), seckillOrderParam.getDvyType(), shopCartItems);
        }
        // 当算完一遍店铺的各种满减活动时,重算一遍订单金额
        confirmOrderManager.recalculateAmountWhenFinishingCalculateShop(shopCartOrderMerger, shopCarts, userDeliveryInfo);
        // 结束平台优惠的计算之后,还要重算一遍金额
        confirmOrderManager.recalculateAmountWhenFinishingCalculatePlatform(shopCartOrderMerger);
        // 计算平台佣金
        confirmOrderManager.calculatePlatformCommission(shopCartOrderMerger);
        // 缓存计算
        confirmOrderManager.cacheCalculatedInfo(userId, shopCartOrderMerger);
        return ServerResponseEntity.success(shopCartOrderMerger);
    }
    /**
     * 购物车/立即购买  提交订单,根据店铺拆单
     */
    @PostMapping("/{orderPath}/submit")
    @Operation(summary = "提交订单" , description = "提交之后返回WebSocket路径,请求对应的WebSocket路径,开始等待响应")
    @Parameter(name = "orderPath", description = "订单路径" , required = true)
    public ServerResponseEntity<OrderNumbersDto> submitOrders(@PathVariable("orderPath") String orderPath,
                                                        @Valid @RequestBody SubmitSeckillOrderParam seckillOrderParam) {
        String userId = SecurityUtils.getUser().getUserId();
        ServerResponseEntity<ShopCartOrderMergerDto> dtoEntity = submitOrderManager.checkSubmitInfo(seckillOrderParam, userId);
        if (!dtoEntity.isSuccess()) {
            return ServerResponseEntity.transform(dtoEntity);
        }
        ShopCartOrderMergerDto dto = dtoEntity.getData();
        if (dto.getUserAddr() == null && !Objects.equals(dto.getMold(),1) && Objects.equals(dto.getDvyType(), DeliveryType.EXPRESS.getValue())) {
            // 请填写收货地址
            throw new YamiShopBindException("yami.delivery.address");
        }
        ShopCartOrderDto shopCartOrderDto = dto.getShopCartOrders().get(0);
        ShopCartItemDto shopCartItemDto = shopCartOrderDto.getShopCartItemDiscounts().get(0).getShopCartItems().get(0);
        seckillCacheManager.checkOrderPath(userId + shopCartItemDto.getProdId(), orderPath);
        // 秒杀活动信息(来自缓存)
        Seckill seckill = seckillService.getSeckillById(dto.getSeckillId());
        // 判断秒杀是否已经下线
        if (Objects.equals(seckill.getStatus(), SeckillEnum.INVALID.getValue())
                || DateUtil.compare(seckill.getEndTime(), new Date()) < 0
                || Objects.equals(seckill.getStatus(), SeckillEnum.OFFLINE.getValue())) {
            // 秒杀活动已结束
            throw new YamiShopBindException("yami.seckill.has.ended");
        }
        if (DateUtil.compare(seckill.getStartTime(), new Date()) > 0) {
            // 秒杀活动未开始
            throw new YamiShopBindException("yami.seckill.not.start");
        }
        // 判断之前秒杀的商品有没有超过限制,-1表示商品不限制秒杀数量
        seckillOrderService.checkOrderProdNum(seckill, userId, shopCartItemDto.getProdCount());
        // 这里一进来就减库存,但是为了防止少卖,120秒会自己更新库存~因为缓存只有60秒
        seckillCacheManager.decrSeckillSkuStocks(dto.getSeckillSkuId(), dto.getSeckillId(), shopCartItemDto.getProdCount(),shopCartItemDto.getProdId());
//        shopCartOrderDto.setMsgId(orderPath);
        dto.setVirtualRemarkList(seckillOrderParam.getVirtualRemarkList());
        dto.setUserId(userId);
        String orderNumber = String.valueOf(snowflake.nextId());
        shopCartOrderDto.setOrderNumber(orderNumber);
//        // 下单
//        SendStatus sendStatus = seckillOrderSubmitTemplate.syncSend(RocketMqConstant.SECKILL_ORDER_SUBMIT_TOPIC, new GenericMessage<>(dto)).getSendStatus();
//        if (!Objects.equals(sendStatus,SendStatus.SEND_OK)) {
//            // 服务器繁忙,请稍后再试
//            throw new YamiShopBindException("yami.network.busy");
//        }
        try {
            EXECUTOR.execute(() -> {
                seckillOrderService.submit(dto);
            });
        } catch (RejectedExecutionException e) {
            // 服务器繁忙,请稍后再试
            throw new YamiShopBindException("yami.network.busy");
        }
        seckillOrderService.removeConfirmOrderCache(userId);
        // 店铺客户创建
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setShopId(dto.getShopCartOrders().get(0).getShopId());
        shopCustomerService.saveCustomerByOrders(Collections.singletonList(order));
        return ServerResponseEntity.success(new OrderNumbersDto(orderNumber));
    }
    @GetMapping("/getSeckillIdByOrderNumber")
    @Operation(summary = "根据订单编号获取秒杀id")
    @Parameter(name = "orderNumber", description = "订单编号" , required = true)
    public ServerResponseEntity<Long> getSeckillIdByOrderNumber(@RequestParam("orderNumber") String orderNumber) {
        SeckillOrder seckillOrder = seckillOrderService.getOne(new LambdaQueryWrapper<SeckillOrder>().eq(SeckillOrder::getOrderNumber, orderNumber));
        if (Objects.isNull(seckillOrder)) {
            throw new YamiShopBindException("yami.data.deleted.or.not.exist");
        }
        return ServerResponseEntity.success(seckillOrder.getSeckillId());
    }
    @GetMapping("/createOrderStatus")
    @Operation(summary = "根据orderNumber获取订单是否创建成功" , description = "订单数量")
    @Parameter(name = "orderNumber", description = "订单流水号" , required = true)
    public ServerResponseEntity<Integer> getCreateOrderStatus(@RequestParam("orderNumber") String orderNumber) {
        return ServerResponseEntity.success(orderService.countByOrderNumber(orderNumber));
    }
    @GetMapping("/prod")
    @Operation(summary = "获取秒杀商品信息" , description = "根据商品id,获取当前秒杀的商品活动状态 0下线 1活动中")
    @Parameter(name = "prodId", description = "商品id" , required = true)
    public ServerResponseEntity<Integer> getProdActivityStatus(@RequestParam("prodId") Long prodId) {
        // 商品信息 缓存
        Product product = productService.getProductByProdId(prodId, I18nMessage.getDbLang());
        if (product == null || !Objects.equals(product.getStatus(), 1)) {
            return ServerResponseEntity.success(0);
        }
        // 秒杀活动信息 缓存
        Seckill seckill = seckillService.getSeckillByProdId(prodId);
        // 秒杀已经下线
        if (seckill == null || Objects.equals(seckill.getStatus(), 0)) {
            return ServerResponseEntity.success(0);
        }
        // 判断是否删除
        if(Objects.equals(seckill.getIsDelete(), 1)){
            return ServerResponseEntity.success(0);
        }
        // 秒杀结束时间
        if (seckill.getEndTime().getTime() < System.currentTimeMillis()) {
            return ServerResponseEntity.success(0);
        }
        // 店铺详情 缓存
        ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(product.getShopId());
        if (!Objects.equals(shopDetail.getShopStatus(), ShopStatus.OPEN.value())) {
            // 店铺不处于营业状态
            return ServerResponseEntity.success(0);
        }
        return ServerResponseEntity.success(1);
    }
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/listener/CheckSecKillOrderListener.java
New file
@@ -0,0 +1,53 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.listener;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.event.CheckSecKillOrderEvent;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.service.SeckillOrderService;
import com.yami.shop.seckill.common.service.SeckillService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * 支付时校验订单过期事件
 * @author lhd
 */
@Component("checkSecKillOrderListener")
@AllArgsConstructor
public class CheckSecKillOrderListener {
    private final SeckillOrderService seckillOrderService;
    private final SeckillService seckillService;
    @EventListener(CheckSecKillOrderEvent.class)
    public void checkSecKillOrderHandle(CheckSecKillOrderEvent event) {
        String orderNumber = event.getOrder().getOrderNumber();
        SeckillOrder seckillOrder = seckillOrderService.getOne(new LambdaQueryWrapper<SeckillOrder>().eq(SeckillOrder::getOrderNumber, orderNumber));
        Seckill seckill = seckillService.getSeckillById(seckillOrder.getSeckillId());
        // 订单创建时间经过秒杀限定分钟,如果为true则早于当前时间表示过期,抛出异常
//        if(!DateUtil.isExpired(seckillOrder.getCreateTime(), DateField.MINUTE, seckill.getMaxCancelTime(), new Date())){
         boolean isExpired = !DateUtil.offsetMinute(seckillOrder.getCreateTime(),seckill.getMaxCancelTime()).after(new Date());
        if(isExpired){
            // 秒杀订单已经过期,无法进行支付
            throw new YamiShopBindException("yami.order.expired");
        }
    }
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/listener/LoadProdActivistListener.java
New file
@@ -0,0 +1,97 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.listener;
import com.yami.shop.bean.app.dto.SeckillSkuDto;
import com.yami.shop.bean.app.dto.SkuDto;
import com.yami.shop.bean.app.vo.ProductVO;
import com.yami.shop.bean.app.vo.SeckillVO;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.LoadProdActivistEvent;
import com.yami.shop.bean.order.LoadProdActivistOrder;
import com.yami.shop.common.enums.StatusEnum;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillSku;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import lombok.AllArgsConstructor;
import ma.glasnost.orika.MapperFacade;
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;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 加载商品秒杀信息
 * @author yami
 */
@Component("seckillProdActivistListener")
@AllArgsConstructor
public class LoadProdActivistListener {
    private SeckillSkuService seckillSkuService;
    private SeckillService seckillService;
    private MapperFacade mapperFacade;
    @EventListener(LoadProdActivistEvent.class)
    @Order(LoadProdActivistOrder.DEFAULT)
    public void loadProdSeckillHandle(LoadProdActivistEvent event) {
        // 不是秒杀商品,不用处理
        if (!Objects.equals(event.getProdType(), ProdType.PROD_TYPE_SECKILL.value())) {
            return;
        }
        ProductVO productVO = event.getProductVO();
        long currentTimeMillis = System.currentTimeMillis();
        // 秒杀活动信息(来自缓存)
        Seckill seckill = seckillService.getSeckillByProdId(event.getProdId());
        // 无法获取秒杀信息、秒杀没有启用、当前时间大于活动结束时间, 则不添加活动信息
        boolean unActivity = Objects.isNull(seckill) || !Objects.equals(seckill.getStatus(), StatusEnum.ENABLE.value()) || currentTimeMillis > seckill.getEndTime().getTime();
        if (unActivity) {
            return;
        }
        // 秒杀活动sku信息(来自缓存)
        List<SeckillSku> seckillSkus = seckillSkuService.listSeckillSkuBySeckillId(seckill.getSeckillId());
        // 获取秒杀sku列表
        Map<Long, SkuDto> skuMap = productVO.getSkuList().stream().collect(Collectors.toMap(SkuDto::getSkuId, s -> s));
        List<SeckillSkuDto> seckillSkuDtos = new ArrayList<>();
        for (SeckillSku seckillSku : seckillSkus) {
            SeckillSkuDto seckillSkuDto = new SeckillSkuDto();
            if (!skuMap.containsKey(seckillSku.getSkuId())) {
                continue;
            }
            SkuDto sku = skuMap.get(seckillSku.getSkuId());
            seckillSkuDto.setPic(sku.getPic());
            seckillSkuDto.setProperties(sku.getProperties());
            seckillSkuDto.setPrice(sku.getPrice());
            seckillSkuDto.setSeckillPrice(seckillSku.getSeckillPrice());
            seckillSkuDto.setSeckillStocks(seckillSku.getSeckillStocks());
            seckillSkuDto.setSkuId(sku.getSkuId());
            seckillSkuDto.setSeckillSkuId(seckillSku.getSeckillSkuId());
            seckillSkuDtos.add(seckillSkuDto);
        }
        SeckillVO seckillVO = mapperFacade.map(seckill, SeckillVO.class);
        // 插入秒杀sku列表信息
        seckillVO.setSeckillSkuList(seckillSkuDtos);
        // 插入秒杀信息
        productVO.setSeckillVO(seckillVO);
    }
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/param/ConfirmParam.java
New file
@@ -0,0 +1,40 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
 * @author LGH
 */
@Data
@Schema(description = "秒杀确认订单参数")
public class ConfirmParam {
    @NotNull(message = "产品数量不能为空")
    @Min(value = 1,message = "产品数量不能为空")
    @Schema(description = "产品数量" ,required=true)
    private Integer prodCount;
    @NotNull(message = "用户地址id")
    @Schema(description = "用户地址id" ,required=true)
    private Long addrId;
    @NotNull(message = "秒杀商品skuId")
    @Schema(description = "秒杀商品skuId" ,required=true)
    private Long seckillSkuId;
    @Schema(description = "推广员使用的推销卡号" )
    private String distributionCardNo;
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/param/SeckillOrderParam.java
New file
@@ -0,0 +1,32 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.param;
import com.yami.shop.bean.app.param.OrderParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
/**
 * 秒杀下单需要的参数
 *
 * @author Yami
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(description = "秒杀下单参数")
public class SeckillOrderParam extends OrderParam {
    @NotNull(message = "秒杀商品skuId不能为空")
    @Schema(description = "秒杀商品skuId" , required = true)
    private Long seckillSkuId;
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/service/SeckillCacheManager.java
New file
@@ -0,0 +1,41 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.service;
/**
 * 秒杀缓存管理
 * @author LGH
 */
public interface SeckillCacheManager {
    /**
     * 创建订单路径
     * @param userId
     * @return
     */
    String createOrderPath(String userId);
    /**
     * 校验订单路径
     * @param userId
     * @param orderPath
     * @return
     */
    void checkOrderPath(String userId, String orderPath);
    /**
     * 减少秒杀sku库存
     * @param seckillSkuId
     * @param seckillId
     * @param prodCount
     * @param prodId
     */
    void decrSeckillSkuStocks(Long seckillSkuId, Long seckillId, Integer prodCount, Long prodId);
}
yami-shop-seckill/yami-shop-seckill-api/src/main/java/com/yami/shop/seckill/api/service/impl/SeckillCacheManagerImpl.java
New file
@@ -0,0 +1,154 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.api.service.impl;
import cn.hutool.core.lang.Snowflake;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.IdUtil;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.seckill.api.service.SeckillCacheManager;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
 * @author LGH
 */
@Slf4j
@Service
@AllArgsConstructor
public class SeckillCacheManagerImpl implements SeckillCacheManager {
    private final RedissonClient redissonClient;
    private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";
    /**
     * 秒杀限流前缀
     */
    private final static String SECKILL_LIMIT_PREFIX = "SECKILL_LIMIT_";
    /**
     * 秒杀路径前缀
     */
    private final static String SECKILL_PATH_PREFIX = "SECKILL_PATH_";
    private final static String SECKILL_SKU_STOCKS_PREFIX = "SECKILL_SKU_STOCKS_";
    private final Snowflake snowflake;
    private final SeckillSkuService seckillSkuService;
    private final SeckillService seckillService;
    @Override
    public String createOrderPath(String userId) {
        String limitKey = SECKILL_LIMIT_PREFIX + userId;
        int maxCount = 5;
        // 秒杀次数+1
        long seckillNum = RedisUtil.incr(limitKey, 1);
        // 5秒只能提交5次请求
        if (seckillNum == 1) {
            // 5秒后失效
            RedisUtil.expire(limitKey, 5);
        }
        if (seckillNum >= maxCount) {
            // 请求过频繁,请稍后再试
            throw new YamiShopBindException("yami.request.too.much");
        }
        String orderPath = IdUtil.encode(snowflake.nextId());
        // 保存秒杀路径,5分钟这个路径就失效
        RedisUtil.set(getPathKey(userId), orderPath, 300);
        return orderPath;
    }
    @Override
    public void checkOrderPath(String userId, String orderPath) {
        String cacheOrderPath = RedisUtil.get(getPathKey(userId));
        if (!Objects.equals(cacheOrderPath, orderPath)) {
            // 订单已过期,请重新选择商品进行秒杀
            throw new YamiShopBindException("yami.order.expired");
        }
    }
    private String getPathKey(String userId) {
        return SECKILL_PATH_PREFIX + userId;
    }
    @Override
    public void decrSeckillSkuStocks(Long seckillSkuId, Long seckillId, Integer prodCount, Long prodId) {
        String key = SECKILL_SKU_STOCKS_PREFIX + seckillSkuId;
        Long cacheStocks = RedisUtil.getLongValue(key);
        if (cacheStocks != null && cacheStocks <= 0) {
            // 库存不足
            throw new YamiShopBindException("yami.insufficient.inventory");
        }
        // 如果没有库存就缓存一个库存
        if (cacheStocks == null || RedisUtil.getExpire(key) < 1) {
            // 加锁,防止缓存击穿
            RLock rLock = redissonClient.getLock(REDISSON_LOCK_PREFIX + ":getSeckillSkuStocks");
            try {
                int lockWait = 10;
                if (rLock.tryLock(lockWait, lockWait, TimeUnit.SECONDS)) {
                    // 再获取一遍缓存
                    cacheStocks = RedisUtil.getLongValue(key);
                    if (cacheStocks == null) {
                        seckillSkuService.removeSeckillCacheSkuById(seckillSkuId, seckillId);
                        seckillService.removeSeckillCache(seckillId, prodId);
                        Integer seckillStocks = seckillSkuService.getSeckillSkuById(seckillSkuId).getSeckillStocks();
                        if (seckillStocks > 0) {
                            RedisUtil.setLongValue(key, Long.valueOf(seckillStocks), -1);
                        } else {
                            RedisUtil.setLongValue(key, 0L, 30);
                        }
                        cacheStocks = Long.valueOf(seckillStocks);
                    }
                } else {
                    // 网络异常
                    throw new YamiShopBindException("yami.network.exception");
                }
            } catch (InterruptedException e) {
                log.error("InterruptedException:", e);
            } finally {
                try {
                    if (rLock.isLocked()) {
                        rLock.unlock();
                    }
                } catch (Exception e) {
                    log.error("Exception:", e);
                }
            }
        }
        if (cacheStocks == null || cacheStocks < prodCount || RedisUtil.decr(key, prodCount) < 0) {
            RedisUtil.expire(key, 30);
            // 本轮商品已被秒杀完毕,还有用户未支付,还有机会哟
            throw new YamiShopBindException("yami.seckill.complete");
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/pom.xml
New file
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>yami-shop-seckill</artifactId>
        <groupId>com.yami.shop</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yami-shop-seckill-common</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-security-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/constant/SeckillCacheNames.java
New file
@@ -0,0 +1,24 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.constant;
/**
 * @author FrozenWatermelon
 * @date 2020/11/23
 */
public interface SeckillCacheNames {
    /**
     * 秒杀信息缓存
     */
    String SECKILL_BY_PROD_ID = "SeckillByProdId";
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dao/SeckillMapper.java
New file
@@ -0,0 +1,119 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.app.vo.SeckillVO;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.ProductParam;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.seckill.common.dto.SeckillProductSimpleDto;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.dto.SeckillPageDto;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
 * 秒杀信息
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
public interface SeckillMapper extends BaseMapper<Seckill> {
    /**
     * 分页获取秒杀商品
     * @param page
     * @param dbLang
     * @return
     */
    IPage<SeckillProductSimpleDto> pageSeckillProd(PageParam<SeckillProductSimpleDto> page, @Param("dbLang") Integer dbLang);
    /**
     * 平台分页获取秒杀活动
     * @param page
     * @param seckill
     * @return
     */
    IPage<SeckillPageDto> getPlatformSeckillPage(@Param("page") PageParam<Seckill> page, @Param("seckill") Seckill seckill);
    /**
     * 修改状态
     * @param seckillId
     * @param status
     * @return
     */
    int updateStatus(@Param("seckillId") Long seckillId, @Param("status") int status);
    /**
     * 还原库存
     * @param orderNumber
     */
    void returnStocksByOrderNumber(@Param("orderNumber") String orderNumber);
    /**
     * 还原库存
     * @param now
     */
    void returnStocks(@Param("now") Date now);
    /**
     * 修改库存
     * @param seckillId
     * @param prodCount
     * @return
     */
    int updateStocksById(@Param("seckillId") Long seckillId, @Param("prodCount") Integer prodCount);
    /**
     * 修改商品类型
     * @param seckillIds
     */
    void changeProdTypeBySeckillIdList(@Param("seckillIds") List<Long> seckillIds);
    /**
     * 改变秒杀活动状态
     * @param seckillIds
     */
    void changeSeckillStatusBySeckillIdList(@Param("seckillIds") List<Long> seckillIds);
    /**
     * 获取秒杀活动通过订单号
     * @param orderNumber
     * @return
     */
    Seckill getSeckillByOrderNumber(@Param("orderNumber") String orderNumber);
    /**
     * 加库存
     * @param seckillId
     * @param countNum
     */
    void addInventory(@Param("seckillId") Long seckillId, @Param("countNum") Integer countNum);
    /**
     * 获取正常状态下的秒杀商品分页
     * @param page 分页信息
     * @param product 筛选参数
     * @return 正常状态下的秒杀商品分页
     */
    IPage<Product> pageSeckillNormalProd(PageParam<Product> page, @Param("product") ProductParam product);
    /**
     * 根据参数分页获取秒杀信息
     * @param page
     * @param seckillVO
     * @return
     */
    IPage<SeckillPageDto> pageSeckill(PageParam<Seckill> page, @Param("seckill") SeckillVO seckillVO);
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dao/SeckillOrderMapper.java
New file
@@ -0,0 +1,50 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yami.shop.seckill.common.model.SeckillOrder;
import org.apache.ibatis.annotations.Param;
/**
 * 秒杀订单
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
public interface SeckillOrderMapper extends BaseMapper<SeckillOrder> {
    /**
     * 修改状态
     * @param orderNumber
     * @param state
     */
    void updateStateByOrderNumber(@Param("orderNumber") String orderNumber, @Param("state") int state);
    /**
     * 取消未支付
     * @param orderNumber
     */
    void cancelUnpayOrderByOrderNumber(@Param("orderNumber") String orderNumber);
    /**
     * 取消未支付订单
     */
    void cancelUnpayOrder();
    /**
     * 获取用户秒杀订单数量
     * @param seckillId
     * @param prodId
     * @param userId
     * @return
     */
    int selectNumByUser(@Param("seckillId") Long seckillId, @Param("prodId") Long prodId, @Param("userId") String userId);
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dao/SeckillSkuMapper.java
New file
@@ -0,0 +1,81 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yami.shop.seckill.common.model.SeckillSku;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
 * 秒杀活动sku
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
public interface SeckillSkuMapper extends BaseMapper<SeckillSku> {
    /**
     * 修改库存
     *
     * @param seckillSkuId
     * @param prodCount
     * @return
     */
    int updateStocks(@Param("seckillSkuId") Long seckillSkuId, @Param("prodCount") Integer prodCount);
    /**
     * 还原库存
     *
     * @param now
     */
    void returnStocks(@Param("now") Date now);
    /**
     * 还原库存由订单号
     *
     * @param orderNumber
     */
    void returnStocksByOrderNumber(@Param("orderNumber") String orderNumber);
    /**
     * 获取秒杀活动的sku由skuid和订单号
     *
     * @param skuId
     * @param orderNumber
     * @return
     */
    SeckillSku getSeckillSkuBySkuIdAndOrderNumber(@Param("skuId") Long skuId, @Param("orderNumber") String orderNumber);
    /**
     * 加库存
     *
     * @param seckillSkuId
     * @param countNum
     */
    void addInventory(@Param("seckillSkuId") Long seckillSkuId, @Param("countNum") Integer countNum);
    /**
     * 找出当前未支付订单的skuId列表,统一删除缓存
     *
     * @return
     */
    List<Long> listSkuIds();
    /**
     * 获取秒杀最低原价
     *
     * @param seckillIds
     * @return
     */
    List<SeckillSku> listSeckillMinOriginalPrice(@Param("seckillIds") List<Long> seckillIds);
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillOrderMergerDto.java
New file
@@ -0,0 +1,68 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.dto;
import com.yami.shop.bean.app.dto.UserAddrDto;
import com.yami.shop.bean.model.OrderInvoice;
import com.yami.shop.bean.vo.VirtualRemarkVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
 * 秒杀订单合并信息
 * @author LGH
 */
@Data
public class SeckillOrderMergerDto {
    @Schema(description = "实际总值" , required = true)
    private Double actualTotal;
    @Schema(description = "商品总值" , required = true)
    private Double total;
    @Schema(description = "秒杀优惠" , required = true)
    private Double seckillReduce;
    @Schema(description = "运费" , required = true)
    private Double transfee;
    @Schema(description = "商品总数" , required = true)
    private Integer totalCount;
    @Schema(description = "地址Dto" , required = true)
    private UserAddrDto userAddr;
    @Schema(description = "商品信息" , required = true)
    private SeckillShopCartItemDto shopCartItem;
    @Schema(description = "msgId" ,required=true)
    private String msgId;
    @Schema(description = "userId" ,required=true)
    private String userId;
    @Schema(description = "订单备注" ,required=true)
    private String remarks;
    @Schema(description = "orderNumber" )
    private String orderNumber;
    @Schema(description = "订单发票信息" )
    private OrderInvoice orderInvoice;
    @Schema(description = "商品类别 0.实物商品 1. 虚拟商品" )
    private Integer mold;
    @Schema(description = "虚拟商品留言备注" )
    private List<VirtualRemarkVO> virtualRemarkList;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillPageDto.java
New file
@@ -0,0 +1,14 @@
package com.yami.shop.seckill.common.dto;
import com.yami.shop.seckill.common.model.Seckill;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "秒杀分页展示信息")
public class SeckillPageDto extends Seckill {
    @Schema(description = "商品名字" , required = true)
    private String prodName;
    @Schema(description = "商品图片" , required = true)
    private String pic;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillProductSimpleDto.java
New file
@@ -0,0 +1,62 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yami.shop.common.serializer.json.ImgJsonSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
 * 秒杀商品信息简单版,用于分页
 * @author LGH
 */
@Data
public class SeckillProductSimpleDto {
    @Schema(description = "商品ID" , required = true)
    private Long prodId;
    @Schema(description = "商品名称" )
    private String prodName;
    @JsonSerialize(using = ImgJsonSerializer.class)
    @Schema(description = "商品主图" , required = true)
    private String pic;
    @Schema(description = "秒杀活动id" , required = true)
    private Long seckillId;
    @Schema(description = "活动名称" , required = true)
    private String seckillName;
    @Schema(description = "开始时间" , required = true)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date startTime;
    @Schema(description = "结束时间" , required = true)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date endTime;
    @Schema(description = "秒杀活动剩余总库存" , required = true)
    private Integer seckillTotalStocks;
    @Schema(description = "秒杀活动原始库存" , required = true)
    private Integer seckillOriginStocks;
    @Schema(description = "商品价格" , required = true)
    private Double price;
    @Schema(description = "秒杀活动最低价" , required = true)
    private Double seckillPrice;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/dto/SeckillShopCartItemDto.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.dto;
import com.yami.shop.bean.app.dto.ShopCartItemDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 秒杀购物项 dto
 * @author LGH
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class SeckillShopCartItemDto extends ShopCartItemDto {
    @Schema(description = "秒杀skuId" , required = true)
    private Long seckillSkuId;
    @Schema(description = "秒杀活动Id" , required = true)
    private Long seckillId;
    @Schema(description = "秒杀价格" , required = true)
    private Double seckillPrice;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/enums/SeckillEnum.java
New file
@@ -0,0 +1,37 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * 秒杀状态枚举类型
 * @author yami
 */
@Getter
@AllArgsConstructor
public enum  SeckillEnum {
    /**    失效        */
    INVALID(0,"失效"),
    /**    正常        */
    NORMAL(1,"正常"),
    /**    违规下架    */
    OFFLINE(2,"违规下架"),
    /**    等待审核    */
    WAIT_AUDIT(3,"等待审核"),
    ;
    private int value;
    private String desc;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/CancelOrderListener.java
New file
@@ -0,0 +1,45 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.yami.shop.bean.enums.OrderType;
import com.yami.shop.bean.event.CancelOrderEvent;
import com.yami.shop.bean.order.CancelOrderOrder;
import com.yami.shop.seckill.common.service.SeckillOrderService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 取消订单事件
 * @author yami
 */
@Component("seckillCancelOrderListener")
@AllArgsConstructor
public class CancelOrderListener {
    private final SeckillOrderService seckillOrderService;
    @EventListener(CancelOrderEvent.class)
    @Order(CancelOrderOrder.SECKILL)
    public void seckillCancelOrderEventListener(CancelOrderEvent event) {
        String orderNumber = event.getOrder().getOrderNumber();
        // 不是秒杀订单直接返回
        if (!OrderType.SECKILL.value().equals(event.getOrder().getOrderType())) {
            return;
        }
        seckillOrderService.cancelUnpayOrderByOrderNumber(orderNumber);
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/CategorySeckillListener.java
New file
@@ -0,0 +1,35 @@
package com.yami.shop.seckill.common.listener;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.bean.event.CategoryWordEvent;
import com.yami.shop.seckill.common.enums.SeckillEnum;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.service.SeckillService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("CategorySeckillListener")
@Slf4j
@AllArgsConstructor
public class CategorySeckillListener {
    private final SeckillService seckillService;
    @EventListener(CategoryWordEvent.class)
    public void categorySeckillListener(CategoryWordEvent event){
        List<Long> prodIds = event.getProdIdList();
        // 失效秒杀活动
        if(CollUtil.isNotEmpty(prodIds)){
            seckillService.update(Wrappers.lambdaUpdate(Seckill.class)
                    .set(Seckill::getStatus, SeckillEnum.INVALID.getValue())
                    .in(Seckill::getProdId, prodIds)
                    .eq(Seckill::getStatus, SeckillEnum.NORMAL.getValue())
            );
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/EsProductActivityInfoListener.java
New file
@@ -0,0 +1,69 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.EsProductActivityInfoEvent;
import com.yami.shop.bean.order.EsProductOrder;
import com.yami.shop.bean.vo.search.ProductSearchVO;
import com.yami.shop.bean.vo.search.SeckillSearchVO;
import com.yami.shop.seckill.common.dao.SeckillMapper;
import com.yami.shop.seckill.common.model.Seckill;
import lombok.AllArgsConstructor;
import ma.glasnost.orika.MapperFacade;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 获取商品团购数据事件
 * @author yami
 */
@Component("seckillEsProductActivityInfoListener")
@AllArgsConstructor
public class EsProductActivityInfoListener {
    private final SeckillMapper seckillMapper;
    private final MapperFacade mapperFacade;
    @EventListener(EsProductActivityInfoEvent.class)
    @Order(EsProductOrder.SECKILL)
    public void seckillEsProductListener(EsProductActivityInfoEvent event) {
        List<ProductSearchVO> productList = event.getProductList();
        List<Long> ids = productList.stream()
                .filter(productSearchVO -> Objects.equals(productSearchVO.getProdType(), ProdType.PROD_TYPE_SECKILL.value()) &&
                        Objects.nonNull(productSearchVO.getActivityStartTime()))
                .map(ProductSearchVO::getActivityId).collect(Collectors.toList());
        // 没有秒杀商品需要处理
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        List<Seckill> seckillActivities = seckillMapper.selectList(new LambdaQueryWrapper<Seckill>().in(Seckill::getSeckillId, ids));
        Map<Long, Seckill> groupMap = seckillActivities.stream().collect(Collectors.toMap(Seckill::getSeckillId, s -> s));
        for (ProductSearchVO productSearchVO : productList) {
            Seckill seckill = groupMap.get(productSearchVO.getActivityId());
            if (Objects.isNull(seckill)) {
                continue;
            }
            productSearchVO.setActivityInProgress(Boolean.TRUE);
            SeckillSearchVO seckillSearchVO = mapperFacade.map(seckill, SeckillSearchVO.class);
            productSearchVO.setSeckillSearchVO(seckillSearchVO);
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/EsProductListener.java
New file
@@ -0,0 +1,88 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.bo.ProductBO;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.EsProductEvent;
import com.yami.shop.bean.order.EsProductOrder;
import com.yami.shop.seckill.common.dao.SeckillMapper;
import com.yami.shop.seckill.common.dao.SeckillSkuMapper;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillSku;
import lombok.AllArgsConstructor;
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;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 获取es商品团购数据事件
 * @author yami
 */
@Component("seckillEsProductListener")
@AllArgsConstructor
public class EsProductListener {
    private final SeckillMapper seckillMapper;
    private final SeckillSkuMapper seckillSkuMapper;
    @EventListener(EsProductEvent.class)
    @Order(EsProductOrder.SECKILL)
    public void seckillEsProductListener(EsProductEvent event) {
        List<ProductBO> productList = event.getProductList();
        if (CollUtil.isEmpty(productList) || Objects.nonNull(event.getOperationType())) {
            return;
        }
        List<ProductBO> seckillProductList = new ArrayList<>();
        List<Long> seckillIds = new ArrayList<>();
        for (ProductBO productBO : productList) {
            if (!Objects.equals(productBO.getProdType(), ProdType.PROD_TYPE_SECKILL.value())) {
                continue;
            }
            seckillProductList.add(productBO);
            seckillIds.add(productBO.getActivityId());
        }
        if (CollUtil.isEmpty(seckillIds)) {
            return;
        }
        // 获取已启用的秒杀商品信息
        List<Seckill> seckillList = seckillMapper.selectList(new LambdaQueryWrapper<Seckill>().in(Seckill::getSeckillId, seckillIds).eq(Seckill::getStatus, 1));
        Map<Long, Seckill> seckillMap = seckillList.stream().collect(Collectors.toMap(Seckill::getSeckillId, s -> s));
        List<SeckillSku> seckillSkus = seckillSkuMapper.listSeckillMinOriginalPrice(seckillIds);
        Map<Long, Double> seckillSkuMap = seckillSkus.stream().collect(Collectors.toMap(SeckillSku::getSeckillId, SeckillSku::getSeckillPrice));
        Long currentTime = System.currentTimeMillis();
        for (ProductBO productBO : seckillProductList) {
            if (!seckillMap.containsKey(productBO.getActivityId())) {
                continue;
            }
            Seckill seckill = seckillMap.get(productBO.getActivityId());
            // 当前时间大于等于活动结束时间, 则不添加活动信息
            if (seckill.getEndTime().getTime() <= currentTime) {
                continue;
            }
            // 插入活动开始时间
            productBO.setActivityStartTime(seckill.getStartTime().getTime());
            // 秒杀最低价
            productBO.setActivityPrice(seckill.getSeckillPrice());
            // 秒杀商品规格中最低的sku原价
            productBO.setActivityOriginalPrice(seckillSkuMap.get(productBO.getActivityId()));
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/GetSeckillOrderInofListener.java
New file
@@ -0,0 +1,58 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.event.SubmitSeckillOrderEvent;
import com.yami.shop.bean.model.Order;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.service.SeckillOrderService;
import com.yami.shop.seckill.common.service.SeckillService;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
 * 获取订单相关信息
 * @author yami
 */
@Component("seckillGetSeckillOrderInofListener")
@AllArgsConstructor
public class GetSeckillOrderInofListener {
    private final SeckillOrderService seckillOrderService;
    private final SeckillService seckillService;
    @EventListener(SubmitSeckillOrderEvent.class)
    public void seckillGetSeckillOrderInofListener(SubmitSeckillOrderEvent event) {
        Order order = event.getOrder();
        event.setMaxCancelTime(0);
        if (Objects.isNull(order)) {
            return;
        }
        String orderNumber = order.getOrderNumber();
        List<SeckillOrder> list = seckillOrderService.list(new LambdaQueryWrapper<SeckillOrder>().eq(SeckillOrder::getOrderNumber, orderNumber));
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        SeckillOrder seckillOrder = list.get(0);
        Seckill seckill = seckillService.getSeckillById(seckillOrder.getSeckillId());
        if (Objects.isNull(seckill)) {
            return;
        }
        event.setMaxCancelTime(seckill.getMaxCancelTime());
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/PaySuccessHandleOrderStockListener.java
New file
@@ -0,0 +1,65 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.event.PaySuccessHandleOrderStockEvent;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.bean.order.PaySuccessOrderOrder;
import com.yami.shop.seckill.common.dao.SeckillOrderMapper;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.service.SeckillService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
 * 秒杀成功回调
 * @author LGH
 */
@Component("paySuccessHandleOrderStockListener")
@AllArgsConstructor
public class PaySuccessHandleOrderStockListener {
    private final SeckillService seckillService;
    private final SeckillOrderMapper seckillOrderMapper;
    /**
     * 更新秒杀状态
     */
    @EventListener(PaySuccessHandleOrderStockEvent.class)
    @Order(PaySuccessOrderOrder.SECKILL)
    public void seckillPaySuccessListener(PaySuccessHandleOrderStockEvent event) {
        com.yami.shop.bean.model.Order order = event.getOrder();
        OrderItem orderItem = order.getOrderItems().get(0);
        boolean stockCheck = true;
        SeckillOrder seckillOrder = seckillOrderMapper.selectOne(new LambdaQueryWrapper<SeckillOrder>().eq(SeckillOrder::getOrderNumber, orderItem.getOrderNumber()));
        // 秒杀活动信息(来自缓存)
        Seckill seckill = seckillService.getSeckillById(seckillOrder.getSeckillId());
        // 判断之前秒杀的商品有没有超过限制,-1表示商品不限制秒杀数量
        if (!Objects.equals(seckill.getMaxNum(), -1)) {
            int dbCount = seckillOrderMapper.selectNumByUser(seckill.getSeckillId(), seckill.getProdId(), order.getUserId());
            if (seckill.getMaxNum() < orderItem.getProdCount() + dbCount) {
                stockCheck = false;
            }
        }
        // 用户没有达到购买上限,且秒杀商品还有库存,则购买成功
        boolean deductStock = seckillService.handlerSeckillOrderStock(orderItem,seckillOrder);
        if(deductStock && stockCheck){
            event.getSuccessOrderList().add(order);
        }else {
            event.getRefundOrderList().add(order);
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/PaySuccessOrderListener.java
New file
@@ -0,0 +1,52 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.yami.shop.bean.enums.OrderType;
import com.yami.shop.bean.event.PaySuccessOrderEvent;
import com.yami.shop.bean.order.PaySuccessOrderOrder;
import com.yami.shop.seckill.common.dao.SeckillOrderMapper;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
 * 秒杀成功回调
 * @author LGH
 */
@Component("seckillPaySuccessListener")
@AllArgsConstructor
public class PaySuccessOrderListener {
    private SeckillOrderMapper seckillOrderMapper;
    /**
     * 更新秒杀状态
     */
    @EventListener(PaySuccessOrderEvent.class)
    @Order(PaySuccessOrderOrder.SECKILL)
    public void seckillPaySuccessListener(PaySuccessOrderEvent event) {
        List<com.yami.shop.bean.model.Order> orders = event.getOrders();
        for (com.yami.shop.bean.model.Order order : orders) {
            if (!Objects.equals(order.getOrderType(), OrderType.SECKILL.value())) {
                return;
            }
            // 更新秒杀状态
            seckillOrderMapper.updateStateByOrderNumber(order.getOrderNumber(), 1);
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/ProcessActivityProdPriceListener.java
New file
@@ -0,0 +1,56 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.ProcessActivityProdPriceEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.ProductParam;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.service.SeckillService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 处理活动商品价格的事件
 * @author lhd
 */
@Component("processSeckillProdPriceListener")
@AllArgsConstructor
public class ProcessActivityProdPriceListener {
    private final SeckillService seckillService;
    @EventListener(ProcessActivityProdPriceEvent.class)
    public void processSeckillProdPrice(ProcessActivityProdPriceEvent event) {
        ProductParam productParam = event.getProduct();
        if(Objects.isNull(productParam.getProdType()) || !Objects.equals(productParam.getProdType(), ProdType.PROD_TYPE_SECKILL.value())) {
            return;
        }
        List<Product> products = event.getProducts();
        List<Long> prodIds = new ArrayList<>();
        products.forEach(product -> prodIds.add(product.getProdId()));
        Date date = new Date();
        List<Seckill> seckillList = seckillService.list(new LambdaQueryWrapper<Seckill>()
                .eq(Seckill::getStatus, 1).lt(Seckill::getStartTime, date)
                .gt(Seckill::getEndTime, date).in(Seckill::getProdId, prodIds));
        Map<Long, Double> priceMap = seckillList.stream().collect(Collectors.toMap(Seckill::getProdId, Seckill::getSeckillPrice));
        for (Product product : products) {
            Double price = priceMap.get(product.getProdId());
            product.setActivityPrice(price);
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/ProdChangeStatusListener.java
New file
@@ -0,0 +1,71 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.yami.shop.bean.enums.ProdStatusEnums;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.ProdChangeStatusEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.order.GeneralActivitiesOrder;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import com.yami.shop.service.ProductService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
 * 秒杀商品状态修改监听
 * @author LGH
 */
@Component("seckillProdChangeStatus")
@AllArgsConstructor
public class ProdChangeStatusListener {
    private final SeckillService seckillService;
    private final ProductService productService;
    private final SeckillSkuService seckillSkuService;
    /**
     * 使秒杀活动失效
     */
    @EventListener(ProdChangeStatusEvent.class)
    @Order(GeneralActivitiesOrder.SECKILL)
    public void seckillProdChangeStatusListener(ProdChangeStatusEvent event) {
        Product product = event.getProduct();
        // 不是秒杀商品,就不用管他
        if(!Objects.equals(product.getProdType(), ProdType.PROD_TYPE_SECKILL.value())){
            return;
        }
        // 如果是商品上线,就不管他了
        if (Objects.equals(ProdStatusEnums.NORMAL.getValue(), event.getStatus())) {
            return;
        }
        // 平台审核
        if (Objects.equals(ProdStatusEnums.PLATFORM_AUDIT.getValue(), event.getStatus())) {
            return;
        }
        Seckill seckill = seckillService.getById(product.getActivityId());
        seckillService.invalidById(seckill);
        productService.removeProdCacheByProdId(seckill.getProdId());
        seckillSkuService.removeSeckillSkuCacheBySeckillId(seckill.getSeckillId());
        seckillService.removeSeckillCache(seckill.getSeckillId(), seckill.getProdId());
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/listener/ShopChangeStatusListener.java
New file
@@ -0,0 +1,58 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.listener;
import com.yami.shop.bean.enums.ShopStatus;
import com.yami.shop.bean.event.ShopChangeStatusEvent;
import com.yami.shop.bean.order.GeneralActivitiesOrder;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import com.yami.shop.service.ProductService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
 * @Author lth
 * @Date 2021/11/23 15:21
 */
@Component("seckillShopChangeStatusListener")
@AllArgsConstructor
public class ShopChangeStatusListener {
    private final SeckillService seckillService;
    private final ProductService productService;
    private final SeckillSkuService seckillSkuService;
    @EventListener(ShopChangeStatusEvent.class)
    @Order(GeneralActivitiesOrder.SECKILL)
    public void seckillShopChangeStatusListener(ShopChangeStatusEvent event) {
        Long shopId = event.getShopId();
        ShopStatus shopStatus = event.getShopStatus();
        if (Objects.equals(shopStatus.value(), ShopStatus.OFFLINE.value())) {
            // 店铺下线,把该店铺的秒杀活动失效
            List<Seckill> invalidSeckillList = seckillService.invalidByShopId(shopId);
            // 清除缓存
            for (Seckill seckill : invalidSeckillList) {
                productService.removeProdCacheByProdId(seckill.getProdId());
                seckillSkuService.removeSeckillSkuCacheBySeckillId(seckill.getSeckillId());
                seckillService.removeSeckillCache(seckill.getSeckillId(), seckill.getProdId());
            }
        }
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/model/Seckill.java
New file
@@ -0,0 +1,85 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
 * 秒杀信息
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Data
@TableName("tz_seckill")
public class Seckill implements Serializable{
    private static final long serialVersionUID = 1L;
    @TableId
    @Schema(description = "秒杀活动id" , required = true)
    private Long seckillId;
    @Schema(description = "商品id" , required = true)
    private Long prodId;
    @NotNull
    @Schema(description = "活动名称" , required = true)
    private String seckillName;
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Schema(description = "开始时间" , required = true)
    private Date startTime;
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Schema(description = "结束时间" , required = true)
    private Date endTime;
    @Schema(description = "活动标签" , required = true)
    private String seckillTag;
    @Schema(description = "限购数量" , required = true)
    private Integer maxNum;
    @Schema(description = "取消订单时间" , required = true)
    private Integer maxCancelTime;
    @Schema(description = "店铺id" , required = true)
    private Long shopId;
    @Schema(description = "是否已删除 0 未删除 1已删除" , required = true)
    private Integer isDelete;
    @Schema(description = "状态:0 失效 1正常" , required = true)
    private Integer status;
    @Schema(description = "秒杀活动剩余库存" , required = true)
    private Integer seckillTotalStocks;
    @Schema(description = "秒杀活动原始库存" , required = true)
    private Integer seckillOriginStocks;
    @Schema(description = "秒杀活动最低价" , required = true)
    private Double seckillPrice;
    @TableField(exist = false)
    @Schema(description = "店铺名称" )
    private String shopName;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/model/SeckillOrder.java
New file
@@ -0,0 +1,58 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * 秒杀订单
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Data
@TableName("tz_seckill_order")
public class SeckillOrder implements Serializable{
    private static final long serialVersionUID = 1L;
    @Schema(description = "秒杀订单id" )
    @TableId
    private Long seckillOrderId;
    @Schema(description = "秒杀id" )
    private Long seckillId;
    @Schema(description = "用户id" )
    private String userId;
    @Schema(description = "商品id" )
    private Long prodId;
    @Schema(description = "订单号" )
    private String orderNumber;
    @Schema(description = "状态 -1指无效,0指成功,1指已付款" )
    private Integer state;
    @Schema(description = "秒杀到的商品数量" )
    private Integer prodCount;
    @Schema(description = "创建时间" )
    private Date createTime;
    @Schema(description = "秒杀规格id" )
    private Long seckillSkuId;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/model/SeckillSku.java
New file
@@ -0,0 +1,61 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
 * 秒杀活动sku
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Data
@TableName("tz_seckill_sku")
public class SeckillSku implements Serializable{
    private static final long serialVersionUID = 1L;
    /**
     * 秒杀活动单个skuid
     */
    @TableId
    private Long seckillSkuId;
    /**
     * skuId
     */
    private Long skuId;
    /**
     * 秒杀活动id
     */
    private Long seckillId;
    /**
     * 用于秒杀的库存
     */
    private Integer seckillStocks;
    /**
     * 秒杀价格
     */
    private Double seckillPrice;
    /**
     * 单个sku的状态
     */
    @TableField(exist = false)
    private Integer status;
    /**
     * sku图片
     */
    @TableField(exist = false)
    private String pic;
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/SeckillOrderService.java
New file
@@ -0,0 +1,72 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
import com.yami.shop.seckill.common.dto.SeckillOrderMergerDto;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
/**
 * 秒杀订单
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
public interface SeckillOrderService extends IService<SeckillOrder> {
    /**
     * 将确认的的订单信息缓存起来
     * @param userId
     * @param dto
     * @return
     */
    SeckillOrderMergerDto putConfirmOrderCache(String userId , SeckillOrderMergerDto dto);
    /**
     * 获取缓存的确认订单信息
     * @param userId
     * @return
     */
    SeckillOrderMergerDto getConfirmOrderCache(String userId);
    /**
     * 移除确认的订单的缓存
     * @param userId
     */
    void removeConfirmOrderCache(String userId);
    /**
     * 提交订单返回订单号
     * @param dto
     * @return
     */
    String submit(ShopCartOrderMergerDto dto);
    /**
     * 校验订单商品数量
     * @param seckill
     * @param userId
     * @param prodCount
     */
    void checkOrderProdNum(Seckill seckill,String userId, int prodCount);
    /**
     * 取消超时订单未支付订单
     */
    void cancelOrderUnpayOrderByTime();
    /**
     * 取消未支付订单
     * @param orderNumber
     */
    void cancelUnpayOrderByOrderNumber(String orderNumber);
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/SeckillService.java
New file
@@ -0,0 +1,178 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yami.shop.bean.app.vo.SeckillVO;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.OfflineHandleEventAuditParam;
import com.yami.shop.bean.param.ProductParam;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.seckill.common.dto.SeckillProductSimpleDto;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.model.SeckillSku;
import com.yami.shop.seckill.common.dto.SeckillPageDto;
import java.util.List;
/**
 * 秒杀信息
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
public interface SeckillService extends IService<Seckill> {
    /**
     * 分页获取正常状态的秒杀商品
     * @param page
     * @param product
     * @return
     */
    IPage<Product> pageSeckillNormalProd(PageParam<Product> page, ProductParam product);
    /**
     * 根据参数分页获取秒杀信息
     * @param page
     * @param seckillVO
     * @return
     */
    IPage<SeckillPageDto> pageSeckill(PageParam<Seckill> page, SeckillVO seckillVO);
    /**
     * 保存秒杀信息
     *
     * @param seckill
     * @param seckillSkus
     */
    void saveSeckill(Seckill seckill, List<SeckillSku> seckillSkus);
    /**
     * 根据id获取秒杀信息
     *
     * @param seckillId
     * @return
     */
    Seckill getSeckillById(Long seckillId);
    /**
     * 根据商品id获取秒杀信息
     *
     * @param prodId
     * @return
     */
    Seckill getSeckillByProdId(Long prodId);
    /**
     * 删除秒杀缓存
     * @param seckillId
     * @param prodId
     */
    void removeSeckillCache(Long seckillId, Long prodId);
    /**
     * 批量删除秒杀缓存
     * @param seckillIds
     * @param prodIds
     */
    void batchRemoveSeckillCache(List<Long> seckillIds, List<Long> prodIds);
    /**
     * 验证
     * @param seckill
     */
    void invalidById(Seckill seckill);
    /**
     * 分页获取秒杀活动商品
     * @param page
     * @param dbLang
     * @return
     */
    IPage<SeckillProductSimpleDto> pageSeckillProd(PageParam<SeckillProductSimpleDto> page, Integer dbLang);
    /**
     * 平台 --获取分页秒杀数据
     * @param page
     * @param seckill
     * @return
     */
    IPage<SeckillPageDto> getPlatformSeckillPage(PageParam<Seckill> page, Seckill seckill);
    /**
     * 平台 --下线活动
     * @param seckill
     * @param offlineReason
     * @param sysUserId
     */
    void offline(Seckill seckill, String offlineReason, Long sysUserId);
    /**
     * 平台 --审核秒杀操作
     * @param offlineHandleEventAuditParam
     * @param sysUserId
     */
    void auditSeckill(OfflineHandleEventAuditParam offlineHandleEventAuditParam, Long sysUserId);
    /**
     * 商家申请审核
     * @param eventId
     * @param seckillId
     * @param reapplyReason
     */
    void auditApply(Long eventId, Long seckillId, String reapplyReason);
    /**
     * 改变秒杀商品类型
     * @param seckillIds
     */
    void changeProdTypeBySeckillIdList(List<Long> seckillIds);
    /**
     * 获取订单对应的秒杀活动
     * @param orderNumber
     * @return
     */
    Seckill getSeckillByOrderNumber(String orderNumber);
    /**
     * 加秒杀库存
     * @param seckillId
     * @param prodCount
     */
    void addInventory(Long seckillId, Integer prodCount);
    /**
     * 根据店铺id失效店铺的秒杀活动
     * @param shopId
     * @return 失效的秒杀活动
     */
    List<Seckill> invalidByShopId(Long shopId);
    /**
     * 根据订单项返回库存处理结果
     * @param orderItem 订单项
     * @param seckillOrder
     * @return 库存处理结果
     */
    Boolean handlerSeckillOrderStock(OrderItem orderItem, SeckillOrder seckillOrder);
    /**
     * 根据商品id获取秒杀活动信息
     * @param prodId
     * @return
     */
    Seckill getByProdId(Long prodId);
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/SeckillSkuService.java
New file
@@ -0,0 +1,66 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yami.shop.seckill.common.model.SeckillSku;
import java.util.List;
/**
 * 秒杀活动sku
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
public interface SeckillSkuService extends IService<SeckillSku> {
    /**
     * 获取某个活动的秒杀sku列表
     * @param seckillId
     * @return
     */
    List<SeckillSku> listSeckillSkuBySeckillId(Long seckillId);
    /**
     * 删除秒杀活动商品的缓存
     * @param seckillId
     */
    void removeSeckillSkuCacheBySeckillId(Long seckillId);
    /**
     * 获取秒杀商品
     * @param seckillSkuId
     * @return
     */
    SeckillSku getSeckillSkuById(Long seckillSkuId);
    /**
     * 删除缓存
     * @param seckillSkuId
     * @param seckillId
     */
    void removeSeckillCacheSkuById(Long seckillSkuId,Long seckillId);
    /**
     * 获取秒杀商品sku
     * @param skuId
     * @param orderNumber
     * @return
     */
    SeckillSku getSeckillSkuBySkuIdAndOrderNumber(Long skuId, String orderNumber);
    /**
     * 加库存
     * @param seckillSkuId
     * @param prodCount
     */
    void addInventory(Long seckillSkuId, Integer prodCount);
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/impl/SeckillOrderServiceImpl.java
New file
@@ -0,0 +1,185 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.app.dto.ShopCartItemDto;
import com.yami.shop.bean.app.dto.ShopCartOrderDto;
import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
import com.yami.shop.bean.event.PayManagerEvent;
import com.yami.shop.bean.model.PayInfo;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.dao.OrderMapper;
import com.yami.shop.dao.PayInfoMapper;
import com.yami.shop.seckill.common.dao.SeckillMapper;
import com.yami.shop.seckill.common.dao.SeckillOrderMapper;
import com.yami.shop.seckill.common.dao.SeckillSkuMapper;
import com.yami.shop.seckill.common.dto.SeckillOrderMergerDto;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.service.SeckillOrderService;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.service.OrderService;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * 秒杀订单
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Service
@AllArgsConstructor
public class SeckillOrderServiceImpl extends ServiceImpl<SeckillOrderMapper, SeckillOrder> implements SeckillOrderService {
    private final SeckillOrderMapper seckillOrderMapper;
    private final SeckillSkuMapper seckillSkuMapper;
    private final PayInfoMapper payInfoMapper;
    private final OrderMapper orderMapper;
    private final OrderService orderService;
    private final SeckillService seckillService;
    private final SeckillMapper seckillMapper;
    private final ApplicationEventPublisher eventPublisher;
    @Override
    @CachePut(cacheNames = "ConfirmSeckillOrderCache", key = "#userId")
    public SeckillOrderMergerDto putConfirmOrderCache(String userId, SeckillOrderMergerDto seckillOrderMergerDto) {
        return seckillOrderMergerDto;
    }
    @Override
    @Cacheable(cacheNames = "ConfirmSeckillOrderCache", key = "#userId")
    public SeckillOrderMergerDto getConfirmOrderCache(String userId) {
        return null;
    }
    @Override
    @CacheEvict(cacheNames = "ConfirmSeckillOrderCache", key = "#userId")
    public void removeConfirmOrderCache(String userId) {
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String submit(ShopCartOrderMergerDto dto) {
        ShopCartOrderDto shopCartOrderDto = dto.getShopCartOrders().get(0);
        ShopCartItemDto shopCartItemDto = shopCartOrderDto.getShopCartItemDiscounts().get(0).getShopCartItems().get(0);
        this.checkOrderProdNum(seckillService.getSeckillById(dto.getSeckillId()), dto.getUserId(), shopCartItemDto.getProdCount());
        // 更新秒杀sku库存,乐观锁
        if (seckillSkuMapper.updateStocks(dto.getSeckillSkuId(), shopCartItemDto.getProdCount()) != 1) {
            // 本商品已被秒杀完毕,下次记得早点来哟
            throw new YamiShopBindException("yami.seckill.finish");
        }
        String orderNumber = shopCartOrderDto.getOrderNumber();
        // 保存秒杀订单信息
        SeckillOrder seckillOrder = new SeckillOrder();
        seckillOrder.setCreateTime(new Date());
        seckillOrder.setOrderNumber(orderNumber);
        seckillOrder.setProdCount(shopCartItemDto.getProdCount());
        seckillOrder.setSeckillId(dto.getSeckillId());
        seckillOrder.setUserId(dto.getUserId());
        seckillOrder.setProdId(shopCartItemDto.getProdId());
        seckillOrder.setSeckillSkuId(dto.getSeckillSkuId());
        // 秒杀成功等待支付
        seckillOrder.setState(0);
        seckillOrderMapper.insert(seckillOrder);
        // 更新活动剩余库存
        if (seckillMapper.updateStocksById(dto.getSeckillId(), shopCartItemDto.getProdCount()) < 1) {
            // 本商品已被秒杀完毕,下次记得早点来哟
            throw new YamiShopBindException("yami.seckill.finish");
        }
        // 创建订单相关信息
        orderService.submit(dto);
        return orderNumber;
    }
    @Override
    public void checkOrderProdNum(Seckill seckill, String userId, int prodCount) {
        // 判断之前秒杀的商品有没有超过限制,-1表示商品不限制秒杀数量
        if (!Objects.equals(seckill.getMaxNum(), -1)) {
            int dbCount = seckillOrderMapper.selectNumByUser(seckill.getSeckillId(), seckill.getProdId(), userId);
            if (seckill.getMaxNum() < prodCount + dbCount) {
                // 本次秒杀商品限购
                String limitMsg = I18nMessage.getMessage("yami.seckill.limit.shop");
                String message = I18nMessage.getMessage("yami.seckill.num");
                throw new YamiShopBindException(limitMsg + seckill.getMaxNum() + message);
            }
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelOrderUnpayOrderByTime() {
        // 查询出微信or支付宝支付并且为二维码支付的,失效掉对应支付二维码
        List<PayInfo> filterPayInfo = payInfoMapper.listByPayTypeAndTime();
        Date now = new Date();
        List<Long> skuIds = seckillSkuMapper.listSkuIds();
        List<String> keys = new ArrayList<>();
        for (Long skuId : skuIds) {
            keys.add(Constant.SECKILL_SKU_STOCKS_PREFIX + skuId);
        }
        // 还原库存
        seckillSkuMapper.returnStocks(now);
        // 还原库存
        seckillMapper.returnStocks(now);
        // 修改订单状态
        orderMapper.cancelSeckillOrderByTime(now);
        // 将秒杀订单状态改为失效状态
        seckillOrderMapper.cancelUnpayOrder();
        // 删除库存的缓存
        RedisUtil.del(keys);
        eventPublisher.publishEvent(new PayManagerEvent(filterPayInfo));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelUnpayOrderByOrderNumber(String orderNumber) {
        SeckillOrder seckillOrder = seckillOrderMapper.selectOne(Wrappers.lambdaQuery(SeckillOrder.class)
                .eq(SeckillOrder::getOrderNumber, orderNumber));
        if(Objects.isNull(seckillOrder)){
            return;
        }
        // 还原秒杀库存
        seckillSkuMapper.returnStocksByOrderNumber(orderNumber);
        // 还原秒杀库存
        seckillMapper.returnStocksByOrderNumber(orderNumber);
        seckillOrderMapper.cancelUnpayOrderByOrderNumber(orderNumber);
        // 清除库存的缓存
        RedisUtil.del(Constant.SECKILL_SKU_STOCKS_PREFIX + seckillOrder.getSeckillSkuId());
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/impl/SeckillServiceImpl.java
New file
@@ -0,0 +1,384 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Snowflake;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.app.vo.SeckillVO;
import com.yami.shop.bean.enums.EsOperationType;
import com.yami.shop.bean.enums.OfflineHandleEventStatus;
import com.yami.shop.bean.enums.OfflineHandleEventType;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.bean.event.RemoveDiscountProdByIdsEvent;
import com.yami.shop.bean.model.OfflineHandleEvent;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.param.OfflineHandleEventAuditParam;
import com.yami.shop.bean.param.ProductParam;
import com.yami.shop.common.constants.CacheNames;
import com.yami.shop.common.enums.StatusEnum;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.CacheManagerUtil;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.seckill.common.constant.SeckillCacheNames;
import com.yami.shop.seckill.common.dao.SeckillMapper;
import com.yami.shop.seckill.common.dao.SeckillSkuMapper;
import com.yami.shop.seckill.common.dto.SeckillProductSimpleDto;
import com.yami.shop.seckill.common.enums.SeckillEnum;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillOrder;
import com.yami.shop.seckill.common.model.SeckillSku;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import com.yami.shop.seckill.common.dto.SeckillPageDto;
import com.yami.shop.service.OfflineHandleEventService;
import com.yami.shop.service.ProductService;
import lombok.AllArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * 秒杀信息
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Service
@AllArgsConstructor
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, Seckill> implements SeckillService {
    private final RedissonClient redissonClient;
    private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";
    private final static String SECKILL_SKU_STOCKS_PREFIX = "SECKILL_SKU_STOCKS_";
    private final Snowflake snowflake;
    private final SeckillMapper seckillMapper;
    private final SeckillSkuMapper seckillSkuMapper;
    private final SeckillSkuService seckillSkuService;
    private final ProductService productService;
    private final OfflineHandleEventService offlineHandleEventService;
    private final ApplicationContext applicationContext;
    private final ApplicationEventPublisher eventPublisher;
    private final CacheManagerUtil cacheManagerUtil;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSeckill(Seckill seckill, List<SeckillSku> seckillSkus) {
        seckill.setStatus(1);
        seckill.setIsDelete(0);
        // 保存秒杀信息
        seckillMapper.insert(seckill);
        // 这里再查下秒杀商品对应的活动数量,如果大于1,就证明已经有一个跟该商品对应的活动了,本次操作不应该继续下去
        Integer seckillNum = seckillMapper.selectCount(new LambdaQueryWrapper<Seckill>()
                .eq(Seckill::getProdId, seckill.getProdId())
                .eq(Seckill::getIsDelete, 0)
                .ge(Seckill::getStatus, StatusEnum.ENABLE.value()));
        if(seckillNum > 1) {
            // 该商品已创建秒杀活动,请勿重复提交
            throw new YamiShopBindException("yami.product.has.created.seckill");
        }
        seckillSkus.forEach(seckillSku -> seckillSku.setSeckillId(seckill.getSeckillId()));
        // 保存秒杀sku信息
        seckillSkuService.saveBatch(seckillSkus);
        // 更新商品信息
        Product product = new Product();
        product.setActivityId(seckill.getSeckillId());
        product.setProdType(ProdType.PROD_TYPE_SECKILL.value());
        product.setProdId(seckill.getProdId());
        productService.updateById(product);
        // 发送事件,清除掉如果是限时特惠中的可用商品
        applicationContext.publishEvent(new RemoveDiscountProdByIdsEvent(Collections.singletonList(seckill.getProdId())));
    }
    /**
     * 这里的缓存时间参考RedisCacheConfig#getRedisCacheConfigurationMap
     * @param seckillId
     * @return
     */
    @Override
    @Cacheable(cacheNames = "SeckillById", key = "#seckillId")
    public Seckill getSeckillById(Long seckillId) {
        return seckillMapper.selectById(seckillId);
    }
    @Override
    @Cacheable(cacheNames = "SeckillByProdId", key = "#prodId")
    public Seckill getSeckillByProdId(Long prodId) {
        return seckillMapper.selectOne(new LambdaQueryWrapper<Seckill>()
                .eq(Seckill::getProdId, prodId)
                .eq(Seckill::getStatus, StatusEnum.ENABLE.value())
        );
    }
    @Override
    @Caching(evict = {
            @CacheEvict(cacheNames = "SeckillById", key = "#seckillId"),
            @CacheEvict(cacheNames = "SeckillByProdId", key = "#prodId")
    })
    public void removeSeckillCache(Long seckillId, Long prodId) {
    }
    @Override
    public void batchRemoveSeckillCache(List<Long> seckillIds, List<Long> prodIds) {
        List<String> keys = new ArrayList<>();
        if (CollUtil.isNotEmpty(prodIds)) {
            for (Long prodId : prodIds) {
                keys.add("SeckillByProdId" + CacheNames.UNION + prodId);
            }
        }
        if (CollUtil.isEmpty(seckillIds)) {
            return;
        }
        for (Long seckillId : seckillIds) {
            keys.add("SeckillById" + CacheNames.UNION + seckillId);
        }
        RedisUtil.del(keys);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invalidById(Seckill seckill) {
        Product productParam = new Product();
        productParam.setProdId(seckill.getProdId());
        productParam.setActivityId(0L);
        productParam.setProdType(ProdType.PROD_TYPE_NORMAL.value());
        productService.updateById(productParam);
        seckill.setStatus(0);
        seckillMapper.updateById(seckill);
        eventPublisher.publishEvent(new EsProductUpdateEvent(seckill.getProdId(), null, EsOperationType.UPDATE));
    }
    @Override
    public IPage<SeckillProductSimpleDto> pageSeckillProd(PageParam<SeckillProductSimpleDto> page, Integer dbLang) {
        return seckillMapper.pageSeckillProd(page,dbLang);
    }
    @Override
    public IPage<SeckillPageDto> getPlatformSeckillPage(PageParam<Seckill> page, Seckill seckill) {
        return seckillMapper.getPlatformSeckillPage(page, seckill);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offline(Seckill seckill, String offlineReason, Long sysUserId) {
        // 添加下线处理记录
        Date now = new Date();
        OfflineHandleEvent offlineHandleEvent = new OfflineHandleEvent();
        offlineHandleEvent.setHandleId(seckill.getSeckillId());
        offlineHandleEvent.setCreateTime(now);
        offlineHandleEvent.setHandleType(OfflineHandleEventType.SECKILL.getValue());
        offlineHandleEvent.setHandlerId(sysUserId);
        offlineHandleEvent.setShopId(seckill.getShopId());
        offlineHandleEvent.setOfflineReason(offlineReason);
        offlineHandleEvent.setStatus(OfflineHandleEventStatus.OFFLINE_BY_PLATFORM.getValue());
        offlineHandleEvent.setUpdateTime(now);
        offlineHandleEventService.save(offlineHandleEvent);
        // 更新活动状态为下线
        seckillMapper.updateStatus(seckill.getSeckillId(), SeckillEnum.OFFLINE.getValue());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditSeckill(OfflineHandleEventAuditParam offlineHandleEventAuditParam, Long sysUserId) {
        // 审核通过
        if (Objects.equals(offlineHandleEventAuditParam.getStatus(), OfflineHandleEventStatus.AGREE_BY_PLATFORM.getValue())) {
            // 更新秒杀活动为正常状态
            seckillMapper.updateStatus(offlineHandleEventAuditParam.getHandleId(), SeckillEnum.NORMAL.getValue());
        }
        // 审核不通过
        else if (Objects.equals(offlineHandleEventAuditParam.getStatus(), OfflineHandleEventStatus.DISAGREE_BY_PLATFORM.getValue())) {
            seckillMapper.updateStatus(offlineHandleEventAuditParam.getHandleId(), SeckillEnum.OFFLINE.getValue());
        }
        // 更新审核时间
        offlineHandleEventService.auditOfflineEvent(offlineHandleEventAuditParam, sysUserId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditApply(Long eventId, Long seckillId, String reapplyReason) {
        // 更新活动为待审核状态
        seckillMapper.updateStatus(seckillId, SeckillEnum.WAIT_AUDIT.getValue());
        // 更新事件状态
        offlineHandleEventService.updateToApply(eventId, reapplyReason);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changeProdTypeBySeckillIdList(List<Long> seckillIds) {
        seckillMapper.changeProdTypeBySeckillIdList(seckillIds);
        seckillMapper.changeSeckillStatusBySeckillIdList(seckillIds);
    }
    @Override
    public Seckill getSeckillByOrderNumber(String orderNumber) {
        return seckillMapper.getSeckillByOrderNumber(orderNumber);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addInventory(Long seckillId, Integer count) {
        seckillMapper.addInventory(seckillId, count);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Seckill> invalidByShopId(Long shopId) {
        // 获取正在进行中的秒杀活动
        List<Seckill> seckills = seckillMapper.selectList(Wrappers.lambdaQuery(Seckill.class)
                .select(Seckill::getSeckillId, Seckill::getProdId)
                .eq(Seckill::getShopId, shopId)
                .eq(Seckill::getIsDelete, 0)
                .eq(Seckill::getStatus, SeckillEnum.NORMAL.getValue())
        );
        if (CollUtil.isEmpty(seckills)) {
            return new ArrayList<>();
        }
        List<Long> seckillIds = new ArrayList<>();
        List<Long> prodIds = new ArrayList<>();
        for (Seckill seckill : seckills) {
            seckillIds.add(seckill.getSeckillId());
            prodIds.add(seckill.getProdId());
        }
        // 将商品变回普通商品
        productService.update(Wrappers.lambdaUpdate(Product.class)
                .set(Product::getActivityId, 0L)
                .set(Product::getProdType, ProdType.PROD_TYPE_NORMAL.value())
                .in(Product::getProdId, prodIds)
        );
        // 失效秒杀活动
        update(Wrappers.lambdaUpdate(Seckill.class)
                .set(Seckill::getStatus, SeckillEnum.INVALID.getValue())
                .in(Seckill::getSeckillId, seckillIds)
        );
        // 更新es中的商品数据
        eventPublisher.publishEvent(new EsProductUpdateEvent(null, prodIds, EsOperationType.UPDATE_BATCH));
        return seckills;
    }
    @Override
    public Boolean handlerSeckillOrderStock(OrderItem orderItem, SeckillOrder seckillOrder) {
        // 这里一进来就减库存,但是为了防止少卖,120秒会自己更新库存~因为缓存只有60秒
        boolean removeStockCache = decrSeckillSkuStocks(seckillOrder.getSeckillSkuId(), seckillOrder.getSeckillId(), seckillOrder.getProdCount(),orderItem.getProdId());
        // 更新活动剩余库存,为false时是本商品已被秒杀完毕,下次记得早点来哟
        boolean removeStock = seckillMapper.updateStocksById(seckillOrder.getSeckillId(), orderItem.getProdCount()) >= 1;
        // 更新秒杀sku库存,乐观锁
        boolean removeSkuStock = seckillSkuMapper.updateStocks(seckillOrder.getSeckillSkuId(), orderItem.getProdCount()) >= 1;
        return  removeStockCache && removeStock && removeSkuStock;
    }
    @Override
    @Cacheable(cacheNames = "SeckillGetByProdId", key = "#prodId", sync = true)
    public Seckill getByProdId(Long prodId) {
        return seckillMapper.selectOne(new LambdaQueryWrapper<Seckill>().eq(Seckill::getProdId, prodId).eq(Seckill::getStatus, SeckillEnum.NORMAL.getValue()));
    }
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public IPage<Product> pageSeckillNormalProd(PageParam<Product> page, ProductParam product) {
        return seckillMapper.pageSeckillNormalProd(page, product);
    }
    @Override
    public IPage<SeckillPageDto> pageSeckill(PageParam<Seckill> page, SeckillVO seckillVO) {
        return seckillMapper.pageSeckill(page, seckillVO);
    }
    private boolean decrSeckillSkuStocks(Long seckillSkuId, Long seckillId, Integer prodCount, Long prodId) {
        String key = SECKILL_SKU_STOCKS_PREFIX + seckillSkuId;
        long var = 10L;
        long var2 = 10L;
        Long cacheStocks = RedisUtil.getLongValue(key);
        if (cacheStocks != null && cacheStocks <= 0) {
            return false;
        }
        // 如果没有库存就缓存一个库存
        if (cacheStocks == null || RedisUtil.getExpire(key) < 1) {
            // 加锁,防止缓存击穿
            RLock rLock = redissonClient.getLock(REDISSON_LOCK_PREFIX + ":getSeckillSkuStocks");
            try {
                if (rLock.tryLock(var,var2, TimeUnit.SECONDS)){
                    // 再获取一遍缓存
                    cacheStocks = RedisUtil.getLongValue(key);
                    if (cacheStocks == null) {
                        // cacheNames = "SeckillByProdId"
                        cacheManagerUtil.evictCache(SeckillCacheNames.SECKILL_BY_PROD_ID, String.valueOf(prodId));
                        seckillSkuService.removeSeckillCacheSkuById(seckillSkuId,seckillId);
                        Integer seckillStocks = seckillSkuService.getSeckillSkuById(seckillSkuId).getSeckillStocks();
                        if (seckillStocks > 0) {
                            RedisUtil.setLongValue(key, Long.valueOf(seckillStocks),-1);
                        } else {
                            RedisUtil.setLongValue(key, 0L, 30);
                        }
                        cacheStocks = Long.valueOf(seckillStocks);
                    }
                } else {
                    // 网络异常
                    return false;
                }
            } catch (InterruptedException e) {
                log.error("InterruptedException:", e);
            } finally {
                try {
                    if (rLock.isLocked()) {
                        rLock.unlock();
                    }
                }catch (Exception e){
                    log.error("Exception:", e);
                }
            }
        }
        if (cacheStocks == null || cacheStocks < prodCount || RedisUtil.decr(key, prodCount) < 0) {
            RedisUtil.expire(key,30);
            // 本轮商品已被秒杀完毕,还有用户未支付,还有机会哟
            return false;
        }
        return true;
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/java/com/yami/shop/seckill/common/service/impl/SeckillSkuServiceImpl.java
New file
@@ -0,0 +1,87 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.common.util.CacheManagerUtil;
import com.yami.shop.seckill.common.dao.SeckillSkuMapper;
import com.yami.shop.seckill.common.model.SeckillSku;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 * 秒杀活动sku
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Service
@AllArgsConstructor
public class SeckillSkuServiceImpl extends ServiceImpl<SeckillSkuMapper, SeckillSku> implements SeckillSkuService {
    private final SeckillSkuMapper seckillSkuMapper;
    private final CacheManagerUtil cacheManagerUtil;
    @Override
    @Cacheable(cacheNames = "SeckillSkuBySeckillId", key = "#seckillId")
    public List<SeckillSku> listSeckillSkuBySeckillId(Long seckillId) {
        return seckillSkuMapper.selectList(new LambdaQueryWrapper<SeckillSku>().eq(SeckillSku::getSeckillId,seckillId));
    }
    @Override
    @CacheEvict(cacheNames = "SeckillSkuBySeckillId", key = "#seckillId")
    public void removeSeckillSkuCacheBySeckillId(Long seckillId) {
        List<SeckillSku> seckillSkus = listSeckillSkuBySeckillId(seckillId);
        //清除缓存
        if (CollectionUtils.isNotEmpty(seckillSkus)) {
            for (SeckillSku seckillSku : seckillSkus) {
                // 不能采用this清除缓存,因为这样没有aop
                cacheManagerUtil.evictCache("SeckillSkuById", String.valueOf(seckillSku.getSeckillSkuId()));
            }
        }
    }
    @Override
    @Cacheable(cacheNames = "SeckillSkuById", key = "#seckillSkuId")
    public SeckillSku getSeckillSkuById(Long seckillSkuId) {
        return seckillSkuMapper.selectById(seckillSkuId);
    }
    @Override
    @Caching(evict = {
            @CacheEvict(cacheNames = "SeckillSkuById", key = "#seckillSkuId"),
            @CacheEvict(cacheNames = "SeckillSkuBySeckillId", key = "#seckillId"),
            @CacheEvict(cacheNames = "SeckillById", key = "#seckillId")
    })
    public void removeSeckillCacheSkuById(Long seckillSkuId,Long seckillId) {
    }
    @Override
    public SeckillSku getSeckillSkuBySkuIdAndOrderNumber(Long skuId, String orderNumber) {
        return seckillSkuMapper.getSeckillSkuBySkuIdAndOrderNumber(skuId, orderNumber);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addInventory(Long seckillSkuId, Integer count) {
        seckillSkuMapper.addInventory(seckillSkuId, count);
    }
}
yami-shop-seckill/yami-shop-seckill-common/src/main/resources/mapper/SeckillMapper.xml
New file
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yami.shop.seckill.common.dao.SeckillMapper">
    <resultMap id="seckillMap" type="com.yami.shop.seckill.common.model.Seckill">
        <id column="seckill_id" property="seckillId"/>
        <result column="seckill_name" property="seckillName"/>
        <result column="start_time" property="startTime"/>
        <result column="end_time" property="endTime"/>
        <result column="seckill_tag" property="seckillTag"/>
        <result column="max_num" property="maxNum"/>
        <result column="max_cancel_time" property="maxCancelTime"/>
        <result column="shop_id" property="shopId"/>
        <result column="is_delete" property="isDelete"/>
        <result column="status" property="status"/>
        <result column="seckill_total_stocks" property="seckillTotalStocks"/>
        <result column="seckill_origin_stocks" property="seckillOriginStocks"/>
        <result column="seckill_price" property="seckillPrice"/>
    </resultMap>
    <select id="pageSeckillProd" resultType="com.yami.shop.seckill.common.dto.SeckillProductSimpleDto">
        select s.seckill_id,s.seckill_name,s.start_time,s.end_time,s.seckill_total_stocks,
        s.seckill_origin_stocks,s.seckill_price,p.prod_id,ifnull(pl.prod_name,p.prod_name) as prod_name,p.pic,p.price
        from tz_seckill s
        join tz_prod p on s.seckill_id = p.activity_id and p.status = 1 AND p.prod_type = 2
        join tz_shop_detail sd on s.shop_id = sd.shop_id and sd.shop_status = 1
        LEFT JOIN tz_prod_lang pl on pl.prod_id = p.prod_id and pl.lang =#{dbLang}
        where s.status = 1 and s.is_delete = 0 AND s.`end_time` &gt;= NOW()
        order by s.start_time desc
    </select>
    <select id="getPlatformSeckillPage" resultType="com.yami.shop.seckill.common.dto.SeckillPageDto">
        SELECT s.*,sd.`shop_name`, tp.prod_name,tp.pic FROM tz_seckill s
        LEFT JOIN tz_shop_detail sd ON s.`shop_id` = sd.`shop_id`
        LEFT JOIN tz_prod tp on tp.prod_id = s.prod_id
        WHERE s.is_delete = 0
        <if test="seckill.shopName != null">
            and trim(replace(sd.shop_name,' ','')) like trim(replace(concat('%',#{seckill.shopName},'%'),' ',''))
        </if>
        <if test="seckill.seckillName != null">
            and trim(replace(s.seckill_name,' ','')) like trim(replace(concat('%',#{seckill.seckillName},'%'),' ',''))
        </if>
        <if test="seckill.status != null">
            AND s.status  = #{seckill.status}
        </if>
        ORDER BY s.`seckill_id` DESC
    </select>
    <update id="updateStatus">
        UPDATE tz_seckill s SET s.`status` =#{status} WHERE s.`seckill_id` = #{seckillId}
    </update>
    <update id="returnStocksByOrderNumber">
        UPDATE tz_seckill s JOIN tz_seckill_order so ON s.seckill_id = so.seckill_id AND so.order_number = #{orderNumber}
        SET s.seckill_total_stocks = s.seckill_total_stocks + so.prod_count
    </update>
    <update id="returnStocks">
        UPDATE tz_seckill s
        JOIN
        (
            SELECT so.seckill_id,so.seckill_sku_id,SUM(so.prod_count) AS prod_count
            FROM tz_seckill_order so
            JOIN tz_seckill s ON s.seckill_id = so.seckill_id AND so.state = 0
            AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(so.`create_time`))/60 &gt; s.`max_cancel_time`
            GROUP BY so.seckill_sku_id
        ) t
        ON s.seckill_id = t.seckill_id
        SET s.seckill_total_stocks = s.seckill_total_stocks + t.prod_count
    </update>
    <update id="updateStocksById">
        UPDATE tz_seckill set seckill_total_stocks = seckill_total_stocks - #{prodCount} where seckill_id = #{seckillId} and seckill_total_stocks >= #{prodCount}
    </update>
    <update id="changeProdTypeBySeckillIdList">
      UPDATE tz_prod SET prod_type = 0 WHERE prod_id IN
      (SELECT prod_id FROM tz_seckill WHERE seckill_id IN
        <foreach collection="seckillIds" item="seckillId" separator="," open="(" close=")">
            #{seckillId}
        </foreach>
      GROUP BY prod_id
      )
    </update>
    <update id="changeSeckillStatusBySeckillIdList">
        UPDATE tz_seckill SET `status` = 0 WHERE seckill_id IN
         <foreach collection="seckillIds" item="seckillId" separator="," open="(" close=")">
             #{seckillId}
         </foreach>
    </update>
    <select id="getSeckillByOrderNumber" resultType="com.yami.shop.seckill.common.model.Seckill">
        SELECT
            s.*
        FROM
            tz_seckill s
            LEFT JOIN tz_seckill_order so ON s.seckill_id = so.seckill_id
        WHERE
            so.order_number = #{orderNumber}
    </select>
    <select id="pageSeckillNormalProd" resultType= "com.yami.shop.bean.model.Product">
        SELECT IFNULL(l.prod_name,p.prod_name) AS prod_name,ts.seckill_price AS activity_price,
        ts.seckill_total_stocks as activity_total_stocks, p.*,s.shop_name  FROM tz_prod p
        LEFT JOIN tz_prod_lang l ON l.`prod_id` = p.`prod_id` AND l.lang = 0
        LEFT JOIN tz_shop_detail s ON p.shop_id = s.shop_id
        JOIN `tz_seckill` ts on ts.prod_id = p.prod_id and ts.status = 1
        <where>
            <if test="product.shopId != null">
                and p.`shop_id` =#{product.shopId}
            </if>
            <if test="product.shopCategoryId != null">
                and p.shop_category_id = #{product.shopCategoryId}
            </if>
            <if test="product.categoryId != null">
                and p.category_id = #{product.categoryId}
            </if>
            <if test="product.prodName != null and product.prodName != ''">
                and trim(replace(l.prod_name,' ','')) like trim(replace(concat('%',#{product.prodName},'%'),' ',''))
            </if>
            <if test="product.status != null">
                and p.status = #{product.status}
            </if>
        </where>
        order by
        <choose>
            <when test="product.sortParam == 1 and product.sortType == 1">
                seq ASC,
            </when>
            <when test="product.sortParam == 1 and product.sortType == 2">
                seq DESC,
            </when>
        </choose>
        p.putaway_time desc
    </select>
    <select id="pageSeckill" resultType="com.yami.shop.seckill.common.dto.SeckillPageDto">
        SELECT ts.*,tp.prod_name,tp.pic
        FROM tz_seckill ts
        JOIN tz_prod tp ON tp.`prod_id` = ts.`prod_id`
        LEFT JOIN tz_prod_lang tpl ON tpl.prod_id = tp.prod_id AND tpl.lang = #{seckill.lang}
        WHERE ts.`is_delete` = 0 AND ts.`shop_id` = #{seckill.shopId}
        <if test="seckill.status != null">
            AND ts.`status` = #{seckill.status}
        </if>
        <if test="seckill.seckillName != null and seckill.seckillName != ''">
            AND ts.seckill_name  LIKE CONCAT('%', #{seckill.seckillName} ,'%')
        </if>
        <if test="seckill.prodName != null and seckill.prodName != ''">
            and ifnull(tpl.prod_name,tp.prod_name) like concat('%',#{seckill.prodName},'%')
        </if>
        ORDER BY seckill_id DESC
    </select>
    <update id="addInventory">
        UPDATE tz_seckill SET seckill_total_stocks = seckill_total_stocks + #{countNum} WHERE `seckill_id` = #{seckillId}
    </update>
</mapper>
yami-shop-seckill/yami-shop-seckill-common/src/main/resources/mapper/SeckillOrderMapper.xml
New file
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yami.shop.seckill.common.dao.SeckillOrderMapper">
  <resultMap id="seckillOrderMap" type="com.yami.shop.seckill.common.model.SeckillOrder">
    <id column="seckill_order_id" property="seckillOrderId" />
    <result column="seckill_id" property="seckillId"/>
    <result column="user_id" property="userId"/>
    <result column="order_number" property="orderNumber"/>
    <result column="prod_count" property="prodCount"/>
    <result column="create_time" property="createTime"/>
    <result column="prod_id" property="prodId" />
    <result column="state" property="state" />
  </resultMap>
    <update id="updateStateByOrderNumber">
      update tz_seckill_order set state =#{state} where order_number = #{orderNumber}
    </update>
  <update id="cancelUnpayOrderByOrderNumber">
    UPDATE tz_seckill_order so
    set so.state = -1
    where order_number = #{orderNumber}
  </update>
  <update id="cancelUnpayOrder">
    UPDATE tz_seckill_order so JOIN tz_seckill s ON s.seckill_id = so.seckill_id AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(so.`create_time`))/60 &gt; s.`max_cancel_time` and so.`state` = 0
    SET so.`state` = -1
  </update>
  <select id="selectNumByUser" resultType="int">
    SELECT ifnull(SUM(so.prod_count),0) FROM tz_seckill_order so
    JOIN tz_order o ON so.`order_number` = o.`order_number` AND o.`status` <![CDATA[ <> ]]> 6
    WHERE so.seckill_id = #{seckillId} AND so.prod_id = #{prodId}
    AND so.user_id = #{userId} AND so.state <![CDATA[ <> ]]>  -1
  </select>
</mapper>
yami-shop-seckill/yami-shop-seckill-common/src/main/resources/mapper/SeckillSkuMapper.xml
New file
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yami.shop.seckill.common.dao.SeckillSkuMapper">
  <resultMap id="seckillSkuMap" type="com.yami.shop.seckill.common.model.SeckillSku">
    <id column="seckill_sku_id" property="seckillSkuId" />
    <result column="sku_id" property="skuId"/>
    <result column="seckill_id" property="seckillId"/>
    <result column="seckill_stocks" property="seckillStocks"/>
    <result column="seckill_price" property="seckillPrice"/>
  </resultMap>
    <update id="updateStocks">
      update tz_seckill_sku set seckill_stocks = seckill_stocks - #{prodCount}  where seckill_sku_id = #{seckillSkuId} and seckill_stocks &gt;= #{prodCount}
    </update>
    <update id="returnStocks">
      UPDATE tz_seckill_sku ss JOIN (
              SELECT so.seckill_sku_id,SUM(so.prod_count) AS prod_count
              FROM tz_seckill_order so
                           JOIN tz_seckill s ON s.seckill_id = so.seckill_id AND so.state = 0
                      AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(so.`create_time`))/60 &gt; s.`max_cancel_time`
              GROUP BY so.seckill_sku_id
        ) t
        ON ss.seckill_sku_id = t.seckill_sku_id
        set ss.seckill_stocks = ss.seckill_stocks + t.prod_count
    </update>
    <update id="returnStocksByOrderNumber">
      UPDATE tz_seckill_sku ss JOIN (
        SELECT so.seckill_sku_id,so.prod_count  FROM tz_seckill_order so JOIN
                                                     tz_seckill s ON s.seckill_id = so.seckill_id and so.state = 0
                                                       AND so.order_number = #{orderNumber} ) t
        ON ss.seckill_sku_id = t.seckill_sku_id
      set ss.seckill_stocks = ss.seckill_stocks + t.prod_count
    </update>
    <select id="getSeckillSkuBySkuIdAndOrderNumber" resultType="com.yami.shop.seckill.common.model.SeckillSku">
      SELECT
        ss.*
      FROM
        tz_seckill s
          LEFT JOIN tz_seckill_sku ss ON s.seckill_id = ss.seckill_id
          LEFT JOIN tz_seckill_order so ON s.seckill_id = so.seckill_id
      WHERE so.order_number = #{orderNumber} AND ss.sku_id = #{skuId}
    </select>
  <update id="addInventory">
    UPDATE tz_seckill_sku set seckill_stocks = seckill_stocks + #{countNum} where seckill_sku_id = #{seckillSkuId}
  </update>
  <select id="listSkuIds" resultType="java.lang.Long">
    SELECT so.seckill_sku_id
    FROM tz_seckill_order so
           JOIN tz_seckill s ON s.seckill_id = so.seckill_id AND so.state = 0
      AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(so.`create_time`))/60 > s.`max_cancel_time`
    GROUP BY so.seckill_sku_id
  </select>
  <select id="listSeckillMinOriginalPrice" resultType="com.yami.shop.seckill.common.model.SeckillSku">
    SELECT ss.seckill_id,MIN(s.price) seckill_price FROM tz_seckill_sku ss
    JOIN tz_sku s ON ss.sku_id = s.sku_id
    JOIN tz_seckill seckill ON seckill.seckill_id = ss.seckill_id AND seckill.seckill_price = ss.seckill_price
    WHERE ss.seckill_id IN
    <foreach collection="seckillIds" item="seckillId" open="(" close=")" separator=",">
      #{seckillId}
    </foreach>
    GROUP BY seckill_id
    </select>
</mapper>
yami-shop-seckill/yami-shop-seckill-multishop/pom.xml
New file
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.yami.shop</groupId>
        <artifactId>yami-shop-seckill</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>yami-shop-seckill-multishop</artifactId>
    <modelVersion>4.0.0</modelVersion>
    <description>商城满减模块后台管理部分</description>
    <dependencies>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-seckill-common</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yami.shop</groupId>
            <artifactId>yami-shop-security-multishop</artifactId>
            <version>${yami.shop.version}</version>
        </dependency>
    </dependencies>
</project>
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/config/SwaggerConfiguration.java
New file
@@ -0,0 +1,37 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.multishop.config;
import lombok.AllArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 秒杀的swagger文档
 * @author LGH
 */
@Configuration("seckillSwaggerConfiguration")
@AllArgsConstructor
public class SwaggerConfiguration {
    @Bean
    public GroupedOpenApi seckillRestApi() {
        return GroupedOpenApi.builder()
                .group("秒杀接口")
                .packagesToScan("com.yami.shop.seckill.multishop.controller")
                .pathsToMatch("/**")
                .build();
    }
}
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/controller/SeckillController.java
New file
@@ -0,0 +1,231 @@
/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许,不可做商业用途!
 *
 * 版权所有,侵权必究!
 */
package com.yami.shop.seckill.multishop.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.app.vo.SeckillVO;
import com.yami.shop.bean.enums.EsOperationType;
import com.yami.shop.bean.enums.OfflineHandleEventType;
import com.yami.shop.bean.enums.ProdType;
import com.yami.shop.bean.enums.StationEnum;
import com.yami.shop.bean.event.EsProductUpdateEvent;
import com.yami.shop.bean.model.OfflineHandleEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.model.Sku;
import com.yami.shop.bean.param.OfflineHandleEventAuditParam;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.dao.SkuMapper;
import com.yami.shop.seckill.common.dto.SeckillPageDto;
import com.yami.shop.seckill.common.enums.SeckillEnum;
import com.yami.shop.seckill.common.model.Seckill;
import com.yami.shop.seckill.common.model.SeckillSku;
import com.yami.shop.seckill.common.service.SeckillService;
import com.yami.shop.seckill.common.service.SeckillSkuService;
import com.yami.shop.seckill.multishop.dto.SeckillProdDto;
import com.yami.shop.seckill.multishop.param.SeckillParam;
import com.yami.shop.security.multishop.util.SecurityUtils;
import com.yami.shop.service.OfflineHandleEventService;
import com.yami.shop.service.ProductService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import ma.glasnost.orika.MapperFacade;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 秒杀信息
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@RestController
@AllArgsConstructor
@RequestMapping("/seckill/seckill")
@Tag(name = "商家端秒杀信息接口")
public class SeckillController {
    private final SeckillService seckillService;
    private final MapperFacade mapperFacade;
    private final ProductService productService;
    private final ApplicationEventPublisher eventPublisher;
    private final SeckillSkuService seckillSkuService;
    private final OfflineHandleEventService offlineHandleEventService;
    private final SkuMapper skuMapper;
    @GetMapping("/page")
    @Operation(summary = "分页查找秒杀信息")
    public ServerResponseEntity<IPage<SeckillPageDto>> getSeckillPage(PageParam<Seckill> page, @ParameterObject SeckillVO seckill) {
        seckill.setShopId(SecurityUtils.getShopUser().getShopId());
        seckill.setLang(I18nMessage.getDbLang());
        IPage<SeckillPageDto> pageSeckill = seckillService.pageSeckill(page, seckill);
        return ServerResponseEntity.success(pageSeckill);
    }
    @GetMapping("/info/{seckillId}")
    @Operation(summary = "通过id查询秒杀信息")
    @Parameter(name = "seckillId", description = "秒杀id" , required = true)
    public ServerResponseEntity<SeckillProdDto> getById(@PathVariable("seckillId") Long seckillId) {
        Seckill seckill = seckillService.getById(seckillId);
        if (!Objects.equals(SecurityUtils.getShopUser().getShopId(), seckill.getShopId())) {
            throw new YamiShopBindException("yami.no.auth");
        }
        SeckillProdDto dto = new SeckillProdDto();
        dto.setSeckill(seckill);
        Product product = productService.getProductByProdId(seckill.getProdId(), I18nMessage.getDbLang());
        dto.setProd(product);
        dto.setSeckillSkus(seckillSkuService.listSeckillSkuBySeckillId(seckillId));
        return ServerResponseEntity.success(dto);
    }
    @PostMapping
    @PreAuthorize("@pms.hasPermission('seckill:seckill:save')")
    @Operation(summary = "新增秒杀信息")
    public ServerResponseEntity<Void> save(@RequestBody @Valid SeckillParam seckillParam) {
        Seckill seckill = mapperFacade.map(seckillParam, Seckill.class);
        if(Objects.isNull(seckill.getMaxNum())||Objects.equals(seckill.getMaxNum(),0)){
            //请输入限购数量
            throw new YamiShopBindException("yami.seckill.input.maxNum");
        }
        Integer prodCount = productService.count(new LambdaQueryWrapper<Product>()
                .eq(Product::getShopId, SecurityUtils.getShopUser().getShopId())
//                .and(wrapper -> wrapper.isNull(Product::getGroupActivityId).or().eq(Product::getGroupActivityId, 0))
//                .and(wrapper -> wrapper.isNull(Product::getSeckillActivityId).or().eq(Product::getSeckillActivityId, 0))
                .and(wrapper -> wrapper.isNull(Product::getProdType).or().eq(Product::getProdType, ProdType.PROD_TYPE_NORMAL))
                .eq(Product::getProdId, seckillParam.getProdId()));
        if (prodCount == 0) {
            // 商品无法参与秒杀活动
            throw new YamiShopBindException("yami.seckill.prod");
        }
        List<SeckillSku> seckillSkus = seckillParam.getSeckillSkus();
        List<Long> skuIds = seckillSkus.stream().map(SeckillSku::getSkuId).collect(Collectors.toList());
        List<Sku> skuList = skuMapper.getSkuBySkuIds(skuIds);
        Map<Long, Integer> skuStatusMap = skuList.stream().collect(Collectors.toMap(Sku::getSkuId, Sku::getStatus));
        Integer seckillTotalStocks = 0;
        double seckillPrice = Double.MAX_VALUE;
        for (SeckillSku seckillSku : seckillSkus) {
            //禁用状态的sku不能参与秒杀活动
            if(skuStatusMap.get(seckillSku.getSkuId()) == 0){
                continue;
            }
            seckillTotalStocks += seckillSku.getSeckillStocks();
            seckillPrice = Math.min(seckillPrice, seckillSku.getSeckillPrice());
            //修改sku状态
            Sku sku = new Sku();
            sku.setSkuId(seckillSku.getSkuId());
            sku.setStatus(seckillSku.getStatus());
            sku.setPic(seckillSku.getPic());
            skuMapper.updateSkuById(sku);
        }
        if(seckillTotalStocks == 0){
            // 商品无法参与秒杀活动
            throw new YamiShopBindException("yami.seckill.prod");
        }
        // 库存
        seckill.setSeckillOriginStocks(seckillTotalStocks);
        seckill.setSeckillTotalStocks(seckillTotalStocks);
        // 配置店铺id
        seckill.setShopId(SecurityUtils.getShopUser().getShopId());
        seckill.setProdId(seckillParam.getProdId());
        seckill.setSeckillPrice(seckillPrice);
        seckillService.saveSeckill(seckill, seckillSkus);
        // 清除缓存
        productService.removeProdCacheByProdId(seckillParam.getProdId());
        seckillSkuService.removeSeckillSkuCacheBySeckillId(seckill.getSeckillId());
        seckillService.removeSeckillCache(seckill.getSeckillId(), seckill.getProdId());
        eventPublisher.publishEvent(new EsProductUpdateEvent(seckill.getProdId(), null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
    @PutMapping("/invalid/{seckillId}")
    @PreAuthorize("@pms.hasPermission('seckill:seckill:invalid')")
    @Operation(summary = "通过id使秒杀商品失效")
    @Parameter(name = "seckillId", description = "秒杀id" , required = true)
    public ServerResponseEntity<Void> invalidById(@PathVariable Long seckillId) {
        Seckill seckill = seckillService.getById(seckillId);
        if (!SecurityUtils.getShopUser().getShopId().equals(seckill.getShopId())) {
            throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED);
        }
        if (Objects.equals(seckill.getStatus(), SeckillEnum.OFFLINE.getValue())) {
            throw new YamiShopBindException("yami.seckill.cannot.invalid");
        }
        seckillService.invalidById(seckill);
        productService.removeProdCacheByProdId(seckill.getProdId());
        seckillSkuService.removeSeckillSkuCacheBySeckillId(seckill.getSeckillId());
        seckillService.removeSeckillCache(seckill.getSeckillId(), seckill.getProdId());
        return ServerResponseEntity.success();
    }
    @DeleteMapping("/{seckillId}")
    @PreAuthorize("@pms.hasPermission('seckill:seckill:delete')")
    @Operation(summary = "通过id删除秒杀信息")
    @Parameter(name = "seckillId", description = "秒杀id" , required = true)
    public ServerResponseEntity<Void> removeById(@PathVariable Long seckillId) {
        Seckill seckill = seckillService.getById(seckillId);
        Integer oldStatus = seckill.getStatus();
        if (Objects.equals(StationEnum.NORMAL.getValue(), seckill.getStatus())) {
            // 秒杀活动未关闭,无法删除
            throw new YamiShopBindException("yami.seckill.cannot.delete");
        }
        seckillService.invalidById(seckill);
        seckill.setIsDelete(1);
        seckill.setStatus(oldStatus);
        seckillService.updateById(seckill);
        productService.removeProdCacheByProdId(seckill.getProdId());
        seckillSkuService.removeSeckillSkuCacheBySeckillId(seckill.getSeckillId());
        seckillService.removeSeckillCache(seckill.getSeckillId(), seckill.getProdId());
        return ServerResponseEntity.success();
    }
    @GetMapping("/getOfflineHandleEventBySeckillId/{seckillId}")
    @Operation(summary = "通过活动id获取下线信息")
    @Parameter(name = "seckillId", description = "秒杀id" , required = true)
    public ServerResponseEntity<OfflineHandleEvent> getOfflineHandleEventBySeckillId(@PathVariable("seckillId") Long seckillId) {
        OfflineHandleEvent offlineHandleEvent = offlineHandleEventService.getProcessingEventByHandleTypeAndHandleId(OfflineHandleEventType.SECKILL.getValue(), seckillId);
        return ServerResponseEntity.success(offlineHandleEvent);
    }
    @PostMapping("/auditApply")
    @PreAuthorize("@pms.hasPermission('seckill:seckill:auditApply')")
    @Operation(summary = "秒杀活动提交审核")
    public ServerResponseEntity<Void> auditApply(@RequestBody OfflineHandleEventAuditParam offlineHandleEventAuditParam) {
        Seckill seckill = seckillService.getById(offlineHandleEventAuditParam.getHandleId());
        if (seckill == null) {
            // 未找到此活动,请稍后重试
            throw new YamiShopBindException("yami.activity.cannot.find");
        }
        seckillService.auditApply(offlineHandleEventAuditParam.getEventId(), offlineHandleEventAuditParam.getHandleId(), offlineHandleEventAuditParam.getReapplyReason());
        // 移除缓存
        seckillService.removeSeckillCache(seckill.getSeckillId(), seckill.getProdId());
        eventPublisher.publishEvent(new EsProductUpdateEvent(seckill.getProdId(), null, EsOperationType.UPDATE));
        return ServerResponseEntity.success();
    }
}
Diff truncated after the above file
yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/controller/SeckillOrderController.java yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/dto/SeckillProdDto.java yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/listener/OrderRefundListener.java yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/listener/UpdateSeckillOrderSkuInfoListener.java yami-shop-seckill/yami-shop-seckill-multishop/src/main/java/com/yami/shop/seckill/multishop/param/SeckillParam.java yami-shop-seckill/yami-shop-seckill-platform/pom.xml yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/config/SwaggerConfiguration.java yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/controller/SeckillController.java yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/controller/SeckillOrderController.java yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/dto/SeckillProdDto.java yami-shop-seckill/yami-shop-seckill-platform/src/main/java/com/yami/shop/seckill/platform/task/SeckillOrderTask.java