From d36485c0b34add9872adc81a13dc542af25b1252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sat, 7 Feb 2026 16:31:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=B7=BB=E5=8A=A0AI=E4=BA=91?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增.gitignore文件配置,忽略IDE和构建相关文件 - 添加AbstractAuditContentService抽象类,提供审计内容服务通用方法 - 创建AiCloudDataCenterUtil工具类,封装阿里云百炼服务API操作 - 新增AiCloudDoc实体类,定义AI云文档目录表结构 - 实现AiCloudDocController控制器,提供文档目录CRUD接口 - 添加前端页面模板文件,支持新增配置和TSX组件生成 --- .gitignore | 44 + Dockerfile | 41 + README.md | 286 +++ docker-compose.yml | 38 + docker-deploy-guide.md | 188 ++ pom.xml | 427 ++++ .../com/gxwebsoft/WebSoftApplication.java | 31 + .../gxwebsoft/ai/config/AuditExcelStyle.java | 120 + .../ai/config/KnowledgeBaseConfig.java | 16 + .../gxwebsoft/ai/config/TemplateConfig.java | 16 + .../AuditContent10PartyConductConstants.java | 432 ++++ .../AuditContent11HistoryConstants.java | 90 + .../AuditContent1EightRegConstants.java | 150 ++ .../AuditContent1ExpenseConstants.java | 106 + .../AuditContent1LeaderListConstants.java | 58 + .../AuditContent2StrategyConstants.java | 194 ++ .../AuditContent3DecisionConstants.java | 33 + .../AuditContent3TripleConstants.java | 105 + .../AuditContent4TargetConstants.java | 151 ++ ...AuditContent5BudgetExecutionConstants.java | 260 +++ .../AuditContent5BudgetManageConstants.java | 323 +++ .../AuditContent6StateAssetsConstants.java | 155 ++ .../ai/constants/AuditContent7Constants.java | 147 ++ ...AuditContent8InternalControlConstants.java | 195 ++ .../AuditContent9PersonnelConstants.java | 273 +++ .../ai/constants/KnowledgeBaseConstants.java | 19 + .../ai/controller/AiCloudDocController.java | 148 ++ .../ai/controller/AiCloudFileController.java | 213 ++ .../ai/controller/AiHistoryController.java | 127 ++ .../controller/AuditContent10Controller.java | 72 + .../controller/AuditContent11Controller.java | 91 + .../controller/AuditContent1Controller.java | 163 ++ .../controller/AuditContent2Controller.java | 72 + .../controller/AuditContent3Controller.java | 118 + .../controller/AuditContent4Controller.java | 86 + .../controller/AuditContent5Controller.java | 127 ++ .../controller/AuditContent6Controller.java | 91 + .../controller/AuditContent7Controller.java | 72 + .../controller/AuditContent8Controller.java | 75 + .../controller/AuditContent9Controller.java | 72 + .../controller/AuditEvidenceController.java | 214 ++ .../ai/controller/AuditReportController.java | 208 ++ .../ai/controller/AuditReportController2.java | 128 ++ .../BaseAuditContentController.java | 352 +++ .../controller/KnowledgeBaseController.java | 108 + .../gxwebsoft/ai/dto/AuditContentRequest.java | 62 + .../ai/dto/AuditEvidenceRequest.java | 35 + .../gxwebsoft/ai/dto/AuditReportRequest.java | 89 + .../ai/dto/KnowledgeBaseRequest.java | 20 + .../export/BudgetExecutionExportEntity.java | 37 + .../dto/export/BudgetManageExportEntity.java | 64 + .../dto/export/DecisionTableExportEntity.java | 40 + .../ai/dto/export/EightRegExportEntity.java | 27 + .../ai/dto/export/ExpenseExportEntity.java | 38 + .../dto/export/HistoryTableExportEntity.java | 46 + .../export/InternalControlExportEntity.java | 40 + .../InvestmentSituationExportEntity.java | 31 + .../ai/dto/export/LeaderListExportEntity.java | 38 + .../dto/export/PartyConductExportEntity.java | 31 + .../ai/dto/export/PersonnelExportEntity.java | 40 + .../export/StateAssetsAuditExportEntity.java | 58 + .../dto/export/StrategyAuditExportEntity.java | 37 + .../dto/export/TargetAuditExportEntity.java | 43 + .../ai/dto/export/TripleOneExportEntity.java | 34 + .../com/gxwebsoft/ai/entity/AiCloudDoc.java | 61 + .../com/gxwebsoft/ai/entity/AiCloudFile.java | 76 + .../com/gxwebsoft/ai/entity/AiHistory.java | 63 + .../gxwebsoft/ai/enums/AuditReportEnum.java | 104 + .../factory/KnowledgeBaseClientFactory.java | 29 + .../gxwebsoft/ai/mapper/AiCloudDocMapper.java | 37 + .../ai/mapper/AiCloudFileMapper.java | 37 + .../gxwebsoft/ai/mapper/AiHistoryMapper.java | 35 + .../ai/mapper/xml/AiCloudDocMapper.xml | 63 + .../ai/mapper/xml/AiCloudFileMapper.xml | 73 + .../ai/mapper/xml/AiHistoryMapper.xml | 64 + .../gxwebsoft/ai/param/AiCloudDocParam.java | 57 + .../gxwebsoft/ai/param/AiCloudFileParam.java | 74 + .../gxwebsoft/ai/param/AiHistoryParam.java | 57 + .../ai/service/AiCloudDocService.java | 49 + .../ai/service/AiCloudFileService.java | 113 + .../ai/service/AiHistoryService.java | 63 + .../AuditContent10PartyConductService.java | 15 + .../service/AuditContent11HistoryService.java | 23 + .../service/AuditContent1EightRegService.java | 11 + .../service/AuditContent1ExpenseService.java | 21 + .../AuditContent1LeaderListService.java | 14 + .../service/AuditContent2StrategyService.java | 24 + .../service/AuditContent3DecisionService.java | 14 + .../service/AuditContent3TripleService.java | 14 + .../service/AuditContent4TargetService.java | 16 + .../AuditContent5BudgetExecutionService.java | 23 + .../AuditContent5BudgetManageService.java | 22 + .../AuditContent6StateAssetsService.java | 15 + .../AuditContent7InvestmentService.java | 23 + .../AuditContent8InternalControlService.java | 13 + .../AuditContent9PersonnelService.java | 13 + .../ai/service/AuditEvidenceService.java | 11 + .../ai/service/AuditReportService.java | 14 + .../ai/service/KnowledgeBaseService.java | 72 + .../impl/AbstractAuditContentService.java | 450 ++++ .../service/impl/AiCloudDocServiceImpl.java | 90 + .../service/impl/AiCloudFileServiceImpl.java | 238 ++ .../ai/service/impl/AiHistoryServiceImpl.java | 75 + ...AuditContent10PartyConductServiceImpl.java | 307 +++ .../AuditContent11HistoryServiceImpl.java | 291 +++ .../AuditContent1EightRegServiceImpl.java | 358 +++ .../impl/AuditContent1ExpenseServiceImpl.java | 290 +++ .../AuditContent1LeaderListServiceImpl.java | 296 +++ .../AuditContent2StrategyServiceImpl.java | 239 ++ .../AuditContent3DecisionServiceImpl.java | 274 +++ .../impl/AuditContent3TripleServiceImpl.java | 284 +++ .../impl/AuditContent4TargetServiceImpl.java | 333 +++ ...ditContent5BudgetExecutionServiceImpl.java | 314 +++ .../AuditContent5BudgetManageServiceImpl.java | 313 +++ .../AuditContent6StateAssetsServiceImpl.java | 337 +++ .../AuditContent7InvestmentServiceImpl.java | 359 +++ ...ditContent8InternalControlServiceImpl.java | 327 +++ .../AuditContent9PersonnelServiceImpl.java | 563 +++++ .../impl/AuditEvidenceServiceImpl.java | 204 ++ .../service/impl/AuditReportServiceImpl.java | 347 +++ .../impl/KnowledgeBaseServiceImpl.java | 221 ++ .../ai/util/AiCloudDataCenterUtil.java | 324 +++ .../ai/util/AiCloudKnowledgeBaseUtil.java | 210 ++ .../gxwebsoft/ai/util/AuditReportUtil.java | 91 + .../ai/util/KnowledgeBaseCreate.java | 384 ++++ .../ai/util/KnowledgeBaseManage.java | 145 ++ .../ai/util/KnowledgeBaseRetrieve.java | 110 + .../ai/util/KnowledgeBaseUpdate.java | 384 ++++ .../ai/util/KnowledgeBaseUploader.java | 303 +++ .../gxwebsoft/ai/util/KnowledgeBaseUtil.java | 156 ++ .../gxwebsoft/ai/utils/ExcelExportTool.java | 139 ++ .../auto/controller/QrLoginController.java | 104 + .../auto/dto/QrLoginConfirmRequest.java | 50 + .../com/gxwebsoft/auto/dto/QrLoginData.java | 55 + .../auto/dto/QrLoginGenerateResponse.java | 29 + .../auto/dto/QrLoginStatusResponse.java | 32 + .../auto/service/QrLoginService.java | 46 + .../auto/service/impl/QrLoginServiceImpl.java | 239 ++ .../bszx/controller/BszxBmController.java | 166 ++ .../bszx/controller/BszxBranchController.java | 121 + .../bszx/controller/BszxClassController.java | 156 ++ .../bszx/controller/BszxEraController.java | 121 + .../bszx/controller/BszxGradeController.java | 121 + .../bszx/controller/BszxOrderController.java | 91 + .../bszx/controller/BszxPayController.java | 343 +++ .../controller/BszxPayRankingController.java | 198 ++ .../com/gxwebsoft/bszx/entity/BszxBm.java | 151 ++ .../com/gxwebsoft/bszx/entity/BszxBranch.java | 43 + .../com/gxwebsoft/bszx/entity/BszxClass.java | 70 + .../com/gxwebsoft/bszx/entity/BszxEra.java | 43 + .../com/gxwebsoft/bszx/entity/BszxGrade.java | 53 + .../com/gxwebsoft/bszx/entity/BszxPay.java | 143 ++ .../gxwebsoft/bszx/entity/BszxPayRanking.java | 67 + .../gxwebsoft/bszx/mapper/BszxBmMapper.java | 37 + .../bszx/mapper/BszxBranchMapper.java | 37 + .../bszx/mapper/BszxClassMapper.java | 37 + .../gxwebsoft/bszx/mapper/BszxEraMapper.java | 37 + .../bszx/mapper/BszxGradeMapper.java | 37 + .../gxwebsoft/bszx/mapper/BszxPayMapper.java | 42 + .../bszx/mapper/BszxPayRankingMapper.java | 37 + .../bszx/mapper/xml/BszxBmMapper.xml | 113 + .../bszx/mapper/xml/BszxBranchMapper.xml | 36 + .../bszx/mapper/xml/BszxClassMapper.xml | 63 + .../bszx/mapper/xml/BszxEraMapper.xml | 36 + .../bszx/mapper/xml/BszxGradeMapper.xml | 54 + .../bszx/mapper/xml/BszxPayMapper.xml | 126 + .../bszx/mapper/xml/BszxPayRankingMapper.xml | 61 + .../com/gxwebsoft/bszx/param/BszxBmParam.java | 114 + .../gxwebsoft/bszx/param/BszxBranchParam.java | 37 + .../gxwebsoft/bszx/param/BszxClassParam.java | 64 + .../gxwebsoft/bszx/param/BszxEraParam.java | 37 + .../gxwebsoft/bszx/param/BszxGradeParam.java | 52 + .../gxwebsoft/bszx/param/BszxPayParam.java | 118 + .../bszx/param/BszxPayRankingParam.java | 57 + .../gxwebsoft/bszx/service/BszxBmService.java | 50 + .../bszx/service/BszxBranchService.java | 42 + .../bszx/service/BszxClassService.java | 42 + .../bszx/service/BszxEraService.java | 42 + .../bszx/service/BszxGradeService.java | 42 + .../bszx/service/BszxPayRankingService.java | 42 + .../bszx/service/BszxPayService.java | 57 + .../bszx/service/impl/BszxBmServiceImpl.java | 161 ++ .../service/impl/BszxBranchServiceImpl.java | 47 + .../service/impl/BszxClassServiceImpl.java | 68 + .../bszx/service/impl/BszxEraServiceImpl.java | 47 + .../service/impl/BszxGradeServiceImpl.java | 47 + .../impl/BszxPayRankingServiceImpl.java | 47 + .../bszx/service/impl/BszxPayServiceImpl.java | 169 ++ .../cms/controller/CmsAdController.java | 119 + .../cms/controller/CmsAdRecordController.java | 114 + .../CmsArticleCategoryController.java | 111 + .../CmsArticleCommentController.java | 120 + .../CmsArticleContentController.java | 113 + .../cms/controller/CmsArticleController.java | 362 +++ .../controller/CmsArticleCountController.java | 120 + .../controller/CmsArticleLikeController.java | 120 + .../cms/controller/CmsDesignController.java | 127 ++ .../controller/CmsDesignRecordController.java | 120 + .../cms/controller/CmsDomainController.java | 166 ++ .../cms/controller/CmsFormController.java | 120 + .../controller/CmsFormRecordController.java | 120 + .../cms/controller/CmsLangController.java | 113 + .../cms/controller/CmsLangLogController.java | 113 + .../cms/controller/CmsLinkController.java | 115 + .../cms/controller/CmsMainController.java | 25 + .../cms/controller/CmsModelController.java | 118 + .../controller/CmsNavigationController.java | 190 ++ .../controller/CmsStatisticsController.java | 127 ++ .../cms/controller/CmsTemplateController.java | 118 + .../cms/controller/CmsWebsiteController.java | 526 +++++ .../controller/CmsWebsiteFieldController.java | 118 + .../CmsWebsiteSettingController.java | 121 + .../java/com/gxwebsoft/cms/entity/CmsAd.java | 106 + .../com/gxwebsoft/cms/entity/CmsAdRecord.java | 58 + .../com/gxwebsoft/cms/entity/CmsAdVo.java | 43 + .../com/gxwebsoft/cms/entity/CmsArticle.java | 264 +++ .../cms/entity/CmsArticleCategory.java | 94 + .../cms/entity/CmsArticleComment.java | 79 + .../cms/entity/CmsArticleContent.java | 42 + .../gxwebsoft/cms/entity/CmsArticleCount.java | 43 + .../gxwebsoft/cms/entity/CmsArticleLike.java | 43 + .../com/gxwebsoft/cms/entity/CmsDesign.java | 123 + .../gxwebsoft/cms/entity/CmsDesignRecord.java | 76 + .../com/gxwebsoft/cms/entity/CmsDomain.java | 73 + .../com/gxwebsoft/cms/entity/CmsForm.java | 91 + .../gxwebsoft/cms/entity/CmsFormRecord.java | 69 + .../com/gxwebsoft/cms/entity/CmsLang.java | 61 + .../com/gxwebsoft/cms/entity/CmsLangLog.java | 46 + .../com/gxwebsoft/cms/entity/CmsLink.java | 80 + .../com/gxwebsoft/cms/entity/CmsModel.java | 97 + .../gxwebsoft/cms/entity/CmsNavigation.java | 241 ++ .../gxwebsoft/cms/entity/CmsStatistics.java | 126 + .../com/gxwebsoft/cms/entity/CmsTemplate.java | 98 + .../com/gxwebsoft/cms/entity/CmsWebsite.java | 322 +++ .../gxwebsoft/cms/entity/CmsWebsiteField.java | 72 + .../cms/entity/CmsWebsiteSetting.java | 89 + .../gxwebsoft/cms/entity/TranslateDataVo.java | 43 + .../com/gxwebsoft/cms/entity/TranslateVo.java | 30 + .../com/gxwebsoft/cms/mapper/CmsAdMapper.java | 42 + .../cms/mapper/CmsAdRecordMapper.java | 37 + .../cms/mapper/CmsArticleCategoryMapper.java | 37 + .../cms/mapper/CmsArticleCommentMapper.java | 37 + .../cms/mapper/CmsArticleContentMapper.java | 37 + .../cms/mapper/CmsArticleCountMapper.java | 37 + .../cms/mapper/CmsArticleLikeMapper.java | 37 + .../cms/mapper/CmsArticleMapper.java | 43 + .../gxwebsoft/cms/mapper/CmsDesignMapper.java | 37 + .../cms/mapper/CmsDesignRecordMapper.java | 37 + .../gxwebsoft/cms/mapper/CmsDomainMapper.java | 40 + .../gxwebsoft/cms/mapper/CmsFormMapper.java | 37 + .../cms/mapper/CmsFormRecordMapper.java | 37 + .../cms/mapper/CmsLangLogMapper.java | 41 + .../gxwebsoft/cms/mapper/CmsLangMapper.java | 37 + .../gxwebsoft/cms/mapper/CmsLinkMapper.java | 43 + .../gxwebsoft/cms/mapper/CmsModelMapper.java | 41 + .../cms/mapper/CmsNavigationMapper.java | 45 + .../cms/mapper/CmsStatisticsMapper.java | 37 + .../cms/mapper/CmsTemplateMapper.java | 37 + .../cms/mapper/CmsWebsiteFieldMapper.java | 43 + .../cms/mapper/CmsWebsiteMapper.java | 53 + .../cms/mapper/CmsWebsiteSettingMapper.java | 37 + .../gxwebsoft/cms/mapper/xml/CmsAdMapper.xml | 92 + .../cms/mapper/xml/CmsAdRecordMapper.xml | 53 + .../mapper/xml/CmsArticleCategoryMapper.xml | 86 + .../mapper/xml/CmsArticleCommentMapper.xml | 71 + .../mapper/xml/CmsArticleContentMapper.xml | 38 + .../cms/mapper/xml/CmsArticleCountMapper.xml | 38 + .../cms/mapper/xml/CmsArticleLikeMapper.xml | 38 + .../cms/mapper/xml/CmsArticleMapper.xml | 184 ++ .../cms/mapper/xml/CmsDesignMapper.xml | 93 + .../cms/mapper/xml/CmsDesignRecordMapper.xml | 71 + .../cms/mapper/xml/CmsDomainMapper.xml | 65 + .../cms/mapper/xml/CmsFormMapper.xml | 83 + .../cms/mapper/xml/CmsFormRecordMapper.xml | 65 + .../cms/mapper/xml/CmsLangLogMapper.xml | 54 + .../cms/mapper/xml/CmsLangMapper.xml | 57 + .../cms/mapper/xml/CmsLinkMapper.xml | 86 + .../cms/mapper/xml/CmsModelMapper.xml | 89 + .../cms/mapper/xml/CmsNavigationMapper.xml | 161 ++ .../cms/mapper/xml/CmsStatisticsMapper.xml | 120 + .../cms/mapper/xml/CmsTemplateMapper.xml | 93 + .../cms/mapper/xml/CmsWebsiteFieldMapper.xml | 82 + .../cms/mapper/xml/CmsWebsiteMapper.xml | 454 ++++ .../mapper/xml/CmsWebsiteSettingMapper.xml | 81 + .../com/gxwebsoft/cms/param/CmsAdParam.java | 92 + .../gxwebsoft/cms/param/CmsAdRecordParam.java | 53 + .../cms/param/CmsArticleCategoryParam.java | 91 + .../cms/param/CmsArticleCommentParam.java | 75 + .../cms/param/CmsArticleContentParam.java | 35 + .../cms/param/CmsArticleCountParam.java | 37 + .../cms/param/CmsArticleImportParam.java | 122 + .../cms/param/CmsArticleLikeParam.java | 37 + .../gxwebsoft/cms/param/CmsArticleParam.java | 185 ++ .../gxwebsoft/cms/param/CmsDesignParam.java | 91 + .../cms/param/CmsDesignRecordParam.java | 72 + .../gxwebsoft/cms/param/CmsDomainParam.java | 66 + .../com/gxwebsoft/cms/param/CmsFormParam.java | 89 + .../cms/param/CmsFormRecordParam.java | 65 + .../gxwebsoft/cms/param/CmsLangLogParam.java | 48 + .../com/gxwebsoft/cms/param/CmsLangParam.java | 51 + .../com/gxwebsoft/cms/param/CmsLinkParam.java | 72 + .../gxwebsoft/cms/param/CmsModelParam.java | 83 + .../cms/param/CmsNavigationParam.java | 154 ++ .../cms/param/CmsStatisticsParam.java | 137 ++ .../gxwebsoft/cms/param/CmsTemplateParam.java | 91 + .../cms/param/CmsWebsiteFieldParam.java | 63 + .../gxwebsoft/cms/param/CmsWebsiteParam.java | 219 ++ .../cms/param/CmsWebsiteSettingParam.java | 86 + .../cms/service/CmsAdRecordService.java | 42 + .../gxwebsoft/cms/service/CmsAdService.java | 48 + .../service/CmsArticleCategoryService.java | 42 + .../cms/service/CmsArticleCommentService.java | 42 + .../cms/service/CmsArticleContentService.java | 40 + .../cms/service/CmsArticleCountService.java | 42 + .../cms/service/CmsArticleLikeService.java | 42 + .../cms/service/CmsArticleService.java | 48 + .../cms/service/CmsDesignRecordService.java | 42 + .../cms/service/CmsDesignService.java | 43 + .../cms/service/CmsDomainService.java | 42 + .../cms/service/CmsFormRecordService.java | 42 + .../gxwebsoft/cms/service/CmsFormService.java | 42 + .../cms/service/CmsLangLogService.java | 42 + .../gxwebsoft/cms/service/CmsLangService.java | 42 + .../gxwebsoft/cms/service/CmsLinkService.java | 42 + .../cms/service/CmsModelService.java | 42 + .../cms/service/CmsNavigationService.java | 43 + .../cms/service/CmsStatisticsService.java | 42 + .../cms/service/CmsTemplateService.java | 42 + .../cms/service/CmsWebsiteFieldService.java | 42 + .../cms/service/CmsWebsiteService.java | 70 + .../cms/service/CmsWebsiteSettingService.java | 42 + .../service/impl/CmsAdRecordServiceImpl.java | 47 + .../cms/service/impl/CmsAdServiceImpl.java | 57 + .../impl/CmsArticleCategoryServiceImpl.java | 47 + .../impl/CmsArticleCommentServiceImpl.java | 47 + .../impl/CmsArticleContentServiceImpl.java | 189 ++ .../impl/CmsArticleCountServiceImpl.java | 47 + .../impl/CmsArticleLikeServiceImpl.java | 47 + .../service/impl/CmsArticleServiceImpl.java | 246 ++ .../impl/CmsDesignRecordServiceImpl.java | 47 + .../service/impl/CmsDesignServiceImpl.java | 157 ++ .../service/impl/CmsDomainServiceImpl.java | 47 + .../impl/CmsFormRecordServiceImpl.java | 47 + .../cms/service/impl/CmsFormServiceImpl.java | 47 + .../service/impl/CmsLangLogServiceImpl.java | 47 + .../cms/service/impl/CmsLangServiceImpl.java | 47 + .../cms/service/impl/CmsLinkServiceImpl.java | 47 + .../cms/service/impl/CmsModelServiceImpl.java | 47 + .../impl/CmsNavigationServiceImpl.java | 161 ++ .../impl/CmsStatisticsServiceImpl.java | 47 + .../service/impl/CmsTemplateServiceImpl.java | 47 + .../impl/CmsWebsiteFieldServiceImpl.java | 47 + .../service/impl/CmsWebsiteServiceImpl.java | 416 ++++ .../impl/CmsWebsiteServiceImplHelper.java | 221 ++ .../impl/CmsWebsiteSettingServiceImpl.java | 47 + .../com/gxwebsoft/common/core/Constants.java | 93 + .../common/core/annotation/IgnoreTenant.java | 29 + .../common/core/annotation/OperationLog.java | 41 + .../core/annotation/OperationModule.java | 21 + .../common/core/annotation/QueryField.java | 22 + .../common/core/annotation/QueryType.java | 42 + .../core/aspect/IgnoreTenantAspect.java | 63 + .../core/aspect/OperationLogAspect.java | 227 ++ .../core/config/BigDecimalDeserializer.java | 41 + .../core/config/CertificateProperties.java | 213 ++ .../common/core/config/ConfigProperties.java | 105 + .../core/config/HttpMessageConverter.java | 15 + .../common/core/config/JacksonConfig.java | 26 + .../config/LocalDateTimeDeserializer.java | 29 + .../core/config/LocalDateTimeSerializer.java | 27 + .../common/core/config/MqttProperties.java | 72 + .../common/core/config/MybatisPlusConfig.java | 143 ++ .../core/config/RestTemplateConfig.java | 29 + .../common/core/config/SpringContextUtil.java | 62 + .../common/core/config/SwaggerConfig.java | 111 + .../common/core/config/WebMvcConfig.java | 31 + .../core/constants/AppUserConstants.java | 8 + .../core/constants/ArticleConstants.java | 6 + .../core/constants/BalanceConstants.java | 10 + .../common/core/constants/BaseConstants.java | 5 + .../common/core/constants/OrderConstants.java | 37 + .../core/constants/PlatformConstants.java | 12 + .../core/constants/ProfitConstants.java | 9 + .../core/constants/QRCodeConstants.java | 10 + .../common/core/constants/RedisConstants.java | 47 + .../common/core/constants/TaskConstants.java | 22 + .../core/constants/WebsiteConstants.java | 14 + .../core/constants/WxOfficialConstants.java | 6 + .../common/core/context/TenantContext.java | 67 + .../controller/CertificateController.java | 187 ++ .../controller/DatabaseFixController.java | 204 ++ .../controller/DevEnvironmentController.java | 236 ++ .../controller/PaymentConfigController.java | 149 ++ .../core/controller/QrCodeController.java | 258 +++ .../core/controller/TestController.java | 302 +++ .../controller/WechatCertTestController.java | 211 ++ .../WechatPayDiagnosticController.java | 318 +++ .../CreateBusinessEncryptedQrCodeRequest.java | 114 + .../dto/qr/CreateEncryptedQrCodeRequest.java | 100 + .../core/dto/qr/DecryptQrDataRequest.java | 56 + .../core/dto/qr/InvalidateTokenRequest.java | 42 + .../core/dto/qr/VerifyBusinessQrRequest.java | 56 + .../core/dto/qr/VerifyQrContentRequest.java | 42 + .../core/exception/BusinessException.java | 48 + .../exception/GlobalExceptionHandler.java | 89 + .../core/security/JwtAccessDeniedHandler.java | 29 + .../security/JwtAuthenticationEntryPoint.java | 30 + .../security/JwtAuthenticationFilter.java | 118 + .../common/core/security/JwtSubject.java | 31 + .../common/core/security/JwtUtil.java | 141 ++ .../common/core/security/SecurityConfig.java | 113 + .../service/CertificateHealthService.java | 253 ++ .../core/service/CertificateService.java | 281 +++ .../EnvironmentAwarePaymentService.java | 143 ++ .../core/service/PaymentCacheService.java | 174 ++ .../common/core/utils/AliYunSender.java | 145 ++ .../common/core/utils/AlipayConfigUtil.java | 110 + .../common/core/utils/CacheClient.java | 265 +++ .../common/core/utils/CertificateLoader.java | 230 ++ .../common/core/utils/CommonUtil.java | 321 +++ .../common/core/utils/DateTimeUtil.java | 93 + .../common/core/utils/DomainUtils.java | 34 + .../core/utils/EncryptedQrCodeUtil.java | 433 ++++ .../common/core/utils/FileServerUtil.java | 401 ++++ .../common/core/utils/HttpUtils.java | 311 +++ .../common/core/utils/ImageUtil.java | 96 + .../common/core/utils/JChardetFacadeUtil.java | 2025 +++++++++++++++++ .../gxwebsoft/common/core/utils/JSONUtil.java | 69 + .../common/core/utils/MyQrCodeUtil.java | 85 + .../common/core/utils/OpenOfficeUtil.java | 124 + .../core/utils/QrCodeDecryptResult.java | 93 + .../common/core/utils/RedisUtil.java | 279 +++ .../common/core/utils/RequestUtil.java | 343 +++ .../common/core/utils/SignCheckUtil.java | 197 ++ .../gxwebsoft/common/core/utils/SpmUtil.java | 23 + .../core/utils/WechatCertAutoConfig.java | 171 ++ .../utils/WechatPayCertificateDiagnostic.java | 314 +++ .../core/utils/WechatPayCertificateFixer.java | 312 +++ .../core/utils/WechatPayConfigChecker.java | 243 ++ .../core/utils/WechatPayConfigValidator.java | 223 ++ .../core/utils/WechatPayDiagnostic.java | 222 ++ .../common/core/utils/WechatPayUtils.java | 111 + .../common/core/utils/WxNativeUtil.java | 20 + .../common/core/utils/WxOfficialUtil.java | 106 + .../gxwebsoft/common/core/utils/WxUtil.java | 132 ++ .../common/core/utils/WxWorkUtil.java | 134 ++ .../gxwebsoft/common/core/web/ApiResult.java | 87 + .../common/core/web/BaseController.java | 333 +++ .../gxwebsoft/common/core/web/BaseParam.java | 98 + .../gxwebsoft/common/core/web/BatchParam.java | 57 + .../common/core/web/ExistenceParam.java | 96 + .../gxwebsoft/common/core/web/PageParam.java | 343 +++ .../gxwebsoft/common/core/web/PageResult.java | 51 + .../core/websocket/WebSocketConfig.java | 19 + .../core/websocket/WebSocketServer.java | 86 + .../system/controller/AiController.java | 139 ++ .../system/controller/CacheController.java | 117 + .../controller/CompanyCommentController.java | 131 ++ .../controller/CompanyContentController.java | 125 + .../system/controller/CompanyController.java | 367 +++ .../controller/CompanyGitController.java | 122 + .../CompanyParameterController.java | 125 + .../controller/CompanyUrlController.java | 125 + .../system/controller/DictController.java | 177 ++ .../system/controller/DictDataController.java | 124 + .../controller/DictionaryController.java | 148 ++ .../controller/DictionaryDataController.java | 123 + .../system/controller/DomainController.java | 127 ++ .../system/controller/EmailController.java | 48 + .../system/controller/FileController.java | 341 +++ .../controller/LoginRecordController.java | 55 + .../system/controller/MainController.java | 314 +++ .../system/controller/MenuController.java | 145 ++ .../controller/OperationRecordController.java | 61 + .../controller/OrganizationController.java | 130 ++ .../system/controller/PaymentController.java | 235 ++ .../system/controller/PlugController.java | 161 ++ .../controller/RedisUtilController.java | 77 + .../system/controller/RoleController.java | 144 ++ .../system/controller/RoleMenuController.java | 96 + .../system/controller/SettingController.java | 178 ++ .../system/controller/TenantController.java | 158 ++ .../controller/UserCollectionController.java | 135 ++ .../system/controller/UserController.java | 401 ++++ .../system/controller/UserFileController.java | 158 ++ .../controller/UserRefereeController.java | 183 ++ .../system/controller/WxLoginController.java | 731 ++++++ .../common/system/dto/PaymentCacheDTO.java | 39 + .../gxwebsoft/common/system/entity/Cache.java | 34 + .../common/system/entity/ChatMessage.java | 48 + .../common/system/entity/Company.java | 337 +++ .../common/system/entity/CompanyComment.java | 61 + .../common/system/entity/CompanyContent.java | 44 + .../common/system/entity/CompanyGit.java | 142 ++ .../system/entity/CompanyParameter.java | 57 + .../common/system/entity/CompanyUrl.java | 66 + .../gxwebsoft/common/system/entity/Dict.java | 60 + .../common/system/entity/DictData.java | 66 + .../common/system/entity/Dictionary.java | 59 + .../common/system/entity/DictionaryData.java | 67 + .../common/system/entity/Domain.java | 75 + .../common/system/entity/EmailRecord.java | 59 + .../common/system/entity/FileRecord.java | 94 + .../common/system/entity/KVEntity.java | 56 + .../common/system/entity/LoginRecord.java | 76 + .../gxwebsoft/common/system/entity/Menu.java | 87 + .../common/system/entity/OperationRecord.java | 98 + .../common/system/entity/Organization.java | 92 + .../common/system/entity/Payment.java | 100 + .../gxwebsoft/common/system/entity/Plug.java | 144 ++ .../gxwebsoft/common/system/entity/Role.java | 56 + .../common/system/entity/RoleMenu.java | 47 + .../common/system/entity/Setting.java | 61 + .../common/system/entity/Tenant.java | 78 + .../gxwebsoft/common/system/entity/User.java | 317 +++ .../common/system/entity/UserBalanceLog.java | 87 + .../common/system/entity/UserCollection.java | 45 + .../common/system/entity/UserFile.java | 79 + .../common/system/entity/UserInfo.java | 260 +++ .../common/system/entity/UserReferee.java | 59 + .../common/system/entity/UserRole.java | 52 + .../system/mapper/CompanyCommentMapper.java | 37 + .../system/mapper/CompanyContentMapper.java | 37 + .../system/mapper/CompanyGitMapper.java | 37 + .../common/system/mapper/CompanyMapper.java | 62 + .../system/mapper/CompanyParameterMapper.java | 37 + .../system/mapper/CompanyUrlMapper.java | 37 + .../common/system/mapper/DictDataMapper.java | 47 + .../common/system/mapper/DictMapper.java | 14 + .../system/mapper/DictionaryDataMapper.java | 47 + .../system/mapper/DictionaryMapper.java | 14 + .../common/system/mapper/DomainMapper.java | 37 + .../system/mapper/EmailRecordMapper.java | 14 + .../system/mapper/FileRecordMapper.java | 47 + .../system/mapper/LoginRecordMapper.java | 48 + .../common/system/mapper/MenuMapper.java | 22 + .../system/mapper/OperationRecordMapper.java | 48 + .../system/mapper/OrganizationMapper.java | 37 + .../common/system/mapper/PaymentMapper.java | 40 + .../common/system/mapper/PlugMapper.java | 42 + .../common/system/mapper/RoleMapper.java | 14 + .../common/system/mapper/RoleMenuMapper.java | 39 + .../common/system/mapper/SettingMapper.java | 40 + .../common/system/mapper/TenantMapper.java | 37 + .../system/mapper/UserBalanceLogMapper.java | 37 + .../system/mapper/UserCollectionMapper.java | 37 + .../common/system/mapper/UserFileMapper.java | 14 + .../common/system/mapper/UserMapper.java | 70 + .../system/mapper/UserRefereeMapper.java | 37 + .../common/system/mapper/UserRoleMapper.java | 45 + .../mapper/xml/CompanyCommentMapper.xml | 53 + .../mapper/xml/CompanyContentMapper.xml | 38 + .../system/mapper/xml/CompanyGitMapper.xml | 65 + .../system/mapper/xml/CompanyMapper.xml | 198 ++ .../mapper/xml/CompanyParameterMapper.xml | 50 + .../system/mapper/xml/CompanyUrlMapper.xml | 59 + .../system/mapper/xml/DictDataMapper.xml | 71 + .../common/system/mapper/xml/DictMapper.xml | 5 + .../mapper/xml/DictionaryDataMapper.xml | 71 + .../system/mapper/xml/DictionaryMapper.xml | 5 + .../common/system/mapper/xml/DomainMapper.xml | 62 + .../system/mapper/xml/EmailRecordMapper.xml | 5 + .../system/mapper/xml/FileRecordMapper.xml | 76 + .../system/mapper/xml/LoginRecordMapper.xml | 62 + .../common/system/mapper/xml/MenuMapper.xml | 32 + .../mapper/xml/OperationRecordMapper.xml | 71 + .../system/mapper/xml/OrganizationMapper.xml | 98 + .../system/mapper/xml/PaymentMapper.xml | 90 + .../common/system/mapper/xml/PlugMapper.xml | 105 + .../common/system/mapper/xml/RoleMapper.xml | 5 + .../system/mapper/xml/RoleMenuMapper.xml | 42 + .../system/mapper/xml/SettingMapper.xml | 33 + .../common/system/mapper/xml/TenantMapper.xml | 56 + .../mapper/xml/UserCollectionMapper.xml | 38 + .../system/mapper/xml/UserFileMapper.xml | 5 + .../common/system/mapper/xml/UserMapper.xml | 264 +++ .../system/mapper/xml/UserRefereeMapper.xml | 50 + .../system/mapper/xml/UserRoleMapper.xml | 35 + .../common/system/param/AlipayParam.java | 31 + .../common/system/param/CacheParam.java | 25 + .../system/param/CompanyCommentParam.java | 58 + .../system/param/CompanyContentParam.java | 35 + .../common/system/param/CompanyGitParam.java | 60 + .../common/system/param/CompanyParam.java | 167 ++ .../system/param/CompanyParameterParam.java | 50 + .../common/system/param/CompanyUrlParam.java | 59 + .../common/system/param/DictDataParam.java | 55 + .../common/system/param/DictParam.java | 38 + .../system/param/DictionaryDataParam.java | 55 + .../common/system/param/DictionaryParam.java | 38 + .../common/system/param/DomainParam.java | 61 + .../common/system/param/FileRecordParam.java | 70 + .../common/system/param/LoginParam.java | 37 + .../common/system/param/LoginRecordParam.java | 60 + .../common/system/param/MenuParam.java | 68 + .../system/param/OperationRecordParam.java | 67 + .../system/param/OrganizationParam.java | 81 + .../common/system/param/PaymentParam.java | 81 + .../common/system/param/PlugParam.java | 95 + .../common/system/param/RoleParam.java | 41 + .../common/system/param/SettingParam.java | 50 + .../common/system/param/SmsCaptchaParam.java | 31 + .../common/system/param/TenantParam.java | 55 + .../system/param/UpdatePasswordParam.java | 31 + .../system/param/UserBalanceLogParam.java | 77 + .../system/param/UserCollectionParam.java | 37 + .../common/system/param/UserFileParam.java | 40 + .../common/system/param/UserImportParam.java | 42 + .../common/system/param/UserParam.java | 249 ++ .../common/system/param/UserRefereeParam.java | 48 + .../common/system/result/CaptchaResult.java | 30 + .../common/system/result/LoginResult.java | 31 + .../common/system/result/RedisResult.java | 34 + .../system/result/SmsCaptchaResult.java | 26 + .../system/service/CompanyCommentService.java | 42 + .../system/service/CompanyContentService.java | 42 + .../system/service/CompanyGitService.java | 42 + .../service/CompanyParameterService.java | 42 + .../common/system/service/CompanyService.java | 51 + .../system/service/CompanyUrlService.java | 42 + .../system/service/DictDataService.java | 52 + .../common/system/service/DictService.java | 14 + .../system/service/DictionaryDataService.java | 51 + .../system/service/DictionaryService.java | 14 + .../common/system/service/DomainService.java | 42 + .../system/service/DomainServiceImpl.java | 46 + .../system/service/EmailRecordService.java | 51 + .../system/service/FileRecordService.java | 58 + .../system/service/LoginRecordService.java | 54 + .../common/system/service/MenuService.java | 18 + .../service/OperationRecordService.java | 49 + .../system/service/OrganizationService.java | 42 + .../common/system/service/PaymentService.java | 43 + .../common/system/service/PlugService.java | 44 + .../system/service/RoleMenuService.java | 35 + .../common/system/service/RoleService.java | 14 + .../common/system/service/SettingService.java | 67 + .../common/system/service/TenantService.java | 42 + .../system/service/UserBalanceLogService.java | 42 + .../system/service/UserCollectionService.java | 42 + .../service/UserCollectionServiceImpl.java | 46 + .../system/service/UserFileService.java | 14 + .../system/service/UserRefereeService.java | 45 + .../system/service/UserRoleService.java | 42 + .../common/system/service/UserService.java | 123 + .../impl/CompanyCommentServiceImpl.java | 47 + .../impl/CompanyContentServiceImpl.java | 47 + .../service/impl/CompanyGitServiceImpl.java | 47 + .../impl/CompanyParameterServiceImpl.java | 47 + .../service/impl/CompanyServiceImpl.java | 86 + .../service/impl/CompanyUrlServiceImpl.java | 47 + .../service/impl/DictDataServiceImpl.java | 52 + .../system/service/impl/DictServiceImpl.java | 18 + .../impl/DictionaryDataServiceImpl.java | 52 + .../service/impl/DictionaryServiceImpl.java | 18 + .../service/impl/EmailRecordServiceImpl.java | 99 + .../service/impl/FileRecordServiceImpl.java | 63 + .../service/impl/LoginRecordServiceImpl.java | 78 + .../system/service/impl/MenuServiceImpl.java | 139 ++ .../impl/OperationRecordServiceImpl.java | 52 + .../service/impl/OrganizationServiceImpl.java | 45 + .../service/impl/PaymentServiceImpl.java | 52 + .../system/service/impl/PlugServiceImpl.java | 112 + .../service/impl/RoleMenuServiceImpl.java | 31 + .../system/service/impl/RoleServiceImpl.java | 18 + .../service/impl/SettingServiceImpl.java | 291 +++ .../service/impl/TenantServiceImpl.java | 46 + .../impl/UserBalanceLogServiceImpl.java | 47 + .../service/impl/UserFileServiceImpl.java | 18 + .../service/impl/UserRefereeServiceImpl.java | 74 + .../service/impl/UserRoleServiceImpl.java | 37 + .../system/service/impl/UserServiceImpl.java | 258 +++ .../controller/EnterpriseController.java | 147 ++ .../enterprise/entity/Enterprise.java | 67 + .../enterprise/mapper/EnterpriseMapper.java | 13 + .../mapper/xml/EnterpriseMapper.xml | 7 + .../enterprise/service/EnterpriseService.java | 13 + .../service/impl/EnterpriseServiceImpl.java | 17 + .../hjm/controller/HjmBxLogController.java | 174 ++ .../hjm/controller/HjmCarController.java | 405 ++++ .../hjm/controller/HjmChoicesController.java | 122 + .../hjm/controller/HjmCoursesController.java | 127 ++ .../hjm/controller/HjmExamLogController.java | 162 ++ .../hjm/controller/HjmFenceController.java | 121 + .../hjm/controller/HjmGpsLogController.java | 121 + .../controller/HjmQuestionsController.java | 143 ++ .../controller/HjmViolationController.java | 140 ++ .../hjm/controller/MQTTClientDemo.java | 150 ++ .../hjm/controller/PushCallback.java | 31 + .../controller/SendSubscriptionMessages.java | 136 ++ .../WxNotificationTestController.java | 222 ++ .../hjm/dto/BatchTemplateMessageRequest.java | 24 + .../hjm/dto/SubscribeMessageRequest.java | 52 + .../hjm/dto/TemplateMessageRequest.java | 73 + .../java/com/gxwebsoft/hjm/entity/Gps.java | 73 + .../com/gxwebsoft/hjm/entity/HjmBxLog.java | 84 + .../java/com/gxwebsoft/hjm/entity/HjmCar.java | 173 ++ .../com/gxwebsoft/hjm/entity/HjmChoices.java | 64 + .../com/gxwebsoft/hjm/entity/HjmCourses.java | 71 + .../com/gxwebsoft/hjm/entity/HjmExamLog.java | 78 + .../com/gxwebsoft/hjm/entity/HjmFence.java | 71 + .../com/gxwebsoft/hjm/entity/HjmGpsLog.java | 77 + .../gxwebsoft/hjm/entity/HjmQuestions.java | 102 + .../gxwebsoft/hjm/entity/HjmViolation.java | 72 + .../gxwebsoft/hjm/mapper/HjmBxLogMapper.java | 37 + .../gxwebsoft/hjm/mapper/HjmCarMapper.java | 47 + .../hjm/mapper/HjmChoicesMapper.java | 37 + .../hjm/mapper/HjmCoursesMapper.java | 37 + .../hjm/mapper/HjmExamLogMapper.java | 37 + .../gxwebsoft/hjm/mapper/HjmFenceMapper.java | 37 + .../gxwebsoft/hjm/mapper/HjmGpsLogMapper.java | 37 + .../hjm/mapper/HjmQuestionsMapper.java | 37 + .../hjm/mapper/HjmViolationMapper.java | 37 + .../hjm/mapper/xml/HjmBxLogMapper.xml | 62 + .../gxwebsoft/hjm/mapper/xml/HjmCarMapper.xml | 153 ++ .../hjm/mapper/xml/HjmChoicesMapper.xml | 60 + .../hjm/mapper/xml/HjmCoursesMapper.xml | 66 + .../hjm/mapper/xml/HjmExamLogMapper.xml | 61 + .../hjm/mapper/xml/HjmFenceMapper.xml | 60 + .../hjm/mapper/xml/HjmGpsLogMapper.xml | 65 + .../hjm/mapper/xml/HjmQuestionsMapper.xml | 70 + .../hjm/mapper/xml/HjmViolationMapper.xml | 75 + .../gxwebsoft/hjm/param/HjmBxLogParam.java | 56 + .../hjm/param/HjmCarImportParam.java | 83 + .../com/gxwebsoft/hjm/param/HjmCarParam.java | 124 + .../gxwebsoft/hjm/param/HjmChoicesParam.java | 56 + .../gxwebsoft/hjm/param/HjmCoursesParam.java | 62 + .../gxwebsoft/hjm/param/HjmExamLogParam.java | 56 + .../gxwebsoft/hjm/param/HjmFenceParam.java | 57 + .../gxwebsoft/hjm/param/HjmGpsLogParam.java | 66 + .../hjm/param/HjmQuestionsParam.java | 66 + .../hjm/param/HjmViolationParam.java | 76 + .../hjm/service/GpsDiagnosticService.java | 289 +++ .../hjm/service/GpsMessageCallback.java | 123 + .../hjm/service/GpsMessageProcessor.java | 258 +++ .../hjm/service/HjmBxLogService.java | 42 + .../gxwebsoft/hjm/service/HjmCarService.java | 47 + .../hjm/service/HjmChoicesService.java | 42 + .../hjm/service/HjmCoursesService.java | 42 + .../hjm/service/HjmExamLogService.java | 42 + .../hjm/service/HjmFenceService.java | 42 + .../hjm/service/HjmGpsLogService.java | 42 + .../hjm/service/HjmQuestionsService.java | 42 + .../hjm/service/HjmViolationService.java | 43 + .../gxwebsoft/hjm/service/MqttService.java | 330 +++ .../hjm/service/WxNotificationService.java | 82 + .../hjm/service/impl/HjmBxLogServiceImpl.java | 47 + .../hjm/service/impl/HjmCarServiceImpl.java | 403 ++++ .../service/impl/HjmChoicesServiceImpl.java | 47 + .../service/impl/HjmCoursesServiceImpl.java | 47 + .../service/impl/HjmExamLogServiceImpl.java | 47 + .../hjm/service/impl/HjmFenceServiceImpl.java | 47 + .../service/impl/HjmGpsLogServiceImpl.java | 47 + .../service/impl/HjmQuestionsServiceImpl.java | 63 + .../service/impl/HjmViolationServiceImpl.java | 106 + .../impl/WxNotificationServiceImpl.java | 258 +++ .../hjm/task/PushHjmFenceOutController.java | 97 + .../house/controller/HouseInfoController.java | 155 ++ .../controller/HouseLikeLogController.java | 119 + .../HouseReservationController.java | 125 + .../house/controller/HouseUserController.java | 115 + .../controller/HouseViewsLogController.java | 120 + .../com/gxwebsoft/house/entity/HouseFile.java | 37 + .../gxwebsoft/house/entity/HouseFiles.java | 29 + .../com/gxwebsoft/house/entity/HouseInfo.java | 186 ++ .../gxwebsoft/house/entity/HouseLikeLog.java | 56 + .../house/entity/HouseReservation.java | 116 + .../com/gxwebsoft/house/entity/HouseUser.java | 206 ++ .../gxwebsoft/house/entity/HouseViewsLog.java | 56 + .../house/mapper/HouseInfoMapper.java | 37 + .../house/mapper/HouseLikeLogMapper.java | 37 + .../house/mapper/HouseReservationMapper.java | 37 + .../house/mapper/HouseUserMapper.java | 37 + .../house/mapper/HouseViewsLogMapper.java | 37 + .../house/mapper/xml/HouseInfoMapper.xml | 172 ++ .../house/mapper/xml/HouseLikeLogMapper.xml | 51 + .../mapper/xml/HouseReservationMapper.xml | 99 + .../house/mapper/xml/HouseUserMapper.xml | 202 ++ .../house/mapper/xml/HouseViewsLogMapper.xml | 51 + .../gxwebsoft/house/param/HouseInfoParam.java | 178 ++ .../house/param/HouseLikeLogParam.java | 46 + .../house/param/HouseReservationParam.java | 109 + .../gxwebsoft/house/param/HouseUserParam.java | 210 ++ .../house/param/HouseViewsLogParam.java | 46 + .../house/service/HouseInfoService.java | 44 + .../house/service/HouseLikeLogService.java | 45 + .../service/HouseReservationService.java | 42 + .../house/service/HouseUserService.java | 42 + .../house/service/HouseViewsLogService.java | 44 + .../service/impl/HouseInfoServiceImpl.java | 324 +++ .../service/impl/HouseLikeLogServiceImpl.java | 64 + .../impl/HouseReservationServiceImpl.java | 47 + .../service/impl/HouseUserServiceImpl.java | 47 + .../impl/HouseViewsLogServiceImpl.java | 65 + .../gxwebsoft/house/util/SortSceneUtil.java | 128 ++ .../oa/controller/OaAppController.java | 330 +++ .../oa/controller/OaAppFieldController.java | 120 + .../oa/controller/OaAppRenewController.java | 120 + .../oa/controller/OaAppUrlController.java | 115 + .../oa/controller/OaAppUserController.java | 120 + .../oa/controller/OaAssetsCodeController.java | 124 + .../oa/controller/OaAssetsController.java | 123 + .../controller/OaAssetsDomainController.java | 124 + .../controller/OaAssetsEmailController.java | 125 + .../controller/OaAssetsMysqlController.java | 122 + .../controller/OaAssetsServerController.java | 121 + .../oa/controller/OaAssetsSiteController.java | 124 + .../OaAssetsSoftwareCertController.java | 123 + .../oa/controller/OaAssetsSslController.java | 125 + .../OaAssetsTrademarkController.java | 121 + .../oa/controller/OaAssetsUserController.java | 120 + .../controller/OaAssetsVhostController.java | 124 + .../oa/controller/OaCompanyController.java | 152 ++ .../controller/OaCompanyFieldController.java | 114 + .../controller/OaCompanyUserController.java | 114 + .../oa/controller/OaLinkController.java | 120 + .../oa/controller/OaProductController.java | 120 + .../controller/OaProductTabsController.java | 120 + .../oa/controller/OaTaskController.java | 120 + .../oa/controller/OaTaskCountController.java | 120 + .../oa/controller/OaTaskUserController.java | 120 + .../java/com/gxwebsoft/oa/entity/OaApp.java | 269 +++ .../com/gxwebsoft/oa/entity/OaAppField.java | 55 + .../com/gxwebsoft/oa/entity/OaAppRenew.java | 69 + .../com/gxwebsoft/oa/entity/OaAppUrl.java | 60 + .../com/gxwebsoft/oa/entity/OaAppUser.java | 51 + .../com/gxwebsoft/oa/entity/OaAssets.java | 161 ++ .../com/gxwebsoft/oa/entity/OaAssetsCode.java | 92 + .../gxwebsoft/oa/entity/OaAssetsDomain.java | 104 + .../gxwebsoft/oa/entity/OaAssetsEmail.java | 104 + .../gxwebsoft/oa/entity/OaAssetsMysql.java | 109 + .../gxwebsoft/oa/entity/OaAssetsServer.java | 147 ++ .../com/gxwebsoft/oa/entity/OaAssetsSite.java | 170 ++ .../oa/entity/OaAssetsSoftwareCert.java | 103 + .../com/gxwebsoft/oa/entity/OaAssetsSsl.java | 115 + .../oa/entity/OaAssetsTrademark.java | 103 + .../com/gxwebsoft/oa/entity/OaAssetsUser.java | 51 + .../gxwebsoft/oa/entity/OaAssetsVhost.java | 112 + .../com/gxwebsoft/oa/entity/OaCompany.java | 203 ++ .../gxwebsoft/oa/entity/OaCompanyField.java | 56 + .../gxwebsoft/oa/entity/OaCompanyUser.java | 53 + .../java/com/gxwebsoft/oa/entity/OaLink.java | 74 + .../com/gxwebsoft/oa/entity/OaProduct.java | 106 + .../gxwebsoft/oa/entity/OaProductTabs.java | 81 + .../java/com/gxwebsoft/oa/entity/OaTask.java | 136 ++ .../com/gxwebsoft/oa/entity/OaTaskCount.java | 73 + .../com/gxwebsoft/oa/entity/OaTaskUser.java | 51 + .../gxwebsoft/oa/mapper/OaAppFieldMapper.java | 37 + .../com/gxwebsoft/oa/mapper/OaAppMapper.java | 37 + .../gxwebsoft/oa/mapper/OaAppRenewMapper.java | 37 + .../gxwebsoft/oa/mapper/OaAppUrlMapper.java | 37 + .../gxwebsoft/oa/mapper/OaAppUserMapper.java | 37 + .../oa/mapper/OaAssetsCodeMapper.java | 37 + .../oa/mapper/OaAssetsDomainMapper.java | 37 + .../oa/mapper/OaAssetsEmailMapper.java | 37 + .../gxwebsoft/oa/mapper/OaAssetsMapper.java | 37 + .../oa/mapper/OaAssetsMysqlMapper.java | 37 + .../oa/mapper/OaAssetsServerMapper.java | 37 + .../oa/mapper/OaAssetsSiteMapper.java | 37 + .../oa/mapper/OaAssetsSoftwareCertMapper.java | 37 + .../oa/mapper/OaAssetsSslMapper.java | 37 + .../oa/mapper/OaAssetsTrademarkMapper.java | 37 + .../oa/mapper/OaAssetsUserMapper.java | 37 + .../oa/mapper/OaAssetsVhostMapper.java | 37 + .../oa/mapper/OaCompanyFieldMapper.java | 37 + .../gxwebsoft/oa/mapper/OaCompanyMapper.java | 37 + .../oa/mapper/OaCompanyUserMapper.java | 37 + .../com/gxwebsoft/oa/mapper/OaLinkMapper.java | 37 + .../gxwebsoft/oa/mapper/OaProductMapper.java | 37 + .../oa/mapper/OaProductTabsMapper.java | 37 + .../oa/mapper/OaTaskCountMapper.java | 37 + .../com/gxwebsoft/oa/mapper/OaTaskMapper.java | 37 + .../gxwebsoft/oa/mapper/OaTaskUserMapper.java | 37 + .../oa/mapper/xml/OaAppFieldMapper.xml | 50 + .../gxwebsoft/oa/mapper/xml/OaAppMapper.xml | 229 ++ .../oa/mapper/xml/OaAppUrlMapper.xml | 54 + .../oa/mapper/xml/OaAppUserMapper.xml | 51 + .../oa/mapper/xml/OaAssetsCodeMapper.xml | 75 + .../oa/mapper/xml/OaAssetsDomainMapper.xml | 84 + .../oa/mapper/xml/OaAssetsEmailMapper.xml | 84 + .../oa/mapper/xml/OaAssetsMapper.xml | 146 ++ .../oa/mapper/xml/OaAssetsMysqlMapper.xml | 90 + .../oa/mapper/xml/OaAssetsServerMapper.xml | 137 ++ .../oa/mapper/xml/OaAssetsSiteMapper.xml | 153 ++ .../mapper/xml/OaAssetsSoftwareCertMapper.xml | 84 + .../oa/mapper/xml/OaAssetsSslMapper.xml | 98 + .../oa/mapper/xml/OaAssetsTrademarkMapper.xml | 84 + .../oa/mapper/xml/OaAssetsUserMapper.xml | 44 + .../oa/mapper/xml/OaAssetsVhostMapper.xml | 93 + .../oa/mapper/xml/OaCompanyFieldMapper.xml | 50 + .../oa/mapper/xml/OaCompanyMapper.xml | 179 ++ .../oa/mapper/xml/OaCompanyUserMapper.xml | 47 + .../gxwebsoft/oa/mapper/xml/OaLinkMapper.xml | 71 + .../oa/mapper/xml/OaProductMapper.xml | 98 + .../oa/mapper/xml/OaProductTabsMapper.xml | 74 + .../oa/mapper/xml/OaTaskCountMapper.xml | 65 + .../gxwebsoft/oa/mapper/xml/OaTaskMapper.xml | 128 ++ .../oa/mapper/xml/OaTaskUserMapper.xml | 47 + .../gxwebsoft/oa/param/OaAppFieldParam.java | 51 + .../com/gxwebsoft/oa/param/OaAppParam.java | 246 ++ .../gxwebsoft/oa/param/OaAppRenewParam.java | 66 + .../com/gxwebsoft/oa/param/OaAppUrlParam.java | 56 + .../gxwebsoft/oa/param/OaAppUserParam.java | 48 + .../gxwebsoft/oa/param/OaAssetsCodeParam.java | 72 + .../oa/param/OaAssetsDomainParam.java | 84 + .../oa/param/OaAssetsEmailParam.java | 83 + .../oa/param/OaAssetsMysqlParam.java | 90 + .../com/gxwebsoft/oa/param/OaAssetsParam.java | 145 ++ .../oa/param/OaAssetsServerParam.java | 142 ++ .../gxwebsoft/oa/param/OaAssetsSiteParam.java | 155 ++ .../oa/param/OaAssetsSoftwareCertParam.java | 83 + .../gxwebsoft/oa/param/OaAssetsSslParam.java | 92 + .../oa/param/OaAssetsTrademarkParam.java | 83 + .../gxwebsoft/oa/param/OaAssetsUserParam.java | 48 + .../oa/param/OaAssetsVhostParam.java | 93 + .../oa/param/OaCompanyFieldParam.java | 51 + .../gxwebsoft/oa/param/OaCompanyParam.java | 191 ++ .../oa/param/OaCompanyUserParam.java | 48 + .../com/gxwebsoft/oa/param/OaLinkParam.java | 72 + .../gxwebsoft/oa/param/OaProductParam.java | 103 + .../oa/param/OaProductTabsParam.java | 74 + .../gxwebsoft/oa/param/OaTaskCountParam.java | 72 + .../com/gxwebsoft/oa/param/OaTaskParam.java | 139 ++ .../gxwebsoft/oa/param/OaTaskRecordParam.java | 68 + .../gxwebsoft/oa/param/OaTaskUserParam.java | 48 + .../oa/service/OaAppFieldService.java | 42 + .../oa/service/OaAppRenewService.java | 42 + .../gxwebsoft/oa/service/OaAppService.java | 42 + .../gxwebsoft/oa/service/OaAppUrlService.java | 42 + .../oa/service/OaAppUserService.java | 42 + .../oa/service/OaAssetsCodeService.java | 42 + .../oa/service/OaAssetsDomainService.java | 42 + .../oa/service/OaAssetsEmailService.java | 42 + .../oa/service/OaAssetsMysqlService.java | 42 + .../oa/service/OaAssetsServerService.java | 42 + .../gxwebsoft/oa/service/OaAssetsService.java | 42 + .../oa/service/OaAssetsSiteService.java | 42 + .../service/OaAssetsSoftwareCertService.java | 42 + .../oa/service/OaAssetsSslService.java | 42 + .../oa/service/OaAssetsTrademarkService.java | 42 + .../oa/service/OaAssetsUserService.java | 42 + .../oa/service/OaAssetsVhostService.java | 42 + .../oa/service/OaCompanyFieldService.java | 42 + .../oa/service/OaCompanyService.java | 64 + .../oa/service/OaCompanyUserService.java | 42 + .../gxwebsoft/oa/service/OaLinkService.java | 42 + .../oa/service/OaProductService.java | 42 + .../oa/service/OaProductTabsService.java | 42 + .../oa/service/OaTaskCountService.java | 42 + .../gxwebsoft/oa/service/OaTaskService.java | 42 + .../oa/service/OaTaskUserService.java | 42 + .../service/impl/OaAppFieldServiceImpl.java | 47 + .../service/impl/OaAppRenewServiceImpl.java | 47 + .../oa/service/impl/OaAppServiceImpl.java | 47 + .../oa/service/impl/OaAppUrlServiceImpl.java | 47 + .../oa/service/impl/OaAppUserServiceImpl.java | 47 + .../service/impl/OaAssetsCodeServiceImpl.java | 47 + .../impl/OaAssetsDomainServiceImpl.java | 47 + .../impl/OaAssetsEmailServiceImpl.java | 47 + .../impl/OaAssetsMysqlServiceImpl.java | 47 + .../impl/OaAssetsServerServiceImpl.java | 47 + .../oa/service/impl/OaAssetsServiceImpl.java | 47 + .../service/impl/OaAssetsSiteServiceImpl.java | 47 + .../impl/OaAssetsSoftwareCertServiceImpl.java | 47 + .../service/impl/OaAssetsSslServiceImpl.java | 55 + .../impl/OaAssetsTrademarkServiceImpl.java | 47 + .../service/impl/OaAssetsUserServiceImpl.java | 47 + .../impl/OaAssetsVhostServiceImpl.java | 47 + .../impl/OaCompanyFieldServiceImpl.java | 47 + .../oa/service/impl/OaCompanyServiceImpl.java | 192 ++ .../impl/OaCompanyUserServiceImpl.java | 47 + .../oa/service/impl/OaLinkServiceImpl.java | 47 + .../oa/service/impl/OaProductServiceImpl.java | 47 + .../impl/OaProductTabsServiceImpl.java | 47 + .../service/impl/OaTaskCountServiceImpl.java | 47 + .../oa/service/impl/OaTaskServiceImpl.java | 47 + .../service/impl/OaTaskUserServiceImpl.java | 47 + .../payment/constants/PaymentConstants.java | 244 ++ .../payment/constants/WechatPayType.java | 81 + .../payment/controller/PaymentController.java | 360 +++ .../controller/PaymentNotifyController.java | 188 ++ .../gxwebsoft/payment/dto/PaymentRequest.java | 207 ++ .../payment/dto/PaymentResponse.java | 294 +++ .../dto/PaymentStatusUpdateRequest.java | 41 + .../payment/dto/PaymentWithOrderRequest.java | 158 ++ .../payment/enums/PaymentChannel.java | 159 ++ .../payment/enums/PaymentStatus.java | 141 ++ .../gxwebsoft/payment/enums/PaymentType.java | 224 ++ .../payment/exception/PaymentException.java | 221 ++ .../exception/PaymentExceptionHandler.java | 153 ++ .../payment/service/PaymentService.java | 182 ++ .../payment/service/WxPayConfigService.java | 338 +++ .../payment/service/WxPayNotifyService.java | 366 +++ .../service/impl/PaymentServiceImpl.java | 668 ++++++ .../payment/strategy/PaymentStrategy.java | 153 ++ .../strategy/WechatNativeStrategy.java | 401 ++++ .../utils/PaymentTypeCompatibilityUtil.java | 165 ++ .../ProjectCollectionController.java | 128 ++ .../project/controller/ProjectController.java | 297 +++ .../controller/ProjectFieldController.java | 134 ++ .../controller/ProjectRenewController.java | 290 +++ .../controller/ProjectUrlController.java | 124 + .../controller/ProjectUserController.java | 124 + .../com/gxwebsoft/project/entity/Project.java | 287 +++ .../project/entity/ProjectCollection.java | 44 + .../project/entity/ProjectField.java | 83 + .../project/entity/ProjectRenew.java | 127 ++ .../gxwebsoft/project/entity/ProjectUrl.java | 62 + .../gxwebsoft/project/entity/ProjectUser.java | 58 + .../mapper/ProjectCollectionMapper.java | 37 + .../project/mapper/ProjectFieldMapper.java | 37 + .../project/mapper/ProjectMapper.java | 47 + .../project/mapper/ProjectRenewMapper.java | 48 + .../project/mapper/ProjectUrlMapper.java | 37 + .../project/mapper/ProjectUserMapper.java | 37 + .../mapper/xml/ProjectCollectionMapper.xml | 42 + .../project/mapper/xml/ProjectFieldMapper.xml | 60 + .../project/mapper/xml/ProjectMapper.xml | 275 +++ .../project/mapper/xml/ProjectRenewMapper.xml | 104 + .../project/mapper/xml/ProjectUrlMapper.xml | 60 + .../project/mapper/xml/ProjectUserMapper.xml | 57 + .../project/param/ProjectCollectionParam.java | 38 + .../project/param/ProjectFieldParam.java | 60 + .../gxwebsoft/project/param/ProjectParam.java | 285 +++ .../project/param/ProjectRenewParam.java | 111 + .../project/param/ProjectUrlParam.java | 57 + .../project/param/ProjectUserParam.java | 55 + .../service/ProjectCollectionService.java | 42 + .../project/service/ProjectFieldService.java | 42 + .../project/service/ProjectRenewService.java | 45 + .../project/service/ProjectService.java | 50 + .../project/service/ProjectUrlService.java | 42 + .../project/service/ProjectUserService.java | 42 + .../impl/ProjectCollectionServiceImpl.java | 47 + .../service/impl/ProjectFieldServiceImpl.java | 47 + .../service/impl/ProjectRenewServiceImpl.java | 155 ++ .../service/impl/ProjectServiceImpl.java | 267 +++ .../service/impl/ProjectUrlServiceImpl.java | 47 + .../service/impl/ProjectUserServiceImpl.java | 47 + .../pwl/controller/PwlProjectController.java | 297 +++ .../PwlProjectLibraryController.java | 162 ++ .../com/gxwebsoft/pwl/entity/PwlProject.java | 222 ++ .../pwl/entity/PwlProjectLibrary.java | 74 + .../pwl/mapper/PwlProjectLibraryMapper.java | 37 + .../pwl/mapper/PwlProjectMapper.java | 40 + .../mapper/xml/PwlProjectLibraryMapper.xml | 75 + .../pwl/mapper/xml/PwlProjectMapper.xml | 169 ++ .../pwl/param/PwlProjectImportParam.java | 76 + .../pwl/param/PwlProjectLibraryParam.java | 71 + .../gxwebsoft/pwl/param/PwlProjectParam.java | 173 ++ .../pwl/service/PwlProjectLibraryService.java | 42 + .../pwl/service/PwlProjectService.java | 44 + .../impl/PwlProjectLibraryServiceImpl.java | 47 + .../service/impl/PwlProjectServiceImpl.java | 58 + .../shop/config/OrderConfigProperties.java | 235 ++ .../shop/constants/WxPayConstants.java | 200 ++ .../controller/CouponStatusController.java | 189 ++ .../controller/ShopArticleController.java | 129 ++ .../shop/controller/ShopBrandController.java | 110 + .../shop/controller/ShopCartController.java | 115 + .../controller/ShopCategoryController.java | 126 + .../ShopChatConversationController.java | 115 + .../controller/ShopChatMessageController.java | 115 + .../ShopCommissionRoleController.java | 121 + .../shop/controller/ShopCountController.java | 110 + .../ShopCouponApplyCateController.java | 124 + .../ShopCouponApplyItemController.java | 125 + .../shop/controller/ShopCouponController.java | 217 ++ .../controller/ShopDealerApplyController.java | 342 +++ .../ShopDealerCapitalController.java | 129 ++ .../controller/ShopDealerOrderController.java | 129 ++ .../ShopDealerRefereeController.java | 129 ++ .../ShopDealerSettingController.java | 124 + .../controller/ShopDealerUserController.java | 171 ++ .../ShopDealerWithdrawController.java | 129 ++ .../controller/ShopExpressController.java | 121 + .../ShopExpressTemplateController.java | 121 + .../ShopExpressTemplateDetailController.java | 121 + .../shop/controller/ShopGiftController.java | 261 +++ .../ShopGoodsCategoryController.java | 128 ++ .../ShopGoodsCommentController.java | 115 + .../shop/controller/ShopGoodsController.java | 163 ++ .../ShopGoodsIncomeConfigController.java | 115 + .../controller/ShopGoodsLogController.java | 115 + .../ShopGoodsRelationController.java | 115 + .../ShopGoodsRoleCommissionController.java | 121 + .../controller/ShopGoodsSkuController.java | 121 + .../controller/ShopGoodsSpecController.java | 121 + .../shop/controller/ShopMainController.java | 57 + .../ShopMerchantAccountController.java | 115 + .../ShopMerchantApplyController.java | 192 ++ .../controller/ShopMerchantController.java | 128 ++ .../ShopMerchantTypeController.java | 110 + .../shop/controller/ShopOrderController.java | 558 +++++ .../ShopOrderDeliveryController.java | 110 + .../ShopOrderDeliveryGoodsController.java | 110 + .../ShopOrderExtractController.java | 115 + .../controller/ShopOrderGoodsController.java | 115 + .../controller/ShopOrderInfoController.java | 115 + .../ShopOrderInfoLogController.java | 110 + .../ShopRechargeOrderController.java | 115 + .../shop/controller/ShopSpecController.java | 126 + .../controller/ShopSpecValueController.java | 121 + .../shop/controller/ShopSplashController.java | 115 + .../controller/ShopUserAddressController.java | 133 ++ .../ShopUserBalanceLogController.java | 115 + .../ShopUserCollectionController.java | 115 + .../controller/ShopUserCouponController.java | 309 +++ .../controller/ShopUserRefereeController.java | 129 ++ .../shop/controller/ShopUsersController.java | 110 + .../ShopWechatDepositController.java | 110 + .../shop/dto/OrderCreateRequest.java | 176 ++ .../shop/dto/UpdatePaymentStatusRequest.java | 32 + .../gxwebsoft/shop/entity/ShopArticle.java | 189 ++ .../com/gxwebsoft/shop/entity/ShopBrand.java | 52 + .../com/gxwebsoft/shop/entity/ShopCart.java | 95 + .../gxwebsoft/shop/entity/ShopCategory.java | 122 + .../shop/entity/ShopChatConversation.java | 63 + .../shop/entity/ShopChatMessage.java | 78 + .../shop/entity/ShopCommissionRole.java | 51 + .../com/gxwebsoft/shop/entity/ShopCount.java | 65 + .../com/gxwebsoft/shop/entity/ShopCoupon.java | 136 ++ .../shop/entity/ShopCouponApplyCate.java | 53 + .../shop/entity/ShopCouponApplyItem.java | 61 + .../shop/entity/ShopDealerApply.java | 89 + .../shop/entity/ShopDealerCapital.java | 58 + .../shop/entity/ShopDealerOrder.java | 77 + .../shop/entity/ShopDealerReferee.java | 73 + .../shop/entity/ShopDealerSetting.java | 38 + .../gxwebsoft/shop/entity/ShopDealerUser.java | 88 + .../shop/entity/ShopDealerWithdraw.java | 76 + .../gxwebsoft/shop/entity/ShopExpress.java | 59 + .../shop/entity/ShopExpressTemplate.java | 65 + .../entity/ShopExpressTemplateDetail.java | 70 + .../com/gxwebsoft/shop/entity/ShopGift.java | 112 + .../com/gxwebsoft/shop/entity/ShopGoods.java | 144 ++ .../shop/entity/ShopGoodsCategory.java | 96 + .../shop/entity/ShopGoodsComment.java | 99 + .../shop/entity/ShopGoodsIncomeConfig.java | 64 + .../gxwebsoft/shop/entity/ShopGoodsLog.java | 86 + .../shop/entity/ShopGoodsRelation.java | 53 + .../shop/entity/ShopGoodsRoleCommission.java | 52 + .../gxwebsoft/shop/entity/ShopGoodsSku.java | 79 + .../gxwebsoft/shop/entity/ShopGoodsSpec.java | 45 + .../gxwebsoft/shop/entity/ShopMerchant.java | 145 ++ .../shop/entity/ShopMerchantAccount.java | 68 + .../shop/entity/ShopMerchantApply.java | 134 ++ .../shop/entity/ShopMerchantType.java | 48 + .../com/gxwebsoft/shop/entity/ShopOrder.java | 308 +++ .../shop/entity/ShopOrderDelivery.java | 66 + .../shop/entity/ShopOrderDeliveryGoods.java | 63 + .../shop/entity/ShopOrderExtract.java | 60 + .../gxwebsoft/shop/entity/ShopOrderGoods.java | 115 + .../gxwebsoft/shop/entity/ShopOrderInfo.java | 122 + .../shop/entity/ShopOrderInfoLog.java | 47 + .../shop/entity/ShopRechargeOrder.java | 103 + .../com/gxwebsoft/shop/entity/ShopSpec.java | 60 + .../gxwebsoft/shop/entity/ShopSpecValue.java | 48 + .../com/gxwebsoft/shop/entity/ShopSplash.java | 65 + .../shop/entity/ShopUserAddress.java | 80 + .../shop/entity/ShopUserBalanceLog.java | 82 + .../shop/entity/ShopUserCollection.java | 45 + .../gxwebsoft/shop/entity/ShopUserCoupon.java | 210 ++ .../shop/entity/ShopUserReferee.java | 81 + .../com/gxwebsoft/shop/entity/ShopUsers.java | 76 + .../shop/entity/ShopWechatDeposit.java | 66 + .../shop/enums/OrderStatusEnum.class | Bin 0 -> 2066 bytes .../gxwebsoft/shop/enums/OrderStatusEnum.java | 32 + .../shop/mapper/ShopArticleMapper.java | 37 + .../shop/mapper/ShopBrandMapper.java | 37 + .../gxwebsoft/shop/mapper/ShopCartMapper.java | 37 + .../shop/mapper/ShopCategoryMapper.java | 37 + .../mapper/ShopChatConversationMapper.java | 37 + .../shop/mapper/ShopChatMessageMapper.java | 37 + .../shop/mapper/ShopCommissionRoleMapper.java | 37 + .../shop/mapper/ShopCountMapper.java | 37 + .../mapper/ShopCouponApplyCateMapper.java | 37 + .../mapper/ShopCouponApplyItemMapper.java | 37 + .../shop/mapper/ShopCouponMapper.java | 37 + .../shop/mapper/ShopDealerApplyMapper.java | 37 + .../shop/mapper/ShopDealerCapitalMapper.java | 37 + .../shop/mapper/ShopDealerOrderMapper.java | 37 + .../shop/mapper/ShopDealerRefereeMapper.java | 37 + .../shop/mapper/ShopDealerSettingMapper.java | 37 + .../shop/mapper/ShopDealerUserMapper.java | 37 + .../shop/mapper/ShopDealerWithdrawMapper.java | 37 + .../shop/mapper/ShopExpressMapper.java | 37 + .../ShopExpressTemplateDetailMapper.java | 37 + .../mapper/ShopExpressTemplateMapper.java | 37 + .../gxwebsoft/shop/mapper/ShopGiftMapper.java | 37 + .../shop/mapper/ShopGoodsCategoryMapper.java | 37 + .../shop/mapper/ShopGoodsCommentMapper.java | 37 + .../mapper/ShopGoodsIncomeConfigMapper.java | 37 + .../shop/mapper/ShopGoodsLogMapper.java | 37 + .../shop/mapper/ShopGoodsMapper.java | 51 + .../shop/mapper/ShopGoodsRelationMapper.java | 37 + .../mapper/ShopGoodsRoleCommissionMapper.java | 37 + .../shop/mapper/ShopGoodsSkuMapper.java | 37 + .../shop/mapper/ShopGoodsSpecMapper.java | 37 + .../mapper/ShopMerchantAccountMapper.java | 37 + .../shop/mapper/ShopMerchantApplyMapper.java | 37 + .../shop/mapper/ShopMerchantMapper.java | 37 + .../shop/mapper/ShopMerchantTypeMapper.java | 37 + .../mapper/ShopOrderDeliveryGoodsMapper.java | 37 + .../shop/mapper/ShopOrderDeliveryMapper.java | 37 + .../shop/mapper/ShopOrderExtractMapper.java | 37 + .../shop/mapper/ShopOrderGoodsMapper.java | 48 + .../shop/mapper/ShopOrderInfoLogMapper.java | 37 + .../shop/mapper/ShopOrderInfoMapper.java | 37 + .../shop/mapper/ShopOrderMapper.java | 55 + .../shop/mapper/ShopRechargeOrderMapper.java | 37 + .../gxwebsoft/shop/mapper/ShopSpecMapper.java | 37 + .../shop/mapper/ShopSpecValueMapper.java | 37 + .../shop/mapper/ShopSplashMapper.java | 37 + .../shop/mapper/ShopUserAddressMapper.java | 37 + .../shop/mapper/ShopUserBalanceLogMapper.java | 37 + .../shop/mapper/ShopUserCollectionMapper.java | 37 + .../shop/mapper/ShopUserCouponMapper.java | 37 + .../shop/mapper/ShopUserRefereeMapper.java | 37 + .../shop/mapper/ShopUsersMapper.java | 37 + .../shop/mapper/ShopWechatDepositMapper.java | 37 + .../shop/mapper/xml/ShopArticleMapper.xml | 189 ++ .../shop/mapper/xml/ShopBrandMapper.xml | 51 + .../shop/mapper/xml/ShopCartMapper.xml | 84 + .../shop/mapper/xml/ShopCategoryMapper.xml | 123 + .../mapper/xml/ShopChatConversationMapper.xml | 60 + .../shop/mapper/xml/ShopChatMessageMapper.xml | 75 + .../mapper/xml/ShopCommissionRoleMapper.xml | 57 + .../shop/mapper/xml/ShopCountMapper.xml | 63 + .../mapper/xml/ShopCouponApplyCateMapper.xml | 54 + .../mapper/xml/ShopCouponApplyItemMapper.xml | 60 + .../shop/mapper/xml/ShopCouponMapper.xml | 103 + .../shop/mapper/xml/ShopDealerApplyMapper.xml | 68 + .../mapper/xml/ShopDealerCapitalMapper.xml | 54 + .../shop/mapper/xml/ShopDealerOrderMapper.xml | 72 + .../mapper/xml/ShopDealerRefereeMapper.xml | 53 + .../mapper/xml/ShopDealerSettingMapper.xml | 36 + .../shop/mapper/xml/ShopDealerUserMapper.xml | 81 + .../mapper/xml/ShopDealerWithdrawMapper.xml | 72 + .../shop/mapper/xml/ShopExpressMapper.xml | 57 + .../xml/ShopExpressTemplateDetailMapper.xml | 72 + .../mapper/xml/ShopExpressTemplateMapper.xml | 66 + .../shop/mapper/xml/ShopGiftMapper.xml | 77 + .../mapper/xml/ShopGoodsCategoryMapper.xml | 93 + .../mapper/xml/ShopGoodsCommentMapper.xml | 96 + .../xml/ShopGoodsIncomeConfigMapper.xml | 63 + .../shop/mapper/xml/ShopGoodsLogMapper.xml | 81 + .../shop/mapper/xml/ShopGoodsMapper.xml | 150 ++ .../mapper/xml/ShopGoodsRelationMapper.xml | 48 + .../xml/ShopGoodsRoleCommissionMapper.xml | 57 + .../shop/mapper/xml/ShopGoodsSkuMapper.xml | 78 + .../shop/mapper/xml/ShopGoodsSpecMapper.xml | 45 + .../mapper/xml/ShopMerchantAccountMapper.xml | 63 + .../mapper/xml/ShopMerchantApplyMapper.xml | 123 + .../shop/mapper/xml/ShopMerchantMapper.xml | 150 ++ .../mapper/xml/ShopMerchantTypeMapper.xml | 48 + .../xml/ShopOrderDeliveryGoodsMapper.xml | 60 + .../mapper/xml/ShopOrderDeliveryMapper.xml | 63 + .../mapper/xml/ShopOrderExtractMapper.xml | 57 + .../shop/mapper/xml/ShopOrderGoodsMapper.xml | 105 + .../mapper/xml/ShopOrderInfoLogMapper.xml | 48 + .../shop/mapper/xml/ShopOrderInfoMapper.xml | 114 + .../shop/mapper/xml/ShopOrderMapper.xml | 420 ++++ .../mapper/xml/ShopRechargeOrderMapper.xml | 99 + .../shop/mapper/xml/ShopSpecMapper.xml | 60 + .../shop/mapper/xml/ShopSpecValueMapper.xml | 48 + .../shop/mapper/xml/ShopSplashMapper.xml | 63 + .../shop/mapper/xml/ShopUserAddressMapper.xml | 82 + .../mapper/xml/ShopUserBalanceLogMapper.xml | 78 + .../mapper/xml/ShopUserCollectionMapper.xml | 45 + .../shop/mapper/xml/ShopUserCouponMapper.xml | 96 + .../shop/mapper/xml/ShopUserRefereeMapper.xml | 62 + .../shop/mapper/xml/ShopUsersMapper.xml | 81 + .../mapper/xml/ShopWechatDepositMapper.xml | 69 + .../shop/param/ShopArticleParam.java | 203 ++ .../gxwebsoft/shop/param/ShopBrandParam.java | 47 + .../gxwebsoft/shop/param/ShopCartParam.java | 96 + .../shop/param/ShopCategoryParam.java | 127 ++ .../shop/param/ShopChatConversationParam.java | 57 + .../shop/param/ShopChatMessageParam.java | 75 + .../shop/param/ShopCommissionRoleParam.java | 50 + .../gxwebsoft/shop/param/ShopCountParam.java | 64 + .../shop/param/ShopCouponApplyCateParam.java | 46 + .../shop/param/ShopCouponApplyItemParam.java | 54 + .../gxwebsoft/shop/param/ShopCouponParam.java | 108 + .../param/ShopDealerApplyImportParam.java | 60 + .../shop/param/ShopDealerApplyParam.java | 66 + .../shop/param/ShopDealerCapitalParam.java | 52 + .../shop/param/ShopDealerOrderParam.java | 77 + .../shop/param/ShopDealerRefereeParam.java | 41 + .../shop/param/ShopDealerSettingParam.java | 35 + .../shop/param/ShopDealerUserImportParam.java | 70 + .../shop/param/ShopDealerUserParam.java | 85 + .../shop/param/ShopDealerWithdrawParam.java | 70 + .../shop/param/ShopExpressParam.java | 49 + .../param/ShopExpressTemplateDetailParam.java | 68 + .../shop/param/ShopExpressTemplateParam.java | 60 + .../gxwebsoft/shop/param/ShopGiftParam.java | 76 + .../shop/param/ShopGoodsCategoryParam.java | 96 + .../shop/param/ShopGoodsCommentParam.java | 98 + .../param/ShopGoodsIncomeConfigParam.java | 57 + .../shop/param/ShopGoodsLogParam.java | 89 + .../gxwebsoft/shop/param/ShopGoodsParam.java | 153 ++ .../shop/param/ShopGoodsRelationParam.java | 44 + .../param/ShopGoodsRoleCommissionParam.java | 50 + .../shop/param/ShopGoodsSkuParam.java | 80 + .../shop/param/ShopGoodsSpecParam.java | 48 + .../shop/param/ShopMerchantAccountParam.java | 62 + .../shop/param/ShopMerchantApplyParam.java | 126 + .../shop/param/ShopMerchantParam.java | 149 ++ .../shop/param/ShopMerchantTypeParam.java | 44 + .../param/ShopOrderDeliveryGoodsParam.java | 58 + .../shop/param/ShopOrderDeliveryParam.java | 60 + .../shop/param/ShopOrderExtractParam.java | 52 + .../shop/param/ShopOrderGoodsParam.java | 109 + .../shop/param/ShopOrderInfoLogParam.java | 45 + .../shop/param/ShopOrderInfoParam.java | 124 + .../gxwebsoft/shop/param/ShopOrderParam.java | 262 +++ .../shop/param/ShopRechargeOrderParam.java | 105 + .../gxwebsoft/shop/param/ShopSpecParam.java | 59 + .../shop/param/ShopSpecValueParam.java | 44 + .../gxwebsoft/shop/param/ShopSplashParam.java | 57 + .../shop/param/ShopUserAddressParam.java | 77 + .../shop/param/ShopUserBalanceLogParam.java | 78 + .../shop/param/ShopUserCollectionParam.java | 42 + .../shop/param/ShopUserCouponParam.java | 96 + .../shop/param/ShopUserRefereeParam.java | 48 + .../gxwebsoft/shop/param/ShopUsersParam.java | 77 + .../shop/param/ShopWechatDepositParam.java | 66 + .../shop/service/CouponStatusService.java | 154 ++ .../shop/service/OrderBusinessService.java | 691 ++++++ .../shop/service/OrderCancelService.java | 65 + .../shop/service/ShopArticleService.java | 42 + .../shop/service/ShopBrandService.java | 42 + .../shop/service/ShopCartService.java | 42 + .../shop/service/ShopCategoryService.java | 42 + .../service/ShopChatConversationService.java | 42 + .../shop/service/ShopChatMessageService.java | 42 + .../service/ShopCommissionRoleService.java | 42 + .../shop/service/ShopCountService.java | 42 + .../service/ShopCouponApplyCateService.java | 43 + .../service/ShopCouponApplyItemService.java | 43 + .../shop/service/ShopCouponService.java | 42 + .../shop/service/ShopDealerApplyService.java | 43 + .../service/ShopDealerCapitalService.java | 42 + .../shop/service/ShopDealerOrderService.java | 42 + .../service/ShopDealerRefereeService.java | 42 + .../service/ShopDealerSettingService.java | 42 + .../shop/service/ShopDealerUserService.java | 42 + .../service/ShopDealerWithdrawService.java | 42 + .../shop/service/ShopExpressService.java | 42 + .../ShopExpressTemplateDetailService.java | 42 + .../service/ShopExpressTemplateService.java | 42 + .../shop/service/ShopGiftService.java | 43 + .../service/ShopGoodsCategoryService.java | 42 + .../shop/service/ShopGoodsCommentService.java | 42 + .../service/ShopGoodsIncomeConfigService.java | 42 + .../shop/service/ShopGoodsLogService.java | 42 + .../service/ShopGoodsRelationService.java | 42 + .../ShopGoodsRoleCommissionService.java | 42 + .../shop/service/ShopGoodsService.java | 52 + .../shop/service/ShopGoodsSkuService.java | 42 + .../shop/service/ShopGoodsSpecService.java | 42 + .../service/ShopMerchantAccountService.java | 42 + .../service/ShopMerchantApplyService.java | 42 + .../shop/service/ShopMerchantService.java | 42 + .../shop/service/ShopMerchantTypeService.java | 42 + .../ShopOrderDeliveryGoodsService.java | 42 + .../service/ShopOrderDeliveryService.java | 42 + .../shop/service/ShopOrderExtractService.java | 42 + .../shop/service/ShopOrderGoodsService.java | 50 + .../shop/service/ShopOrderInfoLogService.java | 42 + .../shop/service/ShopOrderInfoService.java | 42 + .../shop/service/ShopOrderService.java | 79 + .../service/ShopOrderUpdate10550Service.java | 21 + .../service/ShopRechargeOrderService.java | 42 + .../shop/service/ShopSpecService.java | 42 + .../shop/service/ShopSpecValueService.java | 42 + .../shop/service/ShopSplashService.java | 42 + .../shop/service/ShopUserAddressService.java | 58 + .../service/ShopUserBalanceLogService.java | 42 + .../service/ShopUserCollectionService.java | 42 + .../shop/service/ShopUserCouponService.java | 43 + .../shop/service/ShopUserRefereeService.java | 42 + .../shop/service/ShopUsersService.java | 42 + .../shop/service/ShopWebsiteService.java | 27 + .../service/ShopWechatDepositService.java | 42 + .../shop/service/UserBalanceLogService.java | 42 + .../service/impl/CouponStatusServiceImpl.java | 339 +++ .../service/impl/OrderCancelServiceImpl.java | 231 ++ .../service/impl/ShopArticleServiceImpl.java | 47 + .../service/impl/ShopBrandServiceImpl.java | 47 + .../service/impl/ShopCartServiceImpl.java | 47 + .../service/impl/ShopCategoryServiceImpl.java | 47 + .../impl/ShopChatConversationServiceImpl.java | 47 + .../impl/ShopChatMessageServiceImpl.java | 47 + .../impl/ShopCommissionRoleServiceImpl.java | 47 + .../service/impl/ShopCountServiceImpl.java | 47 + .../impl/ShopCouponApplyCateServiceImpl.java | 56 + .../impl/ShopCouponApplyItemServiceImpl.java | 56 + .../service/impl/ShopCouponServiceImpl.java | 71 + .../impl/ShopDealerApplyServiceImpl.java | 54 + .../impl/ShopDealerCapitalServiceImpl.java | 47 + .../impl/ShopDealerOrderServiceImpl.java | 47 + .../impl/ShopDealerRefereeServiceImpl.java | 47 + .../impl/ShopDealerSettingServiceImpl.java | 47 + .../impl/ShopDealerUserServiceImpl.java | 47 + .../impl/ShopDealerWithdrawServiceImpl.java | 47 + .../service/impl/ShopExpressServiceImpl.java | 47 + .../ShopExpressTemplateDetailServiceImpl.java | 47 + .../impl/ShopExpressTemplateServiceImpl.java | 47 + .../service/impl/ShopGiftServiceImpl.java | 71 + .../impl/ShopGoodsCategoryServiceImpl.java | 47 + .../impl/ShopGoodsCommentServiceImpl.java | 47 + .../ShopGoodsIncomeConfigServiceImpl.java | 47 + .../service/impl/ShopGoodsLogServiceImpl.java | 47 + .../impl/ShopGoodsRelationServiceImpl.java | 47 + .../ShopGoodsRoleCommissionServiceImpl.java | 47 + .../service/impl/ShopGoodsServiceImpl.java | 75 + .../service/impl/ShopGoodsSkuServiceImpl.java | 47 + .../impl/ShopGoodsSpecServiceImpl.java | 47 + .../impl/ShopMerchantAccountServiceImpl.java | 47 + .../impl/ShopMerchantApplyServiceImpl.java | 47 + .../service/impl/ShopMerchantServiceImpl.java | 47 + .../impl/ShopMerchantTypeServiceImpl.java | 47 + .../ShopOrderDeliveryGoodsServiceImpl.java | 47 + .../impl/ShopOrderDeliveryServiceImpl.java | 47 + .../impl/ShopOrderExtractServiceImpl.java | 47 + .../impl/ShopOrderGoodsServiceImpl.java | 78 + .../impl/ShopOrderInfoLogServiceImpl.java | 47 + .../impl/ShopOrderInfoServiceImpl.java | 47 + .../service/impl/ShopOrderServiceImpl.java | 1109 +++++++++ .../impl/ShopOrderUpdate10550ServiceImpl.java | 298 +++ .../impl/ShopRechargeOrderServiceImpl.java | 47 + .../service/impl/ShopSpecServiceImpl.java | 47 + .../impl/ShopSpecValueServiceImpl.java | 47 + .../service/impl/ShopSplashServiceImpl.java | 47 + .../impl/ShopUserAddressServiceImpl.java | 68 + .../impl/ShopUserBalanceLogServiceImpl.java | 47 + .../impl/ShopUserCollectionServiceImpl.java | 47 + .../impl/ShopUserCouponServiceImpl.java | 57 + .../impl/ShopUserRefereeServiceImpl.java | 47 + .../service/impl/ShopUsersServiceImpl.java | 47 + .../service/impl/ShopWebsiteServiceImpl.java | 83 + .../impl/ShopWechatDepositServiceImpl.java | 47 + .../gxwebsoft/shop/task/CouponExpireTask.java | 86 + .../shop/task/OrderAutoCancelTask.java | 196 ++ .../java/com/gxwebsoft/shop/vo/MenuVo.java | 58 + .../java/com/gxwebsoft/shop/vo/ShopVo.java | 92 + src/main/java/lib/commons-codec-1.9.jar | Bin 0 -> 263965 bytes src/main/java/lib/json-20200518.jar | Bin 0 -> 65966 bytes src/main/resources/application-dev.yml | 83 + src/main/resources/application-prod.yml | 89 + src/main/resources/application.yml | 237 ++ .../templates/audit_evidence_template.docx | Bin 0 -> 32689 bytes .../templates/audit_report_template.docx | Bin 0 -> 909510 bytes src/test/java/com/gxwebsoft/RedisTest.java | 30 + src/test/java/com/gxwebsoft/TestMain.java | 95 + .../gxwebsoft/WebSoftApplicationTests.java | 13 + src/test/java/com/gxwebsoft/WxDev.java | 110 + .../core/controller/QrCodeControllerTest.java | 102 + .../core/utils/EncryptedQrCodeUtilTest.java | 136 ++ .../controller/WxLoginControllerTest.java | 136 ++ .../system/service/UserIgnoreTenantTest.java | 100 + .../system/service/WeixinConfigTest.java | 174 ++ .../gxwebsoft/config/MqttPropertiesTest.java | 44 + .../gxwebsoft/config/ServerUrlConfigTest.java | 51 + .../gxwebsoft/generator/ShopGenerator.java | 435 ++++ .../engine/BeetlTemplateEnginePlus.java | 50 + .../generator/templates/add.config.ts.btl | 4 + .../gxwebsoft/generator/templates/add.tsx.btl | 115 + .../templates/columns.config.vue.btl | 47 + .../templates/components.edit.vue.btl | 221 ++ .../templates/components.search.vue.btl | 42 + .../generator/templates/controller.java.btl | 278 +++ .../generator/templates/entity.java.btl | 157 ++ .../generator/templates/index.config.ts.btl | 4 + .../generator/templates/index.ts.btl | 105 + .../generator/templates/index.ts.uniapp.btl | 101 + .../generator/templates/index.tsx.btl | 171 ++ .../generator/templates/index.vue.btl | 242 ++ .../generator/templates/mapper.java.btl | 41 + .../generator/templates/mapper.xml.btl | 100 + .../generator/templates/model.ts.btl | 43 + .../generator/templates/model.ts.uniapp.btl | 43 + .../generator/templates/param.java.btl | 146 ++ .../generator/templates/service.java.btl | 55 + .../generator/templates/serviceImpl.java.btl | 62 + .../generator/templates/smart-columns.vue.btl | 89 + .../templates/table-columns-config.js | 60 + .../templates/table-with-column-settings.vue | 263 +++ 1491 files changed, 140275 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 docker-deploy-guide.md create mode 100644 pom.xml create mode 100644 src/main/java/com/gxwebsoft/WebSoftApplication.java create mode 100644 src/main/java/com/gxwebsoft/ai/config/AuditExcelStyle.java create mode 100644 src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java create mode 100644 src/main/java/com/gxwebsoft/ai/config/TemplateConfig.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent10PartyConductConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent11HistoryConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent1EightRegConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent1ExpenseConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent1LeaderListConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent2StrategyConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent3DecisionConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent3TripleConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent4TargetConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetExecutionConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetManageConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent6StateAssetsConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent7Constants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent9PersonnelConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/constants/KnowledgeBaseConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AiHistoryController.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent10Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent11Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent2Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent3Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent4Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent5Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent6Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent7Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent9Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditReportController2.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/BaseAuditContentController.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/KnowledgeBaseController.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/AuditContentRequest.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/AuditReportRequest.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/KnowledgeBaseRequest.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/BudgetExecutionExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/BudgetManageExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/DecisionTableExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/EightRegExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/ExpenseExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/HistoryTableExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/InvestmentSituationExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/LeaderListExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/PartyConductExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/PersonnelExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/StateAssetsAuditExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/StrategyAuditExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/TargetAuditExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/TripleOneExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java create mode 100644 src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java create mode 100644 src/main/java/com/gxwebsoft/ai/entity/AiHistory.java create mode 100644 src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java create mode 100644 src/main/java/com/gxwebsoft/ai/factory/KnowledgeBaseClientFactory.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AiHistoryMapper.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/xml/AiHistoryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java create mode 100644 src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java create mode 100644 src/main/java/com/gxwebsoft/ai/param/AiHistoryParam.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent10PartyConductService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent11HistoryService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent1EightRegService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent1ExpenseService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent1LeaderListService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent2StrategyService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent3DecisionService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent3TripleService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent4TargetService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetExecutionService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetManageService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent6StateAssetsService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent7InvestmentService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent9PersonnelService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditReportService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent10PartyConductServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent11HistoryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1EightRegServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1ExpenseServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1LeaderListServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent2StrategyServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3DecisionServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3TripleServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent4TargetServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetExecutionServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetManageServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent6StateAssetsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent7InvestmentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/AuditReportUtil.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseCreate.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseManage.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseRetrieve.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUpdate.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUploader.java create mode 100644 src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUtil.java create mode 100644 src/main/java/com/gxwebsoft/ai/utils/ExcelExportTool.java create mode 100644 src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java create mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java create mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java create mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java create mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java create mode 100644 src/main/java/com/gxwebsoft/auto/service/QrLoginService.java create mode 100644 src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxBmController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxBranchController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxClassController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxEraController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxGradeController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxOrderController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxPayController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/controller/BszxPayRankingController.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxBm.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxBranch.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxClass.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxEra.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxGrade.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxPay.java create mode 100644 src/main/java/com/gxwebsoft/bszx/entity/BszxPayRanking.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxBmMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxBranchMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxClassMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxEraMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxGradeMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxPayMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/BszxPayRankingMapper.java create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBmMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBranchMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxClassMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxEraMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxGradeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayRankingMapper.xml create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxBmParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxBranchParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxClassParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxEraParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxGradeParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxPayParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/param/BszxPayRankingParam.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxBmService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxBranchService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxClassService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxEraService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxGradeService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxPayRankingService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/BszxPayService.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxBmServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxBranchServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxClassServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxEraServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxGradeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayRankingServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsAdController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsAdRecordController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsArticleCategoryController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsArticleCommentController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsArticleContentController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsArticleController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsArticleCountController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsArticleLikeController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsDesignController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsDesignRecordController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsDomainController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsFormController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsFormRecordController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsLangController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsLangLogController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsLinkController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsMainController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsModelController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsNavigationController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsStatisticsController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsTemplateController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteFieldController.java create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteSettingController.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsAd.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsAdRecord.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsAdVo.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsArticle.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsArticleCategory.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsArticleComment.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsArticleContent.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsArticleCount.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsArticleLike.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsDesign.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsDesignRecord.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsDomain.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsForm.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsFormRecord.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsLang.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsLangLog.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsLink.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsModel.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsNavigation.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsStatistics.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsTemplate.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsWebsite.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteField.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteSetting.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/TranslateDataVo.java create mode 100644 src/main/java/com/gxwebsoft/cms/entity/TranslateVo.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsAdMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsAdRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCategoryMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCommentMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsArticleContentMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCountMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsArticleLikeMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsArticleMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsDesignMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsDesignRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsDomainMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsFormMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsFormRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsLangLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsLangMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsLinkMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsModelMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsNavigationMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsStatisticsMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsTemplateMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteFieldMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteSettingMapper.java create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCategoryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCommentMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleContentMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCountMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleLikeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDomainMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLinkMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsModelMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsNavigationMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsStatisticsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsTemplateMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteFieldMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteSettingMapper.xml create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsAdParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsAdRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleCategoryParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleCommentParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleContentParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleCountParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleImportParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleLikeParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsArticleParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsDesignParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsDesignRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsDomainParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsFormParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsFormRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsLangLogParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsLangParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsLinkParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsModelParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsNavigationParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsStatisticsParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsTemplateParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsWebsiteFieldParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsWebsiteParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsWebsiteSettingParam.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsAdRecordService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsAdService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsArticleCategoryService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsArticleCommentService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsArticleContentService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsArticleCountService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsArticleLikeService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsArticleService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsDesignRecordService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsDesignService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsDomainService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsFormRecordService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsFormService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsLangLogService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsLangService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsLinkService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsModelService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsNavigationService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsStatisticsService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsTemplateService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsWebsiteFieldService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsWebsiteService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsWebsiteSettingService.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsAdRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsAdServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCategoryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCommentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleContentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCountServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleLikeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsDomainServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsFormRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsFormServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsLangLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsLangServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsLinkServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsModelServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsNavigationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsStatisticsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsTemplateServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteFieldServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImplHelper.java create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteSettingServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/core/Constants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/annotation/IgnoreTenant.java create mode 100644 src/main/java/com/gxwebsoft/common/core/annotation/OperationLog.java create mode 100644 src/main/java/com/gxwebsoft/common/core/annotation/OperationModule.java create mode 100644 src/main/java/com/gxwebsoft/common/core/annotation/QueryField.java create mode 100644 src/main/java/com/gxwebsoft/common/core/annotation/QueryType.java create mode 100644 src/main/java/com/gxwebsoft/common/core/aspect/IgnoreTenantAspect.java create mode 100644 src/main/java/com/gxwebsoft/common/core/aspect/OperationLogAspect.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/CertificateProperties.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/ConfigProperties.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/HttpMessageConverter.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeDeserializer.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeSerializer.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/MqttProperties.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/RestTemplateConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/SpringContextUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/config/WebMvcConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/AppUserConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/ArticleConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/BalanceConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/BaseConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/OrderConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/PlatformConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/ProfitConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/QRCodeConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/TaskConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/WebsiteConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/constants/WxOfficialConstants.java create mode 100644 src/main/java/com/gxwebsoft/common/core/context/TenantContext.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/CertificateController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/PaymentConfigController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/QrCodeController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/TestController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/dto/qr/CreateBusinessEncryptedQrCodeRequest.java create mode 100644 src/main/java/com/gxwebsoft/common/core/dto/qr/CreateEncryptedQrCodeRequest.java create mode 100644 src/main/java/com/gxwebsoft/common/core/dto/qr/DecryptQrDataRequest.java create mode 100644 src/main/java/com/gxwebsoft/common/core/dto/qr/InvalidateTokenRequest.java create mode 100644 src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyBusinessQrRequest.java create mode 100644 src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyQrContentRequest.java create mode 100644 src/main/java/com/gxwebsoft/common/core/exception/BusinessException.java create mode 100644 src/main/java/com/gxwebsoft/common/core/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/gxwebsoft/common/core/security/JwtAccessDeniedHandler.java create mode 100644 src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationEntryPoint.java create mode 100644 src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/gxwebsoft/common/core/security/JwtSubject.java create mode 100644 src/main/java/com/gxwebsoft/common/core/security/JwtUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/service/CertificateHealthService.java create mode 100644 src/main/java/com/gxwebsoft/common/core/service/CertificateService.java create mode 100644 src/main/java/com/gxwebsoft/common/core/service/EnvironmentAwarePaymentService.java create mode 100644 src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/AliYunSender.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/AlipayConfigUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/CacheClient.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/CertificateLoader.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/CommonUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/DateTimeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/DomainUtils.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/FileServerUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/HttpUtils.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/ImageUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/JChardetFacadeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/JSONUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/MyQrCodeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/OpenOfficeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/QrCodeDecryptResult.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/RedisUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/RequestUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/SignCheckUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/SpmUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigValidator.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatPayDiagnostic.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WechatPayUtils.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WxOfficialUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WxUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WxWorkUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/ApiResult.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/BaseController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/BaseParam.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/BatchParam.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/ExistenceParam.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/PageParam.java create mode 100644 src/main/java/com/gxwebsoft/common/core/web/PageResult.java create mode 100644 src/main/java/com/gxwebsoft/common/core/websocket/WebSocketConfig.java create mode 100644 src/main/java/com/gxwebsoft/common/core/websocket/WebSocketServer.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/AiController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CacheController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CompanyCommentController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CompanyContentController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CompanyController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CompanyGitController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CompanyParameterController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/CompanyUrlController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/DictController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/DictDataController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/DictionaryController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/DictionaryDataController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/DomainController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/EmailController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/FileController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/LoginRecordController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/MainController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/MenuController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/OperationRecordController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/OrganizationController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/PaymentController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/PlugController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/RedisUtilController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/RoleController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/RoleMenuController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/SettingController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/TenantController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/UserCollectionController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/UserController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/UserFileController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/UserRefereeController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Cache.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/ChatMessage.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Company.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/CompanyComment.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/CompanyContent.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/CompanyGit.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/CompanyParameter.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/CompanyUrl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Dict.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/DictData.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Dictionary.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/DictionaryData.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Domain.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/EmailRecord.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/FileRecord.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/KVEntity.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/LoginRecord.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Menu.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/OperationRecord.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Organization.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Payment.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Plug.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Role.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/RoleMenu.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Setting.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Tenant.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/User.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/UserBalanceLog.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/UserCollection.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/UserFile.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/UserInfo.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/UserReferee.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/UserRole.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/CompanyCommentMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/CompanyContentMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/CompanyGitMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/CompanyMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/CompanyParameterMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/CompanyUrlMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/DictDataMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/DictMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/DictionaryDataMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/DictionaryMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/DomainMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/EmailRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/FileRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/LoginRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/MenuMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/OperationRecordMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/OrganizationMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/PaymentMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/PlugMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/RoleMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/SettingMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/TenantMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/UserBalanceLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/UserCollectionMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/UserFileMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/UserMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/UserRefereeMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyCommentMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyContentMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyGitMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyParameterMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyUrlMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/DictDataMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/DictMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryDataMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/DomainMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/EmailRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/FileRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/LoginRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/MenuMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/OperationRecordMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/OrganizationMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/PaymentMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/PlugMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/SettingMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/TenantMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/UserCollectionMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/UserFileMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/UserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRefereeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml create mode 100644 src/main/java/com/gxwebsoft/common/system/param/AlipayParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CacheParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CompanyCommentParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CompanyContentParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CompanyGitParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CompanyParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CompanyParameterParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/CompanyUrlParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/DictDataParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/DictParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/DictionaryDataParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/DictionaryParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/DomainParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/FileRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/LoginParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/LoginRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/MenuParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/OperationRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/OrganizationParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/PaymentParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/PlugParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/RoleParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/SettingParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/SmsCaptchaParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/TenantParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UpdatePasswordParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UserBalanceLogParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UserCollectionParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UserFileParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UserParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/param/UserRefereeParam.java create mode 100644 src/main/java/com/gxwebsoft/common/system/result/CaptchaResult.java create mode 100644 src/main/java/com/gxwebsoft/common/system/result/LoginResult.java create mode 100644 src/main/java/com/gxwebsoft/common/system/result/RedisResult.java create mode 100644 src/main/java/com/gxwebsoft/common/system/result/SmsCaptchaResult.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/CompanyCommentService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/CompanyContentService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/CompanyGitService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/CompanyParameterService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/CompanyService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/CompanyUrlService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/DictDataService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/DictService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/DictionaryDataService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/DictionaryService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/DomainService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/DomainServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/EmailRecordService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/FileRecordService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/LoginRecordService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/MenuService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/OperationRecordService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/OrganizationService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/PaymentService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/PlugService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/RoleMenuService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/RoleService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/SettingService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/TenantService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserBalanceLogService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserCollectionService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserCollectionServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserFileService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserRefereeService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserRoleService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/UserService.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/CompanyCommentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/CompanyContentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/CompanyGitServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/CompanyParameterServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/CompanyServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/CompanyUrlServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/DictDataServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/DictServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryDataServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/EmailRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/LoginRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/MenuServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/OperationRecordServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/OrganizationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/PaymentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/PlugServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/RoleMenuServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/RoleServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/UserBalanceLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/UserFileServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/UserRefereeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/UserRoleServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/common/system/service/impl/UserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/enterprise/controller/EnterpriseController.java create mode 100644 src/main/java/com/gxwebsoft/enterprise/entity/Enterprise.java create mode 100644 src/main/java/com/gxwebsoft/enterprise/mapper/EnterpriseMapper.java create mode 100644 src/main/java/com/gxwebsoft/enterprise/mapper/xml/EnterpriseMapper.xml create mode 100644 src/main/java/com/gxwebsoft/enterprise/service/EnterpriseService.java create mode 100644 src/main/java/com/gxwebsoft/enterprise/service/impl/EnterpriseServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmBxLogController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmCarController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmChoicesController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmCoursesController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmExamLogController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmFenceController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmGpsLogController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmQuestionsController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/HjmViolationController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/MQTTClientDemo.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/PushCallback.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/SendSubscriptionMessages.java create mode 100644 src/main/java/com/gxwebsoft/hjm/controller/WxNotificationTestController.java create mode 100644 src/main/java/com/gxwebsoft/hjm/dto/BatchTemplateMessageRequest.java create mode 100644 src/main/java/com/gxwebsoft/hjm/dto/SubscribeMessageRequest.java create mode 100644 src/main/java/com/gxwebsoft/hjm/dto/TemplateMessageRequest.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/Gps.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmBxLog.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmCar.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmChoices.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmCourses.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmExamLog.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmFence.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmGpsLog.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmQuestions.java create mode 100644 src/main/java/com/gxwebsoft/hjm/entity/HjmViolation.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmBxLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmCarMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmChoicesMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmCoursesMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmExamLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmFenceMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmGpsLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmQuestionsMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/HjmViolationMapper.java create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmBxLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCarMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmChoicesMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCoursesMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmExamLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmFenceMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmGpsLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmQuestionsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmViolationMapper.xml create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmBxLogParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmCarImportParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmCarParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmChoicesParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmCoursesParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmExamLogParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmFenceParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmGpsLogParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmQuestionsParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/param/HjmViolationParam.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/GpsDiagnosticService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/GpsMessageCallback.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/GpsMessageProcessor.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmBxLogService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmCarService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmChoicesService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmCoursesService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmExamLogService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmFenceService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmGpsLogService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmQuestionsService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/HjmViolationService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/MqttService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/WxNotificationService.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmBxLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmCarServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmChoicesServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmCoursesServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmExamLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmFenceServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmGpsLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmQuestionsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/HjmViolationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/service/impl/WxNotificationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/hjm/task/PushHjmFenceOutController.java create mode 100644 src/main/java/com/gxwebsoft/house/controller/HouseInfoController.java create mode 100644 src/main/java/com/gxwebsoft/house/controller/HouseLikeLogController.java create mode 100644 src/main/java/com/gxwebsoft/house/controller/HouseReservationController.java create mode 100644 src/main/java/com/gxwebsoft/house/controller/HouseUserController.java create mode 100644 src/main/java/com/gxwebsoft/house/controller/HouseViewsLogController.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseFile.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseFiles.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseInfo.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseLikeLog.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseReservation.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseUser.java create mode 100644 src/main/java/com/gxwebsoft/house/entity/HouseViewsLog.java create mode 100644 src/main/java/com/gxwebsoft/house/mapper/HouseInfoMapper.java create mode 100644 src/main/java/com/gxwebsoft/house/mapper/HouseLikeLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/house/mapper/HouseReservationMapper.java create mode 100644 src/main/java/com/gxwebsoft/house/mapper/HouseUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/house/mapper/HouseViewsLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/house/mapper/xml/HouseInfoMapper.xml create mode 100644 src/main/java/com/gxwebsoft/house/mapper/xml/HouseLikeLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/house/mapper/xml/HouseReservationMapper.xml create mode 100644 src/main/java/com/gxwebsoft/house/mapper/xml/HouseUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/house/mapper/xml/HouseViewsLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/house/param/HouseInfoParam.java create mode 100644 src/main/java/com/gxwebsoft/house/param/HouseLikeLogParam.java create mode 100644 src/main/java/com/gxwebsoft/house/param/HouseReservationParam.java create mode 100644 src/main/java/com/gxwebsoft/house/param/HouseUserParam.java create mode 100644 src/main/java/com/gxwebsoft/house/param/HouseViewsLogParam.java create mode 100644 src/main/java/com/gxwebsoft/house/service/HouseInfoService.java create mode 100644 src/main/java/com/gxwebsoft/house/service/HouseLikeLogService.java create mode 100644 src/main/java/com/gxwebsoft/house/service/HouseReservationService.java create mode 100644 src/main/java/com/gxwebsoft/house/service/HouseUserService.java create mode 100644 src/main/java/com/gxwebsoft/house/service/HouseViewsLogService.java create mode 100644 src/main/java/com/gxwebsoft/house/service/impl/HouseInfoServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/house/service/impl/HouseLikeLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/house/service/impl/HouseReservationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/house/service/impl/HouseUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/house/service/impl/HouseViewsLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/house/util/SortSceneUtil.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAppController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAppFieldController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAppRenewController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAppUrlController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAppUserController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsCodeController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsDomainController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsEmailController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsMysqlController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsServerController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsSiteController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsSoftwareCertController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsSslController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsTrademarkController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsUserController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaAssetsVhostController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaCompanyFieldController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaCompanyUserController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaLinkController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaProductController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaProductTabsController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaTaskController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaTaskCountController.java create mode 100644 src/main/java/com/gxwebsoft/oa/controller/OaTaskUserController.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaApp.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAppField.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAppRenew.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAppUrl.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAppUser.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssets.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsCode.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsDomain.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsEmail.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsMysql.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsServer.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsSite.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsSoftwareCert.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsSsl.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsTrademark.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsUser.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaAssetsVhost.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaCompany.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaCompanyField.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaCompanyUser.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaLink.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaProduct.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaProductTabs.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaTask.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaTaskCount.java create mode 100644 src/main/java/com/gxwebsoft/oa/entity/OaTaskUser.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAppFieldMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAppMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAppRenewMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAppUrlMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAppUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsCodeMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsDomainMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsEmailMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMysqlMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsServerMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSiteMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSoftwareCertMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSslMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsTrademarkMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaAssetsVhostMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaCompanyFieldMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaCompanyMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaCompanyUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaLinkMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaProductMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaProductTabsMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaTaskCountMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaTaskMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/OaTaskUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppFieldMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUrlMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsCodeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsDomainMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsEmailMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMysqlMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsServerMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSiteMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSoftwareCertMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSslMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsTrademarkMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsVhostMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyFieldMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaLinkMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductTabsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskCountMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAppFieldParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAppParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAppRenewParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAppUrlParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAppUserParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsCodeParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsDomainParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsEmailParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsMysqlParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsServerParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsSiteParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsSoftwareCertParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsSslParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsTrademarkParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsUserParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaAssetsVhostParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaCompanyFieldParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaCompanyParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaCompanyUserParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaLinkParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaProductParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaProductTabsParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaTaskCountParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaTaskParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaTaskRecordParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/param/OaTaskUserParam.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAppFieldService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAppRenewService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAppService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAppUrlService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAppUserService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsCodeService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsDomainService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsEmailService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsMysqlService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsServerService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsSiteService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsSoftwareCertService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsSslService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsTrademarkService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsUserService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaAssetsVhostService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaCompanyFieldService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaCompanyUserService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaLinkService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaProductService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaProductTabsService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaTaskCountService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaTaskService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/OaTaskUserService.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAppFieldServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAppRenewServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAppServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAppUrlServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAppUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsCodeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsDomainServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsEmailServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsMysqlServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServerServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSiteServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSoftwareCertServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSslServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsTrademarkServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsVhostServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyFieldServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaLinkServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaProductServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaProductTabsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaTaskCountServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaTaskServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/oa/service/impl/OaTaskUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/payment/constants/PaymentConstants.java create mode 100644 src/main/java/com/gxwebsoft/payment/constants/WechatPayType.java create mode 100644 src/main/java/com/gxwebsoft/payment/controller/PaymentController.java create mode 100644 src/main/java/com/gxwebsoft/payment/controller/PaymentNotifyController.java create mode 100644 src/main/java/com/gxwebsoft/payment/dto/PaymentRequest.java create mode 100644 src/main/java/com/gxwebsoft/payment/dto/PaymentResponse.java create mode 100644 src/main/java/com/gxwebsoft/payment/dto/PaymentStatusUpdateRequest.java create mode 100644 src/main/java/com/gxwebsoft/payment/dto/PaymentWithOrderRequest.java create mode 100644 src/main/java/com/gxwebsoft/payment/enums/PaymentChannel.java create mode 100644 src/main/java/com/gxwebsoft/payment/enums/PaymentStatus.java create mode 100644 src/main/java/com/gxwebsoft/payment/enums/PaymentType.java create mode 100644 src/main/java/com/gxwebsoft/payment/exception/PaymentException.java create mode 100644 src/main/java/com/gxwebsoft/payment/exception/PaymentExceptionHandler.java create mode 100644 src/main/java/com/gxwebsoft/payment/service/PaymentService.java create mode 100644 src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java create mode 100644 src/main/java/com/gxwebsoft/payment/service/WxPayNotifyService.java create mode 100644 src/main/java/com/gxwebsoft/payment/service/impl/PaymentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/payment/strategy/PaymentStrategy.java create mode 100644 src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java create mode 100644 src/main/java/com/gxwebsoft/payment/utils/PaymentTypeCompatibilityUtil.java create mode 100644 src/main/java/com/gxwebsoft/project/controller/ProjectCollectionController.java create mode 100644 src/main/java/com/gxwebsoft/project/controller/ProjectController.java create mode 100644 src/main/java/com/gxwebsoft/project/controller/ProjectFieldController.java create mode 100644 src/main/java/com/gxwebsoft/project/controller/ProjectRenewController.java create mode 100644 src/main/java/com/gxwebsoft/project/controller/ProjectUrlController.java create mode 100644 src/main/java/com/gxwebsoft/project/controller/ProjectUserController.java create mode 100644 src/main/java/com/gxwebsoft/project/entity/Project.java create mode 100644 src/main/java/com/gxwebsoft/project/entity/ProjectCollection.java create mode 100644 src/main/java/com/gxwebsoft/project/entity/ProjectField.java create mode 100644 src/main/java/com/gxwebsoft/project/entity/ProjectRenew.java create mode 100644 src/main/java/com/gxwebsoft/project/entity/ProjectUrl.java create mode 100644 src/main/java/com/gxwebsoft/project/entity/ProjectUser.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/ProjectCollectionMapper.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/ProjectFieldMapper.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/ProjectMapper.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/ProjectRenewMapper.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/ProjectUrlMapper.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/ProjectUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/project/mapper/xml/ProjectCollectionMapper.xml create mode 100644 src/main/java/com/gxwebsoft/project/mapper/xml/ProjectFieldMapper.xml create mode 100644 src/main/java/com/gxwebsoft/project/mapper/xml/ProjectMapper.xml create mode 100644 src/main/java/com/gxwebsoft/project/mapper/xml/ProjectRenewMapper.xml create mode 100644 src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUrlMapper.xml create mode 100644 src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/project/param/ProjectCollectionParam.java create mode 100644 src/main/java/com/gxwebsoft/project/param/ProjectFieldParam.java create mode 100644 src/main/java/com/gxwebsoft/project/param/ProjectParam.java create mode 100644 src/main/java/com/gxwebsoft/project/param/ProjectRenewParam.java create mode 100644 src/main/java/com/gxwebsoft/project/param/ProjectUrlParam.java create mode 100644 src/main/java/com/gxwebsoft/project/param/ProjectUserParam.java create mode 100644 src/main/java/com/gxwebsoft/project/service/ProjectCollectionService.java create mode 100644 src/main/java/com/gxwebsoft/project/service/ProjectFieldService.java create mode 100644 src/main/java/com/gxwebsoft/project/service/ProjectRenewService.java create mode 100644 src/main/java/com/gxwebsoft/project/service/ProjectService.java create mode 100644 src/main/java/com/gxwebsoft/project/service/ProjectUrlService.java create mode 100644 src/main/java/com/gxwebsoft/project/service/ProjectUserService.java create mode 100644 src/main/java/com/gxwebsoft/project/service/impl/ProjectCollectionServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/project/service/impl/ProjectFieldServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/project/service/impl/ProjectRenewServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/project/service/impl/ProjectServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/project/service/impl/ProjectUrlServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/project/service/impl/ProjectUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/pwl/controller/PwlProjectController.java create mode 100644 src/main/java/com/gxwebsoft/pwl/controller/PwlProjectLibraryController.java create mode 100644 src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java create mode 100644 src/main/java/com/gxwebsoft/pwl/entity/PwlProjectLibrary.java create mode 100644 src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectLibraryMapper.java create mode 100644 src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectMapper.java create mode 100644 src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectLibraryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectMapper.xml create mode 100644 src/main/java/com/gxwebsoft/pwl/param/PwlProjectImportParam.java create mode 100644 src/main/java/com/gxwebsoft/pwl/param/PwlProjectLibraryParam.java create mode 100644 src/main/java/com/gxwebsoft/pwl/param/PwlProjectParam.java create mode 100644 src/main/java/com/gxwebsoft/pwl/service/PwlProjectLibraryService.java create mode 100644 src/main/java/com/gxwebsoft/pwl/service/PwlProjectService.java create mode 100644 src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectLibraryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/config/OrderConfigProperties.java create mode 100644 src/main/java/com/gxwebsoft/shop/constants/WxPayConstants.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/CouponStatusController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopArticleController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopBrandController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCartController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCategoryController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopChatConversationController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopChatMessageController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCommissionRoleController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCountController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyCateController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyItemController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopCouponController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerApplyController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerCapitalController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerOrderController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerRefereeController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerSettingController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerUserController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopDealerWithdrawController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopExpressController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateDetailController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGiftController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCategoryController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCommentController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsIncomeConfigController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsLogController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRelationController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRoleCommissionController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSkuController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSpecController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopMainController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopMerchantAccountController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopMerchantApplyController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopMerchantController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopMerchantTypeController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryGoodsController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderExtractController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderGoodsController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoLogController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopRechargeOrderController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopSpecController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopSpecValueController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopSplashController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopUserAddressController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopUserBalanceLogController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopUserCollectionController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopUserRefereeController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopUsersController.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/ShopWechatDepositController.java create mode 100644 src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java create mode 100644 src/main/java/com/gxwebsoft/shop/dto/UpdatePaymentStatusRequest.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopArticle.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopBrand.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCart.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCategory.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopChatConversation.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopChatMessage.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCommissionRole.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCount.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyCate.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyItem.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerApply.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerCapital.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerReferee.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerSetting.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerUser.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopDealerWithdraw.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopExpress.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplate.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplateDetail.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGift.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsCategory.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsComment.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsIncomeConfig.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsLog.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRelation.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRoleCommission.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSku.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSpec.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopMerchant.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopMerchantAccount.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopMerchantApply.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopMerchantType.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrder.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrderDelivery.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrderDeliveryGoods.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrderExtract.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrderGoods.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfo.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfoLog.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopRechargeOrder.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopSpec.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopSpecValue.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopSplash.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopUserAddress.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopUserBalanceLog.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopUserCollection.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopUserReferee.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopUsers.java create mode 100644 src/main/java/com/gxwebsoft/shop/entity/ShopWechatDeposit.java create mode 100644 src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.class create mode 100644 src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopArticleMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopBrandMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCartMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCategoryMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopChatConversationMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopChatMessageMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCommissionRoleMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCountMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyCateMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyItemMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopCouponMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerApplyMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerCapitalMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerOrderMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerSettingMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerUserMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopDealerWithdrawMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopExpressMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateDetailMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGiftMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCategoryMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCommentMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsIncomeConfigMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRelationMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRoleCommissionMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSkuMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSpecMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantAccountMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantApplyMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantTypeMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryGoodsMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderExtractMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderGoodsMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopRechargeOrderMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopSpecMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopSpecValueMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopSplashMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopUserAddressMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopUserBalanceLogMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopUserCollectionMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopUserCouponMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopUserRefereeMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopUsersMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/ShopWechatDepositMapper.java create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopArticleMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopBrandMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCartMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCategoryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatConversationMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatMessageMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCommissionRoleMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCountMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyCateMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyItemMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerApplyMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerCapitalMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerOrderMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerSettingMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerUserMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerWithdrawMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateDetailMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGiftMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCategoryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCommentMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsIncomeConfigMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRelationMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRoleCommissionMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSkuMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSpecMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantAccountMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantApplyMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantTypeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryGoodsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderExtractMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderGoodsMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopRechargeOrderMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecValueMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSplashMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserAddressMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserBalanceLogMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCollectionMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCouponMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserRefereeMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUsersMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/mapper/xml/ShopWechatDepositMapper.xml create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopArticleParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopBrandParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCartParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCategoryParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopChatConversationParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopChatMessageParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCommissionRoleParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCountParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyCateParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyItemParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopCouponParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyImportParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerCapitalParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerOrderParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerRefereeParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerSettingParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerUserImportParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerUserParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopDealerWithdrawParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopExpressParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateDetailParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGiftParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsCategoryParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsCommentParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsIncomeConfigParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsLogParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsRelationParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsRoleCommissionParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsSkuParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopGoodsSpecParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopMerchantAccountParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopMerchantApplyParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopMerchantParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopMerchantTypeParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryGoodsParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderExtractParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderGoodsParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoLogParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopOrderParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopRechargeOrderParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopSpecParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopSpecValueParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopSplashParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopUserAddressParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopUserBalanceLogParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopUserCollectionParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopUserCouponParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopUserRefereeParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopUsersParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/param/ShopWechatDepositParam.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/CouponStatusService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/OrderCancelService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopArticleService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopBrandService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCartService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCategoryService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopChatConversationService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopChatMessageService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCommissionRoleService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCountService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyCateService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyItemService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopCouponService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerApplyService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerCapitalService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerOrderService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerRefereeService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerSettingService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerUserService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopDealerWithdrawService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopExpressService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateDetailService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGiftService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsCategoryService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsCommentService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsIncomeConfigService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsLogService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsRelationService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsRoleCommissionService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsSkuService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopGoodsSpecService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopMerchantAccountService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopMerchantApplyService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopMerchantService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopMerchantTypeService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryGoodsService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderExtractService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderGoodsService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoLogService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopOrderUpdate10550Service.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopRechargeOrderService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopSpecService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopSpecValueService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopSplashService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopUserAddressService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopUserBalanceLogService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopUserCollectionService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopUserCouponService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopUserRefereeService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopUsersService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopWebsiteService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/ShopWechatDepositService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/UserBalanceLogService.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/CouponStatusServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopArticleServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopBrandServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCartServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCategoryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopChatConversationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopChatMessageServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCommissionRoleServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCountServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyCateServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyItemServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerApplyServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCapitalServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerOrderServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerRefereeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerSettingServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerUserServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerWithdrawServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateDetailServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGiftServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCategoryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCommentServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsIncomeConfigServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRelationServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRoleCommissionServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSkuServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSpecServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantAccountServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantApplyServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantTypeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryGoodsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderExtractServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderGoodsServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderUpdate10550ServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopRechargeOrderServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecValueServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopSplashServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopUserAddressServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopUserBalanceLogServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCollectionServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCouponServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopUserRefereeServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopUsersServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopWebsiteServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/service/impl/ShopWechatDepositServiceImpl.java create mode 100644 src/main/java/com/gxwebsoft/shop/task/CouponExpireTask.java create mode 100644 src/main/java/com/gxwebsoft/shop/task/OrderAutoCancelTask.java create mode 100644 src/main/java/com/gxwebsoft/shop/vo/MenuVo.java create mode 100644 src/main/java/com/gxwebsoft/shop/vo/ShopVo.java create mode 100644 src/main/java/lib/commons-codec-1.9.jar create mode 100644 src/main/java/lib/json-20200518.jar create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/templates/audit_evidence_template.docx create mode 100644 src/main/resources/templates/audit_report_template.docx create mode 100644 src/test/java/com/gxwebsoft/RedisTest.java create mode 100644 src/test/java/com/gxwebsoft/TestMain.java create mode 100644 src/test/java/com/gxwebsoft/WebSoftApplicationTests.java create mode 100644 src/test/java/com/gxwebsoft/WxDev.java create mode 100644 src/test/java/com/gxwebsoft/common/core/controller/QrCodeControllerTest.java create mode 100644 src/test/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtilTest.java create mode 100644 src/test/java/com/gxwebsoft/common/system/controller/WxLoginControllerTest.java create mode 100644 src/test/java/com/gxwebsoft/common/system/service/UserIgnoreTenantTest.java create mode 100644 src/test/java/com/gxwebsoft/common/system/service/WeixinConfigTest.java create mode 100644 src/test/java/com/gxwebsoft/config/MqttPropertiesTest.java create mode 100644 src/test/java/com/gxwebsoft/config/ServerUrlConfigTest.java create mode 100644 src/test/java/com/gxwebsoft/generator/ShopGenerator.java create mode 100644 src/test/java/com/gxwebsoft/generator/engine/BeetlTemplateEnginePlus.java create mode 100644 src/test/java/com/gxwebsoft/generator/templates/add.config.ts.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/add.tsx.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/columns.config.vue.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/components.edit.vue.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/components.search.vue.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/controller.java.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/entity.java.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/index.config.ts.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/index.ts.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/index.ts.uniapp.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/index.tsx.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/index.vue.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/mapper.java.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/mapper.xml.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/model.ts.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/model.ts.uniapp.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/param.java.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/service.java.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/serviceImpl.java.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/smart-columns.vue.btl create mode 100644 src/test/java/com/gxwebsoft/generator/templates/table-columns-config.js create mode 100644 src/test/java/com/gxwebsoft/generator/templates/table-with-column-settings.vue diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cda18ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ +/cert/ +/src/main/resources/dev/ + +### macOS ### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +/file/ +/websoft-modules.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7aba49b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# 使用更小的 Alpine Linux + OpenJDK 17 镜像 +FROM openjdk:17-jdk-alpine + +# 设置工作目录 +WORKDIR /app + +# 创建日志目录 +RUN mkdir -p /app/logs + +# 创建上传文件目录 +RUN mkdir -p /app/uploads + +# 安装wget用于健康检查,并添加应用用户(安全考虑) +RUN apk add --no-cache wget && \ + addgroup -g 1000 appgroup && \ + adduser -D -u 1000 -G appgroup appuser + +# 复制jar包到容器 +COPY target/*.jar app.jar + +# 设置目录权限 +RUN chown -R appuser:appgroup /app + +# 切换到应用用户 +USER appuser + +# 暴露端口 +EXPOSE 9200 + +# 设置JVM参数 +ENV JAVA_OPTS="-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom" + +# 设置Spring Profile +ENV SPRING_PROFILES_ACTIVE=prod + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:9200/actuator/health || exit 1 + +# 启动应用 +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef5a814 --- /dev/null +++ b/README.md @@ -0,0 +1,286 @@ +
+

🚀 WebSoft API

+

基于 Spring Boot + MyBatis Plus 的企业级后端API服务

+ +

+ Java + Spring Boot + MyBatis Plus + MySQL + Redis + License +

+
+ +## 📖 项目简介 + +WebSoft API 是一个基于 **Spring Boot + MyBatis Plus** 构建的现代化企业级后端API服务,采用最新的Java技术栈: + +- **核心框架**:Spring Boot 2.5.4 + Spring Security + Spring AOP +- **数据访问**:MyBatis Plus 3.4.3 + Druid 连接池 +- **数据库**:MySQL + Redis +- **文档工具**:Swagger 3.0 + Knife4j +- **工具库**:Hutool、Lombok、FastJSON + + + +## 项目演示 +| 后台管理系统 | https://mp.websoft.top | +|--------|-------------------------------------------------------------------------------------------------------------------------------------| +| 测试账号 | 13800010123,123456 +| 正式账号 | [立即注册](https://mp.websoft.top/register/?inviteCode=github) | +| 关注公众号 | ![输入图片说明](https://oss.wsdns.cn/20240327/f1175cc5aae741d3af05484747270bd5.jpeg?x-oss-process=image/resize,m_fixed,w_150/quality,Q_90) | + + + + +## 🛠️ 技术栈 + +### 核心框架 +| 技术 | 版本 | 说明 | +|------|------|------| +| Java | 1.8+ | 编程语言 | +| Spring Boot | 2.5.4 | 微服务框架 | +| Spring Security | 5.5.x | 安全框架 | +| MyBatis Plus | 3.4.3 | ORM框架 | +| MySQL | 8.0+ | 关系型数据库 | +| Redis | 6.0+ | 缓存数据库 | +| Druid | 1.2.6 | 数据库连接池 | + +### 功能组件 +- **Swagger 3.0 + Knife4j** - API文档生成与测试 +- **JWT** - 用户认证与授权 +- **Hutool** - Java工具类库 +- **EasyPOI** - Excel文件处理 +- **阿里云OSS** - 对象存储服务 +- **微信支付/支付宝** - 支付集成 +- **Socket.IO** - 实时通信 +- **MQTT** - 物联网消息传输 + +## 📋 环境要求 + +### 基础环境 +- ☕ **Java 1.8+** +- 🗄️ **MySQL 8.0+** +- 🔴 **Redis 6.0+** +- 📦 **Maven 3.6+** + +### 开发工具 +- **推荐**:IntelliJ IDEA / Eclipse +- **插件**:Lombok Plugin、MyBatis Plugin + +## 🚀 快速开始 + +### 1. 克隆项目 +```bash +git clone https://github.com/websoft-top/mp-java.git +cd mp-java +``` + +### 2. 数据库配置 +```sql +-- 创建数据库 +CREATE DATABASE websoft_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 导入数据库脚本(如果有的话) +-- source /path/to/database.sql +``` + +### 3. 配置文件 +编辑 `src/main/resources/application-dev.yml` 文件,配置数据库连接: +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/websoft_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 + username: your_username + password: your_password + redis: + host: localhost + port: 6379 + password: your_redis_password +``` + +### 4. 启动项目 +```bash +# 使用 Maven 启动 +mvn spring-boot:run + +# 或者使用 IDE 直接运行 WebSoftApplication.java +``` + +访问 `http://localhost:9200` 即可看到API服务。 + +### 5. API文档 +启动项目后,访问以下地址查看API文档: +- Swagger UI: `http://localhost:9200/swagger-ui/index.html` +- Knife4j: `http://localhost:9200/doc.html` + +## ⚙️ 配置说明 + +### 数据库配置 +在 `application-dev.yml` 中配置数据库连接: +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/websoft_db + username: root + password: your_password + driver-class-name: com.mysql.cj.jdbc.Driver +``` + +### Redis配置 +```yaml +spring: + redis: + host: localhost + port: 6379 + password: your_redis_password + database: 0 +``` + +### 阿里云OSS配置 +```yaml +config: + endpoint: https://oss-cn-shenzhen.aliyuncs.com + accessKeyId: your_access_key_id + accessKeySecret: your_access_key_secret + bucketName: your_bucket_name + bucketDomain: https://your-domain.com +``` + +### 其他配置 +- **JWT密钥**:`config.token-key` 用于JWT令牌加密 +- **文件上传路径**:`config.upload-path` 本地文件存储路径 +- **邮件服务**:配置SMTP服务器用于发送邮件 + +## 🎯 核心功能 + +### 🔐 用户认证与授权 +- **JWT认证**:基于JSON Web Token的用户认证 +- **Spring Security**:完整的安全框架集成 +- **角色权限**:基于RBAC的权限控制 +- **图形验证码**:防止恶意登录 + +### 📝 内容管理系统(CMS) +- **文章管理**:支持富文本内容管理 +- **媒体文件**:图片/视频文件上传与管理 +- **分类管理**:内容分类与标签管理 +- **SEO优化**:搜索引擎优化支持 + +### 🛒 电商系统 +- **商品管理**:商品信息、规格、库存管理 +- **订单系统**:完整的订单流程管理 +- **支付集成**:支持微信支付、支付宝 +- **物流跟踪**:快递100物流查询集成 + +### 🔧 系统管理 +- **用户管理**:用户信息维护与管理 +- **系统配置**:动态配置管理 +- **日志监控**:系统操作日志记录 +- **数据备份**:数据库备份与恢复 + +### 📊 数据分析 +- **统计报表**:业务数据统计分析 +- **图表展示**:数据可视化展示 +- **导出功能**:Excel数据导出 +- **实时监控**:系统性能监控 + +## 🏗️ 项目结构 + +``` +src/main/java/com/gxwebsoft/ +├── WebSoftApplication.java # 启动类 +├── cms/ # 内容管理模块 +│ ├── controller/ # 控制器层 +│ ├── service/ # 业务逻辑层 +│ ├── mapper/ # 数据访问层 +│ └── entity/ # 实体类 +├── shop/ # 商城模块 +│ ├── controller/ +│ ├── service/ +│ ├── mapper/ +│ └── entity/ +├── common/ # 公共模块 +│ ├── core/ # 核心配置 +│ ├── utils/ # 工具类 +│ └── exception/ # 异常处理 +└── resources/ + ├── application.yml # 主配置文件 + ├── application-dev.yml # 开发环境配置 + └── application-prod.yml# 生产环境配置 +``` + +## 🔧 开发规范 + +### 代码结构 +- **Controller层**:处理HTTP请求,参数验证 +- **Service层**:业务逻辑处理,事务管理 +- **Mapper层**:数据访问,SQL映射 +- **Entity层**:数据实体,数据库表映射 + +### 命名规范 +- **类名**:使用大驼峰命名法(PascalCase) +- **方法名**:使用小驼峰命名法(camelCase) +- **常量**:使用全大写,下划线分隔 +- **包名**:使用小写字母,点分隔 + +## 📚 API文档 + +项目集成了Swagger和Knife4j,提供完整的API文档: + +### 访问地址 +- **Swagger UI**: `http://localhost:9200/swagger-ui/index.html` +- **Knife4j**: `http://localhost:9200/doc.html` + +### 主要接口模块 +- **用户认证**: `/api/auth/**` - 登录、注册、权限验证 +- **用户管理**: `/api/user/**` - 用户CRUD操作 +- **内容管理**: `/api/cms/**` - 文章、媒体文件管理 +- **商城管理**: `/api/shop/**` - 商品、订单管理 +- **系统管理**: `/api/system/**` - 系统配置、日志管理 + +## 🚀 部署指南 + +### 开发环境部署 +```bash +# 1. 启动MySQL和Redis服务 +# 2. 创建数据库并导入初始数据 +# 3. 修改配置文件 +# 4. 启动应用 +mvn spring-boot:run +``` + +### 生产环境部署 +```bash +# 1. 打包应用 +mvn clean package -Dmaven.test.skip=true + +# 2. 运行jar包 +java -jar target/com-gxwebsoft-modules-1.5.0.jar --spring.profiles.active=prod + +# 3. 使用Docker部署(可选) +docker build -t websoft-api . +docker run -d -p 9200:9200 websoft-api +``` + +## 🤝 贡献指南 + +1. Fork 本仓库 +2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 打开 Pull Request + +## 📄 许可证 + +本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情 + +## 📞 联系我们 + +- 官网:https://websoft.top +- 邮箱:170083662@qq.top +- QQ群:479713884 + +--- + +⭐ 如果这个项目对您有帮助,请给我们一个星标! \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ea6a7d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3.8' + +services: + # 应用服务 + cms-api: + build: . + container_name: cms-api + ports: + - "9200:9200" + environment: + - SPRING_PROFILES_ACTIVE=prod + - JAVA_OPTS=-Xms512m -Xmx1024m + volumes: + # 证书挂载卷 - 将宿主机证书目录挂载到容器 + - ./certs:/app/certs:ro + # 日志挂载卷 + - ./logs:/app/logs + # 上传文件挂载卷 + - ./uploads:/app/uploads + networks: + - cms-network + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9200/actuator/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + +networks: + cms-network: + driver: bridge + +volumes: + mysql_data: + driver: local + redis_data: + driver: local diff --git a/docker-deploy-guide.md b/docker-deploy-guide.md new file mode 100644 index 0000000..4961d64 --- /dev/null +++ b/docker-deploy-guide.md @@ -0,0 +1,188 @@ +# Docker容器化部署指南 + +## 支付证书问题解决方案 + +本项目已经解决了Docker容器中支付证书路径失效的问题,支持多种证书加载方式。 + +## 目录结构 + +``` +project-root/ +├── Dockerfile +├── docker-compose.yml +├── certs/ # 证书目录(需要手动创建) +│ ├── wechat/ # 微信支付证书 +│ │ ├── apiclient_key.pem +│ │ ├── apiclient_cert.pem +│ │ └── wechatpay_cert.pem +│ └── alipay/ # 支付宝证书 +│ ├── app_private_key.pem +│ ├── appCertPublicKey.crt +│ ├── alipayCertPublicKey.crt +│ └── alipayRootCert.crt +├── logs/ # 日志目录 +├── uploads/ # 上传文件目录 +└── src/ +``` + +## 部署步骤 + +### 1. 准备证书文件 + +创建证书目录并放置证书文件: + +```bash +# 创建证书目录 +mkdir -p certs/wechat +mkdir -p certs/alipay + +# 复制微信支付证书到对应目录 +cp /path/to/your/apiclient_key.pem certs/wechat/ +cp /path/to/your/apiclient_cert.pem certs/wechat/ +cp /path/to/your/wechatpay_cert.pem certs/wechat/ + +# 复制支付宝证书到对应目录 +cp /path/to/your/app_private_key.pem certs/alipay/ +cp /path/to/your/appCertPublicKey.crt certs/alipay/ +cp /path/to/your/alipayCertPublicKey.crt certs/alipay/ +cp /path/to/your/alipayRootCert.crt certs/alipay/ + +# 设置证书文件权限(只读) +chmod -R 444 certs/ +``` + +### 2. 配置环境变量 + +创建 `.env` 文件(可选): + +```bash +# 应用配置 +SPRING_PROFILES_ACTIVE=prod +JAVA_OPTS=-Xms512m -Xmx1024m + +# 数据库配置 +MYSQL_ROOT_PASSWORD=root123456 +MYSQL_DATABASE=modules +MYSQL_USER=modules +MYSQL_PASSWORD=8YdLnk7KsPAyDXGA + +# Redis配置 +REDIS_PASSWORD=redis_WSDb88 +``` + +### 3. 构建和启动 + +```bash +# 构建应用 +mvn clean package -DskipTests + +# 启动所有服务 +docker-compose up -d + +# 查看服务状态 +docker-compose ps + +# 查看应用日志 +docker-compose logs -f cms-app +``` + +### 4. 验证部署 + +```bash +# 检查应用健康状态 +curl http://localhost:9200/actuator/health + +# 检查证书是否正确加载 +docker exec cms-java-app ls -la /app/certs/ +``` + +## 证书加载模式 + +### 开发环境 (CLASSPATH) +- 证书文件放在 `src/main/resources/certs/` 目录下 +- 打包时会包含在jar包中 +- 适合开发和测试环境 + +### 生产环境 (VOLUME) +- 证书文件通过Docker挂载卷加载 +- 证书文件在宿主机上,挂载到容器的 `/app/certs` 目录 +- 支持证书文件的动态更新(重启容器后生效) + +### 文件系统模式 (FILESYSTEM) +- 直接从文件系统路径加载证书 +- 适合传统部署方式 + +## 配置说明 + +### application.yml 配置 + +```yaml +certificate: + load-mode: VOLUME # 证书加载模式 + cert-root-path: /app/certs # 证书根目录 + + wechat-pay: + dev: + api-v3-key: "your-api-v3-key" + private-key-file: "apiclient_key.pem" + apiclient-cert-file: "apiclient_cert.pem" + wechatpay-cert-file: "wechatpay_cert.pem" +``` + +### 环境特定配置 + +- **开发环境**: `application-dev.yml` - 使用CLASSPATH模式 +- **生产环境**: `application-prod.yml` - 使用VOLUME模式 + +## 故障排除 + +### 1. 证书文件找不到 + +```bash +# 检查证书文件是否存在 +docker exec cms-java-app ls -la /app/certs/ + +# 检查文件权限 +docker exec cms-java-app ls -la /app/certs/wechat/ +``` + +### 2. 支付接口调用失败 + +```bash +# 查看应用日志 +docker-compose logs cms-app | grep -i cert + +# 检查证书配置 +docker exec cms-java-app cat /app/application.yml | grep -A 10 certificate +``` + +### 3. 容器启动失败 + +```bash +# 查看详细错误信息 +docker-compose logs cms-app + +# 检查容器状态 +docker-compose ps +``` + +## 安全建议 + +1. **证书文件权限**: 设置为只读权限 (444) +2. **证书目录权限**: 限制访问权限 +3. **敏感信息**: 使用环境变量或Docker secrets管理敏感配置 +4. **网络安全**: 使用内部网络,限制端口暴露 + +## 更新证书 + +1. 停止应用容器:`docker-compose stop cms-app` +2. 更新证书文件到 `certs/` 目录 +3. 重启应用容器:`docker-compose start cms-app` + +## 监控和日志 + +- 应用日志:`./logs/` 目录 +- 容器日志:`docker-compose logs` +- 健康检查:访问 `/actuator/health` 端点 + +通过以上配置,你的应用在Docker容器中就能正确加载支付证书了! diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c64a5b4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,427 @@ + + + 4.0.0 + + com.gxwebsoft + com-gxwebsoft-modules + 1.5.0 + + com-gxwebsoft-api + WebSoftApi project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + + + 17 + UTF-8 + UTF-8 + + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.projectlombok + lombok + true + + + + + com.mysql + mysql-connector-j + runtime + + + + + com.alibaba + druid-spring-boot-starter + 1.2.20 + + + + + com.baomidou + mybatis-plus-boot-starter + 3.4.3.3 + + + + + com.github.yulichang + mybatis-plus-join-boot-starter + 1.4.5 + + + + + com.baomidou + mybatis-plus-generator + 3.4.1 + + + + + cn.hutool + hutool-core + 5.8.25 + + + cn.hutool + hutool-extra + 5.8.25 + + + cn.hutool + hutool-http + 5.8.25 + + + cn.hutool + hutool-crypto + 5.8.25 + + + + + cn.afterturn + easypoi-base + 4.4.0 + + + + + org.apache.tika + tika-core + 2.9.1 + + + + + com.github.livesense + jodconverter-core + 1.0.5 + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.ibeetl + beetl + 3.15.10.RELEASE + + + + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + + + org.springframework.boot + spring-boot-starter-security + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + + com.github.whvcse + easy-captcha + 1.6.2 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + com.aliyun + aliyun-java-sdk-core + 4.4.3 + + + + com.alipay.sdk + alipay-sdk-java + 4.35.0.ALL + + + + org.bouncycastle + bcprov-jdk18on + 1.77 + + + + commons-logging + commons-logging + 1.3.0 + + + + com.alibaba + fastjson + 2.0.43 + + + + + com.google.zxing + core + 3.5.2 + + + + com.google.code.gson + gson + 2.10.1 + + + + com.vaadin.external.google + android-json + 0.0.20131108.vaadin1 + compile + + + + + com.corundumstudio.socketio + netty-socketio + 2.0.2 + + + + + com.github.wechatpay-apiv3 + wechatpay-java + 0.2.17 + + + + + org.springframework.integration + spring-integration-mqtt + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.0 + + + + com.github.binarywang + weixin-java-miniapp + 4.6.0 + + + + com.github.binarywang + weixin-java-mp + 4.6.0 + + + + + com.aliyun.oss + aliyun-sdk-oss + 3.17.4 + + + + com.github.kuaidi100-api + sdk + 1.0.13 + + + + + com.nuonuo + open-sdk + 1.0.5.2 + + + + + com.github.xiaoymin + knife4j-openapi3-spring-boot-starter + 4.3.0 + + + + com.belerweb + pinyin4j + 2.5.1 + + + + + com.aliyun + alimt20181012 + 1.0.3 + + + com.aliyun + tea-openapi + 0.2.5 + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + + + + com.freewayso + image-combiner + 2.6.9 + + + + org.springframework.boot + spring-boot-starter-websocket + + + + com.aliyun + bailian20231229 + 2.4.0 + + + + + + + + src/main/java + + **/*Mapper.xml + + + + src/main/resources + + ** + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.project-lombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + + aliYunMaven + https://maven.aliyun.com/repository/public + + + + diff --git a/src/main/java/com/gxwebsoft/WebSoftApplication.java b/src/main/java/com/gxwebsoft/WebSoftApplication.java new file mode 100644 index 0000000..1a7fa35 --- /dev/null +++ b/src/main/java/com/gxwebsoft/WebSoftApplication.java @@ -0,0 +1,31 @@ +package com.gxwebsoft; + +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.config.MqttProperties; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.socket.config.annotation.EnableWebSocket; + +/** + * 启动类 + * Created by WebSoft on 2018-02-22 11:29:03 + */ +@EnableAsync +@EnableTransactionManagement +@MapperScan("com.gxwebsoft.**.mapper") +@EnableConfigurationProperties({ConfigProperties.class, MqttProperties.class}) +@SpringBootApplication +@EnableScheduling +@EnableWebSocket +public class WebSoftApplication { + + public static void main(String[] args) { + SpringApplication.run(WebSoftApplication.class, args); + } + +} diff --git a/src/main/java/com/gxwebsoft/ai/config/AuditExcelStyle.java b/src/main/java/com/gxwebsoft/ai/config/AuditExcelStyle.java new file mode 100644 index 0000000..cfe4535 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/config/AuditExcelStyle.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.ai.config; + +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.afterturn.easypoi.excel.entity.params.ExcelForEachParams; +import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler; +import org.apache.poi.ss.usermodel.*; + +/** + * 自定义Excel导出样式 + */ +public class AuditExcelStyle implements IExcelExportStyler { + + private Workbook workbook; + + public AuditExcelStyle(Workbook workbook) { + this.workbook = workbook; + } + + @Override + public CellStyle getHeaderStyle(short color) { + CellStyle style = workbook.createCellStyle(); + + // 设置边框 + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + + // 设置背景色 - 浅蓝色 + style.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // 设置字体 + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 11); + font.setColor(IndexedColors.BLACK.getIndex()); + style.setFont(font); + + // 居中对齐 + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + return style; + } + + @Override + public CellStyle getTitleStyle(short color) { + CellStyle style = workbook.createCellStyle(); + + // 设置边框 + style.setBorderBottom(BorderStyle.MEDIUM); + style.setBorderLeft(BorderStyle.MEDIUM); + style.setBorderRight(BorderStyle.MEDIUM); + style.setBorderTop(BorderStyle.MEDIUM); + + // 设置背景色 - 深蓝色 + style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // 设置字体 + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 14); + font.setColor(IndexedColors.BLACK.getIndex()); + style.setFont(font); + + // 居中对齐 + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + return style; + } + + @Override + public CellStyle getStyles(boolean noneStyler, ExcelExportEntity entity) { + CellStyle style = workbook.createCellStyle(); + + // 设置边框 + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + + // 设置字体 + Font font = workbook.createFont(); + font.setFontHeightInPoints((short) 10); + style.setFont(font); + + // 自动换行 + style.setWrapText(true); + + // 垂直居中 + style.setVerticalAlignment(VerticalAlignment.CENTER); + + // 根据列名设置不同的水平对齐方式 + if (entity != null && entity.getName() != null) { + switch (entity.getName()) { + case "类别": + case "测试结果": + style.setAlignment(HorizontalAlignment.CENTER); + break; + default: + style.setAlignment(HorizontalAlignment.LEFT); + } + } + + return style; + } + + @Override + public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity entity, Object obj, Object data) { + return getStyles(false, entity); + } + + @Override + public CellStyle getTemplateStyles(boolean isSingle, ExcelForEachParams excelForEachParams) { + return getStyles(false, null); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java b/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java new file mode 100644 index 0000000..5191617 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java @@ -0,0 +1,16 @@ +package com.gxwebsoft.ai.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "aliyun.knowledge-base") +public class KnowledgeBaseConfig { + + private String accessKeyId; + private String accessKeySecret; + private String workspaceId; + private String topCategoryId; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/config/TemplateConfig.java b/src/main/java/com/gxwebsoft/ai/config/TemplateConfig.java new file mode 100644 index 0000000..953673a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/config/TemplateConfig.java @@ -0,0 +1,16 @@ +package com.gxwebsoft.ai.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "ai.template") +public class TemplateConfig { + + private String wordTemplatePath; + + private String evidenceTemplatePath; + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent10PartyConductConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent10PartyConductConstants.java new file mode 100644 index 0000000..4ef77d1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent10PartyConductConstants.java @@ -0,0 +1,432 @@ +package com.gxwebsoft.ai.constants; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import java.util.*; + +/** + * 党风廉政建设责任制审计常量类 + */ +public class AuditContent10PartyConductConstants { + + // 大类定义 + public static final String CATEGORY_TARGET_RESPONSIBILITY = "落实党风廉政建设责任制"; + public static final String CATEGORY_INSPECTION_RECTIFICATION = "巡视整改工作"; + public static final String CATEGORY_DISCIPLINE_REFORM = "深化纪律检查体制改革"; + + // 子类定义 - 落实党风廉政建设责任制 + public static final String SUBCATEGORY_MAIN_RESPONSIBILITY = "落实主体责任和监督责任"; + public static final String SUBCATEGORY_EIGHT_REGULATIONS = "落实中央八项规定精神工作"; + public static final String SUBCATEGORY_ANTI_CORRUPTION = "开展查处发生在群众身边的\"四风\"和腐败问题专项工作"; + public static final String SUBCATEGORY_DISCIPLINE_ENFORCEMENT = "严明纪律确保政令畅通"; + + // 子类定义 - 巡视整改工作 + public static final String SUBCATEGORY_MONTHLY_REPORT = "严格执行月报告制度"; + public static final String SUBCATEGORY_RECTIFICATION_TASK = "按时完成整改任务"; + public static final String SUBCATEGORY_RECTIFICATION_ACCOUNTABILITY = "因巡视整改不给力被追究"; + + // 子类定义 - 深化纪律检查体制改革 + public static final String SUBCATEGORY_THREE_TURNS = "聚焦主业主责,持续深化\"三转\"工作"; + public static final String SUBCATEGORY_SYSTEM_CONSTRUCTION = "健全完善深化纪检体制改革的相关制度文件并抓好贯彻落实"; + + // 小类定义 + public static final String DETAIL_LEADERSHIP_STRENGTHENING = "1 强化组织领导"; + public static final String DETAIL_TASK_DEPLOYMENT = "2 及时部署工作任务"; + public static final String DETAIL_PRESSURE_TRANSMISSION = "3 层层传到责任和压力"; + public static final String DETAIL_CASE_DOUBLE_CHECK = "4 坚持\"一案双查\""; + public static final String DETAIL_WORK_REPORTING = "5 及时汇报工作情况"; + public static final String DETAIL_INSPECTION_AND_SUPERVISION = "1 开展监督检查"; + public static final String DETAIL_DATA_SUBMISSION = "2 报送数据和资料"; + public static final String DETAIL_CASE_HANDLING = "3 查办违反中央八项规定精神案件"; + public static final String DETAIL_EXPOSURE_NOTIFICATION = "4 通报曝光"; + public static final String DETAIL_HOUSING_CLEARANCE = "5 清退干部职工多占住房"; + public static final String DETAIL_PROBLEM_CLUE_HANDLING = "1 及时梳理处置问题线索"; + public static final String DETAIL_CASE_INVESTIGATION = "2 查处一批案件"; + public static final String DETAIL_TYPICAL_CASE_EXPOSURE = "3 通报曝光一批典型案件"; + public static final String DETAIL_ATMOSPHERE_CREATION = "4 营造氛围"; + public static final String DETAIL_PROBLEM_CONFESSION = "5 督促违纪人员主动交代问题"; + public static final String DETAIL_SUMMARY_AND_MECHANISM = "6 进行总结并建立长效机制"; + public static final String DETAIL_NORMALIZED_GOVERNANCE = "7 专项工作纳入常态化治理"; + public static final String DETAIL_POLITICAL_DISCIPLINE = "1 严明政治纪律和组织纪律"; + public static final String DETAIL_MAJOR_DECISION = "2 按时完成中央和自治区重大决策部署"; + public static final String DETAIL_CASE_COMPLETION = "3 按时完成转办督办案件"; + + // 大类与子类映射关系 + public static final Map> CATEGORY_SUBCATEGORY_MAP = new HashMap<>(); + static { + // 落实党风廉政建设责任制 + CATEGORY_SUBCATEGORY_MAP.put(CATEGORY_TARGET_RESPONSIBILITY, Arrays.asList( + SUBCATEGORY_MAIN_RESPONSIBILITY, + SUBCATEGORY_EIGHT_REGULATIONS, + SUBCATEGORY_ANTI_CORRUPTION, + SUBCATEGORY_DISCIPLINE_ENFORCEMENT + )); + + // 巡视整改工作 + CATEGORY_SUBCATEGORY_MAP.put(CATEGORY_INSPECTION_RECTIFICATION, Arrays.asList( + SUBCATEGORY_MONTHLY_REPORT, + SUBCATEGORY_RECTIFICATION_TASK, + SUBCATEGORY_RECTIFICATION_ACCOUNTABILITY + )); + + // 深化纪律检查体制改革 + CATEGORY_SUBCATEGORY_MAP.put(CATEGORY_DISCIPLINE_REFORM, Arrays.asList( + SUBCATEGORY_THREE_TURNS, + SUBCATEGORY_SYSTEM_CONSTRUCTION + )); + } + + // 子类与小类映射关系 + public static final Map> SUBCATEGORY_DETAIL_MAP = new HashMap<>(); + static { + // 落实主体责任和监督责任 + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_MAIN_RESPONSIBILITY, Arrays.asList( + DETAIL_LEADERSHIP_STRENGTHENING, + DETAIL_TASK_DEPLOYMENT, + DETAIL_PRESSURE_TRANSMISSION, + DETAIL_CASE_DOUBLE_CHECK, + DETAIL_WORK_REPORTING + )); + + // 落实中央八项规定精神工作 + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_EIGHT_REGULATIONS, Arrays.asList( + DETAIL_INSPECTION_AND_SUPERVISION, + DETAIL_DATA_SUBMISSION, + DETAIL_CASE_HANDLING, + DETAIL_EXPOSURE_NOTIFICATION, + DETAIL_HOUSING_CLEARANCE + )); + + // 开展查处发生在群众身边的"四风"和腐败问题专项工作 + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_ANTI_CORRUPTION, Arrays.asList( + DETAIL_PROBLEM_CLUE_HANDLING, + DETAIL_CASE_INVESTIGATION, + DETAIL_TYPICAL_CASE_EXPOSURE, + DETAIL_ATMOSPHERE_CREATION, + DETAIL_PROBLEM_CONFESSION, + DETAIL_SUMMARY_AND_MECHANISM, + DETAIL_NORMALIZED_GOVERNANCE + )); + + // 严明纪律确保政令畅通 + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_DISCIPLINE_ENFORCEMENT, Arrays.asList( + DETAIL_POLITICAL_DISCIPLINE, + DETAIL_MAJOR_DECISION, + DETAIL_CASE_COMPLETION + )); + + // 巡视整改工作的子类 + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_MONTHLY_REPORT, Arrays.asList("1 严格执行月报告制度")); + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_RECTIFICATION_TASK, Arrays.asList("2 按时完成整改任务")); + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_RECTIFICATION_ACCOUNTABILITY, Arrays.asList("3 因巡视整改不给力被追究")); + + // 深化纪律检查体制改革的子类 + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_THREE_TURNS, Arrays.asList("1 聚焦主业主责,持续深化\"三转\"工作")); + SUBCATEGORY_DETAIL_MAP.put(SUBCATEGORY_SYSTEM_CONSTRUCTION, Arrays.asList("2 健全完善深化纪检体制改革的相关制度文件并抓好贯彻落实")); + } + + // 固定顺序的示例数据列表(按审计内容10.txt中的顺序) + private static final List FIXED_ORDER_EXAMPLE_DATA = new ArrayList<>(); + static { + // 落实主体责任和监督责任 + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "1 强化组织领导", "建立责任清单、明确党组、纪检责任"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "1 强化组织领导", "建立健全反腐败领导机制,人员调整后及时进行分工,并全年召开两次以上协调会"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "1 强化组织领导", "主要领导认真履行第一责任人责任,对党风廉政建设重要工作亲自部署、重大问题亲自过问、重点环节亲自协调、重要案件亲自督办5次以上"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "1 强化组织领导", "领导班子其他成员对职责范围内的党风廉政建设承担主要责任,与分管联系部门研究安排党风廉政建设工作4次以上"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "2 及时部署工作任务", "有党风廉政建设任务分工方案,党组专题研究部署党风廉政建设工作4次以上"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "3 层层传到责任和压力", "主要领导检查和调研党风廉政建设各一次以上,每年听取分管联系部门的党风廉政建设一次以上,且每年亲自上廉政党课不少于1次"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "3 层层传到责任和压力", "分管领导对分管联系部门党风廉政建设检查和调研各1次以上,每年听取分管联系部门的党风廉政建设情况汇报一次以上,到分管联系部门上廉政党课1次以上"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "3 层层传到责任和压力", "党组、纪检组注重抓早抓小,经常进行咬耳扯袖、红脸出汗,及时开展约谈、集体廉政谈话等工作3次以上"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "3 层层传到责任和压力", "开展述责评议工作"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "4 坚持\"一案双查\"", "对发生在本单位的较严重的违反政治纪律、组织纪律等问题进行\"一案双查\""); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "4 坚持\"一案双查\"", "出现\"四风\"和腐败等顶风违纪问题的"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "4 坚持\"一案双查\"", "出现区域性系统性违纪违法问题突出的"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "4 坚持\"一案双查\"", "出现上级交办的党风廉政建设事项不传达部署和落实的"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "4 坚持\"一案双查\"", "出现违纪行为隐瞒不报压案不查等问题而未进行责任追究被中央、自治区进行挂牌督办或直接问责、约谈等"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "5 及时汇报工作情况", "党组、纪检组向自治区党委、纪委汇报\"两个\"责任落实情况两次以上"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "5 及时汇报工作情况", "班子成员年底向自治区纪委报送个人落实\"两个责任\"情况报告"); + addExampleData("落实党风廉政建设责任制", "落实主体责任和监督责任", "5 及时汇报工作情况", "按照自治区纪委部署开展其他工作,并按时报送总结报告的"); + + // 落实中央八项规定精神工作 + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "1 开展监督检查", "部署开展监督检查3次以上"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "2 报送数据和资料", "每月按时报送中央八项规定精神案件有关统计数据和有关资料"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "2 报送数据和资料", "每月不按时报送或未按要求报送的"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "3 查办违反中央八项规定精神案件", "及时查处监督检查中发现的问题和中央、自治区转办督办的问题线索"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "3 查办违反中央八项规定精神案件", "出现违反中央八项规定精神问题被上级查处的,或查处但问责太轻,被上级责令重新查办"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "4 通报曝光", "对受到党纪政纪处分的问题全部进行通报曝光的"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "5 清退干部职工多占住房", "到年底还不能完成干部职工多占住房清退的"); + addExampleData("落实党风廉政建设责任制", "落实中央八项规定精神工作", "5 清退干部职工多占住房", "信访反映清房工作不到位被查实的"); + + // 开展查处发生在群众身边的"四风"和腐败问题专项工作 + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "1 及时梳理处置问题线索", "对收到或者上级交办问题线索进行转办督办或直接查办的"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "2 查处一批案件", "因查处案件不力被自治区约谈或通报批评的"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "3 通报曝光一批典型案件", "对查处的案件进行通报曝光"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "4 营造氛围", "加大宣传力度,用典型案例进行警示教育"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "5 督促违纪人员主动交代问题", "以文件形式或在网络、报纸、电视等媒介上发布公告,督促违纪人员主动交代问题并主动上缴违纪所得"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "6 进行总结并建立长效机制", "没有在6月20日前进行总结"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "6 进行总结并建立长效机制", "没有结合专项工作中发现存在的突出问题建立完善制度措施"); + addExampleData("落实党风廉政建设责任制", "开展查处发生在群众身边的\"四风\"和腐败问题专项工作", "7 专项工作纳入常态化治理", "6月后,在全部二层单位开展专项工作"); + + // 严明纪律确保政令畅通 + addExampleData("落实党风廉政建设责任制", "严明纪律确保政令畅通", "1 严明政治纪律和组织纪律", "有违反政治纪律等问题被中央、自治区查处的"); + addExampleData("落实党风廉政建设责任制", "严明纪律确保政令畅通", "2 按时完成中央和自治区重大决策部署", "出现问题被中央、自治区问责的"); + addExampleData("落实党风廉政建设责任制", "严明纪律确保政令畅通", "3 按时完成转办督办案件", "按时完成自治区纪委转办督办的党风政风类案件"); + addExampleData("落实党风廉政建设责任制", "严明纪律确保政令畅通", "3 按时完成转办督办案件", "不按时完成或不完成的"); + + // 巡视整改工作 + addExampleData("巡视整改工作", "严格执行月报告制度", "1 严格执行月报告制度", "严格执行月报告制度"); + addExampleData("巡视整改工作", "按时完成整改任务", "2 按时完成整改任务", "按时完成整改任务"); + addExampleData("巡视整改工作", "因巡视整改不给力被追究", "3 因巡视整改不给力被追究", "因巡视整改不给力被追究"); + + // 深化纪律检查体制改革 + addExampleData("深化纪律检查体制改革", "聚焦主业主责,持续深化\"三转\"工作", "1 聚焦主业主责,持续深化\"三转\"工作", "纪检组长或纪工委书记不分管具体业务工作,专职抓执纪监督问责工作"); + addExampleData("深化纪律检查体制改革", "聚焦主业主责,持续深化\"三转\"工作", "1 聚焦主业主责,持续深化\"三转\"工作", "检查发现有参与业务工作"); + addExampleData("深化纪律检查体制改革", "健全完善深化纪检体制改革的相关制度文件并抓好贯彻落实", "2 健全完善深化纪检体制改革的相关制度文件并抓好贯彻落实", "年底前制定有1项以上配套制度文件"); + } + + private static void addExampleData(String category, String subCategory, String detailCategory, String content) { + JSONObject obj = new JSONObject(); + obj.put("category", category); + obj.put("subCategory", subCategory); + obj.put("detailCategory", detailCategory); + obj.put("content", content); + obj.put("executionStatus", ""); + obj.put("workPaperIndex", "[\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\", \"...\"]"); +// obj.put("workPaperIndex", "[\"实际存在的完整FileId1\", \"实际存在的完整FileId2\", \"...\"]"); + FIXED_ORDER_EXAMPLE_DATA.add(obj); + } + + /** + * 获取所有固定顺序的示例数据 + */ + public static List getFixedOrderExampleData() { + return new ArrayList<>(FIXED_ORDER_EXAMPLE_DATA); + } + + /** + * 获取指定子类的示例数据 + */ + public static JSONArray getExampleDataBySubCategory(String subCategory) { + JSONArray result = new JSONArray(); + for (JSONObject example : FIXED_ORDER_EXAMPLE_DATA) { + if (subCategory.equals(example.getString("subCategory"))) { + result.add(example); + } + } + return result; + } + + /** + * 获取固定顺序的内容列表 + */ + public static List getFixedOrderContentList() { + List contentList = new ArrayList<>(); + for (JSONObject example : FIXED_ORDER_EXAMPLE_DATA) { + contentList.add(example.getString("content")); + } + return contentList; + } + + /** + * 获取内容在固定顺序中的索引 + */ + public static int getContentFixedOrderIndex(String content) { + List fixedOrder = getFixedOrderContentList(); + int index = fixedOrder.indexOf(content); + return index != -1 ? index : Integer.MAX_VALUE; + } + + /** + * 获取所有子类列表(包括所有大类的子类) + */ + public static List getAllSubCategories() { + List allSubCategories = new ArrayList<>(); + for (List subCategories : CATEGORY_SUBCATEGORY_MAP.values()) { + allSubCategories.addAll(subCategories); + } + return allSubCategories; + } + + // 大类顺序 - 用于排序 + public static final List CATEGORY_ORDER = Arrays.asList( + CATEGORY_TARGET_RESPONSIBILITY, + CATEGORY_INSPECTION_RECTIFICATION, + CATEGORY_DISCIPLINE_REFORM + ); + + // 大类描述 + public static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + CATEGORY_DESCRIPTIONS.put(CATEGORY_TARGET_RESPONSIBILITY, "落实党风廉政建设主体责任和监督责任"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_INSPECTION_RECTIFICATION, "巡视反馈问题的整改落实情况"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_DISCIPLINE_REFORM, "深化纪律检查体制改革工作情况"); + } + + // 子类描述 + public static final Map SUBCATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_MAIN_RESPONSIBILITY, "党委主体责任和纪委监督责任落实情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_EIGHT_REGULATIONS, "中央八项规定精神落实情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_ANTI_CORRUPTION, "查处群众身边腐败问题工作情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_DISCIPLINE_ENFORCEMENT, "纪律执行和政令畅通情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_MONTHLY_REPORT, "巡视整改月报告制度执行情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_RECTIFICATION_TASK, "巡视整改任务完成情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_RECTIFICATION_ACCOUNTABILITY, "巡视整改责任追究情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_THREE_TURNS, "纪检机构转职能、转方式、转作风工作情况"); + SUBCATEGORY_DESCRIPTIONS.put(SUBCATEGORY_SYSTEM_CONSTRUCTION, "纪检体制改革制度建设情况"); + } + + // 执行状态选项 + public static final String STATUS_IMPLEMENTED = "已落实"; + public static final String STATUS_PARTIALLY_IMPLEMENTED = "部分落实"; + public static final String STATUS_NOT_IMPLEMENTED = "未落实"; + public static final String STATUS_NOT_APPLICABLE = "不适用"; + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("党风廉政建设", 10); + KEYWORD_WEIGHTS.put("主体责任", 9); + KEYWORD_WEIGHTS.put("监督责任", 9); + KEYWORD_WEIGHTS.put("中央八项规定", 8); + KEYWORD_WEIGHTS.put("廉洁从业", 8); + KEYWORD_WEIGHTS.put("巡视整改", 7); + KEYWORD_WEIGHTS.put("纪律检查", 7); + KEYWORD_WEIGHTS.put("廉政谈话", 6); + KEYWORD_WEIGHTS.put("一案双查", 6); + KEYWORD_WEIGHTS.put("四风", 5); + KEYWORD_WEIGHTS.put("腐败", 5); + } + + // 工作底稿关键词 + public static final Map WORK_PAPER_KEYWORDS = new HashMap<>(); + static { + WORK_PAPER_KEYWORDS.put("会议纪要", "党委会议纪要、党风廉政建设专题会议纪要"); + WORK_PAPER_KEYWORDS.put("责任清单", "党风廉政建设责任清单、主体责任清单"); + WORK_PAPER_KEYWORDS.put("检查报告", "党风廉政建设检查报告、巡视整改报告"); + WORK_PAPER_KEYWORDS.put("谈话记录", "廉政谈话记录、提醒谈话记录"); + WORK_PAPER_KEYWORDS.put("制度文件", "党风廉政建设制度、廉洁从业规定"); + WORK_PAPER_KEYWORDS.put("审批文件", "津补贴审批文件、车辆配备审批"); + WORK_PAPER_KEYWORDS.put("举报材料", "信访举报材料、问题线索登记"); + } + + /** + * 对廉政情况数据进行排序(提供给ServiceImpl使用) + */ + public static void sortPartyConductData(JSONArray data) { + if (data == null || data.isEmpty()) { + return; + } + + // 转换为List进行排序 + List dataList = new ArrayList<>(); + for (int i = 0; i < data.size(); i++) { + dataList.add(data.getJSONObject(i)); + } + + // 排序:优先按固定顺序,其次按类别 + dataList.sort(AuditContent10PartyConductConstants::compareJSONObject); + + // 清空原数组并添加排序后的数据 + data.clear(); + data.addAll(dataList); + } + + /** + * 比较两个JSON对象的排序 + */ + public static int compareJSONObject(JSONObject o1, JSONObject o2) { + String content1 = o1.getString("content"); + String content2 = o2.getString("content"); + + // 1. 按固定顺序排序 + int index1 = getContentFixedOrderIndex(content1); + int index2 = getContentFixedOrderIndex(content2); + + if (index1 != index2) { + return Integer.compare(index1, index2); + } + + // 2. 如果都不在固定顺序中,按类别排序 + return compareByCategory(o1, o2); + } + + /** + * 按照大类、子类、小类排序 + */ + public static int compareByCategory(JSONObject o1, JSONObject o2) { + // 清理子类字符串用于比较 + String cleanSubCategory1 = cleanSubCategory(o1.getString("subCategory")); + String cleanSubCategory2 = cleanSubCategory(o2.getString("subCategory")); + + // 1. 按大类排序 + String category1 = o1.getString("category"); + String category2 = o2.getString("category"); + + int categoryIndex1 = CATEGORY_ORDER.indexOf(category1); + int categoryIndex2 = CATEGORY_ORDER.indexOf(category2); + + if (categoryIndex1 != categoryIndex2) { + return Integer.compare(categoryIndex1, categoryIndex2); + } + + // 2. 按子类排序 + String subCategory1 = o1.getString("subCategory"); + String subCategory2 = o2.getString("subCategory"); + + List subCategories = CATEGORY_SUBCATEGORY_MAP.get(category1); + if (subCategories != null) { + int subCategoryIndex1 = subCategories.indexOf(subCategory1); + int subCategoryIndex2 = subCategories.indexOf(subCategory2); + + if (subCategoryIndex1 != subCategoryIndex2) { + return Integer.compare(subCategoryIndex1, subCategoryIndex2); + } + } + + // 3. 按小类排序 + String detailCategory1 = o1.getString("detailCategory"); + String detailCategory2 = o2.getString("detailCategory"); + + List detailCategories = SUBCATEGORY_DETAIL_MAP.get(subCategory1); + if (detailCategories != null) { + int detailCategoryIndex1 = detailCategories.indexOf(detailCategory1); + int detailCategoryIndex2 = detailCategories.indexOf(detailCategory2); + + if (detailCategoryIndex1 != detailCategoryIndex2) { + return Integer.compare(detailCategoryIndex1, detailCategoryIndex2); + } + } + + // 4. 如果小类也相同,按内容排序 + String content1 = o1.getString("content"); + String content2 = o2.getString("content"); + return content1.compareTo(content2); + } + + private static String cleanSubCategory(String subCategory) { + if (subCategory == null) return ""; + if (subCategory.contains(" - ")) { + return subCategory.split(" - ")[0].trim(); + } + return subCategory; + } + + /** + * 获取指定子类的示例数据JSON字符串 + */ + public static String getExampleDataJsonString(String subCategory) { + JSONArray exampleData = getExampleDataBySubCategory(subCategory); + if (exampleData == null || exampleData.isEmpty()) { + return "[]"; + } + + // 直接使用FastJSON的序列化,确保正确转义即可 + return exampleData.toJSONString(); + } + + private AuditContent10PartyConductConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent11HistoryConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent11HistoryConstants.java new file mode 100644 index 0000000..bae4425 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent11HistoryConstants.java @@ -0,0 +1,90 @@ +package com.gxwebsoft.ai.constants; + +import java.util.HashMap; +import java.util.Map; + +/** + * 审计内容11-历史审计问题整改常量类 + */ +public class AuditContent11HistoryConstants { + + // 审计类型分类 + public static final String AUDIT_TYPE_INTERNAL = "内部审计"; + public static final String AUDIT_TYPE_EXTERNAL = "外部审计"; + public static final String AUDIT_TYPE_SPECIAL = "专项审计"; + public static final String AUDIT_TYPE_ECONOMIC = "经济责任审计"; + + // 整改状态 + public static final String STATUS_COMPLETED = "已完成"; + public static final String STATUS_IN_PROGRESS = "整改中"; + public static final String STATUS_NOT_STARTED = "未整改"; + public static final String STATUS_PARTIAL = "部分整改"; + + // 审计重点分类 + public static final String FOCUS_SYSTEM_MECHANISM = "制度机制建立情况"; + public static final String FOCUS_HISTORICAL_ISSUES = "历史遗留问题处理"; + public static final String FOCUS_RECTIFICATION_EFFECT = "整改数量及效果"; + public static final String FOCUS_SYSTEM_IMPROVEMENT = "制度完善情况"; + public static final String FOCUS_SELF_RECTIFICATION = "自行发现整改情况"; + + // 审计方法关键词 + public static final String METHOD_REVIEW_REPORT = "审阅审计报告"; + public static final String METHOD_REVIEW_RECTIFICATION = "审阅整改报告"; + public static final String METHOD_VERIFY_RECTIFICATION = "核实整改情况"; + public static final String METHOD_INTERVIEW = "个别访谈"; + public static final String METHOD_REVIEW_DOCS = "查阅相关资料"; + + // 分类描述 + public static final Map FOCUS_DESCRIPTIONS = new HashMap<>(); + static { + FOCUS_DESCRIPTIONS.put(FOCUS_SYSTEM_MECHANISM, "是否有处理历史遗留问题的制度机制"); + FOCUS_DESCRIPTIONS.put(FOCUS_HISTORICAL_ISSUES, "任期内是否有处理非任期的历史遗留问题"); + FOCUS_DESCRIPTIONS.put(FOCUS_RECTIFICATION_EFFECT, "对历次审计检查发现的问题整改的数量及效果"); + FOCUS_DESCRIPTIONS.put(FOCUS_SYSTEM_IMPROVEMENT, "历次审计检查发现问题后是否有完善相关制度"); + FOCUS_DESCRIPTIONS.put(FOCUS_SELF_RECTIFICATION, "历次审计没有发现的问题是否自行发现并整改完善"); + } + + // 审计方法描述 + public static final Map METHOD_DESCRIPTIONS = new HashMap<>(); + static { + METHOD_DESCRIPTIONS.put(METHOD_REVIEW_REPORT, "审阅任职期间审计报告"); + METHOD_DESCRIPTIONS.put(METHOD_REVIEW_RECTIFICATION, "审阅整改情况报告"); + METHOD_DESCRIPTIONS.put(METHOD_VERIFY_RECTIFICATION, "对提出问题整改情况逐一进行核实"); + METHOD_DESCRIPTIONS.put(METHOD_INTERVIEW, "通过个别访谈了解情况"); + METHOD_DESCRIPTIONS.put(METHOD_REVIEW_DOCS, "查阅相关资料了解单位存在的历年遗留问题和处理情况"); + } + + // 关键词权重(用于知识检索排序) + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("审计发现问题", 10); + KEYWORD_WEIGHTS.put("问题整改", 9); + KEYWORD_WEIGHTS.put("整改情况", 9); + KEYWORD_WEIGHTS.put("整改报告", 8); + KEYWORD_WEIGHTS.put("审计报告", 8); + KEYWORD_WEIGHTS.put("历史遗留问题", 9); + KEYWORD_WEIGHTS.put("制度机制", 7); + KEYWORD_WEIGHTS.put("整改措施", 7); + KEYWORD_WEIGHTS.put("整改责任人", 6); + KEYWORD_WEIGHTS.put("整改完成", 6); + KEYWORD_WEIGHTS.put("审计年度", 5); + KEYWORD_WEIGHTS.put("审计类型", 5); + KEYWORD_WEIGHTS.put("经济责任审计", 6); + } + + // 整改要求常见关键词 + public static final String[] RECTIFICATION_REQUIREMENT_KEYWORDS = { + "限期整改", "建立机制", "完善制度", "责任追究", "资金追回", + "规范管理", "加强内控", "落实责任", "建立台账", "跟踪检查" + }; + + // 整改措施常见关键词 + public static final String[] RECTIFICATION_MEASURES_KEYWORDS = { + "修订制度", "完善流程", "加强培训", "调整人员", "资金归还", + "建立台账", "完善手续", "强化监督", "责任追究", "定期检查" + }; + + private AuditContent11HistoryConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent1EightRegConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent1EightRegConstants.java new file mode 100644 index 0000000..63581b9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent1EightRegConstants.java @@ -0,0 +1,150 @@ +package com.gxwebsoft.ai.constants; + +import java.util.HashMap; +import java.util.Map; + +/** + * 八项规定制度常量类 + */ +public class AuditContent1EightRegConstants { + + // 八项规定分类定义 + public static final String CATEGORY_RESEARCH = "改进调查研究"; + public static final String CATEGORY_MEETINGS = "精简会议活动"; + public static final String CATEGORY_DOCUMENTS = "精简文件简报"; + public static final String CATEGORY_VISITS = "规范出访活动"; + public static final String CATEGORY_GUARD_WORK = "改进警卫工作"; + public static final String CATEGORY_NEWS_REPORT = "改进新闻报道"; + public static final String CATEGORY_PUBLICATIONS = "严格文稿发表"; + public static final String CATEGORY_ECONOMY = "厉行勤俭节约"; + + // 分类描述 + public static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + CATEGORY_DESCRIPTIONS.put(CATEGORY_RESEARCH, "改进调查研究相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MEETINGS, "精简会议活动相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_DOCUMENTS, "精简文件简报相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_VISITS, "规范出访活动相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_GUARD_WORK, "改进警卫工作相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_NEWS_REPORT, "改进新闻报道相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_PUBLICATIONS, "严格文稿发表相关要求"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_ECONOMY, "厉行勤俭节约相关要求"); + } + + // 政策内容 - 中共中央八项规定 + public static final Map POLICY_CONTENTS = new HashMap<>(); + static { + POLICY_CONTENTS.put(CATEGORY_RESEARCH, + "中央政治局全体同志要改进调查研究,到基层调研要深入了解真实情况,总结经验、研究问题、解决困难、指导工作,向群众学习、向实践学习,多同群众座谈,多同干部谈心,多商量讨论,多解剖典型。"); + + POLICY_CONTENTS.put(CATEGORY_MEETINGS, + "要精简会议活动,切实改进会风,严格控制以中央名义召开的各类全国性会议和举行的重大活动,不开泛泛部署工作和提要求的会,未经中央批准一律不出席各类剪彩、奠基活动和庆祝会、纪念会、表彰会、博览会、研讨会及各类论坛;提高会议实效,开短会、讲短话,力戒空话、套话。"); + + POLICY_CONTENTS.put(CATEGORY_DOCUMENTS, + "要精简文件简报,切实改进文风,没有实质内容、可发可不发的文件、简报一律不发。"); + + POLICY_CONTENTS.put(CATEGORY_VISITS, + "要规范出访活动,从外交工作大局需要出发合理安排出访活动,严格控制出访随行人员,严格按照规定乘坐交通工具,一般不安排中资机构、华侨华人、留学生代表等到机场迎送。"); + + POLICY_CONTENTS.put(CATEGORY_GUARD_WORK, + "要改进警卫工作,坚持有利于联系群众的原则,减少交通管制,一般情况下不得封路、不清场闭馆。"); + + POLICY_CONTENTS.put(CATEGORY_NEWS_REPORT, + "要改进新闻报道,中央政治局同志出席会议和活动应根据工作需要、新闻价值、社会效果决定是否报道,进一步压缩报道的数量、字数、时长。"); + + POLICY_CONTENTS.put(CATEGORY_PUBLICATIONS, + "要严格文稿发表,除中央统一安排外,个人不公开出版著作、讲话单行本,不发贺信、贺电,不题词、题字。"); + + POLICY_CONTENTS.put(CATEGORY_ECONOMY, + "要厉行勤俭节约,严格遵守廉洁从政有关规定,严格执行住房、车辆配备等有关工作和生活待遇的规定。"); + } + + // 实施细则内容 - 中央八项规定实施细则 + public static final Map IMPLEMENTATION_DETAILS = new HashMap<>(); + static { + IMPLEMENTATION_DETAILS.put(CATEGORY_RESEARCH, + "注重实际效果。安排中央政治局委员到基层调研要紧紧围绕调研主题,实事求是地安排考查内容,为领导同志深入基层、深入群众、深入实际创造条件。\n" + + "减少陪同人员。中央政治局常委到地方考察调研,陪同的中央和国家机关有关部门负责同志不超过5人,省(自治区、直辖市)陪同的负责同志不超过3人。\n" + + "简化接待工作。中央政治局委员在地方考察调研期间,不张贴悬挂横幅标语,不安排群众迎送,不铺设迎宾地毯,不摆放花草,不组织专场文艺表演。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_MEETINGS, + "减少会议活动。各地区各部门要本着务实高效的原则,严格清理、切实减少各类会议活动,能不开的坚决不开,可以合并的坚决合并。\n" + + "控制会议活动规模和时间。严格控制各类会议活动规模,减少参加人员。各部门召开的全国性会议,只安排与会议内容密切相关的部门参加,人数不超过300人,时间不超过2天。\n" + + "严格控制会议活动经费。各地区各部门举办会议活动,要严格执行有关规定,厉行节约,反对铺张浪费。严禁提高会议用餐、住宿标准,严禁组织高消费娱乐、健身活动。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_DOCUMENTS, + "减少各类文件简报。凡国家法律法规和党内法规已作出明确规定的,一律不再制发文件。没有实质内容、可发可不发的文件简报,一律不发。\n" + + "提高文件简报的质量和时效。各地区各部门应严格按照中央办公厅、国务院办公厅的有关要求,对文件和简报资料的报送程序和格式进行规范。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_VISITS, + "合理安排出访。围绕外交工作需要合理制定年度出访总体方案,中央政治局委员每人每年出访不超过1次,时间不超过10天。\n" + + "控制随行人员。严格根据工作需要安排陪同人员和工作人员。\n" + + "规范乘机安排。严格按照规定乘坐交通工具。\n" + + "简化机场迎送和接待工作。中央政治局委员出访,各有关驻外使领馆不安排中资机构、华侨华人和留学生代表到机场迎送。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_GUARD_WORK, + "改进警卫工作。中央政治局委员的警卫工作,要坚持有利于联系群众的原则,实行内紧外松的警卫方式,减少扰民。中央政治局委员出行时要减少交通管制,不得封路。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_NEWS_REPORT, + "简化中央政治局委员出席会议活动新闻报道。要根据工作需要、新闻价值、社会效果决定是否报道。出席一般性会议和活动不作报道。\n" + + "精简全国性会议活动新闻报道。经中央批准举办的全国性会议活动,除中共中央总书记外,中央政治局常委出席的,文字稿不超过1000字。\n" + + "规范中央政治局委员考察调研活动新闻报道。考察调研活动新闻报道要多反映群众关心的实质性内容,更好贴近实际、贴近生活、贴近群众。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_PUBLICATIONS, + "规范其他新闻报道。经中央批准,中央政治局常委和从中央政治局常委职务上退下来的同志出版著作等作品,由新华社播发简短出版消息。"); + + IMPLEMENTATION_DETAILS.put(CATEGORY_ECONOMY, + "严格控制会议活动经费。各地区各部门举办会议活动,要严格执行有关规定,厉行节约,反对铺张浪费。\n" + + "简化机场迎送和接待工作。驻外使领馆和其他驻外机构一律不得向代表团赠送礼品,外方所赠礼品应严格按国家有关规定处理。"); + } + + + // 审计建议内容 - 新增的审计建议 + public static final Map AUDIT_SUGGESTIONS = new HashMap<>(); + static { + AUDIT_SUGGESTIONS.put(CATEGORY_RESEARCH, + "如审查后认定:确为缺少系统性规范和调研记录。建议:\n" + + "1.补足制度短板,系统性规范公司层面调研工作管理要求;\n" + + "2.强化过程管理与材料归档,确保调研活动可视、可查、可评,切实发挥服务决策、解决问题的实效。"); + + AUDIT_SUGGESTIONS.put(CATEGORY_MEETINGS, + "如审查确认被审计单位存在未严格遵循《中央八项规定》中关于'精简会议活动'的规定,可提出审计建议:\n" + + "建议减少不必要的会议数量,提高会议质量和效率,避免因频繁开会而造成人力资源浪费。在召开会议前,应明确会议的具体目的和议题,避免无目标、无计划的会议。"); + + AUDIT_SUGGESTIONS.put(CATEGORY_VISITS, + "如审查确认被审计单位存在未严格遵循《中央八项规定》中关于'轻车简从''不得扰民'的要求,可提出审计建议:\n" + + "切实规范领导人员公务出行管理,严格贯彻落实'轻车简从''不得扰民'等要求。明确规定考察线路须事前报备、严格控制随员及车辆规模、严禁超标接待与礼仪迎送、严禁使用警车等非必要车辆疏导清场。责立健全常态化监管与问责机制,确保有关规定执行到位,对违规行为严肃追责。"); + + AUDIT_SUGGESTIONS.put(CATEGORY_GUARD_WORK, + "如审查确认被审计单位存在未严格遵循《中央八项规定》中关于'轻车简从''不得扰民'的要求,可提出审计建议:\n" + + "切实规范领导人员公务出行管理,严格贯彻落实'轻车简从''不得扰民'等要求。明确规定考察线路须事前报备、严格控制随员及车辆规模、严禁超标接待与礼仪迎送、严禁使用警车等非必要车辆疏导清场。责立健全常态化监管与问责机制,确保有关规定执行到位,对违规行为严肃追责。"); + + AUDIT_SUGGESTIONS.put(CATEGORY_ECONOMY, + "如审查后认定:确为会议活动经费超支。可提出审计建议:\n" + + "强化会议活动经费预算控制,严查支出明细,杜绝超标准住宿餐饮、高消费活动、豪华布置及纪念品发放。\n\n" + + "其他建议:\n" + + "1.各直属企业定期对内部控制情况开展实质性评价,杜绝形式主义。\n" + + "2.上级公司强化对下属企业的监督与检查,保障相关制度有效执行。"); + } + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("八项规定", 10); + KEYWORD_WEIGHTS.put("调查研究", 9); + KEYWORD_WEIGHTS.put("会议活动", 9); + KEYWORD_WEIGHTS.put("文件简报", 9); + KEYWORD_WEIGHTS.put("出访活动", 9); + KEYWORD_WEIGHTS.put("警卫工作", 9); + KEYWORD_WEIGHTS.put("新闻报道", 9); + KEYWORD_WEIGHTS.put("文稿发表", 9); + KEYWORD_WEIGHTS.put("勤俭节约", 9); + KEYWORD_WEIGHTS.put("实施细则", 8); + KEYWORD_WEIGHTS.put("接待标准", 7); + KEYWORD_WEIGHTS.put("经费管理", 7); + } + + private AuditContent1EightRegConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent1ExpenseConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent1ExpenseConstants.java new file mode 100644 index 0000000..9e8ccd6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent1ExpenseConstants.java @@ -0,0 +1,106 @@ +package com.gxwebsoft.ai.constants; + +import java.util.HashMap; +import java.util.Map; + +/** + * 支出情况表常量类 + */ +public class AuditContent1ExpenseConstants { + + // 支出类型分类 + public static final String EXPENSE_TYPE_RECEPTION = "公务接待"; + public static final String EXPENSE_TYPE_OVERSEAS = "出国"; + public static final String EXPENSE_TYPE_VEHICLE = "公车运行维护"; + public static final String EXPENSE_TYPE_MEETING = "会议培训费"; + + // 年度范围 - 根据知识库动态确定,这里提供默认起始年份 + public static final int DEFAULT_START_YEAR = 2020; + public static final int DEFAULT_END_YEAR = 2023; + + // 分类描述 + public static final Map EXPENSE_DESCRIPTIONS = new HashMap<>(); + static { + EXPENSE_DESCRIPTIONS.put(EXPENSE_TYPE_RECEPTION, "公务接待费用支出情况"); + EXPENSE_DESCRIPTIONS.put(EXPENSE_TYPE_OVERSEAS, "出国(出境)费用支出情况"); + EXPENSE_DESCRIPTIONS.put(EXPENSE_TYPE_VEHICLE, "公车运行维护费用支出情况"); + EXPENSE_DESCRIPTIONS.put(EXPENSE_TYPE_MEETING, "会议、培训费用支出情况"); + } + + // 数据格式要求 - 简化版 + public static final String DATA_FORMAT_REQUIREMENT = + "请严格按照以下JSON格式生成数据,每类支出每个年度至少1条记录,总共至少16条记录:\n" + + "[\n" + + " {\n" + + " \"expenseType\": \"公务接待/出国/公车运行维护/会议培训费\",\n" + + " \"year\": \"具体年份(根据知识库中的连续四个完整年度)\",\n" + + " \"finalStatementAmount\": \"决算报表数\",\n" + + " \"initialBudgetAmount\": \"年初预算数\",\n" + + " \"changePercentage\": \"增减百分比\",\n" + + " \"budgetRatio\": \"占年初预算比例\",\n" + + " \"remark\": \"备注信息\",\n" + + " \"dataSource\": \"数据来源文件\",\n" + +// " \"workPaperIndex\": [\"FileId1\", \"FileId2\"]\n" + + " \"workPaperIndex\": [\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\"]\n" + + " }\n" + + "]"; + + // 关键词权重 - 增强工程类排除 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + // 高优先级:直接支出关键词 + KEYWORD_WEIGHTS.put("公务接待", 15); + KEYWORD_WEIGHTS.put("出国", 15); + KEYWORD_WEIGHTS.put("公车运行维护", 15); + KEYWORD_WEIGHTS.put("会议培训费", 15); + KEYWORD_WEIGHTS.put("三公经费", 14); + KEYWORD_WEIGHTS.put("接待费", 13); + KEYWORD_WEIGHTS.put("差旅费", 12); + KEYWORD_WEIGHTS.put("车辆费", 12); + KEYWORD_WEIGHTS.put("会务费", 12); + KEYWORD_WEIGHTS.put("培训费", 12); + + // 中优先级:财务文档 + KEYWORD_WEIGHTS.put("决算报表", 10); + KEYWORD_WEIGHTS.put("预算报表", 10); + KEYWORD_WEIGHTS.put("财务报表", 9); + KEYWORD_WEIGHTS.put("部门决算", 9); + KEYWORD_WEIGHTS.put("部门预算", 9); + KEYWORD_WEIGHTS.put("预算执行", 8); + + // 年份关键词(中等优先级,用于识别年度) + KEYWORD_WEIGHTS.put("年度", 7); + KEYWORD_WEIGHTS.put("年报表", 7); + KEYWORD_WEIGHTS.put("年度决算", 8); + KEYWORD_WEIGHTS.put("年度预算", 8); + + // 低优先级:通用财务 + KEYWORD_WEIGHTS.put("支出", 7); + KEYWORD_WEIGHTS.put("费用", 6); + KEYWORD_WEIGHTS.put("年度报告", 6); + + // 排除工程类(负权重) + KEYWORD_WEIGHTS.put("工程造价", -20); + KEYWORD_WEIGHTS.put("概预算", -20); + KEYWORD_WEIGHTS.put("工程款", -15); + KEYWORD_WEIGHTS.put("施工", -15); + KEYWORD_WEIGHTS.put("项目投资", -15); + KEYWORD_WEIGHTS.put("基建", -15); + } + + // 需要排除的工程类关键词 + public static final String[] ENGINEERING_EXCLUDE_KEYWORDS = { + "工程造价", "概预算", "工程概算", "工程预算", "工程结算", + "施工合同", "工程款", "项目投资", "基建", "工程项目" + }; + + // 年份正则表达式,用于从知识库中提取年份 + public static final String YEAR_PATTERN = "20\\d{2}"; + + // 最近几年范围(用于查询构建) + public static final int RECENT_YEARS_COUNT = 4; + + private AuditContent1ExpenseConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent1LeaderListConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent1LeaderListConstants.java new file mode 100644 index 0000000..c2f1c6a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent1LeaderListConstants.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.ai.constants; + +import java.util.HashMap; +import java.util.Map; + +/** + * 领导班子名单常量类 + */ +public class AuditContent1LeaderListConstants { + + // 领导职位分类 + public static final String CATEGORY_PARTY_LEADERS = "党组织领导班子"; + public static final String CATEGORY_ADMIN_LEADERS = "行政领导班子"; + public static final String CATEGORY_DEPARTMENT_LEADERS = "部门负责人"; + + // 分类描述 + public static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + CATEGORY_DESCRIPTIONS.put(CATEGORY_PARTY_LEADERS, "党组织领导班子成员信息"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_ADMIN_LEADERS, "行政领导班子成员信息"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_DEPARTMENT_LEADERS, "各部门负责人信息"); + } + + // 数据格式要求 + public static final String DATA_FORMAT_REQUIREMENT = + "每条记录应包含以下字段:\n" + + "- unit:单位名称\n" + + "- name:姓名\n" + + "- department:所在部门\n" + + "- partyPosition:党内职务\n" + + "- adminPosition:行政职务\n" + + "- tenurePeriod:任职期间(格式:YYYY.MM-YYYY.MM)\n" + + "- mainResponsibilities:主要工作责任\n" + + "- remark:备注信息\n" + +// "- workPaperIndex:[相关文件FileId]"; + "- workPaperIndex:[\"实际存在的完整文件名1||FileUrl1\"]"; + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("领导班子", 10); + KEYWORD_WEIGHTS.put("领导成员", 9); + KEYWORD_WEIGHTS.put("任职", 8); + KEYWORD_WEIGHTS.put("职务", 8); + KEYWORD_WEIGHTS.put("职责", 7); + KEYWORD_WEIGHTS.put("分工", 7); + KEYWORD_WEIGHTS.put("党组", 9); + KEYWORD_WEIGHTS.put("党委", 9); + KEYWORD_WEIGHTS.put("支部书记", 8); + KEYWORD_WEIGHTS.put("局长", 8); + KEYWORD_WEIGHTS.put("处长", 7); + KEYWORD_WEIGHTS.put("科长", 7); + } + + private AuditContent1LeaderListConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent2StrategyConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent2StrategyConstants.java new file mode 100644 index 0000000..9780a2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent2StrategyConstants.java @@ -0,0 +1,194 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容2-单位发展战略执行常量类 + * 基于五大发展理念优化为5个分类 + */ +public class AuditContent2StrategyConstants { + + // 分类定义 - 基于五大发展理念优化为5个分类 + public static final String CATEGORY_GOVERNANCE_REFORM = "法人治理与改革创新"; + public static final String CATEGORY_DEVELOPMENT_STRATEGY = "发展规划与协调推进"; + public static final String CATEGORY_RISK_PREVENTION = "风险防控与绿色发展"; + public static final String CATEGORY_SOCIAL_ENVIRONMENTAL = "社会责任与开放共享"; + public static final String CATEGORY_PERFORMANCE_ASSESSMENT = "绩效考核与创新发展"; + + // 分类顺序 + public static final List CATEGORY_ORDER = Arrays.asList( + CATEGORY_GOVERNANCE_REFORM, + CATEGORY_DEVELOPMENT_STRATEGY, + CATEGORY_RISK_PREVENTION, + CATEGORY_SOCIAL_ENVIRONMENTAL, + CATEGORY_PERFORMANCE_ASSESSMENT + ); + + // 分类描述(对应五大发展理念) + public static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + CATEGORY_DESCRIPTIONS.put(CATEGORY_GOVERNANCE_REFORM, + "公司法人治理结构、薪酬制度改革、国有资产管理体制等改革创新情况"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_DEVELOPMENT_STRATEGY, + "单位重要发展规划和政策措施的制定、执行和效果等协调发展情况"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_RISK_PREVENTION, + "金融业务风险、债务风险防控、污染防治等绿色发展情况"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_SOCIAL_ENVIRONMENTAL, + "精准扶贫、环境保护等社会责任履行和开放共享情况"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_PERFORMANCE_ASSESSMENT, + "各项考核指标完成情况的真实性、准确性等创新发展情况"); + } + + // 分类与五大发展理念对应关系 + public static final Map CATEGORY_DEVELOPMENT_CONCEPT = new HashMap<>(); + static { + CATEGORY_DEVELOPMENT_CONCEPT.put(CATEGORY_GOVERNANCE_REFORM, "创新"); + CATEGORY_DEVELOPMENT_CONCEPT.put(CATEGORY_DEVELOPMENT_STRATEGY, "协调"); + CATEGORY_DEVELOPMENT_CONCEPT.put(CATEGORY_RISK_PREVENTION, "绿色"); + CATEGORY_DEVELOPMENT_CONCEPT.put(CATEGORY_SOCIAL_ENVIRONMENTAL, "开放共享"); + CATEGORY_DEVELOPMENT_CONCEPT.put(CATEGORY_PERFORMANCE_ASSESSMENT, "创新"); + } + + // 分类查询关键词 + public static final Map> CATEGORY_QUERIES = new HashMap<>(); + static { + CATEGORY_QUERIES.put(CATEGORY_GOVERNANCE_REFORM, + Arrays.asList("法人治理 薪酬改革 国有资产管理", "改革创新 治理结构 董事会")); + CATEGORY_QUERIES.put(CATEGORY_DEVELOPMENT_STRATEGY, + Arrays.asList("混合所有制 剥离办社会职能 改革措施", "协调发展 改革方案 政策落实")); + CATEGORY_QUERIES.put(CATEGORY_RISK_PREVENTION, + Arrays.asList("发展规划 金融风险 债务风险", "绿色发展 风险防控 资产负债率")); + CATEGORY_QUERIES.put(CATEGORY_SOCIAL_ENVIRONMENTAL, + Arrays.asList("精准扶贫 污染防治 环境保护", "开放共享 社会责任 定点扶贫")); + CATEGORY_QUERIES.put(CATEGORY_PERFORMANCE_ASSESSMENT, + Arrays.asList("考核指标 经营业绩 目标责任", "创新发展 数据核实 完成情况")); + } + + // 完整的审计框架(原Excel内容) + public static final String AUDIT_FRAMEWORK = + "审计内容框架:\n" + + "1. 公司法人治理结构建立健全情况。是否贯彻落实将党组织研究讨论作为董事会、经理层决策重大问题前置程序的要求;是否加强董事会建设、有效落实董事会职权;各治理主体是否职责明确、落实到位。\n" + + "2. 薪酬制度改革情况。薪酬分配差异化改革过程中是否存在进展不平衡、政策不配套、任务不协调等问题;有无超工资总额列支工资性支出;企业领导人员在核定的年度薪酬之外领取报酬,是否严格规范履职待遇、业务支出,有无将公款用于个人支出等问题。\n" + + "3. 完善国有资产管理体制方面。有关部门是否建立健全监管权力清单和责任清单,是否存在越位、缺位、不到位等问题;国有资本投资、运营公司改革试点工作的情况,有无要求方案部署不够明确、资本授权及经营资质受限、相关企业未制定时间表路线图、未与兼并重组等改革试点任务协同推进、资本运营偏离改革方向、战略性前瞻性产业投资不增反降、党政机关和事业单位所属企业的国有资本纳入经营性国有资产集中统一监管体系推进迟缓、国有资本经营预算管理不到位等问题。\n" + + "4. 发展混合所有制经济方面。是否存在\"混而不改\",改革流于形式、效果不佳的问题;清产核资、评估定价、转让交易、登记确权等相关政策、审批和实施程序是否合法合规,在国有资产和产权转让过程中是否存在国有资产流失等问题;员工持股试点推进过程中存在的政策性障碍。此外,还要从国有企业改革工作总休上关注,混合所有制改革作为国有企业改革的重要突破口的作用发挥情况。\n" + + "5. 剥离办社会职能和解决历史遗留问题方面。国有企业有无制定时间表路线图,是否按时限完成国有企业职工家属区\"三供一业\"分离移交,剥离国有企业办医疗、教育等公共服务机构,对国有企业退休人员实行社会化管理,推进厂办大集体改革等问题,遇到什么困难、需要什么政策和多少资金支持,等等;相关部门的配套政策是否建立健全。\n" + + "6. 国有企业改革措施是否制定?制定的措施是否符合党中央、国务院、省市的相关文件规定和精神。\n" + + "7. 企业年度工作报告中关于改革措施的落实情况,进度是否符合预期,是否违反整体改革措施计划。规划执行情况材料是否合规、完整。企业境外资产、投资管理办法和内控制度,及企业向国资委等有关部门报送的境外资产产权登记情况和境外资产财务报表数据等,是否合规、完整、符合企业实际情况。企业制定的公司发展规划,包括总规划和专项规划(如主业、国际化、金融、财务、人力资源、建设、生产、营销、科技、信息化、安全应急、企业文化等),是否符合企业整体实际情况,是否符合党中央、国务院、省市的相关文件规定和精神。公司发展规划滚动调整情况是否符合实际情况,是否为了减轻企业目标故意调整。公司发展规划中提及的制定依据,是否符合国家有关部门制定的行业产业发展规划等。\n" + + "8. 金融业务风险。关注金融业务服务实体经济和主业发展情况,有无脱实向虚、通过金融产品将资金违规投向房地产、地方政府融资平台、产能过剩等限制或禁止领域的问题;关注企业开展境外金融衍生品情况,有无违规开展业务造成重大损失或损失风险等;关注银行、证券、保险、信托等金融子企业的资产管理规模及经营情况,风险管控机制是否健全,业务运营是否合规,有无信贷资金、金融资产造成重大损失的问题,有无金融产品逾期或违规展期、不良资产比率较高、风险与收益不匹配、本金偿付风险或违约风险大等问题;关注企业债券发行、委托理财、对外担保等金融业务开展中存在的突出问题,是否造成重大损失或损失风险等。\n" + + "9. 债务风险。关注企业落实降杠杆减负债政策情况,是否存在企业资产负债率居高不下、超过警戒线或管控线甚至资不抵债,有无建立健全企业债务风险防控机制,发挥资产负债约束机制作用,债务结构是否合理,有无违规对不符合国家产业政策的企业实施债转股,有无违规通过\"名股实债\"等方式变相举债或形成重大隐性债务,以及虚假去杠杆等问题。\n" + + "10. 精准扶贫政策落实情况。关注企业在精准扶贫相关政策落实、项目安排、资金使用等方面的推进情况,是否落实企业定点扶贫工作的任务要求,有无出台具体帮扶措施,履行帮扶责任定点扶贫的目标对象、工作举措、资金使用等是否精准;有无通过发展产业、对接市场、安置就业等多种方式开展精准扶贫行动;以中央企业贫困地区产业投资基金、贫困地区产业发展基金等为代表的央企产业扶贫基金出资额是否及时足额缴纳、有无大量资金闲置和项目运营效果不佳等问题。\n" + + "11. 污染防治工作推进情况。企业贯彻落实生态环境保护和环境污染防治相关政策措施情况,重点是打蹴蓝天保卫战、打好碧水保卫战和推进净土保卫战等相关措施的落实情况。是否存在非法占地、违规改变土地使用条件、倒卖土地、土地闲置的问题;企业是否完成国家节能减排任务目标,有无不顾生态环境盲目决策和建设项目,造成重大环境污染和资源损毁等问题;有无违规偷排、漏排、超排废渣废液废气,瞒报、漏报检测数据,有无违规堆放、未按规定处理工业危险废弃物、危险化学品等问题;对所属企业发生的破坏生态环境情况,是否存在追责问责不到位等问题。企业是否建立能源消耗及污染物排放统计台账;是否按时定期将本企业节能减排汇总报表和总结分析报告报送有关部门,有无漏报、迟报、不按要求报送;重点类、关注类企业是否在总结分析报告中开展与同行业节能减排技术指标的对标和分析。\n" + + "12. 对照国资委、财政部、工业和信息化部等与企业签订的年度经营业绩责任书、任期经营业绩责任书、中央单位定点扶贫工作责任书等,梳理企业承担的考核内容和指标。\n" + + "13. 对于可以量化的审计事项,利用大数据分析方法,系统收集和整理各类考核指标信息,建立企业考核指标数据库,对照企业提供的财务或相关统计报表,检查企业完成情况。检查相关指标的原始数据、计算方法、计算过程,核实其真实性和准确性,对于依靠人工填报、设置调整系数、与以往年度相差较大或与考核值相差较小的数据,重点审计真实性、完整性、准确性。\n"; + + // 审计目标说明 + public static final String AUDIT_OBJECTIVE = + "审计目标:检查被审计领导干部任职期间在" + + "创新、协调、绿色、开放、共享五大发展理念指引下," + + "合法合规制定本部门发展规划和发展思路," + + "推动规划和政策措施实施的时间表、路线图及其执行效果。"; + + // 分类与审计框架片段的映射 + public static final Map CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS = new HashMap<>(); + static { + CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS.put(CATEGORY_GOVERNANCE_REFORM, + "审计内容框架(法人治理与改革创新相关):\n" + + "1. 公司法人治理结构建立健全情况。是否贯彻落实将党组织研究讨论作为董事会、经理层决策重大问题前置程序的要求;是否加强董事会建设、有效落实董事会职权;各治理主体是否职责明确、落实到位。\n" + + "2. 薪酬制度改革情况。薪酬分配差异化改革过程中是否存在进展不平衡、政策不配套、任务不协调等问题;有无超工资总额列支工资性支出;企业领导人员在核定的年度薪酬之外领取报酬,是否严格规范履职待遇、业务支出,有无将公款用于个人支出等问题。\n" + + "3. 完善国有资产管理体制方面。有关部门是否建立健全监管权力清单和责任清单,是否存在越位、缺位、不到位等问题;国有资本投资、运营公司改革试点工作的情况,有无要求方案部署不够明确、资本授权及经营资质受限、相关企业未制定时间表路线图、未与兼并重组等改革试点任务协同推进、资本运营偏离改革方向、战略性前瞻性产业投资不增反降、党政机关和事业单位所属企业的国有资本纳入经营性国有资产集中统一监管体系推进迟缓、国有资本经营预算管理不到位等问题。\n"); + + CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS.put(CATEGORY_DEVELOPMENT_STRATEGY, + "审计内容框架(发展规划与协调推进相关):\n" + + "4. 发展混合所有制经济方面。是否存在\"混而不改\",改革流于形式、效果不佳的问题;清产核资、评估定价、转让交易、登记确权等相关政策、审批和实施程序是否合法合规,在国有资产和产权转让过程中是否存在国有资产流失等问题;员工持股试点推进过程中存在的政策性障碍。此外,还要从国有企业改革工作总休上关注,混合所有制改革作为国有企业改革的重要突破口的作用发挥情况。\n" + + "5. 剥离办社会职能和解决历史遗留问题方面。国有企业有无制定时间表路线图,是否按时限完成国有企业职工家属区\"三供一业\"分离移交,剥离国有企业办医疗、教育等公共服务机构,对国有企业退休人员实行社会化管理,推进厂办大集体改革等问题,遇到什么困难、需要什么政策和多少资金支持,等等;相关部门的配套政策是否建立健全。\n" + + "6. 国有企业改革措施是否制定?制定的措施是否符合党中央、国务院、省市的相关文件规定和精神。\n"); + + CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS.put(CATEGORY_RISK_PREVENTION, + "审计内容框架(风险防控与绿色发展相关):\n" + + "7. 企业年度工作报告中关于改革措施的落实情况,进度是否符合预期,是否违反整体改革措施计划。规划执行情况材料是否合规、完整。企业境外资产、投资管理办法和内控制度,及企业向国资委等有关部门报送的境外资产产权登记情况和境外资产财务报表数据等,是否合规、完整、符合企业实际情况。企业制定的公司发展规划,包括总规划和专项规划(如主业、国际化、金融、财务、人力资源、建设、生产、营销、科技、信息化、安全应急、企业文化等),是否符合企业整体实际情况,是否符合党中央、国务院、省市的相关文件规定和精神。公司发展规划滚动调整情况是否符合实际情况,是否为了减轻企业目标故意调整。公司发展规划中提及的制定依据,是否符合国家有关部门制定的行业产业发展规划等。\n" + + "8. 金融业务风险。关注金融业务服务实体经济和主业发展情况,有无脱实向虚、通过金融产品将资金违规投向房地产、地方政府融资平台、产能过剩等限制或禁止领域的问题;关注企业开展境外金融衍生品情况,有无违规开展业务造成重大损失或损失风险等;关注银行、证券、保险、信托等金融子企业的资产管理规模及经营情况,风险管控机制是否健全,业务运营是否合规,有无信贷资金、金融资产造成重大损失的问题,有无金融产品逾期或违规展期、不良资产比率较高、风险与收益不匹配、本金偿付风险或违约风险大等问题;关注企业债券发行、委托理财、对外担保等金融业务开展中存在的突出问题,是否造成重大损失或损失风险等。\n" + + "9. 债务风险。关注企业落实降杠杆减负债政策情况,是否存在企业资产负债率居高不下、超过警戒线或管控线甚至资不抵债,有无建立健全企业债务风险防控机制,发挥资产负债约束机制作用,债务结构是否合理,有无违规对不符合国家产业政策的企业实施债转股,有无违规通过\"名股实债\"等方式变相举债或形成重大隐性债务,以及虚假去杠杆等问题。\n"); + + CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS.put(CATEGORY_SOCIAL_ENVIRONMENTAL, + "审计内容框架(社会责任与开放共享相关):\n" + + "10. 精准扶贫政策落实情况。关注企业在精准扶贫相关政策落实、项目安排、资金使用等方面的推进情况,是否落实企业定点扶贫工作的任务要求,有无出台具体帮扶措施,履行帮扶责任定点扶贫的目标对象、工作举措、资金使用等是否精准;有无通过发展产业、对接市场、安置就业等多种方式开展精准扶贫行动;以中央企业贫困地区产业投资基金、贫困地区产业发展基金等为代表的央企产业扶贫基金出资额是否及时足额缴纳、有无大量资金闲置和项目运营效果不佳等问题。\n" + + "11. 污染防治工作推进情况。企业贯彻落实生态环境保护和环境污染防治相关政策措施情况,重点是打蹴蓝天保卫战、打好碧水保卫战和推进净土保卫战等相关措施的落实情况。是否存在非法占地、违规改变土地使用条件、倒卖土地、土地闲置的问题;企业是否完成国家节能减排任务目标,有无不顾生态环境盲目决策和建设项目,造成重大环境污染和资源损毁等问题;有无违规偷排、漏排、超排废渣废液废气,瞒报、漏报检测数据,有无违规堆放、未按规定处理工业危险废弃物、危险化学品等问题;对所属企业发生的破坏生态环境情况,是否存在追责问责不到位等问题。企业是否建立能源消耗及污染物排放统计台账;是否按时定期将本企业节能减排汇总报表和总结分析报告报送有关部门,有无漏报、迟报、不按要求报送;重点类、关注类企业是否在总结分析报告中开展与同行业节能减排技术指标的对标和分析。\n"); + + CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS.put(CATEGORY_PERFORMANCE_ASSESSMENT, + "审计内容框架(绩效考核与创新发展相关):\n" + + "12. 对照国资委、财政部、工业和信息化部等与企业签订的年度经营业绩责任书、任期经营业绩责任书、中央单位定点扶贫工作责任书等,梳理企业承担的考核内容和指标。\n" + + "13. 对于可以量化的审计事项,利用大数据分析方法,系统收集和整理各类考核指标信息,建立企业考核指标数据库,对照企业提供的财务或相关统计报表,检查企业完成情况。检查相关指标的原始数据、计算方法、计算过程,核实其真实性和准确性,对于依靠人工填报、设置调整系数、与以往年度相差较大或与考核值相差较小的数据,重点审计真实性、完整性、准确性。\n"); + } + + // 获取分类的简要审计框架概述 + public static String getBriefAuditFrameworkForCategory(String category) { + switch (category) { + case CATEGORY_GOVERNANCE_REFORM: + return "重点审计:法人治理结构、薪酬改革、国有资产管理等改革创新情况(创新理念)"; + case CATEGORY_DEVELOPMENT_STRATEGY: + return "重点审计:混合所有制改革、剥离办社会职能、改革措施制定等协调发展情况(协调理念)"; + case CATEGORY_RISK_PREVENTION: + return "重点审计:发展规划执行、金融业务风险、债务风险等绿色发展情况(绿色理念)"; + case CATEGORY_SOCIAL_ENVIRONMENTAL: + return "重点审计:精准扶贫、污染防治等社会责任履行情况(开放共享理念)"; + case CATEGORY_PERFORMANCE_ASSESSMENT: + return "重点审计:考核指标完成情况的真实性、准确性等创新发展情况(创新理念)"; + default: + return "审计单位发展战略执行情况"; + } + } + + // 审计工作原则 + public static final String AUDIT_PRINCIPLES = + "审计工作原则:\n" + + "1. 基于审计框架,结合企业实际情况生成具体审计内容\n" + + "2. 审计内容要具体可操作,避免笼统描述\n" + + "3. 检查证据要真实具体,有文件依据\n" + + "4. 测试结果判定要严格,有充分依据\n" + + "5. 工作底稿索引要准确对应实际文件\n" + + "6. 注重评估五大发展理念贯彻情况\n" + + "7. 必须深入分析实际执行情况(结果),重点检查:\n" + + " (1) 是否有会议纪要等材料证明决策按照党组织委员会→公司领导班子→董事会逐级落实;\n" + + " (2) 是否有相应材料对执行内容进行说明和证明,展示具体的执行过程和执行效果。"; + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + // 创新类关键词 + KEYWORD_WEIGHTS.put("法人治理", 10); + KEYWORD_WEIGHTS.put("薪酬改革", 9); + KEYWORD_WEIGHTS.put("改革创新", 9); + KEYWORD_WEIGHTS.put("混合所有制", 9); + + // 协调类关键词 + KEYWORD_WEIGHTS.put("发展规划", 10); + KEYWORD_WEIGHTS.put("协调发展", 8); + KEYWORD_WEIGHTS.put("改革措施", 8); + + // 绿色类关键词 + KEYWORD_WEIGHTS.put("金融风险", 9); + KEYWORD_WEIGHTS.put("债务风险", 9); + KEYWORD_WEIGHTS.put("资产负债率", 8); + KEYWORD_WEIGHTS.put("污染防治", 8); + + // 开放共享类关键词 + KEYWORD_WEIGHTS.put("精准扶贫", 9); + KEYWORD_WEIGHTS.put("环境保护", 8); + KEYWORD_WEIGHTS.put("社会责任", 8); + + // 通用关键词 + KEYWORD_WEIGHTS.put("发展战略", 10); + KEYWORD_WEIGHTS.put("考核指标", 8); + KEYWORD_WEIGHTS.put("年度报告", 7); + KEYWORD_WEIGHTS.put("工作底稿", 6); + KEYWORD_WEIGHTS.put("审计证据", 6); + } + + // 各知识源检索限制 + public static final Map SOURCE_LIMITS = new HashMap<>(); + static { + SOURCE_LIMITS.put("enterprise", 120); + SOURCE_LIMITS.put("regulation", 60); + SOURCE_LIMITS.put("auditCase", 40); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent3DecisionConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent3DecisionConstants.java new file mode 100644 index 0000000..315664b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent3DecisionConstants.java @@ -0,0 +1,33 @@ +package com.gxwebsoft.ai.constants; + +/** + * 重大经济决策调查表常量类 + */ +public class AuditContent3DecisionConstants { + + // 审计关键点、审计方法步骤 + public static final String AUDIT_FOCUS_AND_METHODS = + "## 重大经济决策调查表审计重点\n" + + "重大经济事项的决策、执行和效果情况,具体包括重大预算管理、重大项目实施、重大采购项目、重大投资项目、重大外包业务、重大资产处置、大额资金使用的决策和执行情况等。\n\n" + + "## 重点关注\n" + + "\"三重一大\"集体决策制度是否建立健全。重大经济、经营活动必须进行集体决策的标准和必须参加决策的人员是否制定有规定,规定的集体决策的程序(签到表、决策纪要、最终形成的决策、决策人员的签字确认意见等)是否完整,是否有应集体决策而未集体决策的问题,是否有应参加集体决策的人因不合理理由不得参加集体决策的问题,形成的集体决策意见是否符合国家、自治区规定。\n\n" + + "## 审计方法及步骤\n" + + "1. 查阅单位制度,检查议事制度是否建立,查看重大事项进行集体决策的规定,查阅相关会议记录资料,检查会议签到表、会议纪要、会议决定内容及决策参与人员签字确认意见,以确认会议集体决策程序的完整性;对照重大决策内容,检查相关对应资料,确认集体决策内容是否符合国家、自治区规定。\n" + + "2. 抽查一些重大经济决策事项,比如重大建设项目、维修、大宗物资采购、企业投资运营等,审查其决策的背景、决策的过程、决策的执行和落实情况,决策的效果情况,以此来确定是否符合国家、自治区相关规定,执行是否有效。"; + + // 通用查询词 + public static final String[] GLOBAL_REGULATION_QUERIES = { + "重大经济决策 法律法规", + "三重一大 政策规定", + "集体决策 制度规定" + }; + + public static final String[] GLOBAL_AUDIT_CASE_QUERIES = { + "重大经济决策 审计案例", + "三重一大 检查方法" + }; + + private AuditContent3DecisionConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent3TripleConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent3TripleConstants.java new file mode 100644 index 0000000..d88f5df --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent3TripleConstants.java @@ -0,0 +1,105 @@ +package com.gxwebsoft.ai.constants; + +import java.util.HashMap; +import java.util.Map; + +/** + * 三重一大制度常量类 + */ +public class AuditContent3TripleConstants { + + // 分类定义 + public static final String CATEGORY_MAJOR_DECISION = "重大决策"; + public static final String CATEGORY_PERSONNEL_APPOINTMENT = "重要人事任免"; + public static final String CATEGORY_MAJOR_PROJECT = "重大项目安排"; + public static final String CATEGORY_LARGE_FUND_OPERATION = "大额度资金运作"; + public static final String CATEGORY_DECISION_PROCEDURE = "决策权力主体和程序"; + + // 分类描述 + public static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_DECISION, "重大决策事项的定义和范围"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_PERSONNEL_APPOINTMENT, "重要人事任免事项的定义和范围"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_PROJECT, "重大项目安排事项的定义和范围"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_LARGE_FUND_OPERATION, "大额度资金运作事项的定义和范围"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_DECISION_PROCEDURE, "决策权力主体和程序的规定"); + } + + // 政策内容 + public static final Map POLICY_CONTENTS = new HashMap<>(); + static { + POLICY_CONTENTS.put(CATEGORY_MAJOR_DECISION, + "1.重大决策事项:是指依照《中华人民共和国公司法》、《中华人民共和国全民所有制工业企业法》、《中华人民共和国企业国有资产法》、《中华人民共和国商业银行法》、《中华人民共和国证券法》、《中华人民共和国保险法》以及其他有关法律法规和党内法规规定的应当由股东大会(股东会)、董事会、未设董事会的经理班子、职工代表大会和党委(党组)决定的事项。"); + + POLICY_CONTENTS.put(CATEGORY_PERSONNEL_APPOINTMENT, + "2.重要人事任免事项:是指企业直接管理的领导人员以及其他经营管理人员的职务调整事项。主要包括企业中层以上经营管理人员和下属企业、单位领导班子成员的任免、聘用、解除聘用和后备人选的确定,向控股和参股企业委派股东代表,推荐董事会、监事会成员和经理、财务负责人,以及其他重要人事任免事项。"); + + POLICY_CONTENTS.put(CATEGORY_MAJOR_PROJECT, + "3、重大项目安排事项:是指对企业资产规模、资本结构、盈利能力以及生产装备、技术状况等产生重要影响的项目的设立和安排。主要包括年度投资计划,融资、担保项目,期权、期货等金融衍生业务,重要设备和技术引进,采购大宗物资和购买服务,重大工程建设项目,以及其他重大项目安排事项。"); + + POLICY_CONTENTS.put(CATEGORY_LARGE_FUND_OPERATION, + "4.大额度资金运作事项:是指超过由企业或者履行国有资产出资人职责的机构所规定的企业领导人员有权调动、使用的资金限额的资金调动和使用。主要包括年度预算内大额度资金调动和使用,超预算的资金调动和使用,对外大额捐赠、赞助,以及其他大额度资金运作事项。"); + + POLICY_CONTENTS.put(CATEGORY_DECISION_PROCEDURE, + ""); + } + + // 集团制度 + public static final Map GROUP_SYSTEMS = new HashMap<>(); + static { + GROUP_SYSTEMS.put(CATEGORY_MAJOR_DECISION, + "第四条重大决策的范围(一)集团公司贯彻执行国家法律法规,自治区战略部署及重要决策,以及上级主管部门重要决定等的重大举措;(二)集团公司章程制订和重要修订;集团公司重大基本管理制度,包括\"三重一大\"决策、投资、担保、资产交易、收入分配、人事、财务理,授权管理以及责任追究等重大管理制度的制订和重大修订;(三)集团公司生产经营方针、发展战略和中长期发展规划,以及重大投融资规划;(四)集团公司及重要子企业主业确定及变更;集团公司投资设立各类二级企业方案;集团公司及二级子公司发生分立、合并、重组,破产、解散及产权变更、增减资本方案;集团公司及所属子公司改制、股份制改造、混合所有制改革等方案;(五)集团公司重大财务管理事项,包括年度财务预算、年度财务决算、利润分配方案、亏损弥补方案;(六)集团公司内部收入分配事项,包括年度工资总额方案、职工工资、奖金、分流安置、劳动保护和劳动保险等事关职工切身利益的重大事项;集团公司所属部门、二级企业负责人年度考核结果及年度薪酬兑现方案;(七)集团公司制订或实施股权激励和员工持股计划;(八)集团公司及所属企业国有股权比例由控股变为非控股或国有股权由参股变为控股的事项;(九)集团公司及所属企业单笔金额为300万元及以上对外重大担保事项,集团公司及所属企业重大关联交易事项、内部借款单笔金额为2000万元及以上事项;(十)集团公司及所属企业产权(资产)对外非公开协议转让、无偿划转及公开进场转让单笔金额为300万元及以上的事项;集团公司内部重组整合进行的产权(资产)非公开协议转让、无偿划转单笔金额为300万元及以上的事项;(十一)集团公司及所属企业重大资产损失、大额不良资产、核销单笔金额为20万元及以上的事项;集团公司及所属企业单笔放弃金额为30万元及以上的重大权益事项;(十二)集团公司内部机构改革方案,内部管理机构设立和调整方案,内部定岗、定编,定员、定责方案;(十三)集团公司党建群团工作的重大事项、方案;(十四)集团公司董事会年度工作报告、经理层年度工作报告、年度依法治企工作报告,年度内部控制体系工作报告等重要报告;(十五)集团公司应对重大法律纠纷、安全稳定,重大突发事件的重大措施及方案;(十六)法律法规和企业章程规定的事项,以及集团公司认为需要集体决策的关系到企业全局性、方向性、战略性的其他重大事项。"); + + GROUP_SYSTEMS.put(CATEGORY_PERSONNEL_APPOINTMENT, + "第五条重要人事任免的范围(一)对集团公司中层管理人员、二级子公司领导班子成员的任免或聘用、解聘;相当于集团公司中层副职及以上的职业经理人的选聘、解聘;(二)按照企业章程约定,委派、提名、推荐集团公司非全资二级企业董事会、监事会成员和高级经营管理人员;(三)提名或委派集团公司三级企业董事长或执行董事、总经理人选;(四)集团公司中层管理人员后备人选;(五)涉及集团公司总部中层管理人员以及二级子公司班子成员的重要奖惩,对违犯党纪政纪干部职工的处理;(六)重大人事变动及其他人事管理的重要事项。"); + + GROUP_SYSTEMS.put(CATEGORY_MAJOR_PROJECT, + "(一)集团公司年度投资计划方案;(二)集团公司年度融资计划方案;(三)集团公司及所属企业总投资额为3000万元及以上的投资项目(投资项目范围按集团公司投资管理办法明确的投资项目范围) ;(四)应当向自治区国资委报告的重大投资管理事项;(五)其他需要集体研究决定的重要项目安排事项。"); + + GROUP_SYSTEMS.put(CATEGORY_LARGE_FUND_OPERATION, + "(一)除重大决策事项、重大项目安排事项外,涉及单笔7000万元及以上的年度预算内大额度资金使用事项;(二)除重大决策事项、重大项目安排事项外,涉及单笔5000万元及以上的预算外大额度经营性资金的使用事项;(三)预算外非经营性单笔100万元及以上、广西美术馆艺术品收藏单件或单次收藏一批100万元及以上的大额资金使用事项;(四)集团公司及所属企业10万元及以上的对外捐赠、财务资助、公益慈善及用于脱贫攻坚(乡村振兴)等涉及企业政治责任和社会责任方面的重要事项;(五)公司单份工程签证涉及工程造价变更金额为500万元及以上的项目资金支出和其他大额度资金运作事项。"); + + GROUP_SYSTEMS.put(CATEGORY_DECISION_PROCEDURE, + "第十一条 凡属\"三重一大\"事项,应按规定程序决策,除遇重大突发事件和紧急情况外,应由领导班子以党委会、董事会或经理层会议形式集体讨论决定,不得以传阅会签或个别征求意见等方式代替集体决策。第十二条 集团公司决策主体权力运行必须坚持落实党组织的法定地位,规范党组织参与\"三重一大\"决策程序,确保党组织的领导核心和政治核心作用有效发挥。要坚持把党委研究讨论作为董事会、经理层决策重大问题的前置程序,在集团公司董事会决策前,对涉及企业改革发展稳定,重大经营管理和职工切身利益的\"三重一大\"事项决策事项,集团公司党委应当组织有关人员对决策事项进行调研和论证,提出意见建议,充分体现党组织对决策的定向把关作用,确保决策的合法性科学性、准确性。在董事会决策中,进入董事会的党委班子成员要按照党委的决定在董事会上充分发表意见,保证党委的意图在决策中得到体现。在董事会决策后,党委要建立\"三重一大\"决策事项跟踪问效制度、定期汇报制度、事后考核制度等,确保党的决策部署在企业贯彻好、执行好、落实好。第十三条 集团公司党委会、董事会,经理层对\"三重一大\"事项进行决策的具体程序和议题准备、表决、记录等事项严格按照集团公司党委会议事规则、董事会议事规则、总经理办公会议事规则规定的程序和要求执行。第十四条 集团公司研究决定企业改制及经营管理方面的重大问题、涉及职工切身利益的重大事项、制定重要的规章制度,应当听取企业工会的意见,并通过职工代表大会或者其他形式听取职工群众的意见和建议。按照国家有关规定须经职工代表大会或者职工大会审议通过的事项,履行相关程序后董事会或经理层方可批准或者作出决议。涉及重大决策、重大项目、大额度资金的使用,有合同意向的,应将合同的主要条款提交会议讨论。第十五条 \"三重一大\"事项经集体决策后,企业领导人员应当按照分工和职责组织实施。遇有分工和职责交叉的,应明确牵头落实人员。个人对集体决策有不同意见的,可以保留,但在作出新的决策前,应无条件执行。同时,可按组织程序向上级组织反映意见。第十六条 企业\"三重一大\"事项经集体决策后,应及时向自治区国资委报告有关决策情况。特别是属于重大报告事项的应按要求及时上报自治区国资委备案。自治区人民政府、自治区国资委另有规定需要报审、报批和备案的,按规定程序办理。"); + } + + // 公司制度 + public static final Map COMPANY_FORMULATIONS = new HashMap<>(); + static { + COMPANY_FORMULATIONS.put(CATEGORY_MAJOR_DECISION, + "(一)贯彻执行国家法律法规、自治区战略部署及集团公司的重要决策,以及上级主管部门重要决定等的重大举措;(二)章程制订和重要修订;重大基本管理制度,包括\"三重一大\"决策、投资、担保、资产交易、收入分配、人事、财务管理、授权管理以及责任追究等重大管理制度的制订和重大修订;(三)生产经营方针、发展战略和中长期发展规划,以及重大投融资规划;(四)公司主业确定及变更;投资设立各类企业方案;发生分立、合并、重组、破产、解散及产权变更、增减资本方案;公司及所属企业改制、股份制改造、混合所有制改革等方案;(五)重大财务管理事项,包括年度财务预算、年度财务决算、利润分配方案、亏损弥补方案;(六)内部收入分配事项,包括年度工资总额方案、职工工资、奖金、分流安置、劳动保护和劳动保险等事关职工切身利益的重大事项;所属部门年度考核结果及年度薪酬兑现方案;(七)制订或实施股权激励和员工持股计划;(八)公司国有股权比例由控股变为非控股或国有股权由参股变为控股的事项;(九)公司对外担保事项,公司重大关联交易事项和内部借款单笔100万元及以上的事项;(十)公司产权(资产)对外非公开协议转让、无偿划转及公开进场转让;公司内部重组整合进行的产权(资产)非公开协议转让、无偿划转的事项;(十一)公司重大资产损失、大额不良资产核销单笔金额为1万元及以上的事项;公司单笔放弃1万元及以上的重大权益事项;(十二)公司内部机构改革方案,内部管理机构设立和调整方案,内部定岗、定编、定员、定责方案;(十三)公司党建群团工作的重大事项、方案;(十四)公司年度工作报告、经理层年度工作报告、年度依法治企工作报告、年度内部控制体系工作报告等重要报告;(十五)公司应对重大法律纠纷、安全稳定、重大突发事件的措施及方案;(十六)法律法规和企业章程规定的事项,以及公司认为需要集体决策的关系到企业全局性、方向性、战略性的其他重大事项。"); + + COMPANY_FORMULATIONS.put(CATEGORY_PERSONNEL_APPOINTMENT, + "(一)对公司中层管理人员(包括重大项目负责人)的任免或聘用、解除聘用和后备人选的推荐、确定;(二)委派、提名、推荐董事、监事和经理层、财务负责人;(三)干部员工的奖惩;(四)重大人事变动及其他人事管理的重要事项。"); + + COMPANY_FORMULATIONS.put(CATEGORY_MAJOR_PROJECT, + "(一)企业年度投资计划和投资方案;(二)企业年度融资计划和融资方案,包括银行借贷、上市、发行债券等;(三)企业50万元及以上的投资项目以及非主业投资项目;(四)企业的重大技术改造方案、重要技术设备引进项目;(五)重大、关键性的设备引进和重要物资设备购置等重大招投标管理项目;(六)应当向自治区国资委或广西旅游发展集团有限公司(以下简称\"集团公司\")报告的重大投资管理事项;(七)其他需要集体研究决定的重要项目安排事项。"); + + COMPANY_FORMULATIONS.put(CATEGORY_LARGE_FUND_OPERATION, + "(一)除重大决策事项、重大项目安排事项外,涉及单笔500万元及以上的年度预算内大额度资金调动和使用;(二)除重大决策事项、重大项目安排事项外,涉及单笔50万元及以上的预算外大额度资金调动和使用;(三)除重大决策事项、重大项目安排事项外,涉及单笔10万元及以上的非生产性支出;(四)单份工程签证及造价变更额度为10万元及以上的;(五)企业对外捐赠、赞助在1万元及以上的事项;(六)其他大额度资金运作事项。"); + + COMPANY_FORMULATIONS.put(CATEGORY_DECISION_PROCEDURE, + "第十一条 凡属\"三重一大\"事项,党支部委员会成员应参加党支部委员会议对议题进行前置研究审议,保证党组织的意图在决策中得到体现。不得以传阅会签或个别征求意见等方式代替集体决策。第十二条 公司决策主体权力运行必须坚持落实党组织的法定地位,规范党组织参与\"三重一大\"决策程序,确保党组织的领导核心和政治核心作用有效发挥。要坚持把党支部研究讨论作为总经理办公会决策重大问题的前置程序。在总经理办公会会议决策前,对涉及企业改革发展稳定、重大经营管理和职工切身利益的\"三重一大\"事项决策事项,党支部应当组织有关人员对决策事项进行调研和论证,提出意见建议,充分体现党支部对决策的定向把关作用,确保决策的合法性、科学性、准确性。在总经理办公会的决策中,担任执行董事的党支部领导班子成员要按照党支部的决定在总经理办公会上充分发表意见,保证党支部的意图在决策中得到体现。在总经理办公会决策后,党支部要建立\"三重一大\"决策事项跟踪问效制度、定期汇报制度、事后考核制度等,确保党的决策部署在企业贯彻好、执行好、落实好。第十三条 公司党支部、经理层对\"三重一大\"事项进行决策的具体程序和议题准备、表决、记录等事项严格按照公司相关议事规则规定的程序和要求执行。第十四条 公司研究决定企业改制及经营管理方面的重大问题、涉及职工切身利益的重大事项、制定重要的规章制度,应当听取企业工会的意见,并通过职工代表大会或者其他形式听取职工群众的意见和建议。按照国家有关规定须经职工代表大会或者职工大会审议通过的事项,履行相关程序后经理层方可批准或者作出决议。涉及重大决策、重大项目、大额度资金的使用,有合同意向的,应将合同的主要条款提交会议讨论。第十五条 \"三重一大\"事项经集体决策后,企业领导人员应当按照分工和职责组织实施。遇有分工和职责交叉的,应明确牵头落实人员。个人对集体决策有不同意见的,可以保留,但在作出新的决策前,应无条件执行。同时,可按组织程序向上级组织反映意见。"); + } + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("三重一大", 10); + KEYWORD_WEIGHTS.put("重大决策", 9); + KEYWORD_WEIGHTS.put("重要人事任免", 9); + KEYWORD_WEIGHTS.put("重大项目安排", 9); + KEYWORD_WEIGHTS.put("大额度资金运作", 9); + KEYWORD_WEIGHTS.put("决策程序", 8); + KEYWORD_WEIGHTS.put("党委会", 7); + KEYWORD_WEIGHTS.put("董事会", 7); + KEYWORD_WEIGHTS.put("总经理办公会", 7); + KEYWORD_WEIGHTS.put("集体决策", 8); + KEYWORD_WEIGHTS.put("会议纪要", 6); + KEYWORD_WEIGHTS.put("金额标准", 6); + } + + private AuditContent3TripleConstants() { + // 防止实例化 + } +} diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent4TargetConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent4TargetConstants.java new file mode 100644 index 0000000..6d4ad84 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent4TargetConstants.java @@ -0,0 +1,151 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容4-目标责任制完成情况常量类 + * 审计目标:检查被审计领导干部任职期间目标责任制的完成情况 + */ +public class AuditContent4TargetConstants { + + // 分类定义 + public static final String CATEGORY_TARGET_RESPONSIBILITY = "目标责任制完成情况"; + + // 审计框架核心(基于Excel内容优化) + public static final String AUDIT_FRAMEWORK = + "审计核心:检查目标责任制的制定、执行和完成情况\n\n" + + "重点关注:\n" + + "1. 对照被审计人所在单位的职责和主要业务\n" + + "2. 上级主管部门是否规定有目标责任\n" + + "3. 单位是否制定有目标责任\n" + + "4. 检查审计目标责任的完成情况\n\n" + + "审计方法及步骤:\n" + + "1. 审阅上级主管部门下达或单位自定的目标责任制,对照完成情况,检查目标责任制的落实效果\n" + + "2. 主要以单位自定的目标任务为衡量标准\n" + + "3. 对已完成任务的部分,检查是否真实完成\n" + + "4. 对未完成任务的部分,检查未完成的原因\n\n" + + "特别说明:\n" + + "1. 如果企业没有制定单位自定的目标责任制,请填写\"企业未制定年度目标计划\"\n" + + "2. 如果知识库中没有单位自定目标的信息,请根据企业实际情况推断可能的计划内容\n" + + "3. 单位自定目标应包括:年度工作计划、部门工作目标、绩效考核指标等"; + + // 审计目标 + public static final String AUDIT_OBJECTIVE = + "检查目标责任制的制定、执行和完成情况,评估落实效果和完成质量。"; + + // 审计工作原则 + public static final String AUDIT_PRINCIPLES = + "审计工作原则:\n" + + "1. 以目标责任制文件为依据,对照完成情况\n" + + "2. 优先使用上级主管部门下达的目标任务作为衡量标准\n" + + "3. 如无上级目标任务,则以单位自定目标为衡量标准\n" + + "4. 重点关注已完成任务的真实性、未完成任务的客观原因\n" + + "5. 注重目标责任与单位职责的匹配性\n" + + "6. 考核评价机制是否健全有效"; + + // 关键词权重(聚焦核心概念) + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + // 核心概念 + KEYWORD_WEIGHTS.put("目标责任", 10); + KEYWORD_WEIGHTS.put("目标责任制", 10); + KEYWORD_WEIGHTS.put("目标任务", 9); + + // 完成情况 + KEYWORD_WEIGHTS.put("完成情况", 10); + KEYWORD_WEIGHTS.put("落实情况", 9); + KEYWORD_WEIGHTS.put("完成率", 8); + + // 文件依据 + KEYWORD_WEIGHTS.put("下达文件", 9); + KEYWORD_WEIGHTS.put("计划文件", 9); + KEYWORD_WEIGHTS.put("责任书", 8); + KEYWORD_WEIGHTS.put("考核办法", 8); + + // 审计相关 + KEYWORD_WEIGHTS.put("考核指标", 8); + KEYWORD_WEIGHTS.put("未完成", 7); + KEYWORD_WEIGHTS.put("已完成", 7); + KEYWORD_WEIGHTS.put("上级主管", 7); + } + + // 审计要点提示 + public static final String AUDIT_KEY_POINTS = + "审计要点:\n" + + "1. 检查目标责任文件完整性\n" + + "2. 核实完成数据的真实性\n" + + "3. 分析未完成原因合理性\n" + + "4. 评估目标制定科学性\n" + + "5. 检查考核执行规范性"; + + // 数据格式要求 + public static final String DATA_FORMAT_INSTRUCTION = + "JSON数组格式,包含以下字段:\n" + + "1. index: 序号\n" + + "2. year: 年度\n" + + "3. superiorFile: 上级下达文件\n" + + "4. superiorCompletion: 上级完成情况\n" + + "5. superiorReason: 上级未完成原因\n" + + "6. selfPlan: 单位自定计划\n" + + "7. selfCompletion: 自定完成情况\n" + + "8. selfReason: 自定未完成原因\n" + + "9. remark: 备注\n" + +// "10. workPaperIndex: [相关文件FileId]"; + "10. workPaperIndex: [相关文件的完整文件名||FileUrl]"; + + // 获取分类的简要审计框架概述 + public static String getBriefAuditFrameworkForCategory(String category) { + if (CATEGORY_TARGET_RESPONSIBILITY.equals(category)) { + return "审计目标责任制的制定、执行和完成情况,评估落实效果"; + } + return "审计目标责任制完成情况"; + } + + // 字段映射(用于前端展示) + public static final Map FIELD_DISPLAY_NAMES = new HashMap<>(); + static { + FIELD_DISPLAY_NAMES.put("index", "序号"); + FIELD_DISPLAY_NAMES.put("year", "年度"); + FIELD_DISPLAY_NAMES.put("superiorFile", "上级下达文件"); + FIELD_DISPLAY_NAMES.put("superiorCompletion", "上级完成情况"); + FIELD_DISPLAY_NAMES.put("superiorReason", "上级未完成原因"); + FIELD_DISPLAY_NAMES.put("selfPlan", "单位自定计划"); + FIELD_DISPLAY_NAMES.put("selfCompletion", "自定完成情况"); + FIELD_DISPLAY_NAMES.put("selfReason", "自定未完成原因"); + FIELD_DISPLAY_NAMES.put("remark", "备注"); + FIELD_DISPLAY_NAMES.put("workPaperIndex", "工作底稿索引"); + } + + // 审计证据要求 + public static final String AUDIT_EVIDENCE_REQUIREMENTS = + "审计证据要求:\n" + + "1. 查阅文件:目标责任制相关文件、计划、实施方案\n" + + "2. 查阅记录:完成情况报告、考核记录、会议纪要\n" + + "3. 查阅数据:统计数据、财务报表、进度报表\n" + + "4. 查阅凭证:相关凭证、合同、协议\n" + + "5. 现场核实:必要时进行现场核实和访谈"; + + // 输出格式要求 + public static final String OUTPUT_FORMAT = + "输出格式(JSON数组):\n" + + "[\n" + + " {\n" + + " \"index\": 序号,\n" + + " \"year\": \"年度\",\n" + + " \"superiorFile\": \"上级主管部门下达文件名称和文号\",\n" + + " \"superiorCompletion\": \"上级目标完成情况(已完成/部分完成/未完成)\",\n" + + " \"superiorReason\": \"上级目标未完成原因(如已完成,填'无未完成原因')\",\n" + + " \"selfPlan\": \"单位自定计划文件名称\",\n" + + " \"selfCompletion\": \"自定目标完成情况(已完成/部分完成/未完成)\",\n" + + " \"selfReason\": \"自定目标未完成原因(如已完成,填'无未完成原因')\",\n" + + " \"remark\": \"备注\",\n" + +// " \"workPaperIndex\": [\"实际存在的完整FileId1\", \"实际存在的完整FileId2\", ...]\n" + + " \"workPaperIndex\": [\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\", ...]\n" + + " }\n" + + "]\n\n" + + "重要说明:\n" + + "1. 每个审计记录对应一个具体的文件或目标\n" + + "2. 同一年度可能有多个目标责任,每个都应生成独立的审计记录\n" + + "3. 尽可能从知识库中提取所有相关信息,生成尽可能多的记录"; + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetExecutionConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetExecutionConstants.java new file mode 100644 index 0000000..343c761 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetExecutionConstants.java @@ -0,0 +1,260 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容5-预算执行情况审计常量类 + * 审计目标:检查预算执行的进度、合规性、效果,分析预算执行偏差原因,评估预算执行效益 + */ +public class AuditContent5BudgetExecutionConstants { + + // 分类定义 + public static final String CATEGORY_BUDGET_EXECUTION = "预算执行情况审计"; + + // 审计框架核心 + public static final String AUDIT_FRAMEWORK = + "**审计核心:**\n" + + "预算执行情况审计。包括预算执行进度、执行合规性、执行效果评估、预算执行偏差分析、\n" + + "资金到位情况、项目执行进度、预算执行率、资金使用效益、执行风险预警、整改措施落实等情况。\n\n" + + + "**重点关注:**\n" + + "1. 预算执行率是否达到预期目标,是否存在执行进度滞后问题\n" + + "2. 资金是否按时足额到位,是否存在资金沉淀或短缺问题\n" + + "3. 预算执行是否合规,是否存在超预算、无预算执行问题\n" + + "4. 项目执行进度是否符合计划,是否存在项目延期问题\n" + + "5. 预算调整是否及时报批,是否存在擅自调整预算问题\n" + + "6. 预算执行效益如何,是否达到预期经济效益和社会效益\n" + + "7. 预算执行是否存在偏差,偏差原因是否合理\n" + + "8. 预算执行监控是否到位,是否存在风险预警机制\n" + + "9. 预算执行问题整改是否及时有效,整改措施是否落实\n" + + "10. 预算执行信息公开是否及时准确,透明度如何\n" + + "11. 预算执行考核评价机制是否健全,考核结果是否应用\n" + + "12. 预算执行信息化水平如何,是否实现动态监控\n" + + "13. 预算执行与决算衔接是否顺畅,数据是否一致\n" + + "14. 预算执行对单位履职和事业发展的支撑作用如何\n" + + "15. 预算执行中是否存在挤占挪用、虚列支出等问题\n" + + "16. 预算执行与政府采购、资产管理等是否协调衔接\n\n" + + + "**审计方法及步骤:**\n" + + "1. 预算执行进度审计:\n" + + " (1) 审阅被审计领导干部任职期间的预算执行进度报表、月度/季度/年度执行分析报告\n" + + " (2) 对比预算安排与实际执行数据,计算预算执行率、资金到位率等关键指标\n" + + " (3) 分析执行进度滞后项目的原因,检查是否存在客观障碍或主观问题\n" + + " (4) 检查预算执行台账是否完整,执行记录是否及时准确\n\n" + + + "2. 预算执行合规性审计:\n" + + " (1) 对比预算批复文件与会计账务处理,检查预算执行是否严格按照批复执行\n" + + " (2) 检查是否存在超预算执行、无预算执行、预算科目调剂等违规问题\n" + + " (3) 审查预算调整审批手续是否完备,是否存在擅自调整预算问题\n" + + " (4) 检查资金支付凭证是否合规,支付审批流程是否完整\n\n" + + + "3. 预算执行效果审计:\n" + + " (1) 对照项目可行性研究报告和评审报告,检查项目预期目标实现情况\n" + + " (2) 运用成本效益分析方法,评估预算资金使用效益\n" + + " (3) 实地查看项目实施情况,验证项目实际效果\n" + + " (4) 调查相关受益群体,了解项目社会效益和满意度\n\n" + + + "4. 预算执行偏差分析:\n" + + " (1) 分析预算执行偏差数据,找出偏差较大的项目和科目\n" + + " (2) 通过访谈、座谈等方式了解偏差产生的原因\n" + + " (3) 评估偏差是否合理,是否存在管理问题\n" + + " (4) 检查是否建立偏差分析机制和整改措施\n\n" + + + "5. 预算执行风险管理审计:\n" + + " (1) 检查是否建立预算执行风险预警机制\n" + + " (2) 评估风险预警指标是否科学合理\n" + + " (3) 检查风险预警响应和处置是否及时有效\n" + + " (4) 审查风险防范措施是否落实到位\n\n" + + + "6. 预算执行整改审计:\n" + + " (1) 检查以往审计发现问题的整改情况\n" + + " (2) 评估整改措施的有效性和落实情况\n" + + " (3) 检查是否存在屡审屡犯问题\n" + + " (4) 评估整改长效机制建设情况\n\n" + + + "7. 预算执行信息化审计:\n" + + " (1) 检查预算执行信息系统建设情况\n" + + " (2) 评估系统功能是否满足预算执行监控需求\n" + + " (3) 检查系统数据是否准确完整\n" + + " (4) 评估信息化对预算执行管理的支撑作用"; + + // 审计目标 + public static final String AUDIT_OBJECTIVE = + "全面检查预算执行全过程,包括执行进度、执行合规性、执行效果、执行偏差分析、\n" + + "风险管理、整改落实等,确保预算资金规范高效使用,提高预算执行质量和效益。"; + + // 审计工作原则 + public static final String AUDIT_PRINCIPLES = + "审计工作原则:\n" + + "1. 以《预算法》、《预算法实施条例》及相关财政法规为依据\n" + + "2. 全面覆盖预算执行的所有环节和方面\n" + + "3. 重点关注预算执行率、资金到位率等关键指标\n" + + "4. 检查预算执行合规性和规范性\n" + + "5. 评估预算执行效果和效益\n" + + "6. 分析预算执行偏差原因\n" + + "7. 关注预算执行风险防控\n" + + "8. 检查审计问题整改情况\n" + + "9. 促进预算执行管理完善\n" + + "10. 提高财政资金使用效益"; + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + // 预算执行核心概念 + KEYWORD_WEIGHTS.put("预算执行", 10); + KEYWORD_WEIGHTS.put("执行进度", 9); + KEYWORD_WEIGHTS.put("执行率", 10); + KEYWORD_WEIGHTS.put("资金到位", 9); + KEYWORD_WEIGHTS.put("执行合规", 8); + + // 预算执行指标 + KEYWORD_WEIGHTS.put("执行偏差", 8); + KEYWORD_WEIGHTS.put("预算调整", 7); + KEYWORD_WEIGHTS.put("超预算", 8); + KEYWORD_WEIGHTS.put("无预算", 8); + KEYWORD_WEIGHTS.put("科目调剂", 7); + + // 执行效果和效益 + KEYWORD_WEIGHTS.put("执行效果", 8); + KEYWORD_WEIGHTS.put("资金效益", 8); + KEYWORD_WEIGHTS.put("成本效益", 7); + KEYWORD_WEIGHTS.put("社会效益", 7); + KEYWORD_WEIGHTS.put("经济效益", 7); + + // 执行风险管理 + KEYWORD_WEIGHTS.put("风险预警", 7); + KEYWORD_WEIGHTS.put("执行风险", 7); + KEYWORD_WEIGHTS.put("风险防控", 6); + KEYWORD_WEIGHTS.put("预警机制", 6); + + // 执行监控和整改 + KEYWORD_WEIGHTS.put("执行监控", 7); + KEYWORD_WEIGHTS.put("动态监控", 6); + KEYWORD_WEIGHTS.put("整改措施", 7); + KEYWORD_WEIGHTS.put("整改落实", 7); + KEYWORD_WEIGHTS.put("屡审屡犯", 6); + + // 文件类型 + KEYWORD_WEIGHTS.put("执行报表", 8); + KEYWORD_WEIGHTS.put("进度报表", 8); + KEYWORD_WEIGHTS.put("执行分析", 7); + KEYWORD_WEIGHTS.put("执行报告", 7); + KEYWORD_WEIGHTS.put("支付凭证", 7); + KEYWORD_WEIGHTS.put("拨款凭证", 7); + } + + // 审计要点提示 + public static final String AUDIT_KEY_POINTS = + "**审计要点:**\n" + + "1. 检查预算执行率是否达到预期目标,分析执行进度滞后原因\n" + + "2. 核实资金是否按时足额到位,是否存在资金沉淀问题\n" + + "3. 审查预算执行是否合规,是否存在超预算、无预算执行\n" + + "4. 检查项目执行进度是否符合计划,是否存在项目延期\n" + + "5. 评估预算调整审批是否及时合规\n" + + "6. 分析预算执行偏差原因是否合理\n" + + "7. 评估预算执行效益是否达到预期目标\n" + + "8. 检查预算执行风险预警机制是否健全\n" + + "9. 审查审计问题整改是否及时有效\n" + + "10. 评估预算执行信息化水平\n" + + "11. 检查预算执行信息公开情况\n" + + "12. 评估预算执行考核评价机制\n" + + "13. 检查预算执行与决算衔接情况\n" + + "14. 分析预算执行对单位发展的支撑作用\n" + + "15. 检查是否存在挤占挪用、虚列支出问题\n" + + "16. 评估预算执行管理完善程度"; + + // 数据格式要求 + public static final String OUTPUT_FORMAT = + "**输出格式(JSON数组):**\n" + + "[\n" + + " {\n" + + " \"index\": 序号,\n" + + " \"project\": \"项目名称\",\n" + + " \"lastYearCarryOver\": \"上年结转\",\n" + + " \"currentYearBudgetTotal\": \"本年预算小计\",\n" + + " \"initialApprovedBudget\": \"年初批复预算数\",\n" + + " \"additionalBudgetAmount\": \"追加预算数\",\n" + + " \"actualAppropriation\": \"实际拨款数\",\n" + + " \"indicatorBalance\": \"指标结余\",\n" + +// " \"workPaperIndex\": [\"实际存在的完整FileId1\", \"实际存在的完整FileId2\", ...]\n" + + " \"workPaperIndex\": [\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\", ...]\n" + + " }\n" + + "]\n\n" + + "**重要说明:**\n" + + "1. 每个审计记录对应一个具体的预算项目或执行事项\n" + + "2. 尽可能全面识别所有预算执行项目和环节\n" + + "3. 金额字段应填写具体数值,如\"1,000,000.00\"\n" + + "4. 百分比字段应填写具体百分比,如\"85.50%\"\n" + + "5. workPaperIndex必须填写实际查阅的文件名称\n" + + "6. 对于无数据的字段,可填写\"-\"或留空\n" + + "7. 尽可能多地生成审计记录,覆盖所有预算执行方面\n" + + "8. 重点关注预算执行率低于80%或高于120%的项目\n" + + "9. 深入分析预算执行偏差的根本原因"; + + // 审计证据要求 + public static final String AUDIT_EVIDENCE_REQUIREMENTS = + "**审计证据要求:**\n" + + "1. 预算执行相关文件:预算执行进度报表、月度/季度/年度执行分析报告\n" + + "2. 资金拨付文件:拨款凭证、银行回单、付款审批单\n" + + "3. 预算调整文件:预算调整审批文件、追加预算审批单\n" + + "4. 项目执行文件:项目进度报告、项目验收报告、项目结算资料\n" + + "5. 财务核算文件:会计账簿、会计凭证、银行对账单\n" + + "6. 执行监控文件:预算执行台账、执行监控记录、风险预警记录\n" + + "7. 整改落实文件:审计问题整改报告、整改措施落实证明\n" + + "8. 考核评价文件:预算执行考核方案、考核结果、奖惩记录\n" + + "9. 信息系统文件:预算执行系统截图、数据导出报表\n" + + "10. 现场核实资料:实地查看记录、座谈记录、调查问卷"; + + // 字段映射(用于前端展示) + public static final Map FIELD_DISPLAY_NAMES = new HashMap<>(); + static { + FIELD_DISPLAY_NAMES.put("index", "序号"); + FIELD_DISPLAY_NAMES.put("project", "项目名称"); + FIELD_DISPLAY_NAMES.put("lastYearCarryOver", "上年结转"); + FIELD_DISPLAY_NAMES.put("currentYearBudgetTotal", "本年预算小计"); + FIELD_DISPLAY_NAMES.put("initialApprovedBudget", "年初批复预算数"); + FIELD_DISPLAY_NAMES.put("additionalBudgetAmount", "追加预算数"); + FIELD_DISPLAY_NAMES.put("actualAppropriation", "实际拨款数"); + FIELD_DISPLAY_NAMES.put("indicatorBalance", "指标结余"); + FIELD_DISPLAY_NAMES.put("workPaperIndex", "工作底稿索引"); + } + + // 获取分类的简要审计框架概述 + public static String getBriefAuditFrameworkForCategory(String category) { + if (CATEGORY_BUDGET_EXECUTION.equals(category)) { + return "全面审计预算执行全过程,包括执行进度、合规性、效果、偏差分析、风险管理、整改落实等情况"; + } + return "全面审计预算执行情况"; + } + + // 审计项目类型(常见预算执行项目) + public static final List AUDIT_PROJECT_TYPES = Arrays.asList( + "基本支出-人员经费执行", + "基本支出-公用经费执行", + "项目支出-专项业务费执行", + "项目支出-设备购置费执行", + "项目支出-大型修缮费执行", + "项目支出-信息网络购建费执行", + "项目支出-基础设施建设费执行", + "项目支出-会议费执行", + "项目支出-培训费执行", + "项目支出-差旅费执行", + "项目支出-劳务费执行", + "项目支出-咨询费执行", + "项目支出-委托业务费执行", + "政府采购项目执行", + "科研项目经费执行", + "基建项目资金执行", + "信息化项目执行", + "培训项目执行", + "会议项目执行", + "差旅费执行", + "办公设备购置执行", + "车辆运行维护费执行", + "物业管理费执行", + "租赁费执行", + "维修维护费执行", + "专用材料费执行", + "其他商品和服务支出执行" + ); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetManageConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetManageConstants.java new file mode 100644 index 0000000..e75202d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent5BudgetManageConstants.java @@ -0,0 +1,323 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容5-预算管理审计常量类 + * 审计目标:检查预算编制的完整准确、预算调整审批的合规,以及预算支出的真实合法合规情况 + */ +public class AuditContent5BudgetManageConstants { + + // 分类定义 + public static final String CATEGORY_BUDGET_MANAGEMENT = "预算管理审计"; + + // 审计框架核心(完整基于Excel内容) + public static final String AUDIT_FRAMEWORK = + "**审计核心:**\n" + + "财政财务管理情况。包括预算编制的完整准确、预算调整审批的合规,以及预算支出的真实合法合规情况;\n" + + "财务收支的真实、合法和效益情况;自然资源资产管理和生态环境保护责任的履行情况;\n" + + "境外机构、境外资产和境外经济活动的真实、合法和效益情况。\n\n" + + + "**重点关注:**\n" + + "1. 是否存在应纳入部门预算管理的各项收支未纳入预算编报\n" + + "2. 项目预算编制不细化导致预算执行率低\n" + + "3. 将独立核算的经营收支编入部门预算\n" + + "4. 将预算拨款在不同预算科目之间相互调剂使用\n" + + "5. 年初未安排预算、在年中又追加的预算调整事项是否合理\n" + + "6. 有无未经批复自行调整部门预算的问题\n" + + "7. 部门预算的项目是否全部都是本单位的业务\n" + + "8. 是否有所设立企业的经营收支性质的预算项目\n" + + "9. 预算支出中是否存在以虚假经济业务套取财政资金等问题\n" + + "10. 预算收支的效益性,项目效益、效果等预期目标的实现情况\n" + + "11. 国有资产配置是否按规定实行政府采购\n" + + "12. 国有资产对外投资和收益以及国有资产保值增值的情况\n" + + "13. 政府采购执行情况,是否存在规避政府采购的情况\n" + + "14. 项目实施及管理情况,项目资金安全、实施进度\n" + + "15. 债权债务的情况,债务可控程度,债权形成呆账的比例\n" + + "16. 工会、饭堂财务收支情况,有无将经营收入放进工会、饭堂核算\n\n" + + + "**审计方法及步骤:**\n" + + "1. 部门预算编报及调整情况审计:\n" + + " (1) 审阅被审计领导干部任职期间的部门预算、决算资料,分析预算收入来源渠道和使用方向,将各年度收支预算进行对比,检查收入预算编报的完整性,支出预算编报的真实性、准确性。\n" + + " (2) 审阅项目预算申报文本,对项目的可行性研究报告、评审报告进行审查,通过实地调查或座谈等方法了解项目准备情况和进展情况,检查项目预算申报的规范性,项目预算有无细化到具体执行单位。\n" + + " (3) 审阅预算调整文件,审查申报、审批手续的完整性,重点关注年初未安排预算、在年中又追加的预算调整事项是否合理。有无未经批复自行调整部门预算的问题。\n" + + " (4) 审阅部门预算的项目是否全部都是本单位的业务,检查是否有所设立企业的经营收支性质的预算项目。\n" + + " (5) 将预算下达文件与会计账务处理相对比,检查两者的项目名称一致,是否相互调剂项目预算。\n\n" + + + "2. 预算执行和财政财务收支情况审计:\n" + + " (1) 必须了解被审计单位设置的财务帐套有哪些,核实现金、银行的流水账和会计凭证保存的银行账户对账单是否一致,现金、银行的流动是否正常,有没有转到可疑的、无关的银行账户的现象。\n" + + " (2) 核实财务软件的记录与被审计单位提供及保存的纸质版的账簿是否一致,核实财务帐套设置的明细科目是否与预算批复的明细科目一致。\n" + + " (3) 核实财务帐套的支出数是否与决算报表一致,有无调整财务帐套支出数以达到不超出预算目的的现象,有无不按财务帐套支出数填报决算报表,以达到不超出预算目的的现象。\n" + + " (4) 核实财务帐套上基本支出加项目支出的\"三公\"经费等的支出数,对比预算下达的\"三公经费\"等预算数,检查是否超预算。\n" + + " (5) 审阅被审计领导干部任职期间所在单位各年度预算、决算资料,编制收入和支出情况分析表,将各年度收入和支出规模和结构进行对比,运用分析性复核的方法分析收入和支出的变化趋势,考虑合理的变动因素,审查不合理的变化及差异,检查收入和支出的总体规模、结构是否与单位职责履行、事业发展目标或者所承担重要事项相匹配。\n" + + " (6) 对照收入和支出预算和决算,审查被审计领导干部任职期间部门各年度的会计账簿和凭证,检查预算收入、预算支出的真实性。审阅项目预算申报文本,对照项目预算支出明细,审查会计账簿和凭证,检查是否存在挤占挪用项目资金等问题。实地观察项目执行情况,检查是否存在虚报项目套取财政资金、将所属企业开支纳入预算核算等问题。\n" + + " (7) 结合新执行的预算绩效考核方法,审查被审计领导干部任职期间各年度的会计账簿、凭证和有关业务资料,运用成本效益分析方法,检查预算收支的效益性,对照项目可行性研究报告和评审报告以及年度目标责任制,检查项目效益、效果等预期目标的实现情况,以及对经济社会发展产生的影响。\n\n" + + + "3. 国有资产的管理情况审计:\n" + + " (1) 审阅国有资产采购合同及招投标文件等资料,检查国有资产购置是否按规定实行政府采购。\n" + + " (2) 审阅固定资产登记台账与会计明细账,审查账账是否相符。根据固定资产类别或领用单位等进行抽查盘点,审查账实是否相符,已报废固定资产是否及时予以报废、核销。\n" + + " (3) 审阅国有资产出租、出借、报废核销的上级主管部门和财政部门的批准文件,检查手续是否完整。\n" + + " (4) 审阅国有资产处置收入的评估文件、上级主管部门和财政部门的核准文件,对比市场价格,检查处置收入是否合理,是否具备完整的处置手续。\n" + + " (5) 审阅国有资产处置收入的会计处理凭证及上缴财政国库的会计处理凭证,检查处置收益是否已纳入\"收支两条线\"管理。审计房屋出租是否按照规定上平台招租,可调查周边租金水平,来进行对比。\n" + + " (6) 审阅国有资产对外投资,提供抵押、担保的上级主管部门和财政部门的审计文件,以及提供抵押、担保的合同,检查审批手续是否齐全,是否真实、合法;计算本单位国有资产保值增值率,检查本单位国有资产保值增值的情况;审计被审计单位划拨、借用给设立企业的资产来源是否清晰。\n\n" + + + "4. 政府采购执行情况审计:\n" + + " (1) 审查财务支出凭据,和\"固定资产\"科目,检查是否全部固定资产的采购都是通过具备政府采购手续的。\n" + + " (2) 对照政府采购文件和目录,查阅财务支出,审计是否存在应报政府采购而未报的情况,特别注意成批的易耗品、一般资产也要执行政府采购。审计时,必须将有可能是批量采购而拆解采购的线索联系起来,看采购的时间、品目,是否应当同时购置的,采取分散购置,规避政府采购,发现此类情况的,除追究违反政府采购法的责任外,还要追踪至货物、服务的提供方,检查是否存在关联交易,交易价格是否合理,是否存在利益输送。\n" + + " (3) 将年度内所有已执行政府采购的名单列出,与政府采购预算报表核对,审计是否按照预算采购,有无乱采购、无预算采购。凡是发现无预算采购的,都要关注采购价格和关联方交易、利益输送。\n" + + " (4) 对实施单一采购的,核对政府采购目录,检查是否属于单一采购来源的要求。如果不属于单一采购来源的,也要关注采购价格和关联方交易、利益输送。\n\n" + + + "5. 项目实施及管理情况审计:\n" + + " 关注各单位实施项目的资金安全,实施进度、财务收支以及项目的会计核算及项目管理的相关手续。\n\n" + + + "6. 债权债务的情况审计:\n" + + " (1) 检查审计期末的应收款与应付款借方余额的形成年限,对超过3年未收取的应收款项,询问形成呆账的原因,以及有无采取措施收取。计算3年以上呆账占应收款项的比例,如果占比较高,检查是否建立有坏账的处理机制。\n" + + " (2) 检查审计期末的应付款和应收款的贷方余额,对大额的部分,了解形成的原因、被审计单位的偿还能力。\n\n" + + + "7. 工会、饭堂财务收支情况审计:\n" + + " 重点关注有无将房屋租金、经营收入等放进工会、饭堂核算的现象;工会开支范围和标准是否超过文件规定。"; + + // 审计目标 + public static final String AUDIT_OBJECTIVE = + "全面检查预算管理的各个方面,包括预算编制的完整准确、预算调整审批的合规、预算支出的真实合法合规情况、\n" + + "财务收支的真实合法和效益、国有资产管理、政府采购执行、项目实施管理、债权债务管理、工会饭堂财务等,\n" + + "确保财政资金使用的规范性、有效性和安全性。"; + + // 审计工作原则 + public static final String AUDIT_PRINCIPLES = + "审计工作原则:\n" + + "1. 以《预算法》、《政府采购法》、《国有资产管理办法》及相关财政法规为依据\n" + + "2. 全面覆盖预算管理的所有环节和方面\n" + + "3. 检查预算收入编报的完整性和支出编报的真实性、准确性\n" + + "4. 关注项目预算申报的规范性和细化程度\n" + + "5. 检查预算调整审批手续的合规性\n" + + "6. 核实部门预算项目的相关性\n" + + "7. 检查预算下达与账务处理的一致性\n" + + "8. 审查国有资产管理的合规性和效益性\n" + + "9. 监督政府采购执行的规范性\n" + + "10. 评估项目实施和管理的有效性\n" + + "11. 分析债权债务的风险控制\n" + + "12. 核查工会饭堂财务的合规性"; + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + // 预算管理核心概念 + KEYWORD_WEIGHTS.put("部门预算", 10); + KEYWORD_WEIGHTS.put("预算编制", 10); + KEYWORD_WEIGHTS.put("预算调整", 9); + KEYWORD_WEIGHTS.put("预算执行", 9); + KEYWORD_WEIGHTS.put("预算决算", 9); + + // 预算科目和指标 + KEYWORD_WEIGHTS.put("预算科目", 8); + KEYWORD_WEIGHTS.put("指标来源", 8); + KEYWORD_WEIGHTS.put("指标运用", 8); + KEYWORD_WEIGHTS.put("指标结余", 7); + + // 资金类型和用途 + KEYWORD_WEIGHTS.put("财政拨款", 8); + KEYWORD_WEIGHTS.put("工资统发", 7); + KEYWORD_WEIGHTS.put("政府采购", 8); + KEYWORD_WEIGHTS.put("财政管理专户", 6); + KEYWORD_WEIGHTS.put("基本支出", 8); + KEYWORD_WEIGHTS.put("项目支出", 8); + + // 审计要点和方法 + KEYWORD_WEIGHTS.put("项目预算", 8); + KEYWORD_WEIGHTS.put("预算申报", 7); + KEYWORD_WEIGHTS.put("预算审批", 7); + KEYWORD_WEIGHTS.put("预算调剂", 7); + KEYWORD_WEIGHTS.put("国有资产", 8); + KEYWORD_WEIGHTS.put("固定资产", 7); + KEYWORD_WEIGHTS.put("政府采购", 7); + KEYWORD_WEIGHTS.put("债权债务", 6); + KEYWORD_WEIGHTS.put("工会饭堂", 5); + + // 文件类型 + KEYWORD_WEIGHTS.put("预算报表", 7); + KEYWORD_WEIGHTS.put("决算报表", 7); + KEYWORD_WEIGHTS.put("可行性研究", 6); + KEYWORD_WEIGHTS.put("评审报告", 6); + KEYWORD_WEIGHTS.put("会计账簿", 6); + KEYWORD_WEIGHTS.put("会计凭证", 6); + } + + // 审计要点提示 + public static final String AUDIT_KEY_POINTS = + "**审计要点(基于Excel完整内容):**\n" + + "1. 检查应纳入部门预算管理的各项收支是否全部纳入预算编报\n" + + "2. 核实项目预算是否细化到具体执行单位\n" + + "3. 审查预算调整审批手续是否完整合规\n" + + "4. 检查是否存在将经营收支编入部门预算的情况\n" + + "5. 核实预算拨款是否在不同科目间违规调剂使用\n" + + "6. 检查年初未安排预算、年中追加事项的合理性\n" + + "7. 核实部门预算项目是否全部为本单位业务\n" + + "8. 检查是否将设立企业的经营收支纳入部门预算\n" + + "9. 检查预算支出中是否存在以虚假经济业务套取财政资金\n" + + "10. 审查预算收支的效益性,项目效益、效果等预期目标的实现情况\n" + + "11. 检查国有资产配置是否按规定实行政府采购\n" + + "12. 检查国有资产对外投资和收益以及国有资产保值增值情况\n" + + "13. 审计政府采购执行情况,是否存在规避政府采购\n" + + "14. 关注项目实施及管理情况,项目资金安全、实施进度\n" + + "15. 检查债权债务的情况,债务可控程度,债权形成呆账的比例\n" + + "16. 审计工会、饭堂财务收支情况,有无将经营收入放进工会、饭堂核算"; + + // 数据格式要求 + public static final String OUTPUT_FORMAT = + "**输出格式(JSON数组):**\n" + + "[\n" + + " {\n" + + " \"index\": 序号,\n" + + " \"budgetSubject\": \"预算科目名称\",\n" + + " \"indicatorSource\": \"指标来源\",\n" + + " \"indicatorSourceTotal\": \"指标来源合计\",\n" + + " \"indicatorSourceLastYearBalance\": \"上年结余\",\n" + + " \"indicatorSourceInitialBudget\": \"年初部门预算\",\n" + + " \"indicatorSourceAdditionalBudget\": \"追加(减)预算\",\n" + + " \"indicatorUseTotal\": \"指标运用合计\",\n" + + " \"indicatorUseAppropriationSubtotal\": \"拨款小计\",\n" + + " \"indicatorUseAppropriation\": \"拨款\",\n" + + " \"indicatorUseSalaryPayment\": \"工资统发\",\n" + + " \"indicatorUseGovProcurement\": \"政府采购\",\n" + + " \"financeManagementAccount\": \"财政管理专户\",\n" + + " \"budgetUsedForOther\": \"部门预算用于其他\",\n" + + " \"indicatorBalance\": \"指标结余\",\n" + + " \"governmentProcurement\": \"政府采购\",\n" + + " \"payableToUnit\": \"应拨单位款\",\n" + + " \"other\": \"其他(可简要说明审计发现)\",\n" + +// " \"workPaperIndex\": [\"实际存在的完整FileId1\", \"实际存在的完整FileId2\", ...]\n" + + " \"workPaperIndex\": [\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\", ...]\n" + + " }\n" + + "]\n\n" + + "**重要说明:**\n" + + "1. 每个审计记录对应一项具体的预算科目或审计事项,尽可能全面识别所有预算科目和审计事项\n" + + "2. 预算科目包括但不限于:基本支出(人员经费、公用经费)、项目支出(专项业务费、设备购置费、维修维护费、会议费、培训费、差旅费、劳务费、咨询费、印刷费、邮电费、租赁费、物业管理费、专用材料费、委托业务费、其他商品和服务支出)等\n" + + "3. 审计事项应包括:预算管理、财务收支、国有资产管理、政府采购、项目实施、债权债务、工会饭堂财务等各方面\n" + + "4. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"有\"或\"无\"\n" + + "5. workPaperIndex必须填写实际查阅的文件名称,包括各种相关文件\n" + + "6. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"无\"\n" + + "7. 尽可能多地生成审计记录,覆盖所有可能的预算科目和审计事项,不限制数量\n" + + "8. 基于预算编制、调整、执行、决算的全流程进行审计分析\n" + + "9. 多多益善,全面反映审计情况"; + + // 审计证据要求 + public static final String AUDIT_EVIDENCE_REQUIREMENTS = + "**审计证据要求:**\n" + + "1. 预算管理相关文件:部门预算报表、决算报表、项目预算申报文本、可行性研究报告、评审报告\n" + + "2. 预算调整相关文件:预算调整审批文件、预算下达文件、追加预算审批单\n" + + "3. 财务核算相关文件:会计账簿、会计凭证、银行对账单、现金流水账、财务软件记录\n" + + "4. 预算执行相关文件:预算执行进度表、资金拨付记录、拨款凭证、工资发放记录\n" + + "5. 国有资产相关文件:固定资产登记台账、资产盘点表、采购合同、招投标文件、资产处置审批文件、资产评估报告、出租出借合同、投资担保合同\n" + + "6. 政府采购相关文件:政府采购合同、采购发票、采购目录、采购计划、招标文件、投标文件、中标通知书\n" + + "7. 项目实施相关文件:项目合同、项目进度报告、项目验收报告、项目结算资料\n" + + "8. 债权债务相关文件:应收应付款明细账、账龄分析表、坏账核销审批文件、债务偿还计划\n" + + "9. 工会饭堂相关文件:工会账簿、饭堂收支记录、工会经费使用审批单\n" + + "10. 现场核实资料:实地查看记录、座谈记录、业务资料核对记录"; + + // 字段映射(用于前端展示) + public static final Map FIELD_DISPLAY_NAMES = new HashMap<>(); + static { + FIELD_DISPLAY_NAMES.put("index", "序号"); + FIELD_DISPLAY_NAMES.put("budgetSubject", "预算科目名称"); + FIELD_DISPLAY_NAMES.put("indicatorSource", "指标来源"); + FIELD_DISPLAY_NAMES.put("indicatorSourceTotal", "指标来源合计"); + FIELD_DISPLAY_NAMES.put("indicatorSourceLastYearBalance", "上年结余"); + FIELD_DISPLAY_NAMES.put("indicatorSourceInitialBudget", "年初部门预算"); + FIELD_DISPLAY_NAMES.put("indicatorSourceAdditionalBudget", "追加(减)预算"); + FIELD_DISPLAY_NAMES.put("indicatorUseTotal", "指标运用合计"); + FIELD_DISPLAY_NAMES.put("indicatorUseAppropriationSubtotal", "拨款小计"); + FIELD_DISPLAY_NAMES.put("indicatorUseAppropriation", "拨款"); + FIELD_DISPLAY_NAMES.put("indicatorUseSalaryPayment", "工资统发"); + FIELD_DISPLAY_NAMES.put("indicatorUseGovProcurement", "政府采购"); + FIELD_DISPLAY_NAMES.put("financeManagementAccount", "财政管理专户"); + FIELD_DISPLAY_NAMES.put("budgetUsedForOther", "部门预算用于其他"); + FIELD_DISPLAY_NAMES.put("indicatorBalance", "指标结余"); + FIELD_DISPLAY_NAMES.put("governmentProcurement", "政府采购"); + FIELD_DISPLAY_NAMES.put("payableToUnit", "应拨单位款"); + FIELD_DISPLAY_NAMES.put("other", "其他(审计发现)"); + FIELD_DISPLAY_NAMES.put("workPaperIndex", "工作底稿索引"); + } + + // 获取分类的简要审计框架概述 + public static String getBriefAuditFrameworkForCategory(String category) { + if (CATEGORY_BUDGET_MANAGEMENT.equals(category)) { + return "全面审计预算管理的各个方面,包括预算编制、调整、执行、决算的全过程,以及相关的财务收支、国有资产管理、政府采购、项目实施、债权债务、工会饭堂财务等情况"; + } + return "全面审计预算管理情况"; + } + + // 预算科目类型(更全面的列表) + public static final List BUDGET_SUBJECT_TYPES = Arrays.asList( + "基本支出-人员经费-工资福利支出", + "基本支出-人员经费-社会保障缴费", + "基本支出-人员经费-住房公积金", + "基本支出-人员经费-其他人员支出", + "基本支出-公用经费-办公费", + "基本支出-公用经费-印刷费", + "基本支出-公用经费-咨询费", + "基本支出-公用经费-手续费", + "基本支出-公用经费-水费", + "基本支出-公用经费-电费", + "基本支出-公用经费-邮电费", + "基本支出-公用经费-取暖费", + "基本支出-公用经费-物业管理费", + "基本支出-公用经费-差旅费", + "基本支出-公用经费-因公出国(境)费用", + "基本支出-公用经费-维修(护)费", + "基本支出-公用经费-租赁费", + "基本支出-公用经费-会议费", + "基本支出-公用经费-培训费", + "基本支出-公用经费-公务接待费", + "基本支出-公用经费-专用材料费", + "基本支出-公用经费-被装购置费", + "基本支出-公用经费-专用燃料费", + "基本支出-公用经费-劳务费", + "基本支出-公用经费-委托业务费", + "基本支出-公用经费-工会经费", + "基本支出-公用经费-福利费", + "基本支出-公用经费-公务用车运行维护费", + "基本支出-公用经费-其他交通费用", + "基本支出-公用经费-税金及附加费用", + "基本支出-公用经费-其他商品和服务支出", + "项目支出-专项业务费", + "项目支出-设备购置费", + "项目支出-大型修缮费", + "项目支出-信息网络购建费", + "项目支出-基础设施建设费", + "项目支出-物资储备费", + "项目支出-会议费", + "项目支出-培训费", + "项目支出-差旅费", + "项目支出-劳务费", + "项目支出-咨询费", + "项目支出-印刷费", + "项目支出-邮电费", + "项目支出-租赁费", + "项目支出-物业管理费", + "项目支出-专用材料费", + "项目支出-委托业务费", + "项目支出-其他商品和服务支出", + "经营支出-经营成本", + "经营支出-经营费用", + "经营支出-经营税金", + "对个人和家庭的补助-离休费", + "对个人和家庭的补助-退休费", + "对个人和家庭的补助-退职(役)费", + "对个人和家庭的补助-抚恤金", + "对个人和家庭的补助-生活补助", + "对个人和家庭的补助-救济费", + "对个人和家庭的补助-医疗费补助", + "对个人和家庭的补助-助学金", + "对个人和家庭的补助-奖励金", + "对个人和家庭的补助-生产补贴", + "对个人和家庭的补助-住房公积金", + "对个人和家庭的补助-提租补贴", + "对个人和家庭的补助-购房补贴", + "对个人和家庭的补助-其他对个人和家庭的补助", + "债务还本支出-国内债务还本", + "债务还本支出-国外债务还本", + "债务利息支出-国内债务利息", + "债务利息支出-国外债务利息" + ); +} diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent6StateAssetsConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent6StateAssetsConstants.java new file mode 100644 index 0000000..6abe875 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent6StateAssetsConstants.java @@ -0,0 +1,155 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容6-国资管理情况常量类 + * 审计目标:检查国有资产管理情况和政府采购执行情况 + */ +public class AuditContent6StateAssetsConstants { + + // 分类定义 + public static final String CATEGORY_STATE_ASSETS_MANAGEMENT = "国资管理情况"; + + // 审计框架核心(基于Excel内容) + public static final String AUDIT_FRAMEWORK = + "审计核心:检查国有资产管理情况和政府采购执行情况\n\n" + + "重点关注:\n" + + "(1)资产配置是否按规定报上级主管部门采取政府采购方式。资产登记不及时、不完整,资产台账和会计明细账不符,应报废、核销的资产长期挂账。未经批准出租、出借资产,报废、核销资产未履行审批手续。资产处置收益未纳入\"收支两条线\"管理。被审计单位有房屋出租,制定资产出租必须进入交易平台的制度后,是否还有私自出租、不进入平台交易的现象。前期出租出去的房屋,未经过平台的,审计其租金水平是否合理。\n" + + "(2)国有资产对外投资和收益以及国有资产保值增值的情况。主要关注:是否违规利用国有资产对外投资;是否违规利用国有资产对外提供抵押、担保;国有资产投资收益持续亏损的原因;是否实现国有资产保值增值。\n\n" + + "审计方法及步骤:\n" + + "(1)审阅国有资产采购合同及招投标文件等资料,检查国有资产购置是否按规定实行政府采购。审阅固定资产登记台账与会计明细账,审查账账是否相符。根据固定资产类别或领用单位等进行抽查盘点,审查账实是否相符,已报废固定资产是否及时予以报废、核销。审阅国有资产出租、出借、报废核销的上级主管部门和财政部门的批准文件,检查手续是否完整。审阅国有资产处置收入的评估文件、上级主管部门和财政部门的核准文件,对比市场价格,检查处置收入是否合理,是否具备完整的处置手续。审阅国有资产处置收入的会计处理凭证及上缴财政国库的会计处理凭证,检查处置收益是否已纳入\"收支两条线\"管理。审计房屋出租是否按照规定上平台招租,可调查周边租金水平,来进行对比。\n" + + "(2)审阅国有资产对外投资,提供抵押、担保的上级主管部门和财政部门的审计文件,以及提供抵押、担保的合同,检查审批手续是否齐全,是否真实、合法;计算本单位国有资产保值增值率,检查本单位国有资产保值增值的情况;审计被审计单位划拨、借用给设立企业的资产来源是否清晰。\n\n" + + "政府采购执行情况\n\n" + + "重点关注:\n" + + "是否存在应当报政府采购计划的,未报政府采购计划的情况。包括拆解规避政府采购的情况;是否存在已报政府采购计划,但实际未实施政府采购,而是自行采购的情况,且采购的品类、型号是政府采购目录中具备的,达不到自行采购的条件;是否存在不按政府采购预算的品目,而通过政府采购实施其他品目采购的情况。\n\n" + + "审计方法及步骤:\n" + + "①审查财务支出凭据,和\"固定资产\"科目,检查是否全部固定资产的采购都是通过具备政府采购手续的。②对照政府采购文件和目录,查阅财务支出,审计是否存在应报政府采购而未报的情况,特别注意成批的易耗品、一般资产也要执行政府采购。审计时,必须将有可能是批量采购而拆解采购的线索联系起来,看采购的时间、品目,是否应当同时购置的,采取分散购置,规避政府采购,发现此类情况的,除追究违反政府采购法的责任外,还要追踪至货物、服务的提供方,检查是否存在关联交易,交易价格是否合理,是否存在利益输送。③将年度内所有已执行政府采购的名单列出,与政府采购预算报表核对,审计是否按照预算采购,有无乱采购、无预算采购。凡是发现无预算采购的,都要关注采购价格和关联方交易、利益输送。④对实施单一采购的,核对政府采购目录,检查是否属于单一采购来源的要求。如果不属于单一采购来源的,也要关注采购价格和关联方交易、利益输送。"; + + // 审计目标 + public static final String AUDIT_OBJECTIVE = + "检查国有资产管理情况和政府采购执行情况,确保国有资产安全完整、保值增值,规范政府采购行为。"; + + // 审计工作原则 + public static final String AUDIT_PRINCIPLES = + "审计工作原则:\n" + + "1. 以国有资产管理制度和政府采购法规为依据\n" + + "2. 重点检查资产配置、使用、处置全流程的合规性\n" + + "3. 关注资产出租、投资、担保等高风险环节\n" + + "4. 核实资产账实相符、账账相符情况\n" + + "5. 检查政府采购计划执行的真实性和合规性\n" + + "6. 注重发现规避监管、利益输送等违规行为"; + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + // 核心概念 + KEYWORD_WEIGHTS.put("国有资产", 10); + KEYWORD_WEIGHTS.put("政府采购", 10); + KEYWORD_WEIGHTS.put("资产配置", 9); + KEYWORD_WEIGHTS.put("资产出租", 9); + + // 管理流程 + KEYWORD_WEIGHTS.put("台账", 8); + KEYWORD_WEIGHTS.put("会计明细账", 8); + KEYWORD_WEIGHTS.put("报废核销", 8); + KEYWORD_WEIGHTS.put("处置收益", 7); + KEYWORD_WEIGHTS.put("收支两条线", 7); + + // 采购相关 + KEYWORD_WEIGHTS.put("政府采购计划", 9); + KEYWORD_WEIGHTS.put("政府采购目录", 8); + KEYWORD_WEIGHTS.put("单一采购", 7); + KEYWORD_WEIGHTS.put("规避采购", 8); + + // 投资担保 + KEYWORD_WEIGHTS.put("对外投资", 9); + KEYWORD_WEIGHTS.put("抵押担保", 8); + KEYWORD_WEIGHTS.put("保值增值", 8); + + // 合同相关 + KEYWORD_WEIGHTS.put("租赁合同", 7); + KEYWORD_WEIGHTS.put("采购合同", 7); + KEYWORD_WEIGHTS.put("招投标", 7); + } + + // 审计要点提示 + public static final String AUDIT_KEY_POINTS = + "审计要点:\n" + + "1. 检查资产配置是否按规定实施政府采购\n" + + "2. 核实资产登记是否及时完整,账实是否相符\n" + + "3. 审查资产出租是否进入交易平台\n" + + "4. 检查资产处置手续是否完备,收益是否规范管理\n" + + "5. 审计对外投资、抵押担保的审批合规性\n" + + "6. 检查政府采购计划执行情况,是否存在规避行为\n" + + "7. 关注关联交易和利益输送风险"; + + // 数据格式要求 + public static final String OUTPUT_FORMAT = + "输出格式(JSON数组):\n" + + "[\n" + + " {\n" + + " \"index\": 序号,\n" + + " \"assetName\": \"国有资产名称\",\n" + + " \"acquisitionMethod\": \"国有资产取得方式(购置/划拨/接受捐赠/自建等)\",\n" + + " \"assetValue\": \"国有资产价值(原值或评估值)\",\n" + + " \"assetAddress\": \"国有资产地址\",\n" + + " \"area\": \"面积(如适用)\",\n" + + " \"tenant\": \"承租方(如未出租填'未出租')\",\n" + + " \"contractAmount\": \"合同金额(如未出租填'未出租')\",\n" + + " \"leasePeriod\": \"租赁合同起止时间(如未出租填'未出租')\",\n" + + " \"rentPaymentTime\": \"国有资产租金缴纳时间(如未出租填'未出租')\",\n" + + " \"platformLease\": \"国有资产出租是否上平台(是/否/未出租)\",\n" + + " \"approvalDoc\": \"国有资产出租的审批文件(如未出租填'未出租')\",\n" + + " \"inBudget\": \"是否纳入预算(是/否)\",\n" + + " \"remark\": \"备注(包含资产状态、使用情况、审计发现等)\",\n" + +// " \"workPaperIndex\": [\"实际存在的完整FileId1\", \"实际存在的完整FileId2\", ...]\n" + + " \"workPaperIndex\": [\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\", ...]\n" + + " }\n" + + "]\n\n" + + "重要说明:\n" + + "1. 每个审计记录对应一项具体的国有资产,尽可能全面识别所有资产\n" + + "2. 资产类型包括:房屋建筑物、土地、车辆、机械设备、办公设备、电子设备等\n" + + "3. 对于没有出租的资产,相关出租字段填写\"未出租\"\n" + + "4. workPaperIndex必须填写实际查阅的文件名称\n" + + "5. 备注中应详细描述资产状况、使用情况、合规性评价\n" + + "6. 尽可能多地生成审计记录,覆盖所有可能的国有资产\n" + + "7. 即使信息不完整,也要基于现有信息生成记录"; + + // 审计证据要求 + public static final String AUDIT_EVIDENCE_REQUIREMENTS = + "审计证据要求:\n" + + "1. 查阅文件:国有资产登记台账、会计明细账、采购合同、招投标文件\n" + + "2. 查阅记录:租赁合同、审批文件、处置评估报告、政府采购计划\n" + + "3. 查阅数据:资产价值评估报告、租金缴纳凭证、政府采购目录\n" + + "4. 查阅凭证:会计凭证、银行转账记录、审批手续文件\n" + + "5. 现场核实:资产实地盘点、租赁现场查看、供应商访谈"; + + // 字段映射(用于前端展示) + public static final Map FIELD_DISPLAY_NAMES = new HashMap<>(); + static { + FIELD_DISPLAY_NAMES.put("index", "序号"); + FIELD_DISPLAY_NAMES.put("assetName", "国有资产名称"); + FIELD_DISPLAY_NAMES.put("acquisitionMethod", "国有资产取得方式"); + FIELD_DISPLAY_NAMES.put("assetValue", "国有资产价值"); + FIELD_DISPLAY_NAMES.put("assetAddress", "国有资产地址"); + FIELD_DISPLAY_NAMES.put("area", "面积"); + FIELD_DISPLAY_NAMES.put("tenant", "承租方"); + FIELD_DISPLAY_NAMES.put("contractAmount", "合同金额"); + FIELD_DISPLAY_NAMES.put("leasePeriod", "租赁合同起止时间"); + FIELD_DISPLAY_NAMES.put("rentPaymentTime", "国有资产租金缴纳时间"); + FIELD_DISPLAY_NAMES.put("platformLease", "国有资产出租是否上平台"); + FIELD_DISPLAY_NAMES.put("approvalDoc", "国有资产出租的审批文件"); + FIELD_DISPLAY_NAMES.put("inBudget", "是否纳入预算"); + FIELD_DISPLAY_NAMES.put("remark", "备注"); + FIELD_DISPLAY_NAMES.put("workPaperIndex", "工作底稿索引"); + } + + // 获取分类的简要审计框架概述 + public static String getBriefAuditFrameworkForCategory(String category) { + if (CATEGORY_STATE_ASSETS_MANAGEMENT.equals(category)) { + return "审计国有资产管理和政府采购执行情况,确保资产安全完整和采购规范"; + } + return "审计国资管理情况"; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent7Constants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent7Constants.java new file mode 100644 index 0000000..4de0b84 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent7Constants.java @@ -0,0 +1,147 @@ +package com.gxwebsoft.ai.constants; + +import java.util.HashMap; +import java.util.Map; +import java.util.Arrays; +import java.util.List; + +/** + * 重大投资情况审计常量类 + */ +public class AuditContent7Constants { + + // 审计类别定义(按审计内容7.txt要求) + public static final String CATEGORY_MAJOR_INVESTMENT = "重大对外投资审计"; + public static final String CATEGORY_MAJOR_PROJECT = "重大工程建设审计"; + public static final String CATEGORY_MAJOR_CAPITAL = "重大资本运作审计"; + public static final String CATEGORY_MAJOR_ASSET_DISPOSAL = "重大资产处置审计"; + public static final String CATEGORY_MAJOR_PROCUREMENT = "重大物资(服务)采购审计"; + public static final String CATEGORY_MAJOR_GUARANTEE = "重大担保借款审计"; + + // 审计类别描述 + public static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); + static { + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_INVESTMENT, + "核查重大对外投资决策的合规性和民主性、审批手续、可行性研究、效益情况、会计处理、操作过程等"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_PROJECT, + "工程建设项目决策程序、审批手续、安全质量、环保要求、效益情况等"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_CAPITAL, + "重大资本运作事项决策权限、审批程序、交易对价、资金来源、效益效果等"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_ASSET_DISPOSAL, + "重大资产处置决策程序、审批手续、交易价格、操作过程等"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_PROCUREMENT, + "重大采购项目可行性、决策程序、采购过程、价格公允性、效益效果等"); + CATEGORY_DESCRIPTIONS.put(CATEGORY_MAJOR_GUARANTEE, + "重大担保借款决策程序、尽职调查、风险控制、后续跟踪等"); + } + + // 审计方法指令集(从审计内容7.txt提取) + public static final Map AUDIT_INSTRUCTIONS = new HashMap<>(); + static { + AUDIT_INSTRUCTIONS.put("GENERAL_RULE", + "审计方法及步骤:\n" + + "(1)收集全部企业的资产负债表和往来明细表,审计企业\"未分配利润\"的情况,对应查看其往来明细表,分析审计企业债务的偿还能力和债券的回收机率。\n" + + "(2)收集对其他单位进行投资的项目的收益报表及往来情况,审计投资的效益以及往来款项的回收机率。\n" + + "(3)对还在继续运营的特殊情况,审计企业的收益能力,分析继续运行的必要性、效益性。"); + + AUDIT_INSTRUCTIONS.put("DECISION_SYSTEM_CHECK", + "1、\"决策制度健全性验证\"指令:\n" + + "①制度存在性审查:根据关键字,检索被审计单位所有制度,是否有重大对外投资决策的制度规定\n" + + "②纵向合规性比对:将被审计单位的决策制度与国家、自治区、上级单位比较\n" + + "③横向一致性检查:与本单位其他制度比较,检查决策额度、表决比例、否决机制等多个维度的协同性\n"); + + AUDIT_INSTRUCTIONS.put("DECISION_PROCEDURE_CHECK", + "2、\"是否履行相应的决策程序\"指令:\n" + + "①决策程序完备性审查:根据被审计单位制度确定该项目是否属于重大投资\n" + + "②议事程序符合性审查:权限降级决策、参会人员范围、表决比例、材料替代违规、特别事项处理\n" + + "③民主决策真实性分析:发言均衡性检查、反对意见留存、表态顺序异常、决策逆转追踪\n" + + "④时序异常检查:立项时间、首次决策会日期、正式批准日、资金支付时间\n"); + } + + // 审计内容模板(从审计内容7.txt提取的具体审计点) + public static final Map> AUDIT_CONTENT_TEMPLATES = new HashMap<>(); + static { + // 重大对外投资审计内容 + AUDIT_CONTENT_TEMPLATES.put(CATEGORY_MAJOR_INVESTMENT, Arrays.asList( + "核查重大对外投资决策的合规性和民主性。企业是否履行相应的决策程序或相关决策制度是否建立健全,是否经过相应级别的领导班子集体讨论决策,有无简化决策程序或在多数人表示反对意见下仍强行通过决策,即领导个人说了算的情况。", + "核查重大对外投资审批手续。核查被审计企业与履行出资人职责的机构是否有明确的授权范围,对照国资委的授权清单,查看对外投资事项是否属于国资监管部门核准或备案事项,对外投资事项是否属于国家禁止或控制的投资范围。", + "核查重大对外投资可行性研究和尽职调查情况。是否经过可行性研究,相关论证是否充分,是否经过多方案的比较优选和科学论证,是否按规定开展尽职调查。", + "核査对外投资项目的效益情况。梳理企业被审计年度对外投资项目的效益情况,与可行性研究的预期效益相比较,是否存在明显效益低下、严重亏损、计提大额减值等情况。", + "查看长期投资、短期投资、银行存款等会计科目的明细账,确定重大对外投资项目的入账价值,审阅账面价值是否符合投资合同和决策所确定的投资额,重大差异是否有合理原因。审阅资金支付是否经过投资决策、合规审核后支付,是否存在超前支付等情况。", + "查看相关费用类科目或长期投资、短期投资和银行存款等会计科目的明细账,分析有关中介费等费用支岀情况,核实是否存在大额的、可疑的、超合同支付的中间费用;关注溢价收购的交易价格是否合理,核查溢价收购产生的大额商誉,是否根据被收购企业未来业绩实际情况,制定相应的或有对价条款,从而调整交易对价、降低收购不确定性。", + "审核\"长期投资\"等会计科目,分析投资事项的运营效益,是否存在重大亏损;调取被投资项目(或企业)的运营情况、会计报表等,分析企业账面是否如实反映项目盈亏。", + "重大对外投资到期收回、出售或转让的,核实取得的对价是否及时入账;调取被投资企业投资分红账务资料,核实分红派息资金是否按时收回并入账。", + "审核决策程序。详细查阅党委(组)会、股东会、董事会、监事会、总经理办公会和专题小组会等会议记录和会议纪要。查看相关决策程序是否合规,有无严格落实重大事项决策经过党组织前置研究的规定,是否经相应领导级别会议讨论研究;查看决策内容是否民主科学,仔细查阅参会人员的发言,有无存在主持会议的领导人员在多数人持不同意见甚至反对意见的情况下仍强行通过决议的情况,即领导人员\"说了算\"的情况。", + "审核审批过程。按照深化国有企业改革精神和改革国有资本授权经营体制的要求,梳理履行出资人职责的机构对被审计企业的授权范围,核查关注的重大对外投资事项是否需要国资监管部门核准或备案,有无领导人员擅自决策、超权限决策的情况;对照企业投资负面清单,审核对外投资事项是否属于国家禁止或控制的投资范围,核查是否取得行业主管部门审批,相关审批手续是否完备。", + "审核调查论证。查阅对外投资事项从立项到决策实施整个过程的论证材料,如可行性研究报告、财务咨询报告、法律意见书等。应关注以下三个方面:一是立项背景,重大对外投资项目是否符合国家经济结构调整方向,如是否符合供给侧结构性改革和产业发展方向;二是企业发展战略,重大对外投资事项是否符合企业主业经营范围,是否符合企业中长期发展规划;三是预期效益,对比可行性研究报告、财务咨询报告中关于项目预期的经营效益、投资规模、筹资与还款计划、预计资金回收期等内容,判断有无盲目投资、超偿还能力筹措资金等情况。", + "审核操作过程。审查重大对外投资合同内容是否与企业集体决策内容相符,重大对外投资事项是否属于未公开的关联交易事项,相关交易价格是否经过专业机构评估,交易价格是否客观公允,会计师事务所、资产评估机构是否违反有关规定出具虚假报告,或领导人员违规指定交易方,先确定交易价格再进行资产评估,或领导人员擅自更改交易价格等。", + "审核效益效果。核査重大对外投资企业是否正常经营,有无存在被投资企业效益持续低下,严重亏损甚至已经资不抵债,效益远低于预期;投资参股后是否积极行使股东权利,履行股东义务,有无因擅自扩大投资规模导致严重亏损,人为提高交易价格向特定关系人输送利益等违规问题。" + )); + + // 重大工程建设审计内容(类似方式添加其他类别) + AUDIT_CONTENT_TEMPLATES.put(CATEGORY_MAJOR_PROJECT, Arrays.asList( + "工程建设项目是否经企业相应领导层级集体讨论研究,有无领导人员擅自决策,特别是领导班子多数人持不同意见的情况下仍强行通过决策的情况。", + "相关立项审批手续是否完善,根据国资国企授权经营体制改革的有关要求,重大工程建设项目是否符合企业投资项目负面清单范围,有无应报未报、未批先建、违规建设楼堂馆所等。", + "工程建设项目有无出现安全和质量事故,工程设备是否符合污染防治有关要求,相关工程废物、废渣、废水等是否得到有效收集和处理,工程建设是否造成环境污染,环保设施是否有效运营。", + "工程建设项目竣工投产后的效益情况,是否存在竣工投入使用后长期闲置、计提大额减值、效益远低于预期产生巨额亏损等。", + "审核决策程序。对选定的重大工程建设项目,应详细查看其会议纪要和会议记录,审阅管理制度、签报、审批文件等资料。查看相关决策事项是否履行相应决策程序,如是否经党组织前置研究和相应层级领导班子集体讨论研究,决策内容是否符合民主要求,有无被审计领导人员在多数人反对的情况下强行通过决策,即领导人员\"说了算\"的情况。", + "审核审批过程。根据深化国有企业改革精神和改革国有资本授权经营体制的要求,核查重大工程建设项目是否属于企业自主决策事项,是否需要国资监管部门核准或者备案,若属于其他行业主管部门审批事项,企业还应获得如发展改革委、住建部门、环保部门等主管部门的相关审批手续,有无未批先建,化整为零规避审批。", + "审核调查论证。查阅项目从立项到决策实施整个过程的论证材料,如可行性研究报告、财务咨询报告、法律意见书等。可行性研究报告的内容是否完整、真实;建设目标、建设规模是否符合企业发展需要,规划是否符合行业发展前景和产业政策。", + "审核操作过程。重大工程建设项目是否严格按照集体决策的方案开展,工程的建设质量、环保标准是否符合要求,是否有定期的质量检查,有无发生重大安全质量事故、发生重大环保污染问题被有关部门处罚,或者工程建设进度严重滞后、无法按期竣工等问题。", + "审核效益效果。重大工程建设项目的效益,主要是两个方面:一是效益是否达到预期。与可行性研究报告、财务咨询报告等比较,对比项目投产后是否达到预期效益;与市场上同类型项目效益对比,有无存在效益明显低于同类型项目的情况。二是工程建设项目的运营情况,是否存在项目竣工投产后运行未达预期、长期闲置,或因工程建设项目与市场需求严豆偏离计提重大减值,导致大额亏损等问题。" + )); + + // 其他类别类似添加... + } + + // 关键词权重(用于知识检索排序) + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("重大投资", 10); + KEYWORD_WEIGHTS.put("对外投资", 9); + KEYWORD_WEIGHTS.put("工程建设", 9); + KEYWORD_WEIGHTS.put("资本运作", 9); + KEYWORD_WEIGHTS.put("资产处置", 9); + KEYWORD_WEIGHTS.put("物资采购", 9); + KEYWORD_WEIGHTS.put("担保借款", 9); + KEYWORD_WEIGHTS.put("决策程序", 8); + KEYWORD_WEIGHTS.put("审批手续", 8); + KEYWORD_WEIGHTS.put("可行性研究", 8); + KEYWORD_WEIGHTS.put("效益情况", 8); + KEYWORD_WEIGHTS.put("会议纪要", 7); + KEYWORD_WEIGHTS.put("会议记录", 7); + KEYWORD_WEIGHTS.put("可行性报告", 7); + KEYWORD_WEIGHTS.put("尽职调查", 7); + KEYWORD_WEIGHTS.put("关联交易", 7); + KEYWORD_WEIGHTS.put("价格公允", 7); + } + + // 文件类型关键词(用于工作底稿索引) + public static final Map> FILE_TYPE_KEYWORDS = new HashMap<>(); + static { + FILE_TYPE_KEYWORDS.put("会议材料", Arrays.asList("会议通知", "签到表", "会议记录", "会议纪要", "会议决议", "会议录像")); + FILE_TYPE_KEYWORDS.put("审批文件", Arrays.asList("立项批复", "审批文件", "核准文件", "备案文件", "红头文件")); + FILE_TYPE_KEYWORDS.put("论证材料", Arrays.asList("可行性研究报告", "财务咨询报告", "法律意见书", "尽职调查报告", "专家论证意见")); + FILE_TYPE_KEYWORDS.put("合同协议", Arrays.asList("投资合同", "合作协议", "担保合同", "借款合同", "采购合同", "转让协议")); + FILE_TYPE_KEYWORDS.put("财务资料", Arrays.asList("资产负债表", "利润表", "现金流量表", "往来明细表", "会计凭证", "银行流水")); + FILE_TYPE_KEYWORDS.put("评估报告", Arrays.asList("资产评估报告", "审计报告", "验资报告", "价值评估报告")); + } + + private AuditContent7Constants() { + // 防止实例化 + } + + /** + * 获取所有审计类别(按顺序) + */ + public static List getAllCategories() { + return Arrays.asList( + CATEGORY_MAJOR_INVESTMENT, + CATEGORY_MAJOR_PROJECT, + CATEGORY_MAJOR_CAPITAL, + CATEGORY_MAJOR_ASSET_DISPOSAL, + CATEGORY_MAJOR_PROCUREMENT, + CATEGORY_MAJOR_GUARANTEE + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java new file mode 100644 index 0000000..d3c71a5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java @@ -0,0 +1,195 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容8 - 单位层面财务管理内部控制测试常量类 + */ +public class AuditContent8InternalControlConstants { + + // 16个控制环节 + public static final String[] CONTROL_LINKS = { + "有关部门或岗位财务管理职能划分", + "财务管理岗位设置及职责划分", + "财务管理岗位人员配备", + "财务管理关键岗位人员轮岗", + "财务管理事项授权审批(核)", + "财务管理重大事项决定", + "会计核算与文档管理", + "财务电子信息管理", + "重要财务管理事项的信息沟通", + "财务管理风险评估", + "财务管理风险应对", + "内部审计与监督检查", + "反舞弊", + "财务管理内部控制评价", + "财务管理内部控制缺陷跟踪与改进", + "财务管理内部控制实施情况纳入考核体系" + }; + + // 控制要求 + public static final Map CONTROL_REQUIREMENTS = new HashMap<>(); + static { + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[0], "财务管理决策、执行与监督职能相互分离"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[1], "财务管理不相容职务相互分离并相互制约"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[2], "财务管理岗位人员配备符合规定与岗位要求"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[3], "关键岗位实行定期轮岗,不具备轮岗条件的实施其他替代措施"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[4], "全部财务管理业务实施授权审批(核)"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[5], "财务管理重大事项均由单位领导层集体研究决定"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[6], "会计核算及其档案管理规范,会计信息准确"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[7], "信息系统开发、运行和维护以及用户管理与数据备份规范,信息安全"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[8], "对于重要财务管理事项均进行信息沟通"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[9], "对于财务管理风险能够进行识别、评估与预防"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[10], "对于出现的财务管理风险能够应对与控制"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[11], "对于发生的财务管理业务进行定期与不定期的审计与监督检查"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[12], "及时察觉与处置财务舞弊事件"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[13], "对于财务管理内部控制实施评价,并取得成效"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[14], "跟踪发现的财务管理内部控制缺陷,及时进行改进"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[15], "在单位考核体系中包含财务管理内部控制实施情况内容"); + } + + // 控制活动描述 + public static final Map CONTROL_ACTIVITIES = new HashMap<>(); + static { + CONTROL_ACTIVITIES.put(CONTROL_LINKS[0], "按照决策、执行与监督职能相互分离的要求确定有关部门或岗位的财务管理职能,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[1], "按照不相容职务相互分离的要求设置财务管理岗位,所有岗位具有明确的职责,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[2], "按照有关规定与岗位要求配备财务管理人员,各岗位均有明确的任职要求,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[3], "对于财务管理部门负责人与其他关键岗位人员按照规定进行轮岗、相关人员实行回避,不具备轮岗条件的实施其他替代措施,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[4], "对于财务管理业务授权审批的范围、权限、程序与责任具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[5], "对于单位部门预算、大额资金使用、大宗设备采购或重大服务购买、基本建设等重大财务事项均有明确的决定或会签制度规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[6], "对于会计凭证、会计账簿和会计报告处理程序以及会计档案管理具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[7], "对于财务管理信息系统的开发、运行和维护以及用户管理与系统数据备份,信息系统安全保密和泄密责任追究具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[8], "对于财务管理重要事项建立信息沟通制度,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[9], "对于财务管理风险具有明确工作内容与要求的评估预防体系与工作机制,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[10], "对于出现的财务管理风险具有应对机制与控制措施,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[11], "设有独立的内审部门或审计岗位并有明确的职责要求,对内部审计与财务管理监督检查具有明确的规定,由审计管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[12], "对于财务管理舞弊具有举报投诉制度和举报人保护制度以及全体员工被告知制度,对于举报投诉的财务管理舞弊事件与问题的处置具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[13], "设立或指定财务管理内部控制评价的职能部门与岗位,对于财务管理内部控制评价工作的计划、要求具有明确的规定,由财务管理内部控制评价部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[14], "对于财务管理内部控制缺陷具有跟踪与改进制度,对于整改的实施与后续的检查具有明确的规定,由财务管理内部控制评价部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[15], "对于财务管理内部控体系建设与执行在单位的考核制度中具有明确的考核规定,由财务管理内部控制评价部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + } + + // 控制职责岗位 + public static final Map CONTROL_POSITIONS = new HashMap<>(); + static { + CONTROL_POSITIONS.put(CONTROL_LINKS[0], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[1], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[2], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[3], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[4], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[5], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[6], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[7], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[8], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[9], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[10], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[11], "审计管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[12], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[13], "财务管理内部控制评价部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[14], "财务管理内部控制评价部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[15], "财务管理内部控制评价部门负责人、分管领导"); + } + + // 测试步骤 + public static final Map> TEST_STEPS = new HashMap<>(); + static { + TEST_STEPS.put(CONTROL_LINKS[0], Arrays.asList( + "(1)是否有制度规定", + "(2)单位有关部门或岗位财务管理职能设置是否符合相互分离规定", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理组织体系中的决策、执行与监督职能是否实现了分离" + )); + TEST_STEPS.put(CONTROL_LINKS[1], Arrays.asList( + "(1)是否有制度规定", + "(2)单位财务管理岗位设置与职责划分是否符合不相容岗位相互分离规定", + "(3)通过观察、询问以及查阅有关工作文档等方式检查财务管理岗位不相容职务是否实现了分离" + )); + TEST_STEPS.put(CONTROL_LINKS[2], Arrays.asList( + "(1)是否有制度规定", + "(2)财务管理岗位配备的人员是否符合任职资质要求", + "(3)重要财务管理岗位人员的专业背景、工作经验及有关执业资质是否能够胜任岗位工作需要" + )); + TEST_STEPS.put(CONTROL_LINKS[3], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查财务管理关键岗位是否进行了轮岗或采取了其他替代措施" + )); + TEST_STEPS.put(CONTROL_LINKS[4], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理业务流程中的各个审批(核)环节的操作是否符合授权审批(核)规定" + )); + TEST_STEPS.put(CONTROL_LINKS[5], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理重大事项是否由领导层集体研究决定或会签,且研究决定或会签记录内容完整" + )); + TEST_STEPS.put(CONTROL_LINKS[6], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位会计核算流程是否符合制度规定", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位是否有专职岗位负责会计档案保管,且档案文件保管情况良好" + )); + TEST_STEPS.put(CONTROL_LINKS[7], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位是否按规定进行了信息系统的开发、运行和维护以及用户管理与数据备份" + )); + TEST_STEPS.put(CONTROL_LINKS[8], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理有关重要事项是否通过会议、内部报告或会签等方式进行信息沟通" + )); + TEST_STEPS.put(CONTROL_LINKS[9], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位是否进行了财务管理风险的识别、评估与预防工作" + )); + TEST_STEPS.put(CONTROL_LINKS[10], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位是否对出现的财务风险进行了应对,并采取了控制措施" + )); + TEST_STEPS.put(CONTROL_LINKS[11], Arrays.asList( + "(1)是否有制度规定", + "(2)是否设置了独立的审计部门或审计岗位", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位内审部门是否定期或不定期对财务管理业务进行审计与监督检查", + "(4)通过观察、询问以及查阅有关工作文档等方式检查单位内审部门能否及时发现财务管理中的问题,督促整改" + )); + TEST_STEPS.put(CONTROL_LINKS[12], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位员工是否知晓财务管理舞弊的举报投诉制度和举报人保护制度", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位对于举报投诉的财务管理舞弊事件与问题是否及时进行了处置" + )); + TEST_STEPS.put(CONTROL_LINKS[13], Arrays.asList( + "(1)是否有制度规定", + "(2)是否设立或指定有财务管理内部控制评价的职能部门与岗位", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位是否有财务管理内部控制评价工作计划与工作要求", + "(4)通过观察、询问以及查阅有关工作文档等方式检查单位是否按计划开展了财务管理内部控制评价工作,促进了单位财务管理内部控制体系的建立健全" + )); + TEST_STEPS.put(CONTROL_LINKS[14], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位对于发现的内部控制缺陷是否及时通报与跟踪", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位对于发现的缺陷是否及时进行了整改" + )); + TEST_STEPS.put(CONTROL_LINKS[15], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位的考核制度中是否将财务管理内部控制实施情况列入了考核内容中", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位在实际进行考核中对财务管理内部控制实施情况的考核内容" + )); + } + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("内部控制", 10); + KEYWORD_WEIGHTS.put("财务管理", 9); + KEYWORD_WEIGHTS.put("不相容职务分离", 9); + KEYWORD_WEIGHTS.put("授权审批", 8); + KEYWORD_WEIGHTS.put("集体决策", 8); + KEYWORD_WEIGHTS.put("会计档案", 7); + KEYWORD_WEIGHTS.put("信息系统", 7); + KEYWORD_WEIGHTS.put("风险识别", 8); + KEYWORD_WEIGHTS.put("风险评估", 8); + KEYWORD_WEIGHTS.put("内部审计", 9); + KEYWORD_WEIGHTS.put("反舞弊", 9); + KEYWORD_WEIGHTS.put("内部控制评价", 8); + KEYWORD_WEIGHTS.put("岗位轮岗", 7); + KEYWORD_WEIGHTS.put("考核体系", 7); + } + + private AuditContent8InternalControlConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent9PersonnelConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent9PersonnelConstants.java new file mode 100644 index 0000000..e3a66c2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent9PersonnelConstants.java @@ -0,0 +1,273 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容9 - 人员编制管理审计常量类 + */ +public class AuditContent9PersonnelConstants { + + // 7个审计内容 + public static final String[] AUDIT_CONTENTS = { + "检查劳务派遣用工的合规性", + "核查被审计单位是否通过'假外包,真派遣'形式规避用工责任及成本", + "检查人员经费的使用是否规范", + "检查借用人员管理规范性", + "绩效管理体系健全性、合规性", + "检查机构编制合规性", + "预算编制与实际执行差异" + }; + + // 审计子内容映射 + public static final Map> AUDIT_SUB_CONTENTS = new LinkedHashMap<>(); + static { + // 1. 检查劳务派遣用工的合规性 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[0], Arrays.asList( + new AuditSubContent( + "1.检查劳务派遣人数占企业用工总量比例是否≤10%", + "年度、半年度或月度员工花名册(区分正式工与派遣工,标注入职时间)", + "核查被审计单位审计期间年度(或半年度或月度)用工情况,计算派遣工占比,\n公式如下:派遣工比例=派遣工人数/(正式工人数+派遣工人数)\n合规标准:该比例不得超过10%。若计算结果>10%,则判定为用工比例不合规。", + "合并生成一个审计取证单", + "①劳务派遣暂行规定_人力资源和社会保障部_中国政府网——第四条;\n②《中华人民共和国劳动合同法》第六十六条" + ), + new AuditSubContent( + "2.检查派遣岗位是否均符合'三性'标准(临时性≤6个月,辅助性,替代性)", + "①劳务派遣合同及岗位说明书;涉及转正的,需补充提供劳动合同;\n②职代会决议文件,并附公示记录照片;\n③核心岗位清单;\n④如涉及替代性岗位,需提供原岗位员工的脱产、休假证明等。", + "岗位标准:派遣岗位仅限于临时性(不超过6个月)、辅助性(需职代会通过并公示)、替代性(如产假顶岗)岗位。核心岗位(如技术、管理等)必须转为正式工;用工单位决定使用被派遣劳动者的辅助性岗位,应当经职工代表大会或者全体职工讨论,提出方案和意见,与工会或者职工代表平等协商确定,并在用工单位内公示。\n①核查临时性工作岗位存续时间,若超过6个月,则判定该岗位不符合劳务派遣的临时性规定;\n②核查派遣岗位名称是否与公示辅助性岗位清单一致,不一致则违反劳务派遣辅助性规定;\n③检查替代性岗位是否提供休假证明等材料,若材料无法证实其符合设立条件,则派遣岗位设立存在瑕疵,违反劳务派遣的替代性规定。", + "", + "①劳务派遣暂行规定_人力资源和社会保障部_中国政府网——第三条;\n②《中华人民共和国劳动合同法》第六十六条" + ), + new AuditSubContent( + "3.检查派遣工与同类岗位的正式工是否同工同酬", + "正式工与派遣工薪酬对比表(或工资表)、社保缴纳凭证、福利发放记录", + "对同一(类)岗位的劳务派遣和正式工进行薪酬差额分析,并形成派遣工权益保障差异分析记录:\n①核查社保是否按用工单位所在地标准缴纳;\n②抽检加班费、绩效奖金及福利待遇等项目的核算差异。", + "", + "①劳务派遣暂行规定_人力资源和社会保障部_中国政府网——第七、第九条;\n②《中华人民共和国劳动合同法》第六十三条" + ), + new AuditSubContent( + "4.检查工伤处理程序中用工单位的配合义务履行情况", + "①工伤事故通报派遣单位的书面记录;\n②用工单位参与工伤调查的会议纪要/现场核查记录;\n③职业病诊断过程记录(委托书、交接单等);\n④提供的职业史证明、危害因素检测报告;\n⑤《劳务派遣协议》关于工伤保险责任条款及补偿办法;\n⑥用工单位支付协议约定补偿的财务凭证", + "①工伤事故:检查事故发生后及时向派遣单位通报的证据;核实派员参与调查、提供现场材料的记录;\n②职业病诊断:确认用工单位是否主动处理诊断事宜(如对接机构);验证所提供职业史、检测报告的真实性(比对考勤、检测备案);\n③责任约定:审查协议是否包含'用工单位不承担工伤保险责任'等违法条款;核查补偿办法中用工单位义务的财务履行凭证;\n④询问:访谈近审计期间工伤派遣工,核实用工单位配合度。", + "", + "①劳务派遣暂行规定_人力资源和社会保障部_中国政府网——第十条;\n②《工伤保险条例》第十七条、第十九条;\n③《中华人民共和国职业病防治法》第四十七条;\n④企业内控制度" + ) + )); + + // 2. 核查被审计单位是否通过"假外包,真派遣"形式规避用工责任及成本 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[1], Arrays.asList( + new AuditSubContent( + "1.确认'外包'合同约定的是'劳务派遣'还是'劳务外包'的法律关系", + "外包合同及其补充协议", + "检查合同条款性质认定的关键要点:\n①若合同约定发包单位享有人员'用工管理权',包括直接调配权及要求员工遵守发包方《员工手册》等管理规定,则构成'劳务派遣'法律关系;若合同主要约定'业务成果验收标准',且外包公司自主安排生产服务、负责人员考勤管理,则构成'劳务外包'法律关系。\n②费用结算条款约定'按项目工作量'还是'验收成果计费':如存在以'人员数量、岗位配置、服务时长或出勤天数(隐含'人头费'逻辑)'等指标结算费用的情形,则构成'劳务派遣'法律关系;如基于'基于项目成果结算',如验收合格报告数量、项目阶段交付清单等,则构成'劳务外包'法律关系。", + "合并生成一个审计取证单。\n取证单主要内容包括:审计(调查)事项摘要、处理建议、附件(与问题相匹配的支撑资料,即工作底稿索引)等。\n其中'审计(调查)事项摘要'包含:标题、审计记录、审计发现、定性依据。\n1.标题:突出核心问题,采用观点性语句。一般为审计内容、审计目标的结论性描述。例如:\n在审计期间,XX单位存在'假外包,真派遣'行为。\n2.审计记录:仅客观记录审计核查的具体事实(时间、地点、主体、行为、数据等);不使用主观评价性语言(如'违规''不合理')或问题判断性表述;确保事实可交叉验证(引证合同条款、凭证编号等原始文件)。一般采用白描句式:'经核查[具体资料/凭证],……[事实描述]……'例如:\n① 核查2019年1月1日签订的《XX服务合同》(编号:XYZ-2019-001)第3条约定:'乙方(服务商)员工需遵守甲方考勤制度,甲方有权对乙方人员工作质量进行考核并决定留用'。\n② 调取2019年6月外包人员考勤表显示:实际出勤记录由甲方部门主管李某签字确认,绩效评分由甲方人力资源部核定。\n③ 查验2020年3月服务费结算凭证(凭证号:FV20200315):所附明细清单按外包人员当月计薪天数×日工资标准核算费用。\n3.审计发现:基于审计记录的事实,提炼违反规定的具体问题性质。需明确界定违规行为的实质特征。同一性质问题应合并表述,避免分段罗列。一般采用定性句式:'上述行为实质构成……[问题性质],/ 形成……[违规形态]'。例如:\n审计发现:该单位通过签订名义上的《劳务外包合同》,实际由发包单位直接管理外包人员工作过程并控制费用结算标准,形成事实上的劳务派遣用工关系,构成'假外包、真派遣'行为。\n4.定性依据:违反的法律法规、政策规定及被审计单位内部管理制度等。\n选择优先级:法律法规>部门规章>地方政府规范性文件>被审计单位内部制度。\n引用规范:标明文件全称、文号及具体条款;引述条款内容须与问题直接对应;同时违反多层级规定的,按效力层级降序排列。例如:\n定性依据:\n①《劳务派遣暂行规定》(人社部令第22号)第三条:'用工单位只能在临时性、辅助性或者替代性的工作岗位上使用被派遣劳动者'。\n②被审计单位《XX管理办法》(XX集团〔2018〕35号)第X条:'……'。", + "①《中华人民共和国劳动合同法》及其实施条例;\n②《中华人民共和国民法典》" + ), + new AuditSubContent( + "2.检查发包单位是否实质上直接管理外包人员及其工作过程", + "①考勤记录(打卡截图、门禁刷卡数据)、OA系统工作安排(如能获取)、绩效目标确认文件、扣罚工资记录;\n②发包单位《员工手册》、奖惩记录;\n③工位证明、工卡(门禁卡或身份标识卡)发放或佩戴要求、设备领用记录。", + "①核实发包单位是否直接安排、记录和处罚外包人员考勤:查看考勤记录(如钉钉打卡截图、门禁刷卡数据)、绩效目标确认文件、OA系统工作安排记录、工资扣罚凭证(如'早退扣款'记录),若上述安排、记录和处罚由发包单位直接进行(而非外包公司执行),则表明发包单位存在实际考勤管理权,构成'实质派遣'。\n②核实发包单位的《员工手册》等规章制度是否明确适用于外包人员并被执行:审阅发包单位《员工手册》、奖惩通知(如警告、调岗通知)、工资条或相关记录,若实际操作中发包单位可直接对外包人员进行警告、调岗、扣款或奖励等管理动作,强有力地证明发包单位的实质性管理权限已延伸至外包人员,构成'实质派遣';\n③核实外包人员的工作环境和工作工具由谁提供和控制:查看外包人员在发包单位的工位安排记录、工卡发放记录、工作设备领用登记。若外包人员日常在发包单位固定场所工作,使用发包单位提供的专属工位、标识(如工卡)、电脑设备等,表明其工作环境高度受控于发包单位,构成'实质派遣'。", + "", + "①《中华人民共和国劳动合同法》及其实施条例;\n②《中华人民共和国民法典》" + ), + new AuditSubContent( + "3.核查劳务外包服务的费用结算依据是否符合外包法律特征", + "①结算相关的凭证:付款审批单,结算单据(如项目验收单、承揽书、成果清单及对应金额),发票;\n②访谈记录(文档或音频)", + "1.检查凭证,验证实际结算方式。\n检查付款审批流程中的结算依据,以及外包公司提供的结算材料,\n①如果结算材料表明该'外包'实际以'人员数量、岗位配置、服务时长或出勤天数(隐含'人头费'逻辑)'等指标结算费用,则构成'实质派遣'。\n②'劳务外包'结算需以项目成果为依据,形成合同→验收单→发票的完整凭证链。结算金额与成果量挂钩,结算单、请款函等文件需基于项目验收单、承揽书等成果凭证生成,以此构成'劳务外包'的合规流程。\n2.访谈财务及外包项目负责人,核实以下事项:\n①费用计算依据:若与外包人员数量及工时无关,则属于真实'劳务外包';反之,可能构成'劳务派遣';\n②结算调整机制:项目延期或增量时,真实外包应以成果变更调整结算,而非仅追加人员费用。", + "", + "①《中华人民共和国劳动合同法》及其实施条例;\n②《中华人民共和国民法典》" + ) + )); + + // 3. 检查人员经费的使用是否规范 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[2], Arrays.asList( + new AuditSubContent( + "1.检查工资总额控制:工资性支出是否突破国资监管部门核定的年度总额上限,及超限额部分是否按规定完成审批报备", + "① 年度工资总额批复文件;\n② 年度工资汇总统计表;\n③ 超预算调整审批记录", + "核查实发工资总额与批复额度的一致性,确认超支部分是否履行相关决策程序(如职工代表大会审议、国资委备案等),并列出工资总额执行差异", + "生成审计取证单", + "①自治区国资委关于印发《自治区国资委履行出资人职责企业工资总额管理办法》的通知 - 政务公开 - 防城港市人民政府 -\n②企业内控制度\n③《中华人民共和国劳动合同法》第四条 职工代表大会" + ), + new AuditSubContent( + "2.检查福利费列支范围:福利费支出的合规性,有无违规发放补贴、变相薪酬行为", + "① 福利费明细账及原始凭证;\n② 职工福利制度文件;\n③ 补贴发放签收记录", + "对福利费支出项目进行抽查,依据制度文件审核合规性,发现违规支出的,列出超范围支出明细清单。", + "生成审计取证单", + "①财政部关于企业加强职工福利费财务管理的通知\n②企业内控制度" + ), + new AuditSubContent( + "3.检查劳务费真实性:是否存在虚构人员套取资金等虚假劳务支出行为", + "① 劳务合同;\n② 服务成果交付证明(如报告、验收单);\n③ 支付凭证(包括收款方信息及银行流水)", + "①通过天眼查或国家企业信用信息公示系统核查劳务提供方身份真实性;\n②对比服务成果与合同约定内容是否匹配;\n③对核查中发现的问题如实记录。", + "生成审计取证单", + "①《中华人民共和国会计法》第四十三条\n②《财政违法行为处罚处分条例》第七条" + ) + )); + + // 4. 检查借用人员管理规范性 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[3], Arrays.asList( + new AuditSubContent( + "1.检查借用程序合规性", + "①借调相关材料(含沟通记录及正式文件),三方协议(如有)\n②实际在岗证明文件(考勤记录);\n③续借审批文件", + "①借用必要性审查:根据借调相关材料,核查借调事由是否合理,是否存在以工作专班、跟班学习或交流锻炼等名义变相借调的情况。\n②借用程序合规性审查:对照单位借用人员制度,核查借调审批手续是否齐全,以及是否经本单位党组(党委)审批并报同级党委组织部门备案。\n③借用期限审查:核查借调文件约定的首次借调期限是否超过6个月;结合实际在岗证明文件(考勤记录),若实际存在延期情形,是否已提前征得派出单位和本人同意。", + "合并生成一个审计取证单", + "①中共中央办公厅 国务院办公厅印发《整治形式主义为基层减负若干规定》————头条——中央纪委国家监委网站;\n②企业内控制度" + ), + new AuditSubContent( + "2.检查经费列支规范性:借用人员费用报销等是否存在重复列支或违规转嫁费用行为", + "①审计期间借用人员名单;\n②费用报销凭证及其明细账;\n③借用人员访谈记录", + "根据借用人员名单,在费用报销明细账中核查相关报销记录。依据借调文件或三方协议中明确的福利待遇等承担主体,结合对借用人员的访谈记录,审查是否存在同一借用人员的费用同时在借出单位与借入单位列支的情况。", + "", + "①中共中央办公厅 国务院办公厅印发《整治形式主义为基层减负若干规定》————头条——中央纪委国家监委网站;\n②企业内控制度" + ) + )); + + // 5. 绩效管理体系健全性、合规性 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[4], Arrays.asList( + new AuditSubContent( + "1.确认绩效管理体系健全、制度流程符合法规要求", + "①所有内控制度\n②绩效考核制度发文记录、职工代表大会审议签字文件、制度修订记录等", + "①制度存在性审查:根据关键字,检索被审计单位所有制度,是否有员工绩效考核的制度规定。如有派遣工的,需检查是否有管派遣工的考核管理,并输出《制度清单》(含制度名称、文号、生效日期、具体条文等)。\n②纵向合规性比对:将被审计单位的考核办法与国家、自治区、上级单位比较,检查是否存在与上级制度相悖的情况,并标记冲突点。\n③横向一致性检查:与本单位的薪酬分配、干部管理、廉洁从业等其他内部控制制度比较,检查绩效考核的协同性,是否存在单位制度'左右脑互博'的情况,并标记冲突点。\n④确认是否绩效考核体系制度的设立是否已履行民主程序(应当经职工代表大会或全体职工讨论,协商确定);\n⑤根据以上检测出的问题,生成《绩效管理体系健全性、合规性审查审计取证单》,含客观事实描述、违规性质(是否需要?)、制度依据、审计建议等内容。", + "合并生成一个审计取证单", + "①《中华人民共和国劳动合同法》第四条;\n②《中央企业负责人经营业绩考核办法》;\n③国务院国资委《企业绩效评价标准值》(每年更新)\n⑤国资委回复:国有企业考核分配、工资总额相关的25个问题 http://www.sasac.gov.cn/n2588040/n2590387/n9854182/index.html\n⑥自治区国资委关于印发《关于进一步深化自治区国资委履行出资人职责企业劳动用工和收入分配制度改革的指导意见(试行)》的通知;\n⑦企业内控制度等" + ), + new AuditSubContent( + "2.绩效考核程序执行规范性", + "①考核表确认记录;\n②民主评议、领导评价记录;\n③绩效考核相关会议纪要、记录;\n④申诉处理记录(如有)等", + "①每个部门各抽查X份记录,检查考核表、民主评议、领导评价记录及其签字的完整性;\n②如有申述,检查申诉响应时效;\n③生成《绩效考核程序执行规范性审计取证单》", + "", + "企业内控制度" + ), + new AuditSubContent( + "3.绩效考核结果合规性", + "①工资计算表\n②职务任免文件", + "①检查工资计算与绩效考核结果是否呈正相关;\n②审核晋升人员绩效排名情况,是否存在低绩效仍获晋升的现象。", + "", + "企业内控制度" + ), + new AuditSubContent( + "4.验证系统数据准确性", + "①HR系统考核记录\n②纸质存档文件\n③工资发放明细", + "抽查样本,核验系统与纸质文件数据的一致性,并检查人员绩效等级是否存在差异", + "", + "企业内控制度" + ) + )); + + // 6. 检查机构编制合规性 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[5], Arrays.asList( + new AuditSubContent( + "1.验证编制审批流程是否完整", + "机构设立批文、编制批文('三定'方案)", + "对比实际部门数量与编制文件;检查新增机构是否经上级编办审批", + "合并生成一个审计取证单", + "①《中国共产党机构编制工作条例》;\n②《'三定'规定制定和实施办法》559920f6ac788c5c8b2ee86842f36a96.pdf——行政事业单位适用;\n③《机构编制监督检查工作办法》;\n④机构编制违规违纪违法行为处理和问责规则(试行)" + ), + new AuditSubContent( + "2.验证是否按批复编制设置部门", + "被审计单位组织架构图、职能说明书", + "对比实际部门设置与编制文件是否一致;检查新增机构是否经上级编办审批,识别并标注是否存在未经批复的新增、撤销的临时机构。", + "", + "①《中国共产党机构编制工作条例》;\n②《'三定'规定制定和实施办法》559920f6ac788c5c8b2ee86842f36a96.pdf——行政事业单位适用;\n③《机构编制监督检查工作办法》;\n④机构编制违规违纪违法行为处理和问责规则(试行)" + ), + new AuditSubContent( + "3.被审计单位职数是否超标", + "党委、董事会成员名单、高管任职文件、员工花名册(含职务、职级等)", + "统计实际高管人数,并比对编制文件规定的领导职数阈值;", + "", + "①《中国共产党机构编制工作条例》;\n②《'三定'规定制定和实施办法》559920f6ac788c5c8b2ee86842f36a96.pdf——行政事业单位适用;\n③《机构编制监督检查工作办法》;\n④机构编制违规违纪违法行为处理和问责规则(试行)" + ), + new AuditSubContent( + "4.编制总量控制:岗位编制是否超核定标准", + "编制核定表、社保缴纳人数统计", + "抓取编制批文中机构名称、编制数量,与被审计单位实际部门清单、人力资源部门岗位数量自动比对。", + "", + "①《中国共产党机构编制工作条例》;\n②《'三定'规定制定和实施办法》559920f6ac788c5c8b2ee86842f36a96.pdf——行政事业单位适用;\n③《机构编制监督检查工作办法》;\n④机构编制违规违纪违法行为处理和问责规则(试行)" + ), + new AuditSubContent( + "5.编制调整是否履行审批", + "编制调整审批文件(可能包含OA系统日志)", + "需结合被审计单位、国资委或上级单位关于编制调整的审批流程判断。另检查编制调整流程是否在OA系统完整留痕(发起→审核→批复)。", + "", + "①《中国共产党机构编制工作条例》;\n②《'三定'规定制定和实施办法》559920f6ac788c5c8b2ee86842f36a96.pdf——行政事业单位适用;\n③《机构编制监督检查工作办法》;\n④机构编制违规违纪违法行为处理和问责规则(试行)" + ) + )); + + // 7. 预算编制与实际执行差异 + AUDIT_SUB_CONTENTS.put(AUDIT_CONTENTS[6], Arrays.asList( + new AuditSubContent( + "1.验证人员经费真实性,预算申报是否与实有人数匹配", + "工资预算表、工资发放明细表、考勤数据、每月社保缴纳明细等", + "比对预算人数与社保实缴人数,抽查离职人员工资停发时效,检查是否存在虚报人数、'吃空饷'情况,并列出涉及金额。", + "生成审计取证单", + "①中华人民共和国预算法_中国人大网;\n②《中华人民共和国预算法实施条例》;\n③企业内控制度" + ), + new AuditSubContent( + "2.检查职级成本是否合规", + "工资预算表、员工花名册(含职务、职级等)、个税申报记录等", + "识别'高职低编'薪酬倒挂人员,计算其薪酬超出编制标准部分", + "生成审计取证单", + "①中华人民共和国预算法_中国人大网;\n②《中华人民共和国预算法实施条例》;\n③企业内控制度" + ), + new AuditSubContent( + "3.检查项目经费合规性,是否违规使用项目经费支付超编人员成本", + "项目预算明细、费用报销凭证、人员分工表", + "分析项目支出中人工成本占比,核查超编人员参与项目合理性,并列出比例、金额。", + "生成审计取证单", + "①中华人民共和国预算法_中国人大网;\n②《中华人民共和国预算法实施条例》;\n③企业内控制度" + ) + )); + } + + // 审计子内容类 + public static class AuditSubContent { + private String auditTarget; + private String auditEvidence; + private String aiTestContent; + private String generationResult; + private String regulationBasis; + + public AuditSubContent(String auditTarget, String auditEvidence, String aiTestContent, + String generationResult, String regulationBasis) { + this.auditTarget = auditTarget; + this.auditEvidence = auditEvidence; + this.aiTestContent = aiTestContent; + this.generationResult = generationResult; + this.regulationBasis = regulationBasis; + } + + public String getAuditTarget() { return auditTarget; } + public String getAuditEvidence() { return auditEvidence; } + public String getAiTestContent() { return aiTestContent; } + public String getGenerationResult() { return generationResult; } + public String getRegulationBasis() { return regulationBasis; } + } + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("劳务派遣", 10); + KEYWORD_WEIGHTS.put("人员编制", 9); + KEYWORD_WEIGHTS.put("外包", 8); + KEYWORD_WEIGHTS.put("同工同酬", 9); + KEYWORD_WEIGHTS.put("三性标准", 8); + KEYWORD_WEIGHTS.put("工资总额", 8); + KEYWORD_WEIGHTS.put("福利费", 7); + KEYWORD_WEIGHTS.put("绩效考核", 8); + KEYWORD_WEIGHTS.put("机构编制", 9); + KEYWORD_WEIGHTS.put("预算编制", 7); + KEYWORD_WEIGHTS.put("假外包真派遣", 10); + KEYWORD_WEIGHTS.put("借用人员", 7); + KEYWORD_WEIGHTS.put("用工合规", 9); + KEYWORD_WEIGHTS.put("人员经费", 8); + } + + // 审计方法 + public static final List AUDIT_METHODS = Arrays.asList( + "文档审查", "数据分析", "访谈调查", "现场观察", "抽样测试", + "对比分析", "穿行测试", "合规性检查", "制度评价" + ); + + private AuditContent9PersonnelConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/constants/KnowledgeBaseConstants.java b/src/main/java/com/gxwebsoft/ai/constants/KnowledgeBaseConstants.java new file mode 100644 index 0000000..340b215 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/KnowledgeBaseConstants.java @@ -0,0 +1,19 @@ +package com.gxwebsoft.ai.constants; + +public class KnowledgeBaseConstants { + + public static final String[] KEY_WORDS = { + "", + "审计依据 法律法规 审计业务约定书 经济责任审计管理办法 中共中央办公厅 国务院办公厅 党政主要领导干部审计规定 国家法规 公司管理制度 年度工作目标 党政主要领导干部经济责任审计规定", + "审计目标 经济责任审计目标 资产负债损益真实性 合法性 效益性 经济指标完成情况 重大决策执行 遵守财经法规 国有资产保值增值 经济责任评价 任职期间履职评价 责任界定 业绩评价", + "审计对象 审计范围 被审计领导干部 [职务] [姓名] 任职期间 [开始日期]至[结束日期] 职务任期 重大问题追溯 重要事项延伸 审计时限 下属子公司 代管企业", + "被审计单位基本情况 单位概况 组织机构 人员结构 财务会计政策 合并口径财务数据 资产总额 负债总额 营业收入 利润 内部控制制度 子公司 代管企业 职能部门设置 合并财务报表", + "审计内容 审计重点 贯彻执行经济方针 重大决策执行 发展战略 年度目标完成 法人治理结构 内部控制 财务真实性 风险管控 党风廉政建设 以往审计整改 三重一大经济决策 资产管理 采购管理 债权债务", + "审计风险 证据不充分 评价不客观 内部控制失效 法律法规变化 风险应对策略 审计证据充分性 评价客观性 内部控制审查 法规政策跟踪 重要性水平", + "审计方法 穿行测试 趋势分析 比率分析 访谈法 数据分析 分析性程序 检查 监盘 观察 询问 函证 计算 重新执行", + "审计步骤 时间安排 准备阶段 实施阶段 报告阶段 归档阶段 审计人员安排 资料收集 实质性程序 审计报告编写 交换意见 审计归档 进点会 进度表", + "审计组织实施 审计组人员分工 职责分配 审计工作计划 前期调研 审前培训 实地审计 质量控制 内部培训 沟通协调 分级复核 集体讨论 重大事项汇报 里程碑事件清单 审计工作组 项目负责人 主审" + }; + + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java b/src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java new file mode 100644 index 0000000..6d114d0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java @@ -0,0 +1,148 @@ +package com.gxwebsoft.ai.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.service.AiCloudFileService; + +import cn.hutool.core.util.StrUtil; + +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudDocParam; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * AI云文档目录表控制器 + * + * @author yc + */ +@Tag(name = "AI云文档目录表管理") +@RestController +@RequestMapping("/api/ai/doc") +public class AiCloudDocController extends BaseController { + @Resource + private AiCloudDocService aiCloudDocService; + @Resource + private AiCloudFileService aiCloudFileService; + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "分页查询AI云文档目录表") + @GetMapping("/page") + public ApiResult> page(AiCloudDocParam param) { + // 使用关联查询 + return success(aiCloudDocService.pageRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "查询全部AI云文档目录表") + @GetMapping() + public ApiResult> list(AiCloudDocParam param) { + // 使用关联查询 + return success(aiCloudDocService.listRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "根据id查询AI云文档目录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(aiCloudDocService.getByIdRel(id)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "根据ids查询AI云文档目录表") + @GetMapping("/byIds/{ids}") + public ApiResult> getByIds(@PathVariable("ids") String ids) { + List idList = StrUtil.split(ids, ','); + List ret = aiCloudDocService.list(new LambdaQueryWrapper().in(AiCloudDoc::getId, idList)); + return success(ret); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:save')") + @Operation(summary = "添加AI云文档目录表") + @PostMapping() + public ApiResult save(@RequestBody AiCloudDoc aiCloudDoc) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + aiCloudDoc.setUserId(loginUser.getUserId()); + } + if(aiCloudDoc.getParentId()>0) { + AiCloudDoc aiCloudDocParent = aiCloudDocService.getByIdRel(aiCloudDoc.getParentId()); + aiCloudDoc.setCategoryId(aiCloudDocParent.getCategoryId()); + aiCloudDoc.setCompanyId(aiCloudDocParent.getCompanyId()); + } + if (aiCloudDocService.save(aiCloudDoc)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:update')") + @Operation(summary = "修改AI云文档目录表") + @PutMapping() + public ApiResult update(@RequestBody AiCloudDoc aiCloudDoc) { + if (aiCloudDocService.updateById(aiCloudDoc)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:remove')") + @Operation(summary = "删除AI云文档目录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if(aiCloudFileService.count(new LambdaQueryWrapper().eq(AiCloudFile::getDocId, id))>0) { + return fail("删除失败,当前目录下存在文件"); + } + if (aiCloudDocService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:save')") + @Operation(summary = "批量添加AI云文档目录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (aiCloudDocService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:update')") + @Operation(summary = "批量修改AI云文档目录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(aiCloudDocService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:remove')") + @Operation(summary = "批量删除AI云文档目录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (aiCloudDocService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java b/src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java new file mode 100644 index 0000000..8197e4d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java @@ -0,0 +1,213 @@ +package com.gxwebsoft.ai.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudFileParam; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * AI云文件表控制器 + * + * @author yc + */ +@Tag(name = "AI云文件表管理") +@RestController +@RequestMapping("/api/ai/file") +public class AiCloudFileController extends BaseController { + + @Resource + private AiCloudFileService aiCloudFileService; + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "分页查询AI云文件表") + @GetMapping("/page") + public ApiResult> page(AiCloudFileParam param) { + return success(aiCloudFileService.pageRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "查询全部AI云文件表") + @GetMapping() + public ApiResult> list(AiCloudFileParam param) { + return success(aiCloudFileService.listRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "根据id查询AI云文件表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(aiCloudFileService.getByIdRel(id)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "根据ids查询AI云文件表") + @GetMapping("/byIds/{ids}") + public ApiResult> getByIds(@PathVariable("ids") String ids) { + List idList = cn.hutool.core.util.StrUtil.split(ids, ','); + List ret = aiCloudFileService.list(new LambdaQueryWrapper().in(AiCloudFile::getId, idList)); + return success(ret); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:save')") + @Operation(summary = "添加AI云文件表") + @PostMapping() + public ApiResult save(@RequestBody AiCloudFile aiCloudFile) { + User loginUser = getLoginUser(); + if (loginUser != null) { + aiCloudFile.setUserId(loginUser.getUserId()); + } + if (aiCloudFileService.save(aiCloudFile)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:update')") + @Operation(summary = "修改AI云文件表") + @PutMapping() + public ApiResult update(@RequestBody AiCloudFile aiCloudFile) { + if (aiCloudFileService.updateById(aiCloudFile)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:remove')") + @Operation(summary = "删除AI云文件表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (aiCloudFileService.removeFileWithCloudAndIndex(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:save')") + @Operation(summary = "批量添加AI云文件表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (aiCloudFileService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:update')") + @Operation(summary = "批量修改AI云文件表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(aiCloudFileService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:remove')") + @Operation(summary = "批量删除AI云文件表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (aiCloudFileService.removeFilesWithCloudAndIndex(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "上传文档到云中心") + @PostMapping("/uploadFiles") + public ApiResult uploadFiles(@RequestParam Integer docId, @RequestParam String categoryId, @RequestParam("files") MultipartFile[] files) { + try { + if (files == null || files.length == 0) { + return fail("请选择要上传的文件"); + } + + if (files.length > 10) { + return fail("一次最多只能上传10个文件"); + } + + User loginUser = getLoginUser(); + + List> futures = new ArrayList<>(); + List successFiles = new ArrayList<>(); + List failedFiles = new ArrayList<>(); + + // 收集所有异步任务 + for (MultipartFile file : files) { + if (file.isEmpty()) { + continue; + } + if (file.getSize() > 100 * 1024 * 1024) { + failedFiles.add(file.getOriginalFilename() + "(文件过大)"); + continue; + } + + // 调用Service层的异步方法并收集Future + CompletableFuture future = aiCloudFileService.processFileAsync(categoryId, docId, file, loginUser); + futures.add(future); + } + + // 等待所有异步任务完成 + CompletableFuture allFutures = CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ); + + // 获取所有任务结果 + CompletableFuture> allResults = allFutures.thenApply(v -> + futures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()) + ); + + // 等待所有任务完成并处理结果 + List results = allResults.get(10, TimeUnit.MINUTES); // 设置10分钟超时 + + List fileIds = new ArrayList<>(); + // 统计成功和失败的文件 + for (int i = 0; i < futures.size(); i++) { + CompletableFuture future = futures.get(i); + try { + String fileId = future.get(); + fileIds.add(fileId); + successFiles.add(files[i].getOriginalFilename()); + } catch (Exception e) { + failedFiles.add(files[i].getOriginalFilename() + "(" + e.getCause().getMessage() + ")"); + } + } + + // 构建返回消息 + StringBuilder message = new StringBuilder("文件上传完成"); + if (!successFiles.isEmpty()) { + message.append(",成功上传: ").append(successFiles.size()).append("个"); + } + if (!failedFiles.isEmpty()) { + message.append(",失败: ").append(String.join("、", failedFiles)); + } + + // 返回前提交上传的文档到单位默认知识库 + aiCloudFileService.submitDocuments(docId, fileIds); + + return success(message.toString()); + + } catch (Exception e) { + return fail("上传失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiHistoryController.java b/src/main/java/com/gxwebsoft/ai/controller/AiHistoryController.java new file mode 100644 index 0000000..37d3568 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AiHistoryController.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.ai.controller; + +import com.gxwebsoft.ai.entity.AiHistory; +import com.gxwebsoft.ai.param.AiHistoryParam; +import com.gxwebsoft.ai.service.AiHistoryService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * AI审计历史记录表控制器 + * + * @author yc + */ +@Tag(name = "AI审计历史记录表管理") +@RestController +@RequestMapping("/api/ai/history") +public class AiHistoryController extends BaseController { + @Resource + private AiHistoryService aiHistoryService; + + //@PreAuthorize("hasAuthority('ai:aiHistory:list')") + @Operation(summary = "分页查询AI审计历史记录表") + @GetMapping("/page") + public ApiResult> page(AiHistoryParam param) { + // 使用关联查询 + return success(aiHistoryService.pageRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:list')") + @Operation(summary = "查询全部AI审计历史记录表") + @GetMapping() + public ApiResult> list(AiHistoryParam param) { + // 使用关联查询 + return success(aiHistoryService.listRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:list')") + @Operation(summary = "根据id查询AI审计历史记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Long id) { + // 使用关联查询 + return success(aiHistoryService.getByIdRel(id)); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:save')") + @Operation(summary = "添加AI审计历史记录表") + @PostMapping() + public ApiResult save(@RequestBody AiHistory aiHistory) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + aiHistory.setUserId(loginUser.getUserId()); + aiHistory.setUsername(loginUser.getUsername()); + } + if (aiHistoryService.save(aiHistory)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:update')") + @Operation(summary = "修改AI审计历史记录表") + @PutMapping() + public ApiResult update(@RequestBody AiHistory aiHistory) { + if (aiHistoryService.updateById(aiHistory)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:remove')") + @Operation(summary = "删除AI审计历史记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Long id) { + if (aiHistoryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:save')") + @Operation(summary = "批量添加AI审计历史记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + User loginUser = getLoginUser(); + if (loginUser != null) { + for (AiHistory history : list) { + history.setUserId(loginUser.getUserId()); + history.setUsername(loginUser.getUsername()); + } + } + if (aiHistoryService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:update')") + @Operation(summary = "批量修改AI审计历史记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(aiHistoryService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiHistory:remove')") + @Operation(summary = "批量删除AI审计历史记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (aiHistoryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent10Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent10Controller.java new file mode 100644 index 0000000..aab8338 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent10Controller.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.PartyConductExportEntity; +import com.gxwebsoft.ai.service.AuditContent10PartyConductService; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容10控制器 - 党风廉政建设责任制审计 + */ +@Slf4j +@Tag(name = "审计内容10-廉政情况") +@RestController +@RequestMapping("/api/ai/auditContent10") +public class AuditContent10Controller extends BaseAuditContentController { + + @Autowired + private AuditContent10PartyConductService auditContent10PartyConductService; + + /** + * 生成党风廉政建设责任制审计表数据 + */ + @Operation(summary = "生成党风廉政建设责任制审计表") + @PostMapping("/generatePartyConductTable") + public ApiResult generatePartyConductTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent10PartyConductService.generatePartyConductTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出党风廉政建设责任制审计表到Excel + */ + @Operation(summary = "导出党风廉政建设责任制审计表到Excel") + @PostMapping("/exportPartyConductTable") + public void exportPartyConductTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "党风廉政建设责任制审计表", + this::convertToPartyConductEntityList, PartyConductExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToPartyConductEntityList(List> originalData) { + return originalData.stream().map(this::convertToPartyConductEntity).collect(Collectors.toList()); + } + + private PartyConductExportEntity convertToPartyConductEntity(Map item) { + PartyConductExportEntity entity = new PartyConductExportEntity(); + entity.setCategory(getStringValue(item, "category")); + entity.setSubCategory(getStringValue(item, "subCategory")); + entity.setDetailCategory(getStringValue(item, "detailCategory")); + entity.setContent(getStringValue(item, "content")); + entity.setExecutionStatus(getStringValue(item, "executionStatus")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent11Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent11Controller.java new file mode 100644 index 0000000..bc18464 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent11Controller.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.HistoryTableExportEntity; +import com.gxwebsoft.ai.service.AuditContent11HistoryService; +import com.gxwebsoft.ai.utils.ExcelExportTool; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容11控制器 - 历史审计问题整改表 + */ +@Slf4j +@Tag(name = "审计内容11-历史审计问题整改") +@RestController +@RequestMapping("/api/ai/auditContent11") +public class AuditContent11Controller extends BaseAuditContentController { + + @Autowired + private AuditContent11HistoryService auditContent11HistoryService; + + /** + * 生成历史审计问题整改表数据 + */ + @Operation(summary = "生成历史审计问题整改表") + @PostMapping("/generateHistoryTable") + public ApiResult generateHistoryTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent11HistoryService.generateHistoryTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出历史审计问题整改表到Excel + */ + @Operation(summary = "导出历史审计问题整改表到Excel") + @PostMapping("/exportHistoryTable") + public void exportHistoryTable(@RequestBody Map request, HttpServletResponse response) { + List> dataList = (List>) request.get("data"); + String companyName = (String) request.get("companyName"); + String auditTime = (String) request.get("auditTime"); + + List exportData = convertToHistoryTableEntityList(dataList); + + String fileName = "历史审计问题整改表_" + (companyName != null ? companyName : "未知公司"); + String sheetName = "历史审计问题整改表"; + String title = companyName != null ? + companyName + " - 历史审计问题整改表" + (auditTime != null ? "(" + auditTime + ")" : "") : + "历史审计问题整改表"; + + ExcelExportTool.exportExcel(exportData, HistoryTableExportEntity.class, fileName, sheetName, title, response); + } + + // ========== 数据转换方法 ========== + + private List convertToHistoryTableEntityList(List> originalData) { + return originalData.stream() + .map(this::convertToHistoryTableEntity) + .collect(Collectors.toList()); + } + + private HistoryTableExportEntity convertToHistoryTableEntity(Map item) { + HistoryTableExportEntity entity = new HistoryTableExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setAuditYear(getStringValue(item, "auditYear")); + entity.setAuditType(getStringValue(item, "auditType")); + entity.setProblemFound(getStringValue(item, "problemFound")); + entity.setRectificationRequirement(getStringValue(item, "rectificationRequirement")); + entity.setRectificationMeasures(getStringValue(item, "rectificationMeasures")); + entity.setRectificationStatus(getStringValue(item, "rectificationStatus")); + entity.setCompletionDate(getStringValue(item, "completionDate")); + entity.setResponsiblePerson(getStringValue(item, "responsiblePerson")); + entity.setRemark(getStringValue(item, "remark")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java new file mode 100644 index 0000000..8c63b36 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java @@ -0,0 +1,163 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.EightRegExportEntity; +import com.gxwebsoft.ai.dto.export.ExpenseExportEntity; +import com.gxwebsoft.ai.dto.export.LeaderListExportEntity; +import com.gxwebsoft.ai.service.AuditContent1EightRegService; +import com.gxwebsoft.ai.service.AuditContent1ExpenseService; +import com.gxwebsoft.ai.service.AuditContent1LeaderListService; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容1控制器 - 八项规定对比分析 + */ +@Slf4j +@Tag(name = "审计内容1-八项规定") +@RestController +@RequestMapping("/api/ai/auditContent1") +public class AuditContent1Controller extends BaseAuditContentController { + + @Autowired + private AuditContent1LeaderListService auditContent1LeaderListService; + + @Autowired + private AuditContent1ExpenseService auditContent1ExpenseService; + + @Autowired + private AuditContent1EightRegService auditContent1EightRegService; + + /** + * 生成领导班子名单数据 + */ + @Operation(summary = "生成领导班子名单") + @PostMapping("/generateLeaderListTable") + public ApiResult generateLeaderListTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent1LeaderListService.generateLeaderListTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 生成支出情况表数据 + */ + @Operation(summary = "生成支出情况表") + @PostMapping("/generateExpenseTable") + public ApiResult generateExpenseTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent1ExpenseService.generateExpenseTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 生成八项规定对比分析表数据 + */ + @Operation(summary = "生成八项规定对比分析表") + @PostMapping("/generateEightRegTable") + public ApiResult generateEightRegTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent1EightRegService.generateEightRegTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出八项规定对比分析表到Excel + */ + @Operation(summary = "导出八项规定对比分析表到Excel") + @PostMapping("/exportEightRegTable") + public void exportEightRegTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "八项规定对比分析表", + this::convertToEightRegEntityList, EightRegExportEntity.class); + } + + /** + * 导出领导班子名单到Excel + */ + @Operation(summary = "导出领导班子名单到Excel") + @PostMapping("/exportLeaderListTable") + public void exportLeaderListTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "领导班子名单", + this::convertToLeaderListEntityList, LeaderListExportEntity.class); + } + + /** + * 导出支出情况表到Excel + */ + @Operation(summary = "导出支出情况表到Excel") + @PostMapping("/exportExpenseTable") + public void exportExpenseTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "支出情况表", + this::convertToExpenseEntityList, ExpenseExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToEightRegEntityList(List> originalData) { + return originalData.stream().map(this::convertToEightRegEntity).collect(Collectors.toList()); + } + + private EightRegExportEntity convertToEightRegEntity(Map item) { + EightRegExportEntity entity = new EightRegExportEntity(); + entity.setTitle(getStringValue(item, "title")); + entity.setContent(getStringValue(item, "content")); + entity.setTestContent(getStringValue(item, "testContent")); + entity.setResult(getStringValue(item, "result")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } + + private List convertToLeaderListEntityList(List> originalData) { + return originalData.stream().map(this::convertToLeaderListEntity).collect(Collectors.toList()); + } + + private LeaderListExportEntity convertToLeaderListEntity(Map item) { + LeaderListExportEntity entity = new LeaderListExportEntity(); + entity.setUnit(getStringValue(item, "unit")); + entity.setName(getStringValue(item, "name")); + entity.setDepartment(getStringValue(item, "department")); + entity.setPartyPosition(getStringValue(item, "partyPosition")); + entity.setAdminPosition(getStringValue(item, "adminPosition")); + entity.setTenurePeriod(getStringValue(item, "tenurePeriod")); + entity.setMainResponsibilities(getStringValue(item, "mainResponsibilities")); + entity.setRemark(getStringValue(item, "remark")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } + + private List convertToExpenseEntityList(List> originalData) { + return originalData.stream().map(this::convertToExpenseEntity).collect(Collectors.toList()); + } + + private ExpenseExportEntity convertToExpenseEntity(Map item) { + ExpenseExportEntity entity = new ExpenseExportEntity(); + entity.setExpenseType(getStringValue(item, "expenseType")); + entity.setYear(getStringValue(item, "year")); + entity.setFinalStatementAmount(getStringValue(item, "finalStatementAmount")); + entity.setInitialBudgetAmount(getStringValue(item, "initialBudgetAmount")); + entity.setChangePercentage(getStringValue(item, "changePercentage")); + entity.setBudgetRatio(getStringValue(item, "budgetRatio")); + entity.setRemark(getStringValue(item, "remark")); + entity.setDataSource(getStringValue(item, "dataSource")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent2Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent2Controller.java new file mode 100644 index 0000000..386dade --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent2Controller.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.StrategyAuditExportEntity; +import com.gxwebsoft.ai.service.AuditContent2StrategyService; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容2控制器 - 单位发展战略执行审计 + */ +@Slf4j +@Tag(name = "审计内容2-单位发展战略执行") +@RestController +@RequestMapping("/api/ai/auditContent2") +public class AuditContent2Controller extends BaseAuditContentController { + + @Autowired + private AuditContent2StrategyService auditContent2StrategyService; + + /** + * 生成单位发展战略执行审计表数据 + */ + @Operation(summary = "生成单位发展战略执行审计表") + @PostMapping("/generateStrategyAuditTable") + public ApiResult generateStrategyAuditTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent2StrategyService.generateStrategyAuditTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出单位发展战略执行审计表到Excel + */ + @Operation(summary = "导出单位发展战略执行审计表到Excel") + @PostMapping("/exportStrategyAuditTable") + public void exportStrategyAuditTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "单位发展战略执行审计表", + this::convertToExportEntityList, StrategyAuditExportEntity.class); + } + + /** + * 数据转换 + */ + private List convertToExportEntityList(List> originalData) { + return originalData.stream().map(this::convertToExportEntity).collect(Collectors.toList()); + } + + private StrategyAuditExportEntity convertToExportEntity(Map item) { + StrategyAuditExportEntity entity = new StrategyAuditExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setAuditContent(getStringValue(item, "auditContent")); + entity.setCheckEvidence(getStringValue(item, "checkEvidence")); + entity.setTestResult(getStringValue(item, "testResult")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent3Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent3Controller.java new file mode 100644 index 0000000..82670de --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent3Controller.java @@ -0,0 +1,118 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.TripleOneExportEntity; +import com.gxwebsoft.ai.dto.export.DecisionTableExportEntity; +import com.gxwebsoft.ai.service.AuditContent3TripleService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.ai.service.AuditContent3DecisionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容3控制器 - 三重一大制度对比分析 & 重大经济决策调查表 + */ +@Slf4j +@Tag(name = "审计内容3-三重一大制度") +@RestController +@RequestMapping("/api/ai/auditContent3") +public class AuditContent3Controller extends BaseAuditContentController { + + @Autowired + private AuditContent3TripleService auditContent3TripleService; + + @Autowired + private AuditContent3DecisionService auditContent3DecisionService; + + /** + * 生成三重一大制度对比分析表数据 + */ + @Operation(summary = "生成三重一大制度对比分析表") + @PostMapping("/generateTripleOneTable") + public ApiResult generateTripleOneTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent3TripleService.generateTripleOneTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 生成重大经济决策调查表数据 + */ + @Operation(summary = "生成重大经济决策调查表") + @PostMapping("/generateDecisionTable") + public ApiResult generateDecisionTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent3DecisionService.generateDecisionTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion, request.getData() + )); + } + + /** + * 导出三重一大制度对比分析表到Excel + */ + @Operation(summary = "导出三重一大制度对比分析表到Excel") + @PostMapping("/exportTripleOneTable") + public void exportTripleOneTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "三重一大制度对比分析表", + this::convertToTripleOneEntityList, TripleOneExportEntity.class); + } + + /** + * 导出重大经济决策调查表到Excel + */ + @Operation(summary = "导出重大经济决策调查表到Excel") + @PostMapping("/exportDecisionTable") + public void exportDecisionTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "重大经济决策调查表", + this::convertToDecisionTableEntityList, DecisionTableExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToTripleOneEntityList(List> originalData) { + return originalData.stream().map(this::convertToTripleOneEntity).collect(Collectors.toList()); + } + + private List convertToDecisionTableEntityList(List> originalData) { + return originalData.stream().map(this::convertToDecisionTableEntity).collect(Collectors.toList()); + } + + private TripleOneExportEntity convertToTripleOneEntity(Map item) { + TripleOneExportEntity entity = new TripleOneExportEntity(); + entity.setCategory(getStringValue(item, "category")); + entity.setPolicyContent(getStringValue(item, "policyContent")); + entity.setGroupSystem(getStringValue(item, "groupSystem")); + entity.setCompanyFormulation(getStringValue(item, "companyFormulation")); + entity.setCheckEvidence(getStringValue(item, "checkEvidence")); + entity.setTestResult(getStringValue(item, "testResult")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } + + private DecisionTableExportEntity convertToDecisionTableEntity(Map item) { + DecisionTableExportEntity entity = new DecisionTableExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setDecisionItem(getStringValue(item, "name")); + entity.setMeetingTime(getStringValue(item, "meetingTime")); + entity.setDecisionAmount(getStringValue(item, "decisionAmount")); + entity.setProcedure(getStringValue(item, "procedure")); + entity.setExecutionStatus(getStringValue(item, "executionStatus")); + entity.setGood(getStringValue(item, "goods")); + entity.setNormal(getStringValue(item, "normal")); + entity.setBad(getStringValue(item, "bad")); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent4Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent4Controller.java new file mode 100644 index 0000000..7f386fc --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent4Controller.java @@ -0,0 +1,86 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.TargetAuditExportEntity; +import com.gxwebsoft.ai.service.AuditContent4TargetService; +import com.gxwebsoft.ai.utils.ExcelExportTool; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容4控制器 - 目标责任制完成情况审计 + */ +@Slf4j +@Tag(name = "审计内容4-目标责任制完成情况") +@RestController +@RequestMapping("/api/ai/auditContent4") +public class AuditContent4Controller extends BaseAuditContentController { + + @Autowired + private AuditContent4TargetService auditContent4TargetService; + + /** + * 生成目标责任制完成情况审计表数据 + */ + @Operation(summary = "生成目标责任制完成情况审计表") + @PostMapping("/generateTargetTable") + public ApiResult generateTargetTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent4TargetService.generateTargetAuditTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出目标责任制完成情况审计表到Excel + */ + @Operation(summary = "导出目标责任制完成情况审计表到Excel") + @PostMapping("/exportTargetTable") + public void exportTargetTable(@RequestBody Map request, HttpServletResponse response) { + List> dataList = (List>) request.get("data"); + String companyName = (String) request.get("companyName"); + + List exportData = convertToExportEntityList(dataList); + + String fileName = "目标责任制完成情况审计表_" + (companyName != null ? companyName : "未知公司"); + String title = companyName != null ? companyName + " - 目标责任制完成情况审计表" : "目标责任制完成情况审计表"; + String sheetName = "目标责任审计"; + + ExcelExportTool.exportExcel(exportData, TargetAuditExportEntity.class, fileName, sheetName, title, response); + } + + /** + * 数据转换 + */ + private List convertToExportEntityList(List> originalData) { + return originalData.stream().map(this::convertToExportEntity).collect(Collectors.toList()); + } + + private TargetAuditExportEntity convertToExportEntity(Map item) { + TargetAuditExportEntity entity = new TargetAuditExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setYear(getStringValue(item, "year")); + entity.setSuperiorFile(getStringValue(item, "superiorFile")); + entity.setSuperiorCompletion(getStringValue(item, "superiorCompletion")); + entity.setSuperiorReason(getStringValue(item, "superiorReason")); + entity.setSelfPlan(getStringValue(item, "selfPlan")); + entity.setSelfCompletion(getStringValue(item, "selfCompletion")); + entity.setSelfReason(getStringValue(item, "selfReason")); + entity.setRemark(getStringValue(item, "remark")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent5Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent5Controller.java new file mode 100644 index 0000000..4606a56 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent5Controller.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.BudgetManageExportEntity; +import com.gxwebsoft.ai.dto.export.BudgetExecutionExportEntity; +import com.gxwebsoft.ai.service.AuditContent5BudgetManageService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.ai.service.AuditContent5BudgetExecutionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容5控制器 - 预算管理审计 + */ +@Slf4j +@Tag(name = "审计内容5-预算管理审计") +@RestController +@RequestMapping("/api/ai/auditContent5") +public class AuditContent5Controller extends BaseAuditContentController { + + @Autowired + private AuditContent5BudgetManageService auditContent5BudgetManageService; + + @Autowired + private AuditContent5BudgetExecutionService auditContent5BudgetExecutionService; + + /** + * 生成预算管理审计表数据 + */ + @Operation(summary = "生成预算管理审计表") + @PostMapping("/generateBudgetManageTable") + public ApiResult generateBudgetManageTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent5BudgetManageService.generateBudgetManageTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion, request.getData() + )); + } + + /** + * 生成预算执行情况审计表数据 + */ + @Operation(summary = "生成预算执行情况审计表") + @PostMapping("/generateBudgetExecutionTable") + public ApiResult generateBudgetExecutionTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent5BudgetExecutionService.generateBudgetExecutionTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion, request.getData() + )); + } + + /** + * 导出预算管理审计表到Excel + */ + @Operation(summary = "导出预算管理审计表到Excel") + @PostMapping("/exportBudgetManageTable") + public void exportBudgetManageTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "预算管理审计表", + this::convertToBudgetManageEntityList, BudgetManageExportEntity.class); + } + + /** + * 导出预算执行情况审计表到Excel + */ + @Operation(summary = "导出预算执行情况审计表到Excel") + @PostMapping("/exportBudgetExecutionTable") + public void exportBudgetExecutionTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "预算执行情况审计表", + this::convertToBudgetExecutionEntityList, BudgetExecutionExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToBudgetManageEntityList(List> originalData) { + return originalData.stream().map(this::convertToBudgetManageEntity).collect(Collectors.toList()); + } + + private List convertToBudgetExecutionEntityList(List> originalData) { + return originalData.stream().map(this::convertToBudgetExecutionEntity).collect(Collectors.toList()); + } + + private BudgetManageExportEntity convertToBudgetManageEntity(Map item) { + BudgetManageExportEntity entity = new BudgetManageExportEntity(); + entity.setBudgetSubject(getStringValue(item, "budgetSubject")); + entity.setIndicatorSourceTotal(getStringValue(item, "indicatorSourceTotal")); + entity.setIndicatorSourceLastYearBalance(getStringValue(item, "indicatorSourceLastYearBalance")); + entity.setIndicatorSourceInitialBudget(getStringValue(item, "indicatorSourceInitialBudget")); + entity.setIndicatorSourceAdditionalBudget(getStringValue(item, "indicatorSourceAdditionalBudget")); + entity.setIndicatorUseTotal(getStringValue(item, "indicatorUseTotal")); + entity.setIndicatorUseAppropriationSubtotal(getStringValue(item, "indicatorUseAppropriationSubtotal")); + entity.setIndicatorUseAppropriation(getStringValue(item, "indicatorUseAppropriation")); + entity.setIndicatorUseSalaryPayment(getStringValue(item, "indicatorUseSalaryPayment")); + entity.setIndicatorUseGovProcurement(getStringValue(item, "indicatorUseGovProcurement")); + entity.setFinanceManagementAccount(getStringValue(item, "financeManagementAccount")); + entity.setBudgetUsedForOther(getStringValue(item, "budgetUsedForOther")); + entity.setIndicatorBalance(getStringValue(item, "indicatorBalance")); + entity.setGovernmentProcurement(getStringValue(item, "governmentProcurement")); + entity.setPayableToUnit(getStringValue(item, "payableToUnit")); + entity.setOther(getStringValue(item, "other")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } + + private BudgetExecutionExportEntity convertToBudgetExecutionEntity(Map item) { + BudgetExecutionExportEntity entity = new BudgetExecutionExportEntity(); + entity.setProject(getStringValue(item, "project")); + entity.setLastYearCarryOver(getStringValue(item, "lastYearCarryOver")); + entity.setCurrentYearBudgetTotal(getStringValue(item, "currentYearBudgetTotal")); + entity.setInitialApprovedBudget(getStringValue(item, "initialApprovedBudget")); + entity.setAdditionalBudgetAmount(getStringValue(item, "additionalBudgetAmount")); + entity.setActualAppropriation(getStringValue(item, "actualAppropriation")); + entity.setIndicatorBalance(getStringValue(item, "indicatorBalance")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent6Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent6Controller.java new file mode 100644 index 0000000..8953706 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent6Controller.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.StateAssetsAuditExportEntity; +import com.gxwebsoft.ai.service.AuditContent6StateAssetsService; +import com.gxwebsoft.ai.utils.ExcelExportTool; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容6控制器 - 国资管理情况审计 + */ +@Slf4j +@Tag(name = "审计内容6-国资管理情况") +@RestController +@RequestMapping("/api/ai/auditContent6") +public class AuditContent6Controller extends BaseAuditContentController { + + @Autowired + private AuditContent6StateAssetsService auditContent6StateAssetsService; + + /** + * 生成国有资产管理审计表数据 + */ + @Operation(summary = "生成国有资产管理审计表") + @PostMapping("/generateAssetsTable") + public ApiResult generateAssetsTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent6StateAssetsService.generateStateAssetsAuditTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出国有资产管理审计表到Excel + */ + @Operation(summary = "导出国有资产管理审计表到Excel") + @PostMapping("/exportAssetsTable") + public void exportAssetsTable(@RequestBody Map request, HttpServletResponse response) { + List> dataList = (List>) request.get("data"); + String companyName = (String) request.get("companyName"); + + List exportData = convertToExportEntityList(dataList); + + String fileName = "国有资产管理审计表_" + (companyName != null ? companyName : "未知公司"); + String title = companyName != null ? companyName + " - 国有资产管理审计表" : "国有资产管理审计表"; + String sheetName = "国资管理审计"; + + ExcelExportTool.exportExcel(exportData, StateAssetsAuditExportEntity.class, fileName, sheetName, title, response); + } + + /** + * 数据转换 + */ + private List convertToExportEntityList(List> originalData) { + return originalData.stream().map(this::convertToExportEntity).collect(Collectors.toList()); + } + + private StateAssetsAuditExportEntity convertToExportEntity(Map item) { + StateAssetsAuditExportEntity entity = new StateAssetsAuditExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setAssetName(getStringValue(item, "assetName")); + entity.setAcquisitionMethod(getStringValue(item, "acquisitionMethod")); + entity.setAssetValue(getStringValue(item, "assetValue")); + entity.setAssetAddress(getStringValue(item, "assetAddress")); + entity.setArea(getStringValue(item, "area")); + entity.setTenant(getStringValue(item, "tenant")); + entity.setContractAmount(getStringValue(item, "contractAmount")); + entity.setLeasePeriod(getStringValue(item, "leasePeriod")); + entity.setRentPaymentTime(getStringValue(item, "rentPaymentTime")); + entity.setPlatformLease(getStringValue(item, "platformLease")); + entity.setApprovalDoc(getStringValue(item, "approvalDoc")); + entity.setInBudget(getStringValue(item, "inBudget")); + entity.setRemark(getStringValue(item, "remark")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent7Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent7Controller.java new file mode 100644 index 0000000..2d0e60b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent7Controller.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.InvestmentSituationExportEntity; +import com.gxwebsoft.ai.service.AuditContent7InvestmentService; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容7控制器 - 重大投资情况审计 + */ +@Slf4j +@Tag(name = "审计内容7-重大投资情况") +@RestController +@RequestMapping("/api/ai/auditContent7") +public class AuditContent7Controller extends BaseAuditContentController { + + @Autowired + private AuditContent7InvestmentService auditContent7InvestmentService; + + /** + * 生成重大投资情况审计表数据 + */ + @Operation(summary = "生成重大投资情况审计表") + @PostMapping("/generateInvestmentSituationTable") + public ApiResult generateInvestmentSituationTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent7InvestmentService.generateInvestmentSituationTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出重大投资情况审计表到Excel + */ + @Operation(summary = "导出重大投资情况审计表到Excel") + @PostMapping("/exportInvestmentSituationTable") + public void exportInvestmentSituationTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcelWithAuditTime(request, response, "重大投资情况审计表", + this::convertToInvestmentSituationEntityList, InvestmentSituationExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToInvestmentSituationEntityList(List> originalData) { + return originalData.stream().map(this::convertToInvestmentSituationEntity).collect(Collectors.toList()); + } + + private InvestmentSituationExportEntity convertToInvestmentSituationEntity(Map item) { + InvestmentSituationExportEntity entity = new InvestmentSituationExportEntity(); + entity.setCategory(getStringValue(item, "category")); + entity.setAuditContent(getStringValue(item, "auditContent")); + entity.setCheckEvidence(getStringValue(item, "checkEvidence")); + entity.setTestResult(getStringValue(item, "testResult")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + entity.setFileIndex(formatWorkPaperIndex(item.get("fileIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java new file mode 100644 index 0000000..45a25eb --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.InternalControlExportEntity; +import com.gxwebsoft.ai.service.AuditContent8InternalControlService; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容8控制器 - 单位层面财务管理内部控制测试表 + */ +@Slf4j +@Tag(name = "审计内容8-内部控制测试") +@RestController +@RequestMapping("/api/ai/auditContent8") +public class AuditContent8Controller extends BaseAuditContentController { + + @Autowired + private AuditContent8InternalControlService auditContent8InternalControlService; + + /** + * 生成单位层面财务管理内部控制测试表数据 + */ + @Operation(summary = "生成单位层面财务管理内部控制测试表") + @PostMapping("/generateInternalControlTable") + public ApiResult generateInternalControlTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent8InternalControlService.generateInternalControlTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出单位层面财务管理内部控制测试表到Excel + */ + @Operation(summary = "导出单位层面财务管理内部控制测试表到Excel") + @PostMapping("/exportInternalControlTable") + public void exportInternalControlTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "单位层面财务管理内部控制测试表", + this::convertToInternalControlEntityList, InternalControlExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToInternalControlEntityList(List> originalData) { + return originalData.stream().map(this::convertToInternalControlEntity).collect(Collectors.toList()); + } + + private InternalControlExportEntity convertToInternalControlEntity(Map item) { + InternalControlExportEntity entity = new InternalControlExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setControlLink(getStringValue(item, "controlLink")); + entity.setControlRequirement(getStringValue(item, "controlRequirement")); + entity.setControlActivity(getStringValue(item, "controlActivity")); + entity.setControlPosition(getStringValue(item, "controlPosition")); + entity.setTestSteps(getStringValue(item, "testSteps")); + entity.setCheckEvidence(getStringValue(item, "checkEvidence")); + entity.setTestResult(getStringValue(item, "testResult")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent9Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent9Controller.java new file mode 100644 index 0000000..3b1e502 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent9Controller.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.PersonnelExportEntity; +import com.gxwebsoft.ai.service.AuditContent9PersonnelService; +import com.gxwebsoft.common.core.web.ApiResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 审计内容9控制器 - 人员编制管理审计 + */ +@Slf4j +@Tag(name = "审计内容9-人员编制管理审计") +@RestController +@RequestMapping("/api/ai/auditContent9") +public class AuditContent9Controller extends BaseAuditContentController { + + @Autowired + private AuditContent9PersonnelService auditContent9PersonnelService; + + /** + * 生成人员编制管理审计表数据 + */ + @Operation(summary = "生成人员编制管理审计表") + @PostMapping("/generatePersonnelTable") + public ApiResult generatePersonnelTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent9PersonnelService.generatePersonnelTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 导出人员编制管理审计表到Excel + */ + @Operation(summary = "导出人员编制管理审计表到Excel") + @PostMapping("/exportPersonnelTable") + public void exportPersonnelTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "人员编制管理审计表", + this::convertToPersonnelEntityList, PersonnelExportEntity.class); + } + + // ========== 数据转换方法 ========== + + private List convertToPersonnelEntityList(List> originalData) { + return originalData.stream().map(this::convertToPersonnelEntity).collect(Collectors.toList()); + } + + private PersonnelExportEntity convertToPersonnelEntity(Map item) { + PersonnelExportEntity entity = new PersonnelExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setAuditContent(getStringValue(item, "auditContent")); + entity.setAuditTarget(getStringValue(item, "auditTarget")); + entity.setAuditEvidence(getStringValue(item, "auditEvidence")); + entity.setGenerationResult(getStringValue(item, "generationResult")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java b/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java new file mode 100644 index 0000000..0bec5e0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java @@ -0,0 +1,214 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditEvidenceRequest; +import com.gxwebsoft.ai.service.AuditEvidenceService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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 javax.servlet.http.HttpServletResponse; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.openxml4j.util.ZipSecureFile; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; +import org.apache.poi.xwpf.usermodel.XWPFTable; +import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.apache.poi.xwpf.usermodel.XWPFTableRow; + +import java.util.ArrayList; +import java.util.List; + +import com.gxwebsoft.ai.config.TemplateConfig; +import cn.afterturn.easypoi.word.WordExportUtil; + +/** + * 审计取证单控制器 + */ +@Slf4j +@Tag(name = "审计取证单") +@RestController +@RequestMapping("/api/ai/auditEvidence") +public class AuditEvidenceController extends BaseController { + + @Autowired + private AuditEvidenceService auditEvidenceService; + + @Autowired + private TemplateConfig templateConfig; + + /** + * 生成审计取证单 + */ + @Operation(summary = "生成审计取证单") + @PostMapping("/generate") + public ApiResult generateAuditEvidence(@RequestBody AuditEvidenceRequest request) { + try { + final User loginUser = getLoginUser(); + request.setUserName(loginUser.getUsername()); + + log.info("接收到审计取证单生成请求 - 用户: {}, 项目: {}", request.getUserName(), request.getProjectName()); + JSONObject result = auditEvidenceService.generateAuditEvidence(request); + if (Boolean.TRUE.equals(result.getBoolean("success"))) { + return success(result); + } else { + return fail(result.getString("error") != null ? result.getString("error") : "生成审计取证单失败"); + } + } catch (Exception e) { + log.error("生成审计取证单异常", e); + return fail("生成审计取证单异常: " + e.getMessage()); + } + } + + /** + * 下载审计取证单Word文档 + */ + @Operation(summary = "下载审计取证单Word文档") + @PostMapping("/download") + public void downloadAuditEvidence(@RequestBody AuditEvidenceRequest request, HttpServletResponse response) { + double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio(); + + try { + ZipSecureFile.setMinInflateRatio(0.001); + + // 准备模板数据 - 将取证单字段映射到Word模板 + Map map = new HashMap<>(); +// map.put("caseIndex", request.getCaseIndex() != null ? request.getCaseIndex() : ""); + map.put("caseIndex", request.getCaseIndex() != null ? String.format("%-50s", request.getCaseIndex()) : String.format("%-50s", "")); + map.put("pageIndex", request.getPageIndex() != null ? request.getPageIndex() : "1"); + map.put("pageTotal", request.getPageTotal() != null ? request.getPageTotal() : "1"); + map.put("projectName", request.getProjectName() != null ? request.getProjectName() : ""); + map.put("auditedTarget", request.getAuditedTarget() != null ? request.getAuditedTarget() : ""); + map.put("auditMatter", request.getAuditMatter() != null ? request.getAuditMatter() : ""); + map.put("summaryTitle", request.getSummaryTitle() != null ? request.getSummaryTitle() : ""); + map.put("auditRecord", request.getAuditRecord() != null ? request.getAuditRecord() : ""); + map.put("auditFinding", request.getAuditFinding() != null ? request.getAuditFinding() : ""); + map.put("evidenceBasis", request.getEvidenceBasis() != null ? request.getEvidenceBasis() : ""); + map.put("handling", request.getHandling() != null ? request.getHandling() : ""); + map.put("attachment", request.getAttachment() != null ? request.getAttachment() : ""); + map.put("auditors", request.getAuditors() != null ? request.getAuditors() : ""); + map.put("compileDate", request.getCompileDate() != null ? request.getCompileDate() : ""); + map.put("providerOpinion", request.getProviderOpinion() != null ? request.getProviderOpinion() : ""); + map.put("providerDate", request.getProviderDate() != null ? request.getProviderDate() : ""); + map.put("attachmentPages", request.getAttachmentPages() != null ? request.getAttachmentPages() : ""); + map.put("feedbackDeadline", request.getFeedbackDeadline() != null ? request.getFeedbackDeadline() : ""); + + // 获取登录用户信息 + final User loginUser = getLoginUser(); + if (request.getAuditors() == null || request.getAuditors().isEmpty()) { + map.put("auditors", loginUser.getUsername()); + } + + // 使用 Easypoi 的 Word 模板功能生成取证单 + XWPFDocument document = WordExportUtil.exportWord07( + templateConfig.getEvidenceTemplatePath(), // 需要配置取证单模板路径 + map + ); + + // 处理换行,确保 \n 转为硬回车(新段落) + processParagraphs(document); + + // 设置响应头 + String fileName = "审计取证单_" + (request.getProjectName() != null ? request.getProjectName() : "取证单") + ".docx"; + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + response.setHeader("Content-Disposition", "attachment; filename=" + java.net.URLEncoder.encode(fileName, "UTF-8")); + + // 输出文档 + try (OutputStream out = response.getOutputStream()) { + document.write(out); + out.flush(); + } + + } catch (Exception e) { + log.error("生成审计取证单Word文档失败", e); + throw new RuntimeException("生成审计取证单失败", e); + } finally { + ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); + } + } + + /** + * 处理段落换行(增强版,同时处理表格单元格) + */ + private void processParagraphs(XWPFDocument document) { + // 1. 处理普通段落 + List originalParas = new ArrayList<>(document.getParagraphs()); + + for (XWPFParagraph para : originalParas) { + String text = para.getText(); + if (text == null || !text.contains("\n")) { + continue; + } + + String[] parts = text.replace("\r\n", "\n").replace("\r", "\n").split("\n"); + + // 在原段落位置之前插入新段落 + int pos = document.getPosOfParagraph(para); + + // 修正:按正序插入(从前往后) + for (int i = 0; i < parts.length; i++) { + // 创建新段落 + XWPFParagraph newPara = document.insertNewParagraph(para.getCTP().newCursor()); + + // 复制样式 + newPara.getCTP().setPPr(para.getCTP().getPPr()); + + XWPFRun newRun = newPara.createRun(); + newRun.setText(parts[i].trim()); + + if (!para.getRuns().isEmpty()) { + newRun.getCTR().setRPr(para.getRuns().get(0).getCTR().getRPr()); + } + } + + // 删除原段落 + document.removeBodyElement(pos + parts.length); + } + + // 2. 处理表格单元格中的段落 + List tables = document.getTables(); + for (XWPFTable table : tables) { + for (XWPFTableRow row : table.getRows()) { + for (XWPFTableCell cell : row.getTableCells()) { + List cellParagraphs = cell.getParagraphs(); + for (XWPFParagraph para : cellParagraphs) { + String text = para.getText(); + if (text == null || !text.contains("\n")) { + continue; + } + + // 清空原段落内容 + for (int i = para.getRuns().size() - 1; i >= 0; i--) { + para.removeRun(i); + } + + // 分割文本并按行添加 + String[] lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n"); + for (int i = 0; i < lines.length; i++) { + XWPFRun run = para.createRun(); + run.setText(lines[i].trim()); + + // 如果不是最后一行,添加换行 + if (i < lines.length - 1) { + run.addBreak(); + } + } + } + } + } + } + } +} diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java new file mode 100644 index 0000000..760175b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java @@ -0,0 +1,208 @@ +package com.gxwebsoft.ai.controller; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.poi.openxml4j.util.ZipSecureFile; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; +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 com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.config.TemplateConfig; +import com.gxwebsoft.ai.dto.AuditReportRequest; +import com.gxwebsoft.ai.dto.KnowledgeBaseRequest; +import com.gxwebsoft.ai.enums.AuditReportEnum; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.ai.util.AuditReportUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; + +import cn.afterturn.easypoi.word.WordExportUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 审计报告控制器 + * @author GIIT-YC + * + */ +@Tag(name = "审计报告") +@RestController +@RequestMapping("/api/ai/auditReport") +public class AuditReportController extends BaseController { + + @Autowired + private TemplateConfig templateConfig; + + @Autowired + private KnowledgeBaseService knowledgeBaseService; + + private String invok(String query, String knowledge, String history, String suggestion, String title, String userName) { + // 构建请求体 + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + inputs.put("query", query); + inputs.put("knowledge", knowledge); + inputs.put("history", history); + inputs.put("suggestion", suggestion); + inputs.put("title", title); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + + // 发送 POST 请求 + String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run") + .header("Authorization", "Bearer app-d7Ok9FECVZG2Ybw9wpg7tGu9") + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(600000) + .execute() + .body(); + // 解析返回的JSON字符串 + JSONObject jsonResponse = JSONObject.parseObject(result); + // 获取data字段 + JSONObject data = jsonResponse.getJSONObject("data"); + // 获取outputs字段 + JSONObject outputs = data.getJSONObject("outputs"); + // 获取outputs中的result字符串 + String resultStr = outputs.getString("result"); + return resultStr; + } + + /** + * 生成审计报告-单一模块 + */ + @Operation(summary = "生成审计报告-单一模块") + @PostMapping("/generate") + public ApiResult generateAuditReport(@RequestBody AuditReportRequest req) { + final User loginUser = getLoginUser(); + + Set kbIdSet = new HashSet<>(); + List kbIdList = StrUtil.split(req.getKbIds(), ','); + for(String kbId : kbIdList) { + KnowledgeBaseRequest knowledgeBaseRequest = new KnowledgeBaseRequest(); + knowledgeBaseRequest.setKbId(kbId); + //召回切片数上限 + knowledgeBaseRequest.setTopK(100); + knowledgeBaseRequest.setFormCommit((req.getFormCommit() >= 10) ? req.getFormCommit() / 10 : req.getFormCommit()); + kbIdSet.addAll(knowledgeBaseService.queryKnowledgeBase(knowledgeBaseRequest)); + } + String knowledge = kbIdSet.toString(); + + String query = AuditReportEnum.getByCode(req.getFormCommit()).getDesc(); +// String ret = this.invok(query, knowledge, AuditReportUtil.generateReportContent(req), req.getSuggestion(), loginUser.getUsername()); + String ret = this.invok(query, knowledge, AuditReportUtil.generateReportContentByFormCommit(req), req.getSuggestion(), req.getFrom00(), loginUser.getUsername()); + + return success(ret); + } + + /** + * 生成并下载审计报告 + */ + @Operation(summary = "生成并下载审计报告") + @PostMapping("/download") + public void downloadAuditReport(@RequestBody AuditReportRequest req, HttpServletResponse response) { + double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio(); + + try { + ZipSecureFile.setMinInflateRatio(0.001); + + // 准备模板数据 + Map map = new HashMap<>(); + map.put(AuditReportEnum.TITLE.getCodeStr(), req.getFrom00()); + map.put(AuditReportEnum.BASIS.getCodeStr(), req.getFrom10()); + map.put(AuditReportEnum.OBJECTIVE.getCodeStr(), req.getFrom20()); + map.put(AuditReportEnum.SCOPE.getCodeStr(), req.getFrom30()); + map.put(AuditReportEnum.UNIT_OVERVIEW.getCodeStr(), req.getFrom41()); + map.put(AuditReportEnum.ORG_PERSONNEL.getCodeStr(), req.getFrom42()); + map.put(AuditReportEnum.FINANCIAL_ACCOUNTING.getCodeStr(), req.getFrom43()); + map.put(AuditReportEnum.ANNUAL_BUSINESS.getCodeStr(), req.getFrom44()); + map.put(AuditReportEnum.INTERNAL_CONTROL.getCodeStr(), req.getFrom45()); + map.put(AuditReportEnum.ECONOMIC_POLICIES.getCodeStr(), req.getFrom51()); + map.put(AuditReportEnum.DEV_STRATEGY.getCodeStr(), req.getFrom52()); + map.put(AuditReportEnum.MAJOR_ECONOMIC.getCodeStr(), req.getFrom53()); + map.put(AuditReportEnum.CORP_GOVERNANCE.getCodeStr(), req.getFrom54()); + map.put(AuditReportEnum.FINANCIAL_LEGAL.getCodeStr(), req.getFrom55()); + map.put(AuditReportEnum.INTEGRITY_COMPLIANCE.getCodeStr(), req.getFrom56()); + map.put(AuditReportEnum.PREV_AUDIT_ISSUES.getCodeStr(), req.getFrom57()); + map.put(AuditReportEnum.OTHER_MATTERS.getCodeStr(), req.getFrom58()); + map.put(AuditReportEnum.RISK_IDENTIFY.getCodeStr(), req.getFrom61()); + map.put(AuditReportEnum.RISK_RESPONSE.getCodeStr(), req.getFrom62()); + map.put(AuditReportEnum.TECHNIQUES.getCodeStr(), req.getFrom70()); + map.put(AuditReportEnum.SCHEDULE.getCodeStr(), req.getFrom80()); + map.put(AuditReportEnum.ORGANIZATION.getCodeStr(), req.getFrom90()); + + // 使用 Easypoi 的 Word 模板功能 + XWPFDocument document = WordExportUtil.exportWord07(templateConfig.getWordTemplatePath(), map); + + // 处理换行,确保 \n 转为硬回车(新段落) + processParagraphs(document); + + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + response.setHeader("Content-Disposition", "attachment; filename=audit_report.docx"); + + try (OutputStream out = response.getOutputStream()) { + document.write(out); + out.flush(); + } + + } catch (Exception e) { + throw new RuntimeException("生成审计报告失败", e); + } finally { + ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); + } + } + + private void processParagraphs(XWPFDocument document) { + List originalParas = new ArrayList<>(document.getParagraphs()); + + for (XWPFParagraph para : originalParas) { + String text = para.getText(); + if (text == null || !text.contains("\n")) { + continue; + } + + String[] parts = text.replace("\r\n", "\n").replace("\r", "\n").split("\n"); + + // 在原段落位置之前插入新段落 + int pos = document.getPosOfParagraph(para); + + // 修正:按正序插入(从前往后) + for (int i = 0; i < parts.length; i++) { + // 创建新段落 + XWPFParagraph newPara = document.insertNewParagraph(para.getCTP().newCursor()); + + // 复制样式 + newPara.getCTP().setPPr(para.getCTP().getPPr()); + + XWPFRun newRun = newPara.createRun(); + newRun.setText(parts[i].trim()); + + if (!para.getRuns().isEmpty()) { + newRun.getCTR().setRPr(para.getRuns().get(0).getCTR().getRPr()); + } + } + + // 删除原段落 + document.removeBodyElement(pos + parts.length); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditReportController2.java b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController2.java new file mode 100644 index 0000000..b670b2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController2.java @@ -0,0 +1,128 @@ +package com.gxwebsoft.ai.controller; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.openxml4j.util.ZipSecureFile; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +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.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.service.AuditReportService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 审计报告控制器 - 重构版 + */ +@Tag(name = "审计报告") +@RestController +@RequestMapping("/api/ai/auditReport2") +public class AuditReportController2 extends BaseController { + + @Autowired + private AuditReportService auditReportService; + + /** + * 生成审计核查报告 - 重构版 + */ + @Operation(summary = "生成审计核查报告") + @PostMapping("/generate") + public ApiResult generateAuditReport( + @RequestPart("file") MultipartFile file, + @RequestPart(value = "kbId", required = false) String kbId, + @RequestPart(value = "libraryIds", required = false) String libraryIds, + @RequestPart(value = "analysisLibrary", required = false) String analysisLibrary, + @RequestPart(value = "projectLibrary", required = false) String projectLibrary) { + + final User loginUser = getLoginUser(); + try { + // 1. 解析文件五、六点内容 + String auditContent = extractAuditContent(file); + if (StrUtil.isBlank(auditContent)) { + return fail("未能提取到审计内容"); + } + + // 2. 生成完整审计报告 + JSONObject auditReport = auditReportService.generateCompleteAuditReport( + auditContent, file.getOriginalFilename(), kbId, libraryIds, + analysisLibrary, projectLibrary, loginUser.getUsername() + ); + + return success(auditReport); + + } catch (Exception e) { + return fail("生成审计报告失败: " + e.getMessage()); + } + } + + /** + * 提取审计内容(五、六点) + */ + private String extractAuditContent(MultipartFile file) throws IOException { + StringBuilder content = new StringBuilder(); + + // 保存原来的最小解压比率 + double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio(); + // 设置新的最小解压比率 + ZipSecureFile.setMinInflateRatio(0.001); + + try (InputStream is = file.getInputStream(); + XWPFDocument doc = new XWPFDocument(is)) { + + boolean inSection5 = false; + boolean inSection6 = false; + + for (XWPFParagraph para : doc.getParagraphs()) { + String text = para.getText(); + if (StrUtil.isBlank(text)) continue; + + // 检测第五部分 + if (text.contains("五、") || text.contains("5、")) { + inSection5 = true; + inSection6 = false; + content.append("【第五部分】\n"); + continue; + } + + // 检测第六部分 + if (text.contains("六、") || text.contains("6、")) { + inSection5 = false; + inSection6 = true; + content.append("【第六部分】\n"); + continue; + } + + // 检测结束 + if ((text.contains("七、") || text.contains("7、")) && inSection6) { + break; + } + + // 收集内容 + if (inSection5 || inSection6) { + content.append(text).append("\n"); + } + } + } finally { + // 恢复原来的最小解压比率 + ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); + } + + return content.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/BaseAuditContentController.java b/src/main/java/com/gxwebsoft/ai/controller/BaseAuditContentController.java new file mode 100644 index 0000000..9c8eda5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/BaseAuditContentController.java @@ -0,0 +1,352 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.service.AiHistoryService; +import com.gxwebsoft.ai.utils.ExcelExportTool; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.service.PwlProjectLibraryService; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 审计内容基础控制器 + */ +@Slf4j +public abstract class BaseAuditContentController extends BaseController { + + @Autowired + protected AiCloudDocService aiCloudDocService; + + @Autowired + protected AiCloudFileService aiCloudFileService; + + @Autowired + protected KnowledgeBaseService knowledgeBaseService; + + @Autowired + protected PwlProjectLibraryService pwlProjectLibraryService; + + @Autowired + protected AiHistoryService aiHistoryService; + + // 历史记录有效期(分钟) + protected static final int HISTORY_EXPIRE_MINUTES = 10; + + /** + * 通用的表格数据生成方法 + */ + protected ApiResult generateTableData(AuditContentRequest request, String interfaceName, Function generateFunction) { + final User loginUser = getLoginUser(); + String requestHistory = request.getHistory(); + request.setHistory(""); + + // 检查历史记录 + String requestHash = generateRequestHash(request, interfaceName); + var history = aiHistoryService.getValidHistory(requestHash, interfaceName, HISTORY_EXPIRE_MINUTES); + if (history != null) { + log.info("返回历史数据,请求哈希: {}", requestHash); + return success(JSONObject.parseObject(history.getResponseData())); + } + request.setHistory(requestHistory); + + String kbIdTmp = ""; + String libraryKbIds = ""; + + try { + // 创建临时知识库(如果需要) + if (hasUploadedFiles(request)) { + kbIdTmp = createTempKnowledgeBase(request); + } + + // 查询项目库信息 + libraryKbIds = getLibraryKbIds(request.getLibraryIds()); + + // 生成数据 + String knowledgeBaseId = getKnowledgeBaseId(kbIdTmp, request.getKbIds()); + GenerateParams params = new GenerateParams(knowledgeBaseId, libraryKbIds, request.getProjectLibrary(), loginUser.getUsername(), request.getHistory(), request.getSuggestion()); + + JSONObject result = generateFunction.apply(params); + + if(result.getBoolean("success")) { + // 转换workPaperIndex,原数据:["文件名1"+"||"+"下载地址1","文件名2"+"||"+"下载地址2"],转换后:["文件ID1"+"||"+"文件名1"+"||"+"下载地址1","文件ID2"+"||"+"文件名2"+"||"+"下载地址2"] + convertWorkPaperFileInfo(result); + // 保存到历史记录 + saveToHistory(request, interfaceName, requestHash, result, loginUser); + } + + return success(result); + } catch (Exception e) { + log.error("生成表格数据失败,接口: {}", interfaceName, e); + return fail("生成表格数据失败: " + e.getMessage()); + } finally { + cleanupTempKnowledgeBase(kbIdTmp); + } + } + + private void convertWorkPaperFileInfo(JSONObject result) { + JSONArray data = result.getJSONArray("data"); + for (int i = 0; i < data.size(); i++) { + JSONObject obj = data.getJSONObject(i); + JSONArray workPaperIndexFiles = obj.getJSONArray("workPaperIndex"); + if (workPaperIndexFiles != null && !workPaperIndexFiles.isEmpty()) { + for (int j = 0; j < workPaperIndexFiles.size(); j++) { + String files = workPaperIndexFiles.getString(j); + workPaperIndexFiles.set(j, "FileId||" + files); + } + } + } + } + + private void convertWorkPaperFileInfo2(JSONObject result) { + JSONArray data = result.getJSONArray("data"); + + for (int i = 0; i < data.size(); i++) { + JSONObject obj = data.getJSONObject(i); + JSONArray workPaperIndexFileIds = obj.getJSONArray("workPaperIndex"); + + // 先获取并转换 workPaperIndex 字段 + if (workPaperIndexFileIds != null && !workPaperIndexFileIds.isEmpty()) { + // 先查询所有文件 + List aiCloudFiles = aiCloudFileService.list(new LambdaQueryWrapper().in(AiCloudFile::getFileId, workPaperIndexFileIds)); + + // 创建文件ID到文件的映射,便于查找 + Map fileMap = aiCloudFiles.stream().collect(Collectors.toMap(AiCloudFile::getFileId, file -> file)); + + // 创建转换后的数组(保持原顺序) + JSONArray transformedArray = new JSONArray(); + for (Object fileIdObj : workPaperIndexFileIds) { + String fileId = (String) fileIdObj; + AiCloudFile file = fileMap.get(fileId); + if (file != null) { + String transformed = file.getFileId() + "||" + file.getFileName() + "||" + file.getFileUrl(); + transformedArray.add(transformed); + } else { + transformedArray.add(fileIdObj); + } + } + + // 将转换后的数据塞回原字段 + obj.put("workPaperIndex", transformedArray); + + // 使用 workPaperIndexFileIds 中的每个 fileId 替换其他字段中的四种格式 + for (String key : obj.keySet()) { + if ("workPaperIndex".equals(key)) { + continue; // 跳过 workPaperIndex 字段 + } + Object value = obj.get(key); + if (value instanceof String) { + String text = (String) value; + for (Object fileIdObj : workPaperIndexFileIds) { + String fileId = (String) fileIdObj; + text = text.replace("【FileId:" + fileId + "】", "") + .replace("(FileId:" + fileId + ")", "") + .replace("(FileId:" + fileId + ")", "") + .replace("FileId:"+fileId, "") + .replace("【" + fileId + "】", "") + .replace("(" + fileId + ")", "") + .replace("(" + fileId + ")", "") + .replace(fileId, "");; + } + obj.put(key, text); + } + } + } + } + } + + + /** + * 生成请求哈希 + */ + protected String generateRequestHash(AuditContentRequest request, String interfaceName) { + String requestJson = JSONObject.toJSONString(request); + return DigestUtil.md5Hex(interfaceName + ":" + requestJson); + } + + /** + * 保存到历史记录 + */ + protected void saveToHistory(AuditContentRequest request, String interfaceName, String requestHash, JSONObject result, User loginUser) { + try { + aiHistoryService.saveHistory(request.getProjectId(), requestHash, interfaceName, JSONObject.toJSONString(request), result.toJSONString(), loginUser.getUserId(), loginUser.getUsername(), loginUser.getTenantId()); + } catch (Exception e) { + log.warn("保存历史记录失败", e); + } + } + + /** + * 检查是否有上传的文件 + */ + protected boolean hasUploadedFiles(AuditContentRequest request) { + return !request.getDocList().isEmpty() || !request.getFileList().isEmpty(); + } + + /** + * 获取知识库ID + */ + protected String getKnowledgeBaseId(String tempKbId, String requestKbIds) { + return StrUtil.isNotBlank(tempKbId) ? tempKbId : requestKbIds; + } + + /** + * 获取项目库KB IDs + */ + protected String getLibraryKbIds(String libraryIds) { + if (StrUtil.isBlank(libraryIds)) { + return ""; + } + List idList = StrUtil.split(libraryIds, ','); + List ret = pwlProjectLibraryService.list(new LambdaQueryWrapper().in(PwlProjectLibrary::getId, idList)); + return ret.stream().map(PwlProjectLibrary::getKbId).filter(StrUtil::isNotBlank).collect(Collectors.joining(",")); + } + + /** + * 创建临时知识库并提交文档 + */ + protected String createTempKnowledgeBase(AuditContentRequest request) { + String kbIdTmp = knowledgeBaseService.createKnowledgeBaseTemp(); + // 收集文档ID + Set docIds = request.getDocList().stream().flatMap(docId -> aiCloudDocService.getSelfAndChildren(docId).stream()).map(AiCloudDoc::getId).collect(Collectors.toSet()); + // 查询相关文件 + List fileList = getRelatedFiles(docIds, request.getFileList()); + // 提取文件ID并提交到知识库 + Set kbFileIds = fileList.stream().map(AiCloudFile::getFileId).collect(Collectors.toSet()); + if (!kbFileIds.isEmpty()) { + knowledgeBaseService.submitDocuments(kbIdTmp, new ArrayList<>(kbFileIds)); + } + return kbIdTmp; + } + + /** + * 获取相关文件列表 + */ + protected List getRelatedFiles(Set docIds, List fileList) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .in(!docIds.isEmpty(), AiCloudFile::getDocId, docIds) + .or(!fileList.isEmpty()) + .in(!fileList.isEmpty(), AiCloudFile::getId, fileList); + return aiCloudFileService.list(queryWrapper); + } + + /** + * 清理临时知识库 + */ + protected void cleanupTempKnowledgeBase(String kbId) { + if (StrUtil.isNotBlank(kbId)) { + try { + knowledgeBaseService.deleteIndex(kbId); + } catch (Exception e) { + log.warn("删除临时知识库失败: {}", kbId, e); + } + } + } + + /** + * 通用的Excel导出方法 + */ + protected void exportToExcel(Map request, HttpServletResponse response, + String sheetName, Function>, List> converter, + Class entityClass) { + List> dataList = (List>) request.get("data"); + String companyName = (String) request.get("companyName"); + + List exportData = converter.apply(dataList); + + String fileName = sheetName + "_" + (companyName != null ? companyName : "未知公司"); + String title = companyName != null ? companyName + " - " + sheetName : sheetName; + + ExcelExportTool.exportExcel(exportData, entityClass, fileName, sheetName, title, response); + } + + /** + * 带审计时间的Excel导出方法 + */ + protected void exportToExcelWithAuditTime(Map request, HttpServletResponse response, + String sheetName, Function>, List> converter, + Class entityClass) { + List> dataList = (List>) request.get("data"); + String companyName = (String) request.get("companyName"); + String auditTime = (String) request.get("auditTime"); + + List exportData = converter.apply(dataList); + + String fileName = sheetName + "_" + (companyName != null ? companyName : "未知公司"); + String title = companyName != null ? companyName + " - " + sheetName : sheetName; + + if (auditTime != null) { + title += "(审计时间:" + auditTime + ")"; + } + + ExcelExportTool.exportExcel(exportData, entityClass, fileName, sheetName, title, response); + } + + /** + * 参数包装类 + */ + protected static class GenerateParams { + public final String knowledgeBaseId; + public final String libraryKbIds; + public final String projectLibrary; + public final String username; + public final String history; + public final String suggestion; + + public GenerateParams(String knowledgeBaseId, String libraryKbIds, String projectLibrary, String username, String history, String suggestion) { + this.knowledgeBaseId = knowledgeBaseId; + this.libraryKbIds = libraryKbIds; + this.projectLibrary = projectLibrary; + this.username = username; + this.history = history; + this.suggestion = suggestion; + } + } + + // ========== 通用的工具方法 ========== + + /** + * 获取字符串值 + */ + protected String getStringValue(Map map, String key) { + Object value = map.get(key); + return value != null ? value.toString() : ""; + } + + /** + * 格式化底稿索引 + */ + protected String formatWorkPaperIndex(Object workPaperIndex) { + if (workPaperIndex == null) { + return ""; + } + + if (workPaperIndex instanceof List) { + List list = (List) workPaperIndex; + return String.join(", ", list.stream() + .map(Object::toString) + .collect(Collectors.toList())); + } + + return workPaperIndex.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/KnowledgeBaseController.java b/src/main/java/com/gxwebsoft/ai/controller/KnowledgeBaseController.java new file mode 100644 index 0000000..61ff0fb --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/KnowledgeBaseController.java @@ -0,0 +1,108 @@ +package com.gxwebsoft.ai.controller; + +import com.gxwebsoft.ai.dto.KnowledgeBaseRequest; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.enterprise.entity.Enterprise; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "知识库") +@RestController +@RequestMapping("/api/ai/knowledgeBase") +public class KnowledgeBaseController extends BaseController { + + @Autowired + private KnowledgeBaseService knowledgeBaseService; + + @Operation(summary = "查询知识库") + @GetMapping("/query") + public ApiResult query(KnowledgeBaseRequest req) { + try { + return success(knowledgeBaseService.queryKnowledgeBase(req)); + } catch (Exception e) { + return fail("查询失败:" + e.getMessage()); + } + } + + @Operation(summary = "创建知识库") + @PostMapping("/create") + public ApiResult create(@RequestParam String companyName, @RequestParam String companyCode) { + try { + String indexId = knowledgeBaseService.createKnowledgeBase(companyName, companyCode); + return success(indexId); + } catch (Exception e) { + return fail("创建失败:" + e.getMessage()); + } + } + + @Operation(summary = "查询知识库下的文档列表") + @GetMapping("/documents") + public ApiResult listDocuments(String kbId, Integer pageSize, Integer pageNumber) { + try { + Map map = knowledgeBaseService.listDocuments(kbId, pageSize, pageNumber); + List data = Convert.toList(Object.class, map.get("data")); + Long total = MapUtil.getLong(map, "total"); + return success(new PageResult(data, total)); + } catch (Exception e) { + return fail("查询文档列表失败:" + e.getMessage()); + } + } + + @Operation(summary = "删除知识库下的文档") + @DeleteMapping("/document") + public ApiResult deleteDocument(String kbId, String fileIds) { + try { + boolean result = knowledgeBaseService.deleteIndexDocument(kbId, fileIds); + if (result) { + return success("删除成功"); + } else { + return fail("删除失败"); + } + } catch (Exception e) { + return fail("删除文档失败: " + e.getMessage()); + } + } + + @Operation(summary = "上传文档到知识库") + @PostMapping("/upload") + public ApiResult uploadDocuments(@RequestParam String kbId, @RequestParam("files") MultipartFile[] files) { + try { + if (files == null || files.length == 0) { + return fail("请选择要上传的文件"); + } + + for (MultipartFile file : files) { + if (file.isEmpty()) { + return fail("文件不能为空: " + file.getOriginalFilename()); + } + if (file.getSize() > 100 * 1024 * 1024) { + return fail("文件大小不能超过100MB: " + file.getOriginalFilename()); + } + } + + List fileIds = knowledgeBaseService.uploadDocuments(kbId, files); + boolean result = knowledgeBaseService.submitDocuments(kbId, fileIds); + + if (result) { + return success("成功上传 " + files.length + " 个文件"); + } else { + return success("部分文件上传成功"); + } + } catch (Exception e) { + return fail("上传失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/AuditContentRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AuditContentRequest.java new file mode 100644 index 0000000..ef95bac --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/AuditContentRequest.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.ai.dto; + +import java.util.List; + +import lombok.Data; + +/** + * 审计内容请求参数 + */ +@Data +public class AuditContentRequest { + + /** + * 项目ID + */ + private Long projectId; + + /** + * 企业库 + */ + private String kbIds; + + /** + * 公共库 + */ + private String libraryIds; + + /** + * 案例库 + */ + private String projectLibrary; + + /** + * 历史数据 + */ + private String history; + + /** + * 知识库关键词 + */ + private String keywords; + + /** + * 优化建议 + */ + private String suggestion; + + /** + * 目录列表 + */ + private List docList; + + /** + * 文件列表 + */ + private List fileList; + + /** + * 相关数据 + */ + private Object data; +} diff --git a/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java new file mode 100644 index 0000000..d32d1ec --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.ai.dto; + +import lombok.Data; + +@Data +public class AuditEvidenceRequest { + // 基础信息 + private String caseIndex; // 案引号 + private String projectName; // 项目名称 + private String auditedTarget; // 被审计单位或个人 + private String auditMatter; // 审计事项 + private String summaryTitle; // 标题 + private String auditRecord; // 审计记录 + private String auditFinding; // 审计发现 + private String evidenceBasis; // 定性依据 + private String handling; // 处理 + private String suggestion; // 建议 + private String attachment; // 附件 + private String auditors; // 审计人员 + private String compileDate; // 编制日期 + private String providerOpinion; // 证据提供单位意见 + private String providerDate; // 证据提供日期 + private String attachmentPages; // 附件页数 + private String feedbackDeadline; // 反馈期限 + + // 导出取证单使用 + private String pageIndex; + private String pageTotal; + + // 历史内容(用于工作流生成) + private String history; + + // 用户信息 + private String userName; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/AuditReportRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AuditReportRequest.java new file mode 100644 index 0000000..b10e94a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/AuditReportRequest.java @@ -0,0 +1,89 @@ +package com.gxwebsoft.ai.dto; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class AuditReportRequest{ + + @Schema(description = "审计标题") + private String from00; + + @Schema(description = "审计依据") + private String from10; + + @Schema(description = "审计目标") + private String from20; + + @Schema(description = "审计对象和范围") + private String from30; + + @Schema(description = "被审计单位基本情况-单位概况") + private String from41; + + @Schema(description = "被审计单位基本情况-机构和人员相关情况") + private String from42; + + @Schema(description = "被审计单位基本情况-财务会计、重大会计政策选用及变动情况") + private String from43; + + @Schema(description = "审计期间合并口径年度资产负债及各项业务总体情况") + private List from44; + + @Schema(description = "被审计单位基本情况-相关内部控制") + private String from45; + + @Schema(description = "审计内容和重点及审计方法-贯彻执行党和国家有关经济方针和上级决策部署情况") + private String from51; + + @Schema(description = "审计内容和重点及审计方法-公司发展战略规划的制定、执行和效果情况以及年度责任目标完成情况") + private String from52; + + @Schema(description = "审计内容和重点及审计方法-重大经济事项的决策、执行和效果情况") + private String from53; + + @Schema(description = "审计内容和重点及审计方法-公司法人治理结构的建立、健全和运行情况,内部控制制度的制定和执行情况") + private String from54; + + @Schema(description = "审计内容和重点及审计方法-公司财务的真实合法效益情况,风险管控情况,境外资产管理情况") + private String from55; + + @Schema(description = "审计内容和重点及审计方法-在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况") + private String from56; + + @Schema(description = "审计内容和重点及审计方法-对以往审计中发现问题的整改情况") + private String from57; + + @Schema(description = "审计内容和重点及审计方法-其他需要审计的事项") + private String from58; + + @Schema(description = "重要风险的识别及应对-重要风险的识别") + private String from61; + + @Schema(description = "重要风险的识别及应对-风险的应对策略") + private String from62; + + @Schema(description = "审计技术方法") + private String from70; + + @Schema(description = "工作步骤与时间安排") + private String from80; + + @Schema(description = "审计工作的组织实施") + private String from90; + + @Schema(description = "知识库ID") + private String kbIds;//英文逗号分割 + + @Schema(description = "生成模块:AuditReportEnum.code") + private Integer formCommit; + + @Schema(description = "历史内容") + private String history; + + @Schema(description = "修改建议") + private String suggestion; + +} diff --git a/src/main/java/com/gxwebsoft/ai/dto/KnowledgeBaseRequest.java b/src/main/java/com/gxwebsoft/ai/dto/KnowledgeBaseRequest.java new file mode 100644 index 0000000..bee6ee3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/KnowledgeBaseRequest.java @@ -0,0 +1,20 @@ +package com.gxwebsoft.ai.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class KnowledgeBaseRequest { + + @Schema(description = "知识库ID") + private String kbId; + + @Schema(description = "召回内容") + private String query; + + @Schema(description = "召回模块(1~9)") + private Integer formCommit; + + @Schema(description = "返回TOP切片数量") + private Integer topK; +} diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/BudgetExecutionExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/BudgetExecutionExportEntity.java new file mode 100644 index 0000000..a4bbc7d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/BudgetExecutionExportEntity.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 预算执行情况审计表导出实体 + */ +@Data +@ExcelTarget("BudgetExecutionExportEntity") +public class BudgetExecutionExportEntity { + + @Excel(name = "项目", orderNum = "1", width = 30) + private String project; + + @Excel(name = "上年结转", orderNum = "2", width = 15) + private String lastYearCarryOver; + + @Excel(name = "本年预算小计", orderNum = "3", width = 15) + private String currentYearBudgetTotal; + + @Excel(name = "年初批复预算数", orderNum = "4", width = 15) + private String initialApprovedBudget; + + @Excel(name = "追加预算数", orderNum = "5", width = 15) + private String additionalBudgetAmount; + + @Excel(name = "实际拨款数", orderNum = "6", width = 15) + private String actualAppropriation; + + @Excel(name = "指标结余", orderNum = "7", width = 15) + private String indicatorBalance; + + @Excel(name = "工作底稿索引", orderNum = "8", width = 20) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/BudgetManageExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/BudgetManageExportEntity.java new file mode 100644 index 0000000..e9e78e1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/BudgetManageExportEntity.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 预算管理审计表导出实体 + */ +@Data +@ExcelTarget("BudgetManageExportEntity") +public class BudgetManageExportEntity { + + @Excel(name = "预算科目名称", orderNum = "1", width = 25) + private String budgetSubject; + + @Excel(name = "指标来源-合计", orderNum = "2", width = 15) + private String indicatorSourceTotal; + + @Excel(name = "指标来源-上年结余", orderNum = "3", width = 15) + private String indicatorSourceLastYearBalance; + + @Excel(name = "指标来源-年初部门预算", orderNum = "4", width = 15) + private String indicatorSourceInitialBudget; + + @Excel(name = "指标来源-追加(减)预算", orderNum = "5", width = 15) + private String indicatorSourceAdditionalBudget; + + @Excel(name = "指标运用-合计", orderNum = "6", width = 15) + private String indicatorUseTotal; + + @Excel(name = "指标运用-拨款小计", orderNum = "7", width = 15) + private String indicatorUseAppropriationSubtotal; + + @Excel(name = "指标运用-拨款", orderNum = "8", width = 15) + private String indicatorUseAppropriation; + + @Excel(name = "指标运用-工资统发", orderNum = "9", width = 15) + private String indicatorUseSalaryPayment; + + @Excel(name = "指标运用-政府采购", orderNum = "10", width = 15) + private String indicatorUseGovProcurement; + + @Excel(name = "财政管理专户", orderNum = "11", width = 15) + private String financeManagementAccount; + + @Excel(name = "部门预算用于其他", orderNum = "12", width = 15) + private String budgetUsedForOther; + + @Excel(name = "指标结余", orderNum = "13", width = 15) + private String indicatorBalance; + + @Excel(name = "政府采购", orderNum = "14", width = 15) + private String governmentProcurement; + + @Excel(name = "应拨单位款", orderNum = "15", width = 15) + private String payableToUnit; + + @Excel(name = "其他", orderNum = "16", width = 15) + private String other; + + @Excel(name = "工作底稿索引", orderNum = "17", width = 20) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/DecisionTableExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/DecisionTableExportEntity.java new file mode 100644 index 0000000..57b440e --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/DecisionTableExportEntity.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 重大经济决策调查表导出实体 + */ +@Data +@ExcelTarget("DecisionTableExportEntity") +public class DecisionTableExportEntity { + + @Excel(name = "序号", orderNum = "1", width = 8) + private String index; + + @Excel(name = "重大经济决策事项", orderNum = "2", width = 30) + private String decisionItem; + + @Excel(name = "会议时间", orderNum = "3", width = 15) + private String meetingTime; + + @Excel(name = "决策事项金额", orderNum = "4", width = 15) + private String decisionAmount; + + @Excel(name = "程序", orderNum = "5", width = 20) + private String procedure; + + @Excel(name = "执行情况(是/否)", orderNum = "6", width = 15) + private String executionStatus; + + @Excel(name = "好", orderNum = "7", width = 8) + private String good; + + @Excel(name = "一般", orderNum = "8", width = 8) + private String normal; + + @Excel(name = "差", orderNum = "9", width = 8) + private String bad; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/EightRegExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/EightRegExportEntity.java new file mode 100644 index 0000000..ae5e348 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/EightRegExportEntity.java @@ -0,0 +1,27 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 八项规定导出实体 + */ +@Data +public class EightRegExportEntity { + + @Excel(name = "审计标题", orderNum = "1", width = 8) + private String title; + + @Excel(name = "审计内容", orderNum = "1", width = 8) + private String content; + + @Excel(name = "审计检查证据", orderNum = "1", width = 8) + private String testContent; + + @Excel(name = "测试结果", orderNum = "1", width = 8) + private String result; + + @Excel(name = "工作底稿索引", orderNum = "1", width = 8) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/ExpenseExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/ExpenseExportEntity.java new file mode 100644 index 0000000..8dbcdde --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/ExpenseExportEntity.java @@ -0,0 +1,38 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +/** + * 支出情况表导出实体类 + */ +@Data +public class ExpenseExportEntity { + + @Excel(name = "支出类型", orderNum = "0", width = 12) + private String expenseType; + + @Excel(name = "年份", orderNum = "1", width = 8) + private String year; + + @Excel(name = "决算报表数(元)", orderNum = "2", width = 15) + private String finalStatementAmount; + + @Excel(name = "年初预算数(元)", orderNum = "3", width = 15) + private String initialBudgetAmount; + + @Excel(name = "增减情况(%)", orderNum = "4", width = 12) + private String changePercentage; + + @Excel(name = "占年初预算比例(%)", orderNum = "5", width = 12) + private String budgetRatio; + + @Excel(name = "备注", orderNum = "6", width = 20) + private String remark; + + @Excel(name = "数据来源", orderNum = "7", width = 20) + private String dataSource; + + @Excel(name = "工作底稿索引", orderNum = "8", width = 25) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/HistoryTableExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/HistoryTableExportEntity.java new file mode 100644 index 0000000..7571d28 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/HistoryTableExportEntity.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 历史审计问题整改表导出实体 + */ +@Data +@ExcelTarget("HistoryTableExportEntity") +public class HistoryTableExportEntity { + + @Excel(name = "序号", orderNum = "1", width = 8) + private String index; + + @Excel(name = "审计年度", orderNum = "2", width = 12) + private String auditYear; + + @Excel(name = "审计类型", orderNum = "3", width = 15) + private String auditType; + + @Excel(name = "审计发现的问题", orderNum = "4", width = 40, needMerge = true) + private String problemFound; + + @Excel(name = "整改要求", orderNum = "5", width = 30, needMerge = true) + private String rectificationRequirement; + + @Excel(name = "整改措施", orderNum = "6", width = 30, needMerge = true) + private String rectificationMeasures; + + @Excel(name = "整改完成情况", orderNum = "7", width = 15) + private String rectificationStatus; + + @Excel(name = "整改完成时间", orderNum = "8", width = 15) + private String completionDate; + + @Excel(name = "整改责任人", orderNum = "9", width = 15) + private String responsiblePerson; + + @Excel(name = "备注", orderNum = "10", width = 20, needMerge = true) + private String remark; + + @Excel(name = "工作底稿索引", orderNum = "11", width = 25, needMerge = true) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java new file mode 100644 index 0000000..1987abd --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 单位层面财务管理内部控制测试表导出实体 + */ +@Data +@ExcelTarget("InternalControlExportEntity") +public class InternalControlExportEntity { + + @Excel(name = "序号", orderNum = "1", width = 8) + private String index; + + @Excel(name = "控制环节", orderNum = "2", width = 25) + private String controlLink; + + @Excel(name = "控制要求", orderNum = "3", width = 25) + private String controlRequirement; + + @Excel(name = "控制活动描述", orderNum = "4", width = 30) + private String controlActivity; + + @Excel(name = "控制职责岗位", orderNum = "5", width = 20) + private String controlPosition; + + @Excel(name = "测试步骤", orderNum = "6", width = 30) + private String testSteps; + + @Excel(name = "检查的证据及测试内容", orderNum = "7", width = 40) + private String checkEvidence; + + @Excel(name = "测试结果", orderNum = "8", width = 12) + private String testResult; + + @Excel(name = "工作底稿索引", orderNum = "9", width = 25) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/InvestmentSituationExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/InvestmentSituationExportEntity.java new file mode 100644 index 0000000..f79c70c --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/InvestmentSituationExportEntity.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 重大投资情况审计表导出实体 + */ +@Data +@ExcelTarget("InvestmentSituationExportEntity") +public class InvestmentSituationExportEntity { + + @Excel(name = "审计类别", orderNum = "1", width = 20) + private String category; + + @Excel(name = "审计内容", orderNum = "2", width = 60) + private String auditContent; + + @Excel(name = "检查的证据及测试内容", orderNum = "3", width = 80) + private String checkEvidence; + + @Excel(name = "测试结果", orderNum = "4", width = 15) + private String testResult; + + @Excel(name = "工作底稿索引", orderNum = "5", width = 40) + private String workPaperIndex; + + @Excel(name = "文件索引", orderNum = "6", width = 40) + private String fileIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/LeaderListExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/LeaderListExportEntity.java new file mode 100644 index 0000000..cfa26e1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/LeaderListExportEntity.java @@ -0,0 +1,38 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +/** + * 领导班子名单导出实体 + */ +@Data +public class LeaderListExportEntity { + + @Excel(name = "单位", orderNum = "1", width = 8) + private String unit; + + @Excel(name = "姓名", orderNum = "1", width = 8) + private String name; + + @Excel(name = "部门", orderNum = "1", width = 8) + private String department; + + @Excel(name = "党内职务", orderNum = "1", width = 8) + private String partyPosition; + + @Excel(name = "行政职务", orderNum = "1", width = 8) + private String adminPosition; + + @Excel(name = "任职期间", orderNum = "1", width = 8) + private String tenurePeriod; + + @Excel(name = "主要工作责任", orderNum = "1", width = 8) + private String mainResponsibilities; + + @Excel(name = "备注", orderNum = "1", width = 8) + private String remark; + + @Excel(name = "工作底稿索引", orderNum = "1", width = 8) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/PartyConductExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/PartyConductExportEntity.java new file mode 100644 index 0000000..103751a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/PartyConductExportEntity.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 党风廉政建设责任制审计表导出实体 + */ +@Data +@ExcelTarget("PartyConductExportEntity") +public class PartyConductExportEntity { + + @Excel(name = "大类", orderNum = "1", width = 20, isWrap = true) + private String category; + + @Excel(name = "子类", orderNum = "2", width = 25, isWrap = true) + private String subCategory; + + @Excel(name = "小类", orderNum = "3", width = 15, isWrap = true) + private String detailCategory; + + @Excel(name = "内容", orderNum = "4", width = 40, isWrap = true) + private String content; + + @Excel(name = "执行情况", orderNum = "5", width = 12) + private String executionStatus; + + @Excel(name = "工作底稿索引", orderNum = "6", width = 30, isWrap = true) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/PersonnelExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/PersonnelExportEntity.java new file mode 100644 index 0000000..1f65bb1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/PersonnelExportEntity.java @@ -0,0 +1,40 @@ +// PersonnelExportEntity.java +package com.gxwebsoft.ai.dto.export; + +import lombok.Data; + +/** + * 人员编制管理审计导出实体 + */ +@Data +public class PersonnelExportEntity { + /** + * 序号 + */ + private String index; + + /** + * 审计内容 + */ + private String auditContent; + + /** + * 审计目标 + */ + private String auditTarget; + + /** + * 审计证据 + */ + private String auditEvidence; + + /** + * 生成结果 + */ + private String generationResult; + + /** + * 工作底稿索引 + */ + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/StateAssetsAuditExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/StateAssetsAuditExportEntity.java new file mode 100644 index 0000000..5d85e51 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/StateAssetsAuditExportEntity.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 国有资产管理审计导出实体 + */ +@Data +@ExcelTarget("StateAssetsAuditExportEntity") +public class StateAssetsAuditExportEntity { + + @Excel(name = "序号", orderNum = "0", width = 8) + private String index; + + @Excel(name = "国有资产名称", orderNum = "1", width = 30) + private String assetName; + + @Excel(name = "国有资产取得方式", orderNum = "2", width = 25) + private String acquisitionMethod; + + @Excel(name = "国有资产价值", orderNum = "3", width = 20) + private String assetValue; + + @Excel(name = "国有资产地址", orderNum = "4", width = 30) + private String assetAddress; + + @Excel(name = "面积", orderNum = "5", width = 15) + private String area; + + @Excel(name = "承租方", orderNum = "6", width = 25) + private String tenant; + + @Excel(name = "合同金额", orderNum = "7", width = 20) + private String contractAmount; + + @Excel(name = "租赁合同起止时间", orderNum = "8", width = 25) + private String leasePeriod; + + @Excel(name = "国有资产租金缴纳时间", orderNum = "9", width = 25) + private String rentPaymentTime; + + @Excel(name = "国有资产出租是否上平台", orderNum = "10", width = 25) + private String platformLease; + + @Excel(name = "国有资产出租的审批文件", orderNum = "11", width = 35) + private String approvalDoc; + + @Excel(name = "是否纳入预算", orderNum = "12", width = 15) + private String inBudget; + + @Excel(name = "备注", orderNum = "13", width = 30) + private String remark; + + @Excel(name = "工作底稿索引", orderNum = "14", width = 40) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/StrategyAuditExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/StrategyAuditExportEntity.java new file mode 100644 index 0000000..41638bb --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/StrategyAuditExportEntity.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 单位发展战略执行审计导出实体 + */ +@Data +@ExcelTarget("StrategyAuditExportEntity") +public class StrategyAuditExportEntity { + + @Excel(name = "序号", orderNum = "0", width = 10) + private String index; + +// @Excel(name = "审计分类", orderNum = "1", width = 15) +// private String category; + + @Excel(name = "审计内容", orderNum = "2", width = 50) + private String auditContent; + +// @Excel(name = "政策法规要求", orderNum = "3", width = 40) +// private String policyRequirement; + +// @Excel(name = "审计标准", orderNum = "4", width = 30) +// private String auditStandard; + + @Excel(name = "检查的证据及测试内容", orderNum = "5", width = 40) + private String checkEvidence; + + @Excel(name = "测试结果", orderNum = "6", width = 10) + private String testResult; + + @Excel(name = "工作底稿索引", orderNum = "7", width = 20) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/TargetAuditExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/TargetAuditExportEntity.java new file mode 100644 index 0000000..5d60038 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/TargetAuditExportEntity.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 目标责任制完成情况审计导出实体 + */ +@Data +@ExcelTarget("TargetAuditExportEntity") +public class TargetAuditExportEntity { + + @Excel(name = "序号", orderNum = "0", width = 8) + private String index; + + @Excel(name = "年度", orderNum = "1", width = 12) + private String year; + + @Excel(name = "上级主管部门下达目标责任制-下达文件", orderNum = "2", width = 40) + private String superiorFile; + + @Excel(name = "上级主管部门下达目标责任制-完成情况", orderNum = "3", width = 15) + private String superiorCompletion; + + @Excel(name = "上级主管部门下达目标责任制-未完成原因", orderNum = "4", width = 30) + private String superiorReason; + + @Excel(name = "单位自定下达目标责任制-计划文件", orderNum = "5", width = 40) + private String selfPlan; + + @Excel(name = "单位自定下达目标责任制-完成情况", orderNum = "6", width = 15) + private String selfCompletion; + + @Excel(name = "单位自定下达目标责任制-未完成原因", orderNum = "7", width = 30) + private String selfReason; + + @Excel(name = "备注", orderNum = "8", width = 30) + private String remark; + + @Excel(name = "工作底稿索引", orderNum = "9", width = 40) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/TripleOneExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/TripleOneExportEntity.java new file mode 100644 index 0000000..27a998d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/TripleOneExportEntity.java @@ -0,0 +1,34 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 三重一大制度对比分析表导出实体 + */ +@Data +@ExcelTarget("TripleOneExportEntity") +public class TripleOneExportEntity { + + @Excel(name = "类别", orderNum = "1", width = 15) + private String category; + + @Excel(name = "政策内容", orderNum = "2", width = 40) + private String policyContent; + + @Excel(name = "集团制度", orderNum = "3", width = 30) + private String groupSystem; + + @Excel(name = "公司制度", orderNum = "4", width = 30) + private String companyFormulation; + + @Excel(name = "检查的证据及测试内容", orderNum = "5", width = 25) + private String checkEvidence; + + @Excel(name = "测试结果", orderNum = "6", width = 10) + private String testResult; + + @Excel(name = "工作底稿索引", orderNum = "7", width = 20) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java b/src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java new file mode 100644 index 0000000..280dca1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI云文档目录表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AiCloudDoc对象", description = "AI云文档目录表") +public class AiCloudDoc implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "云目录ID") + private String categoryId; + + @Schema(description = "单位ID") + private Integer companyId; + + @Schema(description = "上级目录ID") + private Integer parentId; + + @Schema(description = "目录名称") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java b/src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java new file mode 100644 index 0000000..2fe1db2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI云文件表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AiCloudFile对象", description = "AI云文件表") +public class AiCloudFile implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "文档目录ID") + private Integer docId; + + @Schema(description = "文件名") + private String fileName; + + @Schema(description = "文件大小(字节)") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "工作空间ID") + private String workspaceId; + + @Schema(description = "云文件ID") + private String fileId; + + @Schema(description = "文件地址URL") + private String fileUrl; + + @Schema(description = "文件扩展名") + private String fileExt; + + @Schema(description = "上传时间") + private LocalDateTime uploadTime; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "上传用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiHistory.java b/src/main/java/com/gxwebsoft/ai/entity/AiHistory.java new file mode 100644 index 0000000..39acedf --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AiHistory.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * AI审计历史记录表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AiHistory对象", description = "AI审计历史记录表") +@TableName("ai_history") +public class AiHistory implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "项目ID") + private Long projectId; + + @Schema(description = "请求哈希值") + private String requestHash; + + @Schema(description = "接口名称") + private String interfaceName; + + @Schema(description = "请求参数序列化数据") + private String requestData; + + @Schema(description = "响应结果序列化数据") + private String responseData; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java b/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java new file mode 100644 index 0000000..0cec26b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java @@ -0,0 +1,104 @@ +package com.gxwebsoft.ai.enums; + +public enum AuditReportEnum { + + // 基础信息 + TITLE(00, "审计标题"), + BASIS(10, "一、审计依据"), + OBJECTIVE(20, "二、审计目标"), + SCOPE(30, "三、审计对象和范围"), + + // 被审计单位基本情况 + UNIT_OVERVIEW(41, "四、被审计单位基本情况-(一)单位概况"), + ORG_PERSONNEL(42, "四、被审计单位基本情况-(二)机构和人员相关情况"), + FINANCIAL_ACCOUNTING(43, "四、被审计单位基本情况-(三)财务会计、重大会计政策选用及变动情况"), + ANNUAL_BUSINESS(44, "四、被审计单位基本情况-(四)审计期间合并口径年度资产负债及各项业务总体情况"), + INTERNAL_CONTROL(45, "四、被审计单位基本情况-(五)相关内部控制"), + + // 审计内容和重点及审计方法 + ECONOMIC_POLICIES(51, "五、审计内容和重点及审计方法-(一)贯彻执行党和国家有关经济方针和上级决策部署情况"), + DEV_STRATEGY(52, "五、审计内容和重点及审计方法-(二)公司发展战略规划的制定、执行和效果情况以及年度责任目标完成情况"), + MAJOR_ECONOMIC(53, "五、审计内容和重点及审计方法-(三)重大经济事项的决策、执行和效果情况"), + CORP_GOVERNANCE(54, "五、审计内容和重点及审计方法-(四)公司法人治理结构的建立、健全和运行情况,内部控制制度的制定和执行情况"), + FINANCIAL_LEGAL(55, "五、审计内容和重点及审计方法-(五)公司财务的真实合法效益情况,风险管控情况,境外资产管理情况"), + INTEGRITY_COMPLIANCE(56, "五、审计内容和重点及审计方法-(六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况"), + PREV_AUDIT_ISSUES(57, "五、审计内容和重点及审计方法-(七)对以往审计中发现问题的整改情况"), + OTHER_MATTERS(58, "五、审计内容和重点及审计方法-(八)其他需要审计的事项"), + + // 重要风险的识别及应对 + RISK_IDENTIFY(61, "六、重要风险的识别及应对-(一)重要风险的识别"), + RISK_RESPONSE(62, "六、重要风险的识别及应对-(二)风险的应对策略"), + + // 其他部分 + TECHNIQUES(70, "七、审计技术方法"), + SCHEDULE(80, "八、工作步骤与时间安排"), + ORGANIZATION(90, "九、审计工作的组织实施"); + + private final Integer code; + private final String desc; + + AuditReportEnum(Integer code, String desc) { + this.code = code; + this.desc = desc; + } + + public Integer getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public String getCodeStr() { + return String.format("%02d", code); + } + + public static AuditReportEnum getByCode(Integer code) { + for (AuditReportEnum value : values()) { + if (value.code.equals(code)) { + return value; + } + } + return null; + } + + /** + * 根据代码获取描述信息 + */ + public static String getDescByCode(Integer code) { + AuditReportEnum enumValue = getByCode(code); + return enumValue != null ? enumValue.getDesc() : null; + } + + /** + * 根据代码获取枚举名称 + */ + public static String getNameByCode(Integer code) { + AuditReportEnum enumValue = getByCode(code); + return enumValue != null ? enumValue.name() : null; + } + + /** + * 根据代码获取标题部分(不带序号) + */ + public static String getTitleByCode(Integer code) { + AuditReportEnum enumValue = getByCode(code); + if (enumValue == null) return null; + + String desc = enumValue.getDesc(); + int lastDashIndex = desc.lastIndexOf("-"); + if (lastDashIndex > 0) { + return desc.substring(lastDashIndex + 1); + } + return desc; + } + + /** + * 根据代码获取完整路径(带序号) + */ + public static String getFullPathByCode(Integer code) { + AuditReportEnum enumValue = getByCode(code); + return enumValue != null ? enumValue.getDesc() : null; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/factory/KnowledgeBaseClientFactory.java b/src/main/java/com/gxwebsoft/ai/factory/KnowledgeBaseClientFactory.java new file mode 100644 index 0000000..cdba0f9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/factory/KnowledgeBaseClientFactory.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.ai.factory; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.teaopenapi.models.Config; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class KnowledgeBaseClientFactory { + + @Autowired + private KnowledgeBaseConfig config; + + private Client client; + + public Client createClient() throws Exception { + if(client == null) { + com.aliyun.teaopenapi.models.Config authConfig = new com.aliyun.teaopenapi.models.Config() + .setAccessKeyId(config.getAccessKeyId()) + .setAccessKeySecret(config.getAccessKeySecret()); + // 下方接入地址以公有云的公网接入地址为例,可按需更换接入地址。 + authConfig.endpoint = "bailian.cn-beijing.aliyuncs.com"; + client = new com.aliyun.bailian20231229.Client(authConfig); + } + + return client; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java new file mode 100644 index 0000000..4a73b3b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * AI云文档目录表Mapper + * + * @author yc + */ +public interface AiCloudDocMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") AiCloudDocParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") AiCloudDocParam param); + +} diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java new file mode 100644 index 0000000..3eb8586 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudFileParam; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * AI云文件表Mapper + * + * @author yc + */ +public interface AiCloudFileMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") AiCloudFileParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") AiCloudFileParam param); + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiHistoryMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiHistoryMapper.java new file mode 100644 index 0000000..a7cb1be --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AiHistoryMapper.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.ai.entity.AiHistory; +import com.gxwebsoft.ai.param.AiHistoryParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * AI审计历史记录表Mapper + * + * @author yc + */ +public interface AiHistoryMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") AiHistoryParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") AiHistoryParam param); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml new file mode 100644 index 0000000..be5a5a5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM ai_cloud_doc a + + + AND a.id = #{param.id} + + + AND a.category_id = #{param.categoryId} + + + AND a.company_id = #{param.companyId} + + + AND a.parent_id = #{param.parentId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.comments LIKE CONCAT('%', #{param.keywords}, '%')) + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml new file mode 100644 index 0000000..b19363a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml @@ -0,0 +1,73 @@ + + + + + + + SELECT a.* + FROM ai_cloud_file a + + + AND a.id = #{param.id} + + + AND a.doc_id = #{param.docId} + + + AND a.file_name LIKE CONCAT('%', #{param.fileName}, '%') + + + AND a.file_size = #{param.fileSize} + + + AND a.file_type = #{param.fileType} + + + AND a.workspace_id = #{param.workspaceId} + + + AND a.file_id = #{param.fileId} + + + AND a.file_ext = #{param.fileExt} + + + AND a.upload_time >= #{param.uploadTimeStart} + + + AND a.upload_time <= #{param.uploadTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND (a.file_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.file_type LIKE CONCAT('%', #{param.keywords}, '%') + OR a.file_ext LIKE CONCAT('%', #{param.keywords}, '%')) + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiHistoryMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiHistoryMapper.xml new file mode 100644 index 0000000..6c54f38 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiHistoryMapper.xml @@ -0,0 +1,64 @@ + + + + + + + SELECT a.* + FROM ai_history a + + + AND a.id = #{param.id} + + + AND a.project_id = #{param.projectId} + + + AND a.request_hash = #{param.requestHash} + + + AND a.interface_name = #{param.interfaceName} + + + AND a.user_id = #{param.userId} + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.tenant_id = #{param.tenantId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.request_hash LIKE CONCAT('%', #{param.keywords}, '%') + OR a.interface_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.username LIKE CONCAT('%', #{param.keywords}, '%')) + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java b/src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java new file mode 100644 index 0000000..f09aade --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.ai.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI云文档目录表查询参数 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "AiCloudDocParam对象", description = "AI云文档目录表查询参数") +public class AiCloudDocParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "云目录ID") + @QueryField(type = QueryType.EQ) + private String categoryId; + + @Schema(description = "单位ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "上级目录ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "目录名称") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "创建用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java b/src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java new file mode 100644 index 0000000..85c6427 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java @@ -0,0 +1,74 @@ +package com.gxwebsoft.ai.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * AI云文件表查询参数 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "AiCloudFileParam对象", description = "AI云文件表查询参数") +public class AiCloudFileParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "文档目录ID") + @QueryField(type = QueryType.EQ) + private Integer docId; + + @Schema(description = "文件名") + private String fileName; + + @Schema(description = "文件大小(字节)") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "工作空间ID") + private String workspaceId; + + @Schema(description = "云文件ID") + private String fileId; + + @Schema(description = "文件扩展名") + private String fileExt; + + @Schema(description = "上传时间开始") + private LocalDateTime uploadTimeStart; + + @Schema(description = "上传时间结束") + private LocalDateTime uploadTimeEnd; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "上传用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "关键词搜索") + private String keywords; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/param/AiHistoryParam.java b/src/main/java/com/gxwebsoft/ai/param/AiHistoryParam.java new file mode 100644 index 0000000..fe2a025 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/param/AiHistoryParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.ai.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI审计历史记录表查询参数 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "AiHistoryParam对象", description = "AI审计历史记录表查询参数") +public class AiHistoryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Long id; + + @Schema(description = "项目ID") + @QueryField(type = QueryType.EQ) + private Long projectId; + + @Schema(description = "请求哈希值") + @QueryField(type = QueryType.EQ) + private String requestHash; + + @Schema(description = "接口名称") + @QueryField(type = QueryType.EQ) + private String interfaceName; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "租户id") + @QueryField(type = QueryType.EQ) + private Integer tenantId; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java b/src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java new file mode 100644 index 0000000..d21997e --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java @@ -0,0 +1,49 @@ +package com.gxwebsoft.ai.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; +import com.gxwebsoft.common.core.web.PageResult; + +import java.util.List; + +/** + * AI云文档目录表Service + * + * @author yc + */ +public interface AiCloudDocService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(AiCloudDocParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(AiCloudDocParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return AiCloudDoc + */ + AiCloudDoc getByIdRel(Integer id); + + /** + * 根据目录ID获取本身及所有子孙目录 + * + * @param docId 目录ID + * @return List 目录列表 + */ + List getSelfAndChildren(Integer docId); + +} diff --git a/src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java b/src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java new file mode 100644 index 0000000..743a5b2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java @@ -0,0 +1,113 @@ +package com.gxwebsoft.ai.service; + +import com.gxwebsoft.common.system.entity.User; +import org.springframework.web.multipart.MultipartFile; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudFileParam; +import com.gxwebsoft.common.core.web.PageResult; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * AI云文件表Service + * + * @author yc + */ +public interface AiCloudFileService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(AiCloudFileParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(AiCloudFileParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return AiCloudFile + */ + AiCloudFile getByIdRel(Integer id); + + /** + * 异步处理文件上传 + * + * @param client 阿里云客户端 + * @param workspaceId 工作空间ID + * @param categoryId 分类ID + * @param docId 文档ID + * @param file 文件 + * @param loginUser 登录用户 + * @return CompletableFuture 返回包含文件ID的Future + */ + CompletableFuture processFileAsync(String categoryId, Integer docId, MultipartFile file, User loginUser); + + /** + * 删除文件(包括云存储和数据库记录) + * + * @param id 文件ID + * @return 是否删除成功 + */ + boolean removeFileWithCloud(Integer id); + + /** + * 批量删除文件(包括云存储和数据库记录) + * + * @param ids 文件ID列表 + * @return 是否删除成功 + */ + boolean removeFilesWithCloud(List ids); + + /** + * 获取文件扩展名 + * + * @param filename 文件名 + * @return 文件扩展名 + */ + String getFileExtension(String filename); + + /** + * 获取公司知识库ID + * + * @param docId 文档ID + * @return 知识库ID + */ + String getCompanyKbId(Integer docId); + + /** + * 提交文档到知识库 + * + * @param docId 文档ID + * @param fileIds 文件ID列表 + */ + void submitDocuments(Integer docId, List fileIds); + + /** + * 删除文件(包括云存储、数据库记录和知识库索引) + * + * @param id 文件ID + * @return 是否删除成功 + */ + boolean removeFileWithCloudAndIndex(Integer id); + + /** + * 批量删除文件(包括云存储、数据库记录和知识库索引) + * + * @param ids 文件ID列表 + * @return 是否删除成功 + */ + boolean removeFilesWithCloudAndIndex(List ids); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java b/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java new file mode 100644 index 0000000..0c18f51 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.ai.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.ai.entity.AiHistory; +import com.gxwebsoft.ai.param.AiHistoryParam; +import com.gxwebsoft.common.core.web.PageResult; + +import java.util.List; + +/** + * AI审计历史记录表Service + * + * @author yc + */ +public interface AiHistoryService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(AiHistoryParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(AiHistoryParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return AiHistory + */ + AiHistory getByIdRel(Long id); + + /** + * 获取有效的历史记录 + * + * @param requestHash 请求哈希值 + * @param interfaceName 接口名称 + * @param minutes 有效时间(分钟) + * @return AiHistory + */ + AiHistory getValidHistory(String requestHash, String interfaceName, int minutes); + + /** + * 保存历史记录 + * + * @param projectId 项目ID + * @param requestHash 请求哈希值 + * @param interfaceName 接口名称 + * @param requestData 请求数据 + * @param responseData 响应数据 + * @param userId 用户ID + * @param username 用户名 + */ + void saveHistory(Long projectId, String requestHash, String interfaceName, String requestData, String responseData, Integer userId, String username, Integer tenantId); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent10PartyConductService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent10PartyConductService.java new file mode 100644 index 0000000..ffee717 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent10PartyConductService.java @@ -0,0 +1,15 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容10服务接口 - 党风廉政建设责任制审计 + */ +public interface AuditContent10PartyConductService { + + /** + * 生成党风廉政建设责任制审计表数据 + */ + JSONObject generatePartyConductTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent11HistoryService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent11HistoryService.java new file mode 100644 index 0000000..3a29fbd --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent11HistoryService.java @@ -0,0 +1,23 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容11-历史审计问题整改服务接口 + */ +public interface AuditContent11HistoryService { + + /** + * 生成历史审计问题整改表数据 + * + * @param kbIds 知识库ID + * @param libraryKbIds 项目库知识库ID + * @param projectLibrary 项目库 + * @param userName 用户名 + * @param history 历史内容 + * @param suggestion 用户建议 + * @return JSON结果 + */ + JSONObject generateHistoryTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent1EightRegService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent1EightRegService.java new file mode 100644 index 0000000..4b161e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent1EightRegService.java @@ -0,0 +1,11 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +public interface AuditContent1EightRegService { + + /** + * 生成八项规定对比分析表数据 + */ + JSONObject generateEightRegTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent1ExpenseService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent1ExpenseService.java new file mode 100644 index 0000000..045ceb4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent1ExpenseService.java @@ -0,0 +1,21 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 支出情况表服务接口 + */ +public interface AuditContent1ExpenseService { + + /** + * 生成支出情况表数据 + * @param kbIds 知识库ID + * @param libraryKbIds 项目库KB IDs + * @param projectLibrary 项目库ID + * @param userName 用户名 + * @param history 历史记录 + * @param suggestion 建议 + * @return JSON格式的结果 + */ + JSONObject generateExpenseTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent1LeaderListService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent1LeaderListService.java new file mode 100644 index 0000000..f56951b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent1LeaderListService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 领导班子名单服务接口 + */ +public interface AuditContent1LeaderListService { + + /** + * 生成领导班子名单数据 + */ + JSONObject generateLeaderListTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent2StrategyService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent2StrategyService.java new file mode 100644 index 0000000..cb91867 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent2StrategyService.java @@ -0,0 +1,24 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 单位发展战略执行审计服务接口 + */ +public interface AuditContent2StrategyService { + + /** + * 生成单位发展战略执行审计表数据 + * + * @param kbIds 企业知识库ID(多个用逗号分隔) + * @param libraryKbIds 公共法律法规库ID(多个用逗号分隔) + * @param projectLibrary 审计案例库ID + * @param userName 用户名 + * @param history 历史记录 + * @param suggestion 用户建议 + * @return JSON格式的审计表数据 + */ + JSONObject generateStrategyAuditTableData(String kbIds, String libraryKbIds, + String projectLibrary, String userName, + String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent3DecisionService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent3DecisionService.java new file mode 100644 index 0000000..011e6e9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent3DecisionService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容3 - 重大经济决策调查表 + */ +public interface AuditContent3DecisionService { + + /** + * 生成重大经济决策调查表数据 + */ + JSONObject generateDecisionTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion, Object tripleOneData); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent3TripleService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent3TripleService.java new file mode 100644 index 0000000..985f6f0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent3TripleService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容3 - 三重一大 + */ +public interface AuditContent3TripleService { + + /** + * 生成三重一大制度对比分析表数据 + */ + JSONObject generateTripleOneTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent4TargetService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent4TargetService.java new file mode 100644 index 0000000..eaff64c --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent4TargetService.java @@ -0,0 +1,16 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容4-目标责任制完成情况服务接口 + */ +public interface AuditContent4TargetService { + + /** + * 生成目标责任制完成情况审计表数据 + */ + JSONObject generateTargetAuditTableData(String kbIds, String libraryKbIds, + String projectLibrary, String userName, + String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetExecutionService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetExecutionService.java new file mode 100644 index 0000000..055e5d0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetExecutionService.java @@ -0,0 +1,23 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容5-预算执行情况审计服务接口 + */ +public interface AuditContent5BudgetExecutionService { + + /** + * 生成预算执行情况审计表数据 + * @param kbIds 知识库ID + * @param libraryKbIds 项目库知识库ID + * @param projectLibrary 项目库 + * @param userName 用户名 + * @param history 历史记录 + * @param suggestion 用户建议 + * @param data 前端传入的预算管理审计数据(可选) + * @return JSON格式的审计表数据 + */ + JSONObject generateBudgetExecutionTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion, Object data); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetManageService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetManageService.java new file mode 100644 index 0000000..062e897 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent5BudgetManageService.java @@ -0,0 +1,22 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容5-预算管理审计服务接口 + */ +public interface AuditContent5BudgetManageService { + + /** + * 生成预算管理审计表数据 + * @param kbIds 知识库ID + * @param libraryKbIds 项目库知识库ID + * @param projectLibrary 项目库 + * @param userName 用户名 + * @param history 历史记录 + * @param suggestion 用户建议 + * @return JSON格式的审计表数据 + */ + JSONObject generateBudgetManageTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion, Object data); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent6StateAssetsService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent6StateAssetsService.java new file mode 100644 index 0000000..875036d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent6StateAssetsService.java @@ -0,0 +1,15 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容6-国资管理情况服务接口 + */ +public interface AuditContent6StateAssetsService { + + /** + * 生成国有资产管理审计表数据 + */ + JSONObject generateStateAssetsAuditTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent7InvestmentService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent7InvestmentService.java new file mode 100644 index 0000000..0d4401a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent7InvestmentService.java @@ -0,0 +1,23 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +/** + * 审计内容7-重大投资情况服务接口 + */ +public interface AuditContent7InvestmentService { + + /** + * 生成重大投资情况审计表数据 + * @param kbIds 知识库ID + * @param libraryKbIds 项目库知识库ID + * @param projectLibrary 审计案例库ID + * @param userName 用户名 + * @param history 历史记录 + * @param suggestion 用户建议 + * @return 生成的表格数据 + */ + JSONObject generateInvestmentSituationTableData(String kbIds, String libraryKbIds, + String projectLibrary, String userName, + String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java new file mode 100644 index 0000000..75a0169 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java @@ -0,0 +1,13 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +public interface AuditContent8InternalControlService { + + /** + * 生成单位层面财务管理内部控制测试表数据 + */ + JSONObject generateInternalControlTableData( + String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent9PersonnelService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent9PersonnelService.java new file mode 100644 index 0000000..be489fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent9PersonnelService.java @@ -0,0 +1,13 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +public interface AuditContent9PersonnelService { + + /** + * 生成人员编制管理审计表数据 + */ + JSONObject generatePersonnelTableData( + String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java b/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java new file mode 100644 index 0000000..d711f32 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java @@ -0,0 +1,11 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditEvidenceRequest; + +public interface AuditEvidenceService { + /** + * 生成审计取证单 + */ + JSONObject generateAuditEvidence(AuditEvidenceRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java b/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java new file mode 100644 index 0000000..40efa9c --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +public interface AuditReportService { + + /** + * 生成完整审计报告 + */ + JSONObject generateCompleteAuditReport(String auditContent, String fileName, + String kbId, String libraryIds, + String analysisLibrary, String projectLibrary, + String userName); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java b/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java new file mode 100644 index 0000000..3f59a7f --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.ai.service; + +import com.gxwebsoft.ai.dto.KnowledgeBaseRequest; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.web.multipart.MultipartFile; + +public interface KnowledgeBaseService { + + /** + * 查询知识库 + */ + Set queryKnowledgeBase(KnowledgeBaseRequest req); + + /** + * 查询知识库(带参数) + */ + Set queryKnowledgeBase(String kbId, String query, Integer topK, Integer formCommit); + + /** + * 创建知识库 + */ + String createKnowledgeBase(String companyName, String companyCode); + + /** + * 创建临时知识库 + */ + String createKnowledgeBaseTemp(); + + /** + * 检查知识库是否存 + */ + boolean existsKnowledgeBase(String companyCode); + + /** + * 通过知识库名称查询知识库ID + */ + String getKnowledgeBaseIdByName(String companyCode); + + /** + * 查询知识库下的文档列表 + */ + Map listDocuments(String kbId, Integer pageSize, Integer pageNumber); + + /** + * 删除知识库 + */ + boolean deleteIndex(String kbId); + + /** + * 删除知识库下的文档 + */ + boolean deleteIndexDocument(String kbId, String fileIds); + + /** + * 上传知识库文档 + */ + List uploadDocuments(String kbId, MultipartFile[] files); + + /** + * 知识库追加导入已解析的文档 + */ + boolean submitDocuments(String kbId, String fileId); + + /** + * 知识库追加导入已解析的文档 + */ + boolean submitDocuments(String kbId, List fileIds); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java b/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java new file mode 100644 index 0000000..52e2e30 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java @@ -0,0 +1,450 @@ +package com.gxwebsoft.ai.service.impl; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.RetrieveResponse; +import com.aliyun.bailian20231229.models.RetrieveResponseBody; +import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyData; +import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyDataNodes; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import com.gxwebsoft.common.core.context.TenantContext; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Slf4j +public abstract class AbstractAuditContentService { + + @Autowired + protected KnowledgeBaseClientFactory clientFactory; + + @Autowired + protected KnowledgeBaseConfig config; + + @Autowired + protected AiCloudFileService aiCloudFileService; + + protected static final String DIFY_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; + + // 用于同步的锁对象池 + private static final Map kbLocks = new ConcurrentHashMap<>(); + + /** + * 调用工作流通用方法 + */ + protected JSONArray callWorkflow(String url, String token, JSONObject requestBody, String workflowName) { + try { + log.info("调用{}工作流,请求体长度: {}", workflowName, requestBody.toString().length()); + + String result = HttpUtil.createPost(url) + .header("Authorization", token) + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(10 * 60 * 1000) + .execute() + .body(); + + log.info("{}工作流返回结果长度: {}", workflowName, result.length()); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(result); + + String outputText = rootNode.path("data") + .path("outputs") + .path("result") + .asText(); + + if (StrUtil.isBlank(outputText)) { + log.warn("{}工作流返回 result 为空", workflowName); + return new JSONArray(); + } + + // ★ NEW:先从 ```json``` 中提取 + String jsonText = extractJsonFromMarkdown(outputText); + + // ★ NEW:兜底提取 + if (StrUtil.isBlank(jsonText)) { + jsonText = extractFirstJson(outputText); + } + + if (StrUtil.isBlank(jsonText)) { + log.error("{} 工作流返回内容无法解析为 JSON,原始内容:{}", workflowName, outputText); + throw new RuntimeException("Dify 返回内容中未找到有效 JSON"); + } + + JSONArray jsonArray = JSONArray.parseArray(jsonText); + + log.info("成功解析{}工作流返回数据,记录数: {}", workflowName, jsonArray.size()); + return jsonArray; + + } catch (Exception e) { + log.error("调用{}工作流失败", workflowName, e); + throw new RuntimeException("调用" + workflowName + "工作流失败: " + e.getMessage(), e); + } + } + + /** + * 从 ```json 代码块中提取 JSON + */ + private static String extractJsonFromMarkdown(String text) { + if (StrUtil.isBlank(text)) { + return null; + } + Pattern pattern = Pattern.compile("```json\\s*(.*?)\\s*```", Pattern.DOTALL); + Matcher matcher = pattern.matcher(text); + if (matcher.find()) { + return matcher.group(1).trim(); + } + return null; + } + + /** + * 兜底:从混合文本中提取第一个完整 JSON({} 或 []) + */ + private static String extractFirstJson(String text) { + if (StrUtil.isBlank(text)) return null; + + int start = -1; + char open = 0, close = 0; + + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '{' || c == '[') { + start = i; + open = c; + close = (c == '{') ? '}' : ']'; + break; + } + } + if (start == -1) return null; + + int count = 0; + for (int i = start; i < text.length(); i++) { + char c = text.charAt(i); + if (c == open) count++; + if (c == close) count--; + if (count == 0) { + return text.substring(start, i + 1); + } + } + return null; + } + + + + /** + * 构建工作流请求通用方法 + */ + protected JSONObject buildWorkflowRequest(String knowledge, String userName) { + return buildWorkflowRequest(knowledge, userName, null); + } + + protected JSONObject buildWorkflowRequest(String knowledge, String userName, Integer timeout) { + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + + inputs.put("knowledge", knowledge); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + if (timeout != null) { + requestBody.put("timeout", timeout); + } + + return requestBody; + } + + /** + * 查询知识库通用方法 + */ + protected List queryKnowledgeBase(String kbId, List queries, int topK) { + Object lock = kbLocks.computeIfAbsent(kbId, k -> new Object()); + + synchronized (lock) { + try { + // 1. 收集所有节点和文档ID + List allNodes = collectKnowledgeNodes(kbId, queries, topK); + if (allNodes.isEmpty()) { + return new ArrayList<>(); + } + + // 2. 批量查询文件URL + Map fileUrlMap = TenantContext.callIgnoreTenant(() -> batchQueryFileUrls(allNodes)); + + // 3. 处理节点生成结果 + return processNodesToResults(allNodes, fileUrlMap); + + } catch (Exception e) { + log.error("查询知识库失败 - kbId: {}", kbId, e); + return new ArrayList<>(); + } + } + } + + /** + * 收集知识库节点 + */ + private List collectKnowledgeNodes(String kbId, List queries, int topK) { + List allNodes = new ArrayList<>(); + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + for (String query : queries) { + try { + RetrieveResponse resp = AiCloudKnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query); + List nodes = Optional.ofNullable(resp) + .map(RetrieveResponse::getBody) + .map(RetrieveResponseBody::getData) + .map(RetrieveResponseBodyData::getNodes) + .orElse(Collections.emptyList()) + .stream() + .limit(topK) + .collect(Collectors.toList()); + allNodes.addAll(nodes); + } catch (Exception e) { + log.warn("查询知识库失败 - kbId: {}, query: {}", kbId, query, e); + } + } + } catch (Exception e) { + log.error("创建知识库客户端失败", e); + } + return allNodes; + } + + /** + * 批量查询文件URL + */ + protected Map batchQueryFileUrls(List nodes) { + // 收集所有文档ID + Set docIds = nodes.stream().map(this::extractDocumentId).filter(StrUtil::isNotBlank).collect(Collectors.toSet()); + if (docIds.isEmpty()) { + return Collections.emptyMap(); + } + try { + // 批量查询 + List files = aiCloudFileService.list(new LambdaQueryWrapper().in(AiCloudFile::getFileId, docIds)); + // 构建映射表 + return files.stream() + .filter(file -> file.getFileUrl() != null) + .collect(Collectors.toMap( + AiCloudFile::getFileId, + AiCloudFile::getFileUrl + )); + } catch (Exception e) { + log.error("批量查询文件信息失败", e); + return Collections.emptyMap(); + } + } + + /** + * 处理节点生成结果 + */ + private List processNodesToResults(List nodes, Map fileUrlMap) { + Set results = new LinkedHashSet<>(); + + // 构建可用文件列表 + StringBuilder fileListBuilder = new StringBuilder(); + fileListBuilder.append("\n\n**可用真实文件列表(仅限以下文件,不得虚构其他文件名):**\n"); + + Set processedFiles = new HashSet<>(); // 避免重复 + + for (RetrieveResponseBodyDataNodes node : nodes) { + try { + // 检查文本有效性 + String text = node.getText(); + if (StrUtil.isBlank(text) || text.length() < 10) { + continue; + } + + // 获取文档信息 + String docName = extractDocumentName(node); + String docId = extractDocumentId(node); + String fileUrl = fileUrlMap.get(docId); + if (StrUtil.isBlank(fileUrl)) { + fileUrl = extractDocumentUrl(node); + } + if (StrUtil.isBlank(fileUrl)) { + fileUrl = "无链接"; + } + + // 添加文件名到列表(去重) + if (StrUtil.isNotBlank(docName) && !processedFiles.contains(docName)) { + processedFiles.add(docName); + fileListBuilder.append("- 《").append(docName).append("》"); + if (!"无链接".equals(fileUrl)) { + fileListBuilder.append(" || ").append(fileUrl); + } + fileListBuilder.append("\n"); + } + + // 格式化为知识库内容 + JSONObject json = new JSONObject(); + json.put("document_name", docName); + json.put("file_url", fileUrl); + json.put("content", text); + String formattedText = json.toJSONString(); + results.add(formattedText); + + } catch (Exception e) { + log.warn("处理知识库节点失败", e); + } + } + + // 添加文件列表到结果 + fileListBuilder.append("\n**重要约束:**\n"); + fileListBuilder.append("1. 只能使用上述列表中的文件,不得虚构其他文件名\n"); + fileListBuilder.append("2. 如果某项检查未涉及上述文件,则 workPaperIndex 填写\"[]\"\n"); + fileListBuilder.append("3. 所有文件必须来自提供的知识库内容\n"); + + results.add(fileListBuilder.toString()); + + return new ArrayList<>(results); + } + + /** + * 提取文档名称通用方法 + */ + protected String extractDocumentName(RetrieveResponseBodyDataNodes node) { + try { + Object metadataObj = node.getMetadata(); + if (metadataObj instanceof Map) { + Map metadata = (Map) metadataObj; + Object docNameObj = metadata.get("doc_name"); + if (docNameObj != null) { + return docNameObj.toString(); + } + } + } catch (Exception e) { + log.debug("提取文档名称失败", e); + } + return "相关文档"; + } + + /** + * 提取文档Id通用方法 + */ + protected String extractDocumentId(RetrieveResponseBodyDataNodes node) { + try { + Object metadataObj = node.getMetadata(); + if (metadataObj instanceof Map) { + Map metadata = (Map) metadataObj; + Object docIdObj = metadata.get("doc_id"); + if (docIdObj != null) { + return docIdObj.toString(); + } + } + } catch (Exception e) { + log.debug("提取文档名称失败", e); + } + return "相关文档"; + } + + /** + * 提取文档url通用方法 + */ + protected String extractDocumentUrl(RetrieveResponseBodyDataNodes node) { + try { + Object metadataObj = node.getMetadata(); + if (metadataObj instanceof Map) { + Map metadata = (Map) metadataObj; + Object docIdObj = metadata.get("doc_url"); + if (docIdObj != null) { + return docIdObj.toString(); + } + } + } catch (Exception e) { + log.debug("提取文档名称失败", e); + } + return "相关文档"; + } + + /** + * 构建成功响应通用方法 + */ + protected JSONObject buildSuccessResponse(JSONArray data, long startTime) { + return buildSuccessResponse(data, startTime, null); + } + + protected JSONObject buildSuccessResponse(JSONArray data, long startTime, String dataSource) { + JSONObject result = new JSONObject(); + result.put("success", true); + result.put("data", data); + result.put("total_records", data.size()); + result.put("generated_time", new Date().toString()); + result.put("processing_time", (System.currentTimeMillis() - startTime) + "ms"); + if (dataSource != null) { + result.put("data_source", dataSource); + } + return result; + } + + /** + * 构建失败响应通用方法 + */ + protected JSONObject buildErrorResponse(String errorMessage) { + JSONObject result = new JSONObject(); + result.put("success", false); + result.put("error", errorMessage); + return result; + } + + /** + * 异步处理分类数据通用方法 + */ + protected Map> processCategoriesAsync( + List categories, + CategoryProcessor processor) { + + Map> futures = new LinkedHashMap<>(); + for (String category : categories) { + CompletableFuture future = processor.processCategory(category); + futures.put(category, future); + } + return futures; + } + + /** + * 合并分类结果通用方法 + */ + protected JSONArray mergeCategoryResults(List categoryOrder, + Map> futures) { + JSONArray allData = new JSONArray(); + for (String category : categoryOrder) { + try { + JSONArray categoryData = futures.get(category).get(); + if (categoryData != null && !categoryData.isEmpty()) { + allData.addAll(categoryData); + } + } catch (Exception e) { + log.error("获取分类 {} 数据失败", category, e); + } + } + return allData; + } + + /** + * 分类处理器函数式接口 + */ + @FunctionalInterface + public interface CategoryProcessor { + CompletableFuture processCategory(String category); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java new file mode 100644 index 0000000..a3924f2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java @@ -0,0 +1,90 @@ +package com.gxwebsoft.ai.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.ai.mapper.AiCloudDocMapper; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * AI云文档目录表Service实现 + * + * @author yc + */ +@Service +public class AiCloudDocServiceImpl extends ServiceImpl implements AiCloudDocService { + + @Override + public PageResult pageRel(AiCloudDocParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(AiCloudDocParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public AiCloudDoc getByIdRel(Integer id) { + AiCloudDocParam param = new AiCloudDocParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public List getSelfAndChildren(Integer docId) { + // 查询所有未删除的目录 + AiCloudDocParam param = new AiCloudDocParam(); + param.setDeleted(0); + List allDocs = this.listRel(param); + + // 查找指定目录 + AiCloudDoc targetDoc = allDocs.stream() + .filter(doc -> doc.getId().equals(docId)) + .findFirst() + .orElse(null); + + if (targetDoc == null) { + return new ArrayList<>(); + } + + // 递归获取所有子孙目录 + List result = new ArrayList<>(); + result.add(targetDoc); + getChildrenRecursive(allDocs, targetDoc.getId(), result); + + return result; + } + + /** + * 递归获取所有子目录 + * + * @param allDocs 所有目录列表 + * @param parentId 父目录ID + * @param result 结果列表 + */ + private void getChildrenRecursive(List allDocs, Integer parentId, List result) { + List children = allDocs.stream() + .filter(doc -> parentId.equals(doc.getParentId())) + .collect(Collectors.toList()); + + for (AiCloudDoc child : children) { + result.add(child); + getChildrenRecursive(allDocs, child.getId(), result); + } + } +} diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java new file mode 100644 index 0000000..c38d5a3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java @@ -0,0 +1,238 @@ +package com.gxwebsoft.ai.service.impl; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.AddFileResponse; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.util.AiCloudDataCenterUtil; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import com.gxwebsoft.common.system.controller.FileController; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.ai.mapper.AiCloudFileMapper; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.ai.param.AiCloudFileParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaCompany; +import com.gxwebsoft.oa.service.OaCompanyService; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * AI云文件表Service实现 + * + * @author yc + */ +@Slf4j +@Service +public class AiCloudFileServiceImpl extends ServiceImpl implements AiCloudFileService { + + @Resource + private KnowledgeBaseConfig config; + + @Resource + private KnowledgeBaseClientFactory clientFactory; + + @Resource + private AiCloudDocService aiCloudDocService; + + @Resource + @Lazy + private OaCompanyService oaCompanyService; + + @Resource + private KnowledgeBaseService knowledgeBaseService; + + @Resource + private FileController fileController; + + @Override + public PageResult pageRel(AiCloudFileParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, upload_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(AiCloudFileParam param) { + List list = baseMapper.selectListRel(param); + //文件名排序 + list.sort(Comparator.comparing(AiCloudFile::getFileName)); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, upload_time desc"); + return page.sortRecords(list); + } + + @Override + public AiCloudFile getByIdRel(Integer id) { + AiCloudFileParam param = new AiCloudFileParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Async + @Override + public CompletableFuture processFileAsync(String categoryId, Integer docId, MultipartFile file, User loginUser) { + return CompletableFuture.supplyAsync(() -> { + try { + String workspaceId = config.getWorkspaceId(); + Client client = clientFactory.createClient(); + AddFileResponse addFileResponse = AiCloudDataCenterUtil.uploadFile(client, workspaceId, categoryId, file); + String fileId = addFileResponse.getBody().getData().getFileId(); + + FileRecord fileRecord = fileController.upload(file, loginUser.getTenantId()); + + // 保存文件信息到数据库 + AiCloudFile aiCloudFile = new AiCloudFile(); + aiCloudFile.setDocId(docId); + aiCloudFile.setFileName(file.getOriginalFilename()); + aiCloudFile.setFileSize(file.getSize()); + aiCloudFile.setFileType(getFileExtension(file.getOriginalFilename())); + aiCloudFile.setFileExt(getFileExtension(file.getOriginalFilename())); + aiCloudFile.setFileId(fileId); + aiCloudFile.setFileUrl(fileRecord.getDownloadUrl()); + aiCloudFile.setUploadTime(LocalDateTime.now()); + aiCloudFile.setWorkspaceId(workspaceId); + + if (loginUser != null) { + aiCloudFile.setUserId(loginUser.getUserId()); + aiCloudFile.setTenantId(loginUser.getTenantId()); + } + + this.save(aiCloudFile); + log.info("文件上传成功: {}", file.getOriginalFilename()); + return fileId; + + } catch (Exception e) { + log.error("异步处理文件上传失败: {}", e.getMessage(), e); + throw new RuntimeException("文件上传失败: " + file.getOriginalFilename(), e); + } + }); + } + + @Override + public boolean removeFileWithCloud(Integer id) { + AiCloudFile aiCloudFile = this.getById(id); + if (aiCloudFile == null) { + return false; + } + + try { + String workspaceId = config.getWorkspaceId(); + Client client = clientFactory.createClient(); + AiCloudDataCenterUtil.deleteFile(client, workspaceId, aiCloudFile.getFileId()); + } catch (Exception e) { + log.error("删除云文件失败: {}", e.getMessage(), e); + return false; + } + + return this.removeById(id); + } + + @Override + public boolean removeFilesWithCloud(List ids) { + boolean allSuccess = true; + for (Integer id : ids) { + if (!removeFileWithCloud(id)) { + allSuccess = false; + } + } + return allSuccess; + } + + @Override + public String getFileExtension(String filename) { + if (filename == null || filename.lastIndexOf(".") == -1) { + return ""; + } + return filename.substring(filename.lastIndexOf(".") + 1); + } + + @Override + public String getCompanyKbId(Integer docId) { + if (docId == null) { + return ""; + } + AiCloudDoc aiCloudDoc = aiCloudDocService.getById(docId); + if (aiCloudDoc == null || aiCloudDoc.getCompanyId() == null) { + return ""; + } + OaCompany oaCompany = oaCompanyService.getById(aiCloudDoc.getCompanyId()); + return oaCompany != null && oaCompany.getKbId() != null ? oaCompany.getKbId() : ""; + } + + @Override + public void submitDocuments(Integer docId, List fileIds) { + if (docId == null || fileIds == null || fileIds.isEmpty()) { + return; + } + String kbId = getCompanyKbId(docId); + if (kbId == null || kbId.isEmpty()) { + return; + } + // 异步添加文件 + for(String fileId : fileIds) { + if (fileId != null && !fileId.isEmpty()) { + knowledgeBaseService.submitDocuments(kbId, fileId); + } + } + } + + @Override + public boolean removeFileWithCloudAndIndex(Integer id) { + AiCloudFile aiCloudFile = this.getById(id); + if (aiCloudFile == null) { + return false; + } + + // 删除云文件 + if (!removeFileWithCloud(id)) { + return false; + } + + // 同时删除默认知识库文件 + String kbId = getCompanyKbId(aiCloudFile.getDocId()); + if (!kbId.isEmpty()) { + try { + String workspaceId = config.getWorkspaceId(); + Client client = clientFactory.createClient(); + AiCloudKnowledgeBaseUtil.deleteIndexDocument(client, workspaceId, kbId, Arrays.asList(aiCloudFile.getFileId())); + } catch (Exception e) { + log.error("删除知识库索引失败: {}", e.getMessage(), e); + // 这里可以根据业务需求决定是否抛出异常 + } + } + + return true; + } + + @Override + public boolean removeFilesWithCloudAndIndex(List ids) { + boolean allSuccess = true; + for (Integer id : ids) { + if (!removeFileWithCloudAndIndex(id)) { + allSuccess = false; + } + } + return allSuccess; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java new file mode 100644 index 0000000..f69837f --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.ai.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.ai.entity.AiHistory; +import com.gxwebsoft.ai.mapper.AiHistoryMapper; +import com.gxwebsoft.ai.param.AiHistoryParam; +import com.gxwebsoft.ai.service.AiHistoryService; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * AI审计历史记录表Service实现 + * + * @author yc + */ +@Service +public class AiHistoryServiceImpl extends ServiceImpl implements AiHistoryService { + + @Override + public PageResult pageRel(AiHistoryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(AiHistoryParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public AiHistory getByIdRel(Long id) { + AiHistoryParam param = new AiHistoryParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public AiHistory getValidHistory(String requestHash, String interfaceName, int minutes) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AiHistory::getRequestHash, requestHash) + .eq(AiHistory::getInterfaceName, interfaceName) + .eq(AiHistory::getStatus, 0) + .eq(AiHistory::getDeleted, 0) + .ge(AiHistory::getCreateTime, LocalDateTime.now().minusMinutes(minutes)); + return getOne(queryWrapper, false); + } + + @Override + public void saveHistory(Long projectId, String requestHash, String interfaceName, String requestData, String responseData, Integer userId, String username, Integer tenantId) { + AiHistory history = new AiHistory(); + history.setProjectId(projectId); + history.setRequestHash(requestHash); + history.setInterfaceName(interfaceName); + history.setRequestData(requestData); + history.setResponseData(responseData); + history.setUserId(userId); + history.setUsername(username); + history.setStatus(0); + history.setDeleted(0); + history.setTenantId(tenantId); + + save(history); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent10PartyConductServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent10PartyConductServiceImpl.java new file mode 100644 index 0000000..d52ee28 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent10PartyConductServiceImpl.java @@ -0,0 +1,307 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent10PartyConductConstants; +import com.gxwebsoft.ai.service.AuditContent10PartyConductService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent10PartyConductServiceImpl extends AbstractAuditContentService implements AuditContent10PartyConductService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-NpWFaPdBAl3LH7HqRXenlnVD"; + + @Override + public JSONObject generatePartyConductTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + log.info("开始生成党风廉政建设责任制审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 获取所有子类(基于常量类中的映射关系) + List allSubCategories = AuditContent10PartyConductConstants.getAllSubCategories(); + log.info("共发现 {} 个子类需要处理", allSubCategories.size()); + + // 异步并行处理每个子类 + Map> futures = processCategoriesAsync( + allSubCategories, + subCategory -> generateSubCategoryDataAsync(subCategory, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有子类的结果 + JSONArray allData = mergeCategoryResults(allSubCategories, futures); + + // 对结果进行排序(直接调用常量类的排序方法) + AuditContent10PartyConductConstants.sortPartyConductData(allData); + + log.info("党风廉政建设责任制审计表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "party_conduct_audit"); + + } catch (Exception e) { + log.error("生成党风廉政建设责任制审计表失败", e); + return buildErrorResponse("生成党风廉政建设责任制审计表失败: " + e.getMessage()); + } + } + + /** + * 异步生成单个子类的数据 + */ + @Async + public CompletableFuture generateSubCategoryDataAsync(String subCategory, String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成子类 {} 的数据", subCategory); + + // 1. 为当前子类召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForSubCategory( + subCategory, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文(包含固定内容和动态内容) + String knowledgeContext = buildCompleteKnowledgeContext( + subCategory, knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray subCategoryData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "廉政情况-" + subCategory); + + log.info("子类 {} 数据生成完成,生成 {} 条记录", subCategory, subCategoryData.size()); + return subCategoryData; + + } catch (Exception e) { + log.error("生成子类 {} 数据失败", subCategory, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个子类检索相关知识 + */ + private Map> retrieveKnowledgeForSubCategory(String subCategory, String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建当前子类的查询词 + List subCategoryQueries = buildSubCategoryQueries(subCategory); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, subCategoryQueries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, subCategoryQueries, 80))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, subCategoryQueries, 60)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::partyConductComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("子类 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + subCategory, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建子类特定的查询词 + */ + private List buildSubCategoryQueries(String subCategory) { + List queries = new ArrayList<>(); + + // 基础查询词 + queries.add(subCategory); + queries.add(subCategory + " 制度规定"); + queries.add(subCategory + " 执行情况"); + + // 根据子类类型添加特定查询词 + if (subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_MAIN_RESPONSIBILITY)) { + queries.add("主体责任 监督责任 责任清单"); + queries.add("党风廉政建设责任制 党组责任 纪检责任"); + } else if (subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_EIGHT_REGULATIONS)) { + queries.add("中央八项规定 监督检查 问题线索"); + queries.add("八项规定精神 违规问题 通报曝光"); + } else if (subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_ANTI_CORRUPTION)) { + queries.add("群众身边腐败 四风问题 案件查处"); + queries.add("基层腐败 专项治理 长效机制"); + } else if (subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_DISCIPLINE_ENFORCEMENT)) { + queries.add("政治纪律 组织纪律 政令畅通"); + queries.add("纪律执行 重大决策 转办督办"); + } else if (subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_MONTHLY_REPORT) || + subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_RECTIFICATION_TASK) || + subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_RECTIFICATION_ACCOUNTABILITY)) { + queries.add("巡视整改 整改报告 整改措施"); + queries.add("巡视反馈 整改方案 整改完成"); + } else if (subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_THREE_TURNS) || + subCategory.equals(AuditContent10PartyConductConstants.SUBCATEGORY_SYSTEM_CONSTRUCTION)) { + queries.add("纪律检查体制改革 三转工作 纪检体制改革"); + queries.add("纪检制度建设 监督执纪问责"); + } + + return queries; + } + + /** + * 构建完整的知识上下文 - 包含完整的子类示例数据 + */ + private String buildCompleteKnowledgeContext(String subCategory, Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 获取当前子类的大类 + String category = findCategoryBySubCategory(subCategory); + String categoryDesc = AuditContent10PartyConductConstants.CATEGORY_DESCRIPTIONS.getOrDefault(category, ""); + String subCategoryDesc = AuditContent10PartyConductConstants.SUBCATEGORY_DESCRIPTIONS.getOrDefault(subCategory, ""); + + // 2. 党风廉政建设责任制审计要求 + context.append("## 党风廉政建设责任制审计要求\n"); + context.append("请基于以下知识生成").append(category).append("-").append(subCategory).append("相关的党风廉政建设责任制审计表数据:\n\n"); + context.append("1. 严格按照廉政情况表的结构生成数据:大类、子类、小类、内容、执行情况、工作底稿索引\n"); + context.append("2. **执行情况字段必须填写:详细情况内容说明,基于知识库证据判断,不能留空**\n"); + context.append("3. 工作底稿索引必须填写实际存在的完整文件名,确保能在文件夹中搜索到\n"); + context.append("4. 内容字段必须填写具体、可核查的检查点描述\n\n"); + + // 3. 数据结构说明 + context.append("## 数据结构说明\n"); + context.append("当前大类:").append(category).append(" - ").append(categoryDesc).append("\n"); + context.append("当前子类:").append(subCategory).append(" - ").append(subCategoryDesc).append("\n"); + + // 获取当前子类的小类列表 + List detailCategories = AuditContent10PartyConductConstants.SUBCATEGORY_DETAIL_MAP.getOrDefault(subCategory, Collections.emptyList()); + if (!detailCategories.isEmpty()) { + context.append("\n可用的子类包括:\n"); + for (String detailCategory : detailCategories) { + context.append("- ").append(detailCategory).append("\n"); + } + } + + context.append("\n可用的执行情况:详细情况内容说明\n\n"); + + // 4. 完整的示例数据 + context.append("## 示例数据(参考结构)\n"); + String exampleDataJson = AuditContent10PartyConductConstants.getExampleDataJsonString(subCategory); + context.append("以下是相关审计内容的").append(subCategory).append("的完整示例数据,请参考这些内容的结构生成:\n\n"); + context.append(exampleDataJson).append("\n\n"); + + // 5. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 6. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 7. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 9. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + return context.toString(); + } + + /** + * 根据子类查找对应的大类 + */ + private String findCategoryBySubCategory(String subCategory) { + for (Map.Entry> entry : AuditContent10PartyConductConstants.CATEGORY_SUBCATEGORY_MAP.entrySet()) { + if (entry.getValue().contains(subCategory)) { + return entry.getKey(); + } + } + return AuditContent10PartyConductConstants.CATEGORY_TARGET_RESPONSIBILITY; // 默认返回第一个大类 + } + + /** + * 廉政情况相关性比较器 + */ + private int partyConductComparator(String reg1, String reg2) { + int score1 = calculatePartyConductRelevanceScore(reg1); + int score2 = calculatePartyConductRelevanceScore(reg2); + return Integer.compare(score2, score1); + } + + /** + * 计算廉政情况相关性分数 + */ + private int calculatePartyConductRelevanceScore(String content) { + return AuditContent10PartyConductConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 100; + case "regulation": return 80; + case "auditCase": return 60; + default: return 50; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent11HistoryServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent11HistoryServiceImpl.java new file mode 100644 index 0000000..52da3f3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent11HistoryServiceImpl.java @@ -0,0 +1,291 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent11HistoryConstants; +import com.gxwebsoft.ai.service.AuditContent11HistoryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent11HistoryServiceImpl extends AbstractAuditContentService implements AuditContent11HistoryService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-tMty75ly5NmqQXvBGZMxOKdk"; + + @Override + public JSONObject generateHistoryTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成历史审计问题整改表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 1. 检索相关知识 + Map> knowledgeSources = retrieveHistoryAuditKnowledge( + kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray tableData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "历史审计问题整改"); + + // 4. 处理返回数据,确保格式正确 + JSONArray processedData = processTableData(tableData); + + log.info("历史审计问题整改表生成成功 - 记录数: {}, 处理时间: {}ms", + processedData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(processedData, startTime, "history_audit_rectification"); + + } catch (Exception e) { + log.error("生成历史审计问题整改表失败", e); + return buildErrorResponse("生成历史审计问题整改表失败: " + e.getMessage()); + } + } + + /** + * 检索历史审计问题相关知识 + */ + private Map> retrieveHistoryAuditKnowledge(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("auditReports", new ArrayList<>()); + knowledgeSources.put("rectificationReports", new ArrayList<>()); + knowledgeSources.put("regulations", new ArrayList<>()); + + // 构建查询关键词 + List queries = buildHistoryAuditQueries(); + + // 企业单位库检索(历史审计问题) + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, queries, 150))); + } + + // 审计报告库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> { + knowledgeSources.get("auditReports") + .addAll(queryKnowledgeBase(libId, Arrays.asList("审计报告", "审计发现问题"), 100)); + knowledgeSources.get("rectificationReports") + .addAll(queryKnowledgeBase(libId, Arrays.asList("整改报告", "整改情况", "整改措施"), 80)); + }); + } + + // 法律法规库检索(从项目库) + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("regulations").addAll( + queryKnowledgeBase(projectLibrary, + Arrays.asList("审计制度", "整改机制", "责任追究"), 60)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::historyAuditComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("历史审计问题知识检索完成 - 企业: {}条, 审计报告: {}条, 整改报告: {}条, 法规: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("auditReports").size(), + knowledgeSources.get("rectificationReports").size(), + knowledgeSources.get("regulations").size()); + + return knowledgeSources; + } + + /** + * 构建历史审计问题查询关键词 + */ + private List buildHistoryAuditQueries() { + return Arrays.asList( + "审计发现问题 整改情况", + "历史审计 遗留问题", + "审计报告 整改报告", + "问题整改 措施 效果", + "审计年度 审计类型" + ); + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, + String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 审计内容11的要求 + context.append("## 审计内容11:历史审计问题整改\n"); + context.append("**审计重点:**\n"); + context.append("1. ").append(AuditContent11HistoryConstants.FOCUS_DESCRIPTIONS.get(AuditContent11HistoryConstants.FOCUS_SYSTEM_MECHANISM)).append("\n"); + context.append("2. ").append(AuditContent11HistoryConstants.FOCUS_DESCRIPTIONS.get(AuditContent11HistoryConstants.FOCUS_HISTORICAL_ISSUES)).append("\n"); + context.append("3. ").append(AuditContent11HistoryConstants.FOCUS_DESCRIPTIONS.get(AuditContent11HistoryConstants.FOCUS_RECTIFICATION_EFFECT)).append("\n"); + context.append("4. ").append(AuditContent11HistoryConstants.FOCUS_DESCRIPTIONS.get(AuditContent11HistoryConstants.FOCUS_SYSTEM_IMPROVEMENT)).append("\n"); + context.append("5. ").append(AuditContent11HistoryConstants.FOCUS_DESCRIPTIONS.get(AuditContent11HistoryConstants.FOCUS_SELF_RECTIFICATION)).append("\n\n"); + + context.append("**审计方法及步骤:**\n"); + context.append("1. ").append(AuditContent11HistoryConstants.METHOD_DESCRIPTIONS.get(AuditContent11HistoryConstants.METHOD_REVIEW_REPORT)).append("\n"); + context.append("2. ").append(AuditContent11HistoryConstants.METHOD_DESCRIPTIONS.get(AuditContent11HistoryConstants.METHOD_REVIEW_RECTIFICATION)).append("\n"); + context.append("3. ").append(AuditContent11HistoryConstants.METHOD_DESCRIPTIONS.get(AuditContent11HistoryConstants.METHOD_VERIFY_RECTIFICATION)).append("\n"); + context.append("4. ").append(AuditContent11HistoryConstants.METHOD_DESCRIPTIONS.get(AuditContent11HistoryConstants.METHOD_INTERVIEW)).append("\n"); + context.append("5. ").append(AuditContent11HistoryConstants.METHOD_DESCRIPTIONS.get(AuditContent11HistoryConstants.METHOD_REVIEW_DOCS)).append("\n\n"); + + // 2. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("需要生成历史审计问题整改表数据,尽可能生成多个年度、多种类型的审计问题,数量不限:\n\n"); + + context.append("**表格字段说明:**\n"); + context.append("1. **审计年度**:审计报告所属年份,如:2023年\n"); + context.append("2. **审计类型**:内部审计、外部审计、专项审计、经济责任审计等\n"); + context.append("3. **审计发现的问题**:具体审计发现的问题描述,需详细说明\n"); + context.append("4. **整改要求**:对问题的整改要求,包括时间、内容等\n"); + context.append("5. **整改措施**:具体的整改措施和实施情况\n"); + context.append("6. **整改完成情况**:已完成、整改中、未整改、部分整改\n"); + context.append("7. **整改完成时间**:具体完成时间或计划完成时间\n"); + context.append("8. **整改责任人**:负责整改的责任人姓名和职务\n"); + context.append("9. **备注**:其他需要说明的情况\n"); + context.append("10. **工作底稿索引**:相关审计报告、整改报告等文件名称,必须是实际存在的完整文件名\n\n"); + + context.append("**生成要求:**\n"); + context.append("1. 基于知识库内容,尽可能全面地生成历次审计发现的问题和整改情况\n"); + context.append("2. 每个问题应包含完整的审计年度、类型、问题描述、整改要求、措施、完成情况等\n"); + context.append("3. 重点关注整改措施的落实情况和实际效果\n"); + context.append("4. 工作底稿索引必须准确对应实际文件名称,确保能在文件夹中搜索到\n"); + context.append("5. 整改完成情况需根据实际情况判断,证据充分的才能判定为'已完成'\n"); + context.append("6. 对于历史遗留问题,需特别注明是否为非任期问题\n"); + context.append("7. 对于制度完善情况,需说明审计后是否建立了相关制度机制\n\n"); + + // 3. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 4. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 5. 企业单位知识(审计问题) + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位审计问题知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 审计报告知识 + if (!knowledgeSources.get("auditReports").isEmpty()) { + context.append("## 审计报告知识\n"); + knowledgeSources.get("auditReports").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 整改报告知识 + if (!knowledgeSources.get("rectificationReports").isEmpty()) { + context.append("## 整改报告知识\n"); + knowledgeSources.get("rectificationReports").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 法规制度知识 + if (!knowledgeSources.get("regulations").isEmpty()) { + context.append("## 审计整改相关法规制度\n"); + knowledgeSources.get("regulations").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + return context.toString(); + } + + /** + * 处理表格数据,确保格式正确 + */ + private JSONArray processTableData(JSONArray rawData) { + JSONArray processedData = new JSONArray(); + + for (int i = 0; i < rawData.size(); i++) { + JSONObject item = rawData.getJSONObject(i); + JSONObject processedItem = new JSONObject(); + + // 设置序号 + processedItem.put("index", i + 1); + + // 复制所有字段 + processedItem.putAll(item); + + // 确保必填字段存在 + if (!processedItem.containsKey("auditYear")) { + processedItem.put("auditYear", "2023年"); + } + if (!processedItem.containsKey("auditType")) { + processedItem.put("auditType", AuditContent11HistoryConstants.AUDIT_TYPE_INTERNAL); + } + if (!processedItem.containsKey("rectificationStatus")) { + processedItem.put("rectificationStatus", AuditContent11HistoryConstants.STATUS_IN_PROGRESS); + } + + processedData.add(processedItem); + } + + return processedData; + } + + /** + * 历史审计问题相关性比较器 + */ + private int historyAuditComparator(String reg1, String reg2) { + int score1 = calculateHistoryAuditRelevanceScore(reg1); + int score2 = calculateHistoryAuditRelevanceScore(reg2); + return Integer.compare(score2, score1); + } + + /** + * 计算历史审计问题相关性分数 + */ + private int calculateHistoryAuditRelevanceScore(String content) { + return AuditContent11HistoryConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 150; + case "auditReports": return 100; + case "rectificationReports": return 80; + case "regulations": return 60; + default: return 50; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1EightRegServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1EightRegServiceImpl.java new file mode 100644 index 0000000..ec85d98 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1EightRegServiceImpl.java @@ -0,0 +1,358 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent1EightRegConstants; +import com.gxwebsoft.ai.service.AuditContent1EightRegService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent1EightRegServiceImpl extends AbstractAuditContentService implements AuditContent1EightRegService { + + // 八项规定工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-hwUG7mEAZtN0jeZkebtIJYrU"; + + // 八项规定分类定义 - 按照要求的顺序 + private static final List CATEGORY_ORDER = Arrays.asList( + AuditContent1EightRegConstants.CATEGORY_RESEARCH, + AuditContent1EightRegConstants.CATEGORY_MEETINGS, + AuditContent1EightRegConstants.CATEGORY_DOCUMENTS, + AuditContent1EightRegConstants.CATEGORY_VISITS, + AuditContent1EightRegConstants.CATEGORY_GUARD_WORK, + AuditContent1EightRegConstants.CATEGORY_NEWS_REPORT, + AuditContent1EightRegConstants.CATEGORY_PUBLICATIONS, + AuditContent1EightRegConstants.CATEGORY_ECONOMY + ); + + // 八项规定标题映射 + private static final Map POLICY_TITLE_MAP = new HashMap<>(); + static { + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_RESEARCH, "一"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_MEETINGS, "二"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_DOCUMENTS, "三"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_VISITS, "四"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_GUARD_WORK, "五"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_NEWS_REPORT, "六"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_PUBLICATIONS, "七"); + POLICY_TITLE_MAP.put(AuditContent1EightRegConstants.CATEGORY_ECONOMY, "八"); + } + + @Override + public JSONObject generateEightRegTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + log.info("开始生成八项规定对比分析表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个分类 + Map> futures = processCategoriesAsync( + CATEGORY_ORDER, + category -> generateCategoryDataAsync(category, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有分类的结果,并按照八项规定顺序排序 + JSONArray allData = mergeAndSortCategoryResults(CATEGORY_ORDER, futures); + + log.info("八项规定对比分析表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "eight_regulation_audit"); + + } catch (Exception e) { + log.error("生成八项规定对比分析表失败", e); + return buildErrorResponse("生成八项规定对比分析表失败: " + e.getMessage()); + } + } + + /** + * 合并并按照八项规定顺序排序分类结果 + */ + private JSONArray mergeAndSortCategoryResults(List categoryOrder, Map> futures) { + JSONArray allData = new JSONArray(); + + // 按照八项规定顺序遍历每个分类 + for (String category : categoryOrder) { + CompletableFuture future = futures.get(category); + if (future != null) { + try { + JSONArray categoryData = future.get(); + if (categoryData != null) { + // 为当前分类的数据设置排序标识 + for (Object obj : categoryData) { + if (obj instanceof JSONObject) { + JSONObject item = (JSONObject) obj; + // 设置排序标识,用于前端展示 + item.put("categoryOrder", POLICY_TITLE_MAP.get(category)); + item.put("categoryName", category); + } + } + allData.addAll(categoryData); + } + } catch (Exception e) { + log.error("获取分类 {} 数据失败", category, e); + } + } + } + + return allData; + } + + /** + * 异步生成单个分类的数据 + */ + @Async + public CompletableFuture generateCategoryDataAsync(String category, String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成八项规定分类 {} 的数据", category); + + // 1. 为当前分类召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForCategory( + category, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文(包含固定内容和动态内容) + String knowledgeContext = buildCompleteKnowledgeContext( + category, knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray categoryData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "八项规定-" + category); + + log.info("八项规定分类 {} 数据生成完成,生成 {} 条记录", category, categoryData.size()); + return categoryData; + + } catch (Exception e) { + log.error("生成八项规定分类 {} 数据失败", category, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个分类检索相关知识 + */ + private Map> retrieveKnowledgeForCategory(String category, String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建当前分类的查询词 + List categoryQueries = buildCategoryQueries(category); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, categoryQueries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, categoryQueries, 80))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, categoryQueries, 60)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::eightRegComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("八项规定分类 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + category, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建分类特定的查询词 + */ + private List buildCategoryQueries(String category) { + switch (category) { + case AuditContent1EightRegConstants.CATEGORY_RESEARCH: + return Arrays.asList("调查研究 基层调研 调研陪同", "调研接待 调研规定"); + case AuditContent1EightRegConstants.CATEGORY_MEETINGS: + return Arrays.asList("会议活动 会议费 会议规模", "会议审批 会议时长"); + case AuditContent1EightRegConstants.CATEGORY_DOCUMENTS: + return Arrays.asList("文件简报 文件数量 文风", "简报精简 文件管理"); + case AuditContent1EightRegConstants.CATEGORY_VISITS: + return Arrays.asList("出访活动 因公出国 出访经费", "出访审批 出访陪同"); + case AuditContent1EightRegConstants.CATEGORY_GUARD_WORK: + return Arrays.asList("警卫工作 交通管制 封路", "安保工作 警卫规定"); + case AuditContent1EightRegConstants.CATEGORY_NEWS_REPORT: + return Arrays.asList("新闻报道 新闻稿 宣传报道", "媒体采访 新闻审批"); + case AuditContent1EightRegConstants.CATEGORY_PUBLICATIONS: + return Arrays.asList("文稿发表 出版著作 题词题字", "发表文章 出版审批"); + case AuditContent1EightRegConstants.CATEGORY_ECONOMY: + return Arrays.asList("勤俭节约 三公经费 公务接待", "差旅费 会议费 培训费"); + default: + return Arrays.asList(category + " 八项规定"); + } + } + + /** + * 构建完整的知识上下文 - 包含固定内容和动态内容 + */ + private String buildCompleteKnowledgeContext(String category, Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 八项规定对比分析要求 + context.append("## 八项规定对比分析要求 - ").append(category).append("\n"); + context.append("请基于以下知识生成").append(category).append("相关的八项规定对比分析表数据:\n\n"); + context.append("1. 仅关注与八项规定直接相关的制度内容,过滤不相关制度\n"); + context.append("2. 对比分析中共中央八项规定与公司制度的差异\n"); +// context.append("3. 重点关注调查研究、会议活动、文件简报、出访活动、警卫工作、新闻报道、文稿发表、勤俭节约等八个方面\n"); + context.append("3. 重点关注").append(category).append("方面\n"); + context.append("4. 识别制度执行中的风险和问题\n"); + context.append("5. 严格判断测试结果,只有证据充分才能判定为通过,证据不足或存在差异必须判定为不通过\n"); + + // 增加分类限制要求 + context.append("\n**特别重要要求:**\n"); + context.append("1. **只生成与").append(category).append("(对应八项规定第").append(POLICY_TITLE_MAP.get(category)).append("条)相关的内容**\n"); + context.append("2. **所有记录的title字段必须为\"").append(POLICY_TITLE_MAP.get(category)).append("\"**\n"); + context.append("3. **不得包含其他七项规定的内容**\n"); + + // 如果是厉行勤俭节约分类,增加费用核定特别要求 + if (AuditContent1EightRegConstants.CATEGORY_ECONOMY.equals(category)) { + context.append("\n**费用核定特别要求:**\n"); + context.append("1. **每次活动单独计算**:每笔凭证、每次招待活动必须单独计算费用合规性\n"); + context.append("2. **单独计算人均费用**:每次活动的总费用除以该次活动的总人数,得出该次活动的人均费用\n"); + context.append("3. **单独对比标准**:将每次活动的人均费用单独与公司标准对比,判定是否合规\n"); + context.append("4. **不得合并计算**:不得将多笔凭证、多次活动合并计算平均值\n"); + context.append("5. **逐笔描述**:每笔凭证的检查结果应作为独立记录\n"); + } + + // 2. 数据格式要求 + context.append("\n## 数据格式要求\n"); + context.append("需要生成").append(category).append("分类的数据,尽可能生成多个实例、多条记录:\n"); + context.append("- ").append(category).append(":").append(AuditContent1EightRegConstants.CATEGORY_DESCRIPTIONS.get(category)).append("\n"); + context.append("\n"); + + context.append("每条记录应包含5个字段:\n"); +// context.append("- title:审计标题(必须按照八项规定顺序输出:一、二、三...八,对应八项规定的八个方面)\n"); + context.append("- title:审计标题(必须按照八项规定顺序输出:").append(POLICY_TITLE_MAP.get(category)).append(",对应").append(category).append(")\n"); + context.append("- content:审计内容(具体中共中央八项规定/具体中央八项规定实施细则)\n"); + context.append("- testContent:审计检查的证据,需详细描述查阅过程、查阅的具体文件和内容\n"); + context.append("- result:审计测试的结果(通过/不通过),严格判断,从严掌握通过标准\n"); +// context.append("- workPaperIndex:相关《参考文件FileId》,必须是实际存在的完整文件FileId,不能使用附表标题,确保能在文件夹中搜索到\n"); + context.append("- workPaperIndex:相关[\"实际存在的完整文件名||FileUrl\"],必须是实际存在的完整文件名,不能使用附表标题,确保能在文件夹中搜索到\n"); + context.append("\n注意:\n"); + context.append("1. 请根据知识库内容尽可能全面地生成所有相关制度规定和检查点\n"); + context.append("2. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n"); + context.append("3. 测试结果判定需严格,对于制度不一致、执行不到位、证据不充分的情况必须判定为不通过\n"); +// context.append("4. title字段必须按照八项规定顺序输出:一、二、三...八\n\n"); + context.append("4. **title字段必须为\"").append(POLICY_TITLE_MAP.get(category)).append("\",不得生成其他标题**\n\n"); + + // 3. 参考数据(从常量类中获取) + context.append("## 参考数据\n"); + context.append("### 政策内容\n").append(AuditContent1EightRegConstants.POLICY_CONTENTS.get(category)).append("\n\n"); + context.append("### 实施细则\n").append(AuditContent1EightRegConstants.IMPLEMENTATION_DETAILS.get(category)).append("\n\n"); + + // 4. 审计建议 - 新增部分 + String auditSuggestion = AuditContent1EightRegConstants.AUDIT_SUGGESTIONS.get(category); + if (StrUtil.isNotBlank(auditSuggestion)) { + context.append("## 审计建议\n"); + context.append("如发现被审计单位在").append(category).append("方面存在不符合中央八项规定的情况,可参考以下审计建议:\n\n"); + context.append(auditSuggestion).append("\n\n"); + } + + // 4. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 5. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 6. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + return context.toString(); + } + + /** + * 八项规定相关性比较器 + */ + private int eightRegComparator(String reg1, String reg2) { + int score1 = calculateEightRegRelevanceScore(reg1); + int score2 = calculateEightRegRelevanceScore(reg2); + return Integer.compare(score2, score1); + } + + /** + * 计算八项规定相关性分数 + */ + private int calculateEightRegRelevanceScore(String content) { + return AuditContent1EightRegConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 100; + case "regulation": return 80; + case "auditCase": return 60; + default: return 50; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1ExpenseServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1ExpenseServiceImpl.java new file mode 100644 index 0000000..ddda567 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1ExpenseServiceImpl.java @@ -0,0 +1,290 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent1ExpenseConstants; +import com.gxwebsoft.ai.service.AuditContent1ExpenseService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent1ExpenseServiceImpl extends AbstractAuditContentService implements AuditContent1ExpenseService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-xwWxGF6hERgdeKB5OPpj9Tq8"; + + @Override + public JSONObject generateExpenseTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成支出情况表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 1. 检索相关知识(优化查询策略,避免工程类报表) + Map> knowledgeSources = retrieveKnowledgeForExpense(kbIds, libraryKbIds, projectLibrary); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext(knowledgeSources, history, suggestion); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray expenseData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "支出情况表"); + + // 4. 按 expenseType 和 year 排序 + if (expenseData != null && !expenseData.isEmpty()) { + expenseData.sort((o1, o2) -> { + JSONObject obj1 = (JSONObject) o1; + JSONObject obj2 = (JSONObject) o2; + // 先按 expenseType 排序 + int typeCompare = obj1.getString("expenseType").compareTo(obj2.getString("expenseType")); + if (typeCompare != 0) { + return typeCompare; + } + // 再按 year 排序 + return obj1.getString("year").compareTo(obj2.getString("year")); + }); + } + + // 5. 不进行数据验证和补充,直接返回大模型输出 + log.info("支出情况表生成成功 - 记录数: {}, 处理时间: {}ms", expenseData.size(), (System.currentTimeMillis() - startTime)); + return buildSuccessResponse(expenseData, startTime, "expense_audit"); + } catch (Exception e) { + log.error("生成支出情况表失败", e); + return buildErrorResponse("生成支出情况表失败: " + e.getMessage()); + } + } + + /** + * 检索支出情况表相关知识 - 优化查询策略,排除工程类报表 + */ + private Map> retrieveKnowledgeForExpense(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("financial", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建更精确的查询词,专门针对四类支出 + List queries = buildPreciseExpenseQueries(); + + // 财务数据库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> { + List financialKnowledge = queryKnowledgeBase(kbId, queries, 200); + + // 过滤掉工程类报表内容 + financialKnowledge = filterOutEngineeringReports(financialKnowledge); + + knowledgeSources.get("financial").addAll(financialKnowledge); + log.debug("财务知识库 {} 检索到 {} 条相关知识", kbId, financialKnowledge.size()); + }); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> { + List regulationKnowledge = queryKnowledgeBase(libId, queries, 50); + knowledgeSources.get("regulation").addAll(regulationKnowledge); + }); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + List auditCaseKnowledge = queryKnowledgeBase(projectLibrary, queries, 30); + knowledgeSources.get("auditCase").addAll(auditCaseKnowledge); + } + + // 智能去重、排序和过滤 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .filter(this::isRelevantExpenseContent) // 过滤相关性 + .sorted(this::expenseComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.info("支出情况表知识检索完成 - 财务: {}条, 法规: {}条, 案例: {}条", + knowledgeSources.get("financial").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建精确的支出情况表查询词 + */ + private List buildPreciseExpenseQueries() { + return Arrays.asList( + // 直接针对四类支出的查询 + "公务接待 接待费 招待费 餐饮费 公务用餐", + "出国 出境 国际差旅 境外考察 出国费用", + "公车运行维护 车辆费 汽车费 交通费 公务用车", + "会议培训费 会议费 培训费 会务费 培训支出", + + // 财务文档查询(排除工程类) + "决算报表 公务接待 出国 公车 会议培训", + "预算报表 三公经费 支出明细", + "部门决算 公务支出 费用统计", + "部门预算 经费预算 支出预算", + + // 通用年度查询,不指定具体年份,让知识库返回所有年份数据 + "年度 公务接待 出国 公车 会议培训", + "决算 公务接待 出国 公车 会议培训", + "预算 公务接待 出国 公车 会议培训", + "三公经费 支出 决算 预算" + ); + } + + /** + * 过滤掉工程类报表内容 + */ + private List filterOutEngineeringReports(List knowledgeList) { + return knowledgeList.stream() + .filter(content -> { + // 检查是否包含工程类关键词 + for (String excludeKeyword : AuditContent1ExpenseConstants.ENGINEERING_EXCLUDE_KEYWORDS) { + if (content.contains(excludeKeyword)) { + log.debug("过滤工程类内容: {}", content.substring(0, Math.min(50, content.length()))); + return false; + } + } + return true; + }) + .collect(Collectors.toList()); + } + + /** + * 检查内容是否与支出相关 + */ + private boolean isRelevantExpenseContent(String content) { + String lowerContent = content.toLowerCase(); + + // 必须包含至少一个支出类型关键词 + boolean hasExpenseType = lowerContent.contains("公务接待") || + lowerContent.contains("接待费") || + lowerContent.contains("出国") || + lowerContent.contains("出境") || + lowerContent.contains("公车") || + lowerContent.contains("车辆费") || + lowerContent.contains("会议") || + lowerContent.contains("培训") || + lowerContent.contains("三公经费"); + + // 排除工程类内容 + boolean isEngineering = false; + for (String excludeKeyword : AuditContent1ExpenseConstants.ENGINEERING_EXCLUDE_KEYWORDS) { + if (content.contains(excludeKeyword)) { + isEngineering = true; + break; + } + } + + return hasExpenseType && !isEngineering; + } + + /** + * 构建完整的知识上下文 - 简化版本,只传递知识内容 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 审计关注要点(简化版) + context.append("## 审计关注要点\n"); + context.append("请基于以下知识生成完整的公务接待/出国/公车运行维护/会议培训费等支出情况表数据。\n"); + context.append("重点关注以下审计要点:\n"); + context.append("1. 检查公务接待、出国、公车、会议培训四类支出的预算执行情况\n"); + context.append("2. 对比年初预算数和决算报表数,分析增减情况\n"); + context.append("3. 关注是否存在未经预算批准变相公款消费的情况\n"); + context.append("4. 检查报销手续是否完善,是否存在超支使用\n\n"); + + // 2. 特别提醒 + context.append("## 特别提醒\n"); + context.append("1. 必须准确识别支出类型:只提取\"公务接待\"、\"出国\"、\"公车运行维护\"、\"会议培训费\"四类支出\n"); + context.append("2. 不要将《工程造价和概(预)算执行情况表》等工程类报表误识别为\"三公经费\"报表\n"); +// context.append("3. 工作底稿索引必须使用实际存在的完整文件名称作为FileId\n\n"); + context.append("3. 工作底稿索引必须使用实际存在的完整文件名||FileUrl\n\n"); + + // 3. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容参考\n"); + context.append(history).append("\n\n"); + } + + // 4. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append(suggestion).append("\n\n"); + } + + // 5. 财务知识(核心内容) + if (!knowledgeSources.get("financial").isEmpty()) { + context.append("## 财务知识库内容\n"); + context.append("以下是财务相关的报表和文件信息,请仔细分析并提取所有支出数据:\n\n"); + knowledgeSources.get("financial").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规参考\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例参考\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + log.debug("构建的知识上下文长度: {}", context.length()); + return context.toString(); + } + + /** + * 支出情况表相关性比较器 + */ + private int expenseComparator(String content1, String content2) { + int score1 = calculateExpenseRelevanceScore(content1); + int score2 = calculateExpenseRelevanceScore(content2); + return Integer.compare(score2, score1); + } + + /** + * 计算支出情况表相关性分数 + */ + private int calculateExpenseRelevanceScore(String content) { + return AuditContent1ExpenseConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "financial": return 100; // 减少数量,提高质量 + case "regulation": return 50; + case "auditCase": return 30; + default: return 50; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1LeaderListServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1LeaderListServiceImpl.java new file mode 100644 index 0000000..14ef58f --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent1LeaderListServiceImpl.java @@ -0,0 +1,296 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent1LeaderListConstants; +import com.gxwebsoft.ai.service.AuditContent1LeaderListService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent1LeaderListServiceImpl extends AbstractAuditContentService implements AuditContent1LeaderListService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-G0xD8Yu0tqwYEudtXuKFk2BH"; + + @Override + public JSONObject generateLeaderListTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成领导班子名单数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 1. 检索相关知识 + Map> knowledgeSources = retrieveKnowledgeForLeaderList(kbIds, libraryKbIds, projectLibrary); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext(knowledgeSources, history, suggestion); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray leaderListData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "领导班子名单"); + + // 4. 数据验证和补充 +// leaderListData = validateAndEnhanceLeaderListData(leaderListData); + + log.info("领导班子名单生成成功 - 记录数: {}, 处理时间: {}ms", + leaderListData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(leaderListData, startTime, "leader_list_audit"); + + } catch (Exception e) { + log.error("生成领导班子名单失败", e); + return buildErrorResponse("生成领导班子名单失败: " + e.getMessage()); + } + } + + /** + * 验证和增强领导班子名单数据 + */ + private JSONArray validateAndEnhanceLeaderListData(JSONArray originalData) { + if (originalData == null || originalData.isEmpty()) { + log.warn("原始数据为空,返回空数组"); + return new JSONArray(); + } + + JSONArray enhancedData = new JSONArray(); + + for (int i = 0; i < originalData.size(); i++) { + JSONObject item = originalData.getJSONObject(i); + if (item != null) { + // 确保所有必需字段都存在 + enhanceLeaderData(item); + enhancedData.add(item); + } + } + + log.info("数据增强完成 - 原始记录数: {}, 增强后记录数: {}", originalData.size(), enhancedData.size()); + return enhancedData; + } + + /** + * 增强单个领导数据 + */ + private void enhanceLeaderData(JSONObject leaderData) { + // 确保单位名称存在 + if (StrUtil.isBlank(leaderData.getString("unit"))) { + leaderData.put("unit", "广西千汇食品有限公司"); + } + + // 确保部门信息存在 + if (StrUtil.isBlank(leaderData.getString("department"))) { + String adminPosition = leaderData.getString("adminPosition"); + if (adminPosition != null) { + if (adminPosition.contains("董事")) { + leaderData.put("department", "董事会"); + } else if (adminPosition.contains("经理") || adminPosition.contains("总监")) { + leaderData.put("department", "经理层"); + } else { + leaderData.put("department", "管理部门"); + } + } + } + + // 确保任职期间格式正确 + if (StrUtil.isBlank(leaderData.getString("tenurePeriod"))) { + leaderData.put("tenurePeriod", "2023.4-至今"); + } + + // 确保主要工作责任完整 + if (StrUtil.isBlank(leaderData.getString("mainResponsibilities"))) { + String position = leaderData.getString("adminPosition"); + if (position != null) { + if (position.contains("董事长")) { + leaderData.put("mainResponsibilities", "主持公司全面工作,主管综合管理服务、安全生产、环境保护、品牌建设与管理等工作;主管综合管理部、设备安环部。"); + } else if (position.contains("总经理")) { + leaderData.put("mainResponsibilities", "负责战略规划、投资管理、项目招投标、质量安全、食品安全、应急管理、生产经营管理、组织人事、日常经营管理等工作;主管采购部、销售部、质量管理部、生产部。"); + } else if (position.contains("财务总监")) { + leaderData.put("mainResponsibilities", "主管财务管理、筹融资管理、会计核算、法律事务、合规管理、风险防控、审计管理、党建、党风廉政建设、宣传思想意识形态、统战工作、协助上级开展巡察工作等;主管财务部。"); + } + } + } + } + + /** + * 检索领导班子名单相关知识 - 优化查询策略 + */ + private Map> retrieveKnowledgeForLeaderList(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建更全面的查询词 + List queries = buildEnhancedLeaderListQueries(); + + // 企业单位库检索 - 扩大检索范围 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> { + List enterpriseKnowledge = queryKnowledgeBase(kbId, queries, 150); // 增加检索数量 + knowledgeSources.get("enterprise").addAll(enterpriseKnowledge); + log.debug("企业知识库 {} 检索到 {} 条相关知识", kbId, enterpriseKnowledge.size()); + }); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> { + List regulationKnowledge = queryKnowledgeBase(libId, queries, 80); + knowledgeSources.get("regulation").addAll(regulationKnowledge); + }); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + List auditCaseKnowledge = queryKnowledgeBase(projectLibrary, queries, 50); + knowledgeSources.get("auditCase").addAll(auditCaseKnowledge); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::leaderListComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.info("领导班子名单知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建增强的领导班子名单查询词 + */ + private List buildEnhancedLeaderListQueries() { + return Arrays.asList( + "领导班子 领导成员 任职文件 任命通知", + "党组 党委 支部 书记 委员 党组成员", + "董事长 总经理 财务总监 部门负责人", + "职务任免 职责分工 工作分工 岗位职责", + "组织机构 人员编制 干部名册 管理人员", + "党内职务 行政职务 任职时间 任期", + "公司治理 管理架构 组织架构", + "工作职责 主要责任 分管工作", + "干部任免 人事任命 组织任命", + "管理层 决策层 执行层" + ); + } + + /** + * 构建完整的知识上下文 - 优化提示 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 领导班子名单分析要求 - 增强要求 + context.append("## 领导班子名单分析要求\n"); + context.append("请基于以下知识生成完整详细的领导班子名单数据:\n\n"); + context.append("1. 必须生成完整的领导班子成员信息,包括党组织领导班子、行政领导班子、各部门负责人等\n"); + context.append("2. 从企业单位知识库中提取所有实际的领导班子成员信息,要求全面完整\n"); + context.append("3. 重点关注领导成员的姓名、单位、部门、党内职务、行政职务、任职期间、工作职责等信息\n"); + context.append("4. 确保信息的准确性和完整性,每个字段都要填写具体内容\n"); + context.append("5. 工作底稿索引必须准确对应实际文件名称,可以包含多个相关文件\n"); + context.append("6. 要求生成尽可能多、尽可能完整的领导班子成员记录,覆盖各个管理层级\n\n"); + + // 2. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("需要生成完整详细的领导班子名单数据:\n\n"); + context.append(AuditContent1LeaderListConstants.DATA_FORMAT_REQUIREMENT); + context.append("\n\n重要要求:\n"); + context.append("1. 必须根据知识库内容生成尽可能多、尽可能完整的领导班子成员信息\n"); + context.append("2. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n"); + context.append("3. 任职期间格式统一为:YYYY.MM-YYYY.MM 或 YYYY.MM-至今\n"); + context.append("4. 主要工作责任要具体明确,不能笼统描述\n"); + context.append("5. 部门信息要具体,如:董事会、经理层、财务部、生产部等\n"); + context.append("6. 备注信息可以包含任命方式、试用期等特殊说明\n\n"); + + // 3. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化和补充:\n"); + context.append(history).append("\n\n"); + } + + // 4. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 5. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识\n"); + context.append("这是企业单位的相关制度文件和信息,请仔细分析并提取所有领导班子成员信息:\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + log.debug("构建的知识上下文长度: {}", context.length()); + return context.toString(); + } + + /** + * 领导班子名单相关性比较器 + */ + private int leaderListComparator(String content1, String content2) { + int score1 = calculateLeaderListRelevanceScore(content1); + int score2 = calculateLeaderListRelevanceScore(content2); + return Integer.compare(score2, score1); + } + + /** + * 计算领导班子名单相关性分数 + */ + private int calculateLeaderListRelevanceScore(String content) { + return AuditContent1LeaderListConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 100; + case "regulation": return 50; + case "auditCase": return 30; + default: return 50; + } + } +} diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent2StrategyServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent2StrategyServiceImpl.java new file mode 100644 index 0000000..dbd65b1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent2StrategyServiceImpl.java @@ -0,0 +1,239 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent2StrategyConstants; +import com.gxwebsoft.ai.service.AuditContent2StrategyService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent2StrategyServiceImpl extends AbstractAuditContentService implements AuditContent2StrategyService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-3cPaXHIPFPS6lIfMGV67NOu0"; + + @Override + public JSONObject generateStrategyAuditTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成单位发展战略执行审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个分类 + Map> futures = processCategoriesAsync( + AuditContent2StrategyConstants.CATEGORY_ORDER, + category -> generateCategoryDataAsync(category, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有分类的结果 + JSONArray allData = mergeCategoryResults(AuditContent2StrategyConstants.CATEGORY_ORDER, futures); + + log.info("单位发展战略执行审计表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "strategy_audit"); + + } catch (Exception e) { + log.error("生成单位发展战略执行审计表失败", e); + return buildErrorResponse("生成单位发展战略执行审计表失败: " + e.getMessage()); + } + } + + /** + * 异步生成单个分类的数据 + */ + @Async + public CompletableFuture generateCategoryDataAsync(String category, String kbIds, String libraryKbIds, + String projectLibrary, String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成分类 {} 的数据", category); + + // 1. 为当前分类召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForCategory( + category, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + category, knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray categoryData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "发展战略-" + category); + + log.info("分类 {} 数据生成完成,生成 {} 条记录", category, categoryData.size()); + return categoryData; + + } catch (Exception e) { + log.error("生成分类 {} 数据失败", category, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个分类检索相关知识 + */ + private Map> retrieveKnowledgeForCategory(String category, String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 从常量类获取当前分类的查询词 + List categoryQueries = AuditContent2StrategyConstants.CATEGORY_QUERIES.getOrDefault( + category, Arrays.asList(category + " 审计 检查") + ); + + // 企业单位库检索 - 这是主要考察内容 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, categoryQueries, 150))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, categoryQueries, 80))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, categoryQueries, 60)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .limit(AuditContent2StrategyConstants.SOURCE_LIMITS.getOrDefault(key, 50)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("分类 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + category, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(String category, Map> knowledgeSources, + String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 核心审计任务 + context.append("## 核心审计任务\n"); + context.append(AuditContent2StrategyConstants.AUDIT_OBJECTIVE).append("\n\n"); + context.append("本次重点审计分类:").append(category).append("\n"); + context.append("对应发展理念:").append(AuditContent2StrategyConstants.CATEGORY_DEVELOPMENT_CONCEPT.get(category)).append("\n"); + context.append("分类描述:").append(AuditContent2StrategyConstants.CATEGORY_DESCRIPTIONS.get(category)).append("\n"); + context.append("审计重点:").append(AuditContent2StrategyConstants.getBriefAuditFrameworkForCategory(category)).append("\n\n"); + + // 2. 执行结果分析要求 + context.append("## 执行结果分析要求(必须遵循)\n"); + context.append("**必须深入分析实际执行结果,重点关注贯彻落实的证据:**\n"); + context.append("**检查要求(满足以下任一条件即可):**\n"); + context.append("1. **会议纪要路径**:检查是否有会议纪要证明按照\"党组织委员会→公司领导班子→董事会\"逐级开会落实;\n"); + context.append(" *或者*\n"); + context.append("2. **其他落实证据**:检查是否有其他材料(签批文件、任务分解、执行报告等)证明相关审计内容已从上往下逐级贯彻落实。\n"); + context.append("\n"); + context.append("**在检查证据中必须明确指出:**\n"); + context.append("①是否存在有效的贯彻落实证据(会议纪要或其他材料);\n"); + context.append("②执行过程是否有充分材料支撑;\n"); + context.append("③执行结果是否达到预期。\n\n"); + + // 3. 审计框架 + context.append("## 审计框架(审计规则)\n"); + String categoryFramework = AuditContent2StrategyConstants.CATEGORY_AUDIT_FRAMEWORK_FRAGMENTS.get(category); + if (categoryFramework != null) { + context.append(categoryFramework).append("\n"); + } else { + context.append(AuditContent2StrategyConstants.AUDIT_FRAMEWORK).append("\n"); + } + + // 4. 企业单位知识(主要考察内容) + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识(具体考察内容)\n"); + context.append("基于审计框架,结合以下企业实际资料生成审计记录:\n\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } else { + context.append("## 企业单位知识\n"); + context.append("未检索到相关企业资料,请基于审计框架生成审计内容。\n\n"); + } + + // 5. 审计工作原则 + context.append("## 审计工作原则\n"); + context.append(AuditContent2StrategyConstants.AUDIT_PRINCIPLES).append("\n\n"); + + // 6. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("生成JSON数组,每个元素包含4个字段:\n"); + context.append("1. auditContent: 具体审计内容(基于框架,结合企业实际)\n"); + context.append("2. checkEvidence: 检查证据(引用企业具体文件,必须包含执行结果分析)\n"); + context.append("3. testResult: 测试结果(通过/不通过)(基于充分证据严格判断)\n"); +// context.append("4. workPaperIndex: 工作底稿索引(具体文件FileId)\n\n"); + context.append("4. workPaperIndex: 工作底稿索引(具体文件完整文件名||FileUrl)\n\n"); + + // 7. 法规和案例参考(如果有) + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规参考\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例参考\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 历史内容(如果有) + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append(history).append("\n\n"); + } + + // 9. 用户建议(如果有) + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n"); + } + + // 记录上下文长度用于监控 + log.debug("分类 {} 构建的知识上下文长度: {} 字符", category, context.length()); + + return context.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3DecisionServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3DecisionServiceImpl.java new file mode 100644 index 0000000..a817bc5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3DecisionServiceImpl.java @@ -0,0 +1,274 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent3DecisionConstants; +import com.gxwebsoft.ai.service.AuditContent3DecisionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent3DecisionServiceImpl extends AbstractAuditContentService implements AuditContent3DecisionService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-yN7Q8GEN9E7SCMkhI6HaAagW"; + + @Override + public JSONObject generateDecisionTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion, Object data) { + log.info("开始生成重大经济决策调查表数据 - 用户: {}", userName); + + JSONObject result = new JSONObject(); + long startTime = System.currentTimeMillis(); + + try { + // 1. 解析三重一大数据 + JSONArray tripleOneData = parseTripleOneData(data); + + if (tripleOneData == null || tripleOneData.isEmpty()) { + log.warn("未传入三重一大数据,无法生成重大经济决策调查表"); + result.put("success", false); + result.put("error", "需要三重一大数据作为生成依据"); + return result; + } + + // 2. 检索相关知识库内容 + Map> knowledgeSources = retrieveKnowledge(kbIds, libraryKbIds, projectLibrary, tripleOneData); + + // 3. 构建上下文并调用工作流 + String knowledgeContext = buildKnowledgeContext(tripleOneData, knowledgeSources, history, suggestion); + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray decisionTableData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "重大经济决策调查表"); + + // 4. 构建返回结果 + return buildSuccessResponse(decisionTableData, startTime, "triple_one_primary"); + + } catch (Exception e) { + log.error("生成重大经济决策调查表失败", e); + return buildErrorResponse("生成重大经济决策调查表失败: " + e.getMessage()); + } + } + + /** + * 检索相关知识 + */ + private Map> retrieveKnowledge(String kbIds, String libraryKbIds, String projectLibrary, JSONArray tripleOneData) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 企业单位知识检索 + if (StrUtil.isNotBlank(kbIds)) { + List enterpriseQueries = buildEnterpriseQueries(tripleOneData); + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, enterpriseQueries, 20))); + } + + // 法律法规知识检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + List regulationQueries = buildRegulationQueries(tripleOneData); + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, regulationQueries, 100))); + } + + // 审计案例知识检索 + if (StrUtil.isNotBlank(projectLibrary)) { + List auditCaseQueries = buildAuditCaseQueries(tripleOneData); + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, auditCaseQueries, 50)); + } + + // 智能去重 + knowledgeSources.forEach((sourceType, knowledgeList) -> { + List distinctList = knowledgeList.stream().distinct().collect(Collectors.toList()); + knowledgeSources.put(sourceType, distinctList); + }); + + log.info("知识库检索完成 - 企业单位: {}条, 法律法规: {}条, 审计案例: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建企业单位查询 + */ + private List buildEnterpriseQueries(JSONArray tripleOneData) { + List queries = new ArrayList<>(); + + for (int i = 0; i < tripleOneData.size(); i++) { + JSONObject item = tripleOneData.getJSONObject(i); + if (item != null) { + String category = item.getString("category"); + if (StrUtil.isNotBlank(category)) { + queries.add(category + " 会议记录 决策事项"); + queries.add(category + " 经济决策 程序"); + } + + String policyContent = item.getString("policyContent"); + if (StrUtil.isNotBlank(policyContent)) { + if (policyContent.contains("投资")) queries.add("投资项目 决策"); + if (policyContent.contains("采购")) queries.add("采购项目 决策"); + if (policyContent.contains("资产")) queries.add("资产处置 决策"); + } + } + } + + return queries; + } + + /** + * 构建法律法规查询 + */ + private List buildRegulationQueries(JSONArray tripleOneData) { + List queries = new ArrayList<>(Arrays.asList(AuditContent3DecisionConstants.GLOBAL_REGULATION_QUERIES)); + + // 提取分类信息 + Set categories = new HashSet<>(); + for (int i = 0; i < tripleOneData.size(); i++) { + JSONObject item = tripleOneData.getJSONObject(i); + if (item != null) { + String category = item.getString("category"); + if (StrUtil.isNotBlank(category)) { + categories.add(category); + } + } + } + + // 添加分类相关查询 + for (String category : categories) { + queries.add(category + " 法律法规"); + } + + return queries; + } + + /** + * 构建审计案例查询 + */ + private List buildAuditCaseQueries(JSONArray tripleOneData) { + List queries = new ArrayList<>(Arrays.asList(AuditContent3DecisionConstants.GLOBAL_AUDIT_CASE_QUERIES)); + + // 提取分类信息 + Set categories = new HashSet<>(); + for (int i = 0; i < tripleOneData.size(); i++) { + JSONObject item = tripleOneData.getJSONObject(i); + if (item != null) { + String category = item.getString("category"); + if (StrUtil.isNotBlank(category)) { + categories.add(category); + } + } + } + + // 添加分类相关查询 + for (String category : categories) { + queries.add(category + " 审计案例"); + } + + return queries; + } + + /** + * 构建知识上下文 + */ + private String buildKnowledgeContext(JSONArray tripleOneData, Map> knowledgeSources, + String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 核心指令 + context.append("## 核心生成指令\n"); + context.append("请基于三重一大制度对比分析数据生成重大经济决策调查表。\n\n"); + context.append(AuditContent3DecisionConstants.AUDIT_FOCUS_AND_METHODS).append("\n\n"); + + // 2. 三重一大数据 + context.append("## 三重一大制度对比分析数据\n"); + for (int i = 0; i < tripleOneData.size(); i++) { + JSONObject item = tripleOneData.getJSONObject(i); + if (item != null) { + context.append("### 三重一大事项 ").append(i + 1).append("\n"); + context.append(JSONObject.toJSONString(item, true)).append("\n\n"); + } + } + + // 3. 知识库内容 + context.append("## 相关知识库内容\n"); + + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("### 企业单位知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append("- ").append(knowledge).append("\n")); + } + + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("### 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append("- ").append(knowledge).append("\n")); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("### 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append("- ").append(knowledge).append("\n")); + } + + // 4. 历史内容和用户建议 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n").append(history).append("\n"); + } + + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n").append(suggestion).append("\n"); + } + + return context.toString(); + } + + /** + * 解析三重一大数据 + */ + private JSONArray parseTripleOneData(Object data) { + if (data == null) { + return null; + } + + try { + JSONArray tripleData; + if (data instanceof JSONArray) { + tripleData = (JSONArray) data; + } else if (data instanceof List) { + tripleData = JSONArray.parseArray(JSONObject.toJSONString(data)); + } else { + JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(data)); + if (jsonObject.containsKey("data") && jsonObject.get("data") instanceof JSONArray) { + tripleData = jsonObject.getJSONArray("data"); + } else { + tripleData = new JSONArray(); + tripleData.add(jsonObject); + } + } + + log.info("解析三重一大数据成功,条数: {}", tripleData.size()); + return tripleData; + + } catch (Exception e) { + log.error("解析三重一大数据失败", e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3TripleServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3TripleServiceImpl.java new file mode 100644 index 0000000..30b537e --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent3TripleServiceImpl.java @@ -0,0 +1,284 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent3TripleConstants; +import com.gxwebsoft.ai.service.AuditContent3TripleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent3TripleServiceImpl extends AbstractAuditContentService implements AuditContent3TripleService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-tjXbmHDE6daMbYOT4O13ev2X"; + + // 三重一大分类定义 - 按照要求的顺序 + private static final List CATEGORY_ORDER = Arrays.asList( + AuditContent3TripleConstants.CATEGORY_MAJOR_DECISION, + AuditContent3TripleConstants.CATEGORY_PERSONNEL_APPOINTMENT, + AuditContent3TripleConstants.CATEGORY_MAJOR_PROJECT, + AuditContent3TripleConstants.CATEGORY_LARGE_FUND_OPERATION, + AuditContent3TripleConstants.CATEGORY_DECISION_PROCEDURE + ); + + @Override + public JSONObject generateTripleOneTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + log.info("开始生成三重一大制度对比分析表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个分类 + Map> futures = processCategoriesAsync( + CATEGORY_ORDER, + category -> generateCategoryDataAsync(category, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有分类的结果 + JSONArray allData = mergeCategoryResults(CATEGORY_ORDER, futures); + + log.info("三重一大制度对比分析表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "triple_one_audit"); + + } catch (Exception e) { + log.error("生成三重一大制度对比分析表失败", e); + return buildErrorResponse("生成三重一大制度对比分析表失败: " + e.getMessage()); + } + } + + /** + * 异步生成单个分类的数据 + */ + @Async + public CompletableFuture generateCategoryDataAsync(String category, String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成分类 {} 的数据", category); + + // 1. 为当前分类召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForCategory( + category, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文(包含固定内容和动态内容) + String knowledgeContext = buildCompleteKnowledgeContext( + category, knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray categoryData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "三重一大-" + category); + + log.info("分类 {} 数据生成完成,生成 {} 条记录", category, categoryData.size()); + return categoryData; + + } catch (Exception e) { + log.error("生成分类 {} 数据失败", category, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个分类检索相关知识 + */ + private Map> retrieveKnowledgeForCategory(String category, String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建当前分类的查询词 + List categoryQueries = buildCategoryQueries(category); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, categoryQueries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, categoryQueries, 80))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, categoryQueries, 60)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::tripleOneComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("分类 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + category, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建分类特定的查询词 + */ + private List buildCategoryQueries(String category) { + switch (category) { + case AuditContent3TripleConstants.CATEGORY_MAJOR_DECISION: + return Arrays.asList("重大决策事项 决策范围 决策程序", "重大决策制度 集体决策规定"); + case AuditContent3TripleConstants.CATEGORY_PERSONNEL_APPOINTMENT: + return Arrays.asList("重要人事任免 人事任免范围", "中层管理人员 领导班子成员 任免"); + case AuditContent3TripleConstants.CATEGORY_MAJOR_PROJECT: + return Arrays.asList("重大项目安排 项目安排范围", "年度投资计划 融资担保项目"); + case AuditContent3TripleConstants.CATEGORY_LARGE_FUND_OPERATION: + return Arrays.asList("大额度资金运作 资金运作范围", "年度预算 大额度资金使用"); + case AuditContent3TripleConstants.CATEGORY_DECISION_PROCEDURE: + return Arrays.asList("决策权力主体 决策程序", "集体决策 前置程序"); + default: + return Arrays.asList(category + " 制度规定"); + } + } + + /** + * 构建完整的知识上下文 - 包含固定内容和动态内容 + */ + private String buildCompleteKnowledgeContext(String category, Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 三重一大制度对比分析要求 + context.append("## 三重一大制度对比分析要求 - ").append(category).append("\n"); + context.append("请基于以下知识生成").append(category).append("相关的三重一大制度对比分析表数据:\n\n"); + context.append("1. 仅关注与").append(category).append("直接相关的制度内容,过滤不相关制度\n"); + context.append("2. 对比分析政策内容、集团制度、公司制度的差异\n"); + context.append("3. 重点关注决策范围、金额标准、程序要求的差异\n"); + context.append("4. 识别制度执行中的风险和问题\n"); + context.append("5. 严格判断测试结果,只有证据充分才能判定为通过,证据不足或存在差异必须判定为不通过\n\n"); + + // 2. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("需要生成").append(category).append("分类的数据,尽可能生成多个实例、多条记录:\n"); + context.append("- ").append(category).append(":").append(AuditContent3TripleConstants.CATEGORY_DESCRIPTIONS.get(category)).append("\n"); + context.append("\n"); + + context.append("每条记录应包含6个字段:\n"); + context.append("- policyContent:国家法律法规和政策规定,需引用具体法规名称和条款\n"); + context.append("- groupSystem:集团层面的三重一大相关制度规定,非相关制度不得纳入\n"); + context.append("- companyFormulation:公司层面的三重一大相关制度规定及执行情况,需包含详细分析过程和查阅依据\n"); + context.append("- checkEvidence:审计检查的证据,需详细描述查阅过程、查阅的具体文件和内容\n"); + context.append("- testResult:审计测试的结果(通过/不通过),严格判断,从严掌握通过标准\n"); +// context.append("- workPaperIndex:相关《参考文件名FileId》,必须是实际存在的完整文件FileId,不能使用附表标题,确保能在文件夹中搜索到\n"); + context.append("- workPaperIndex:相关[\"实际存在的完整文件名||FileUrl\"],必须是实际存在的完整文件名,不能使用附表标题,确保能在文件夹中搜索到\n"); + context.append("\n注意:\n"); + context.append("1. 请根据知识库内容尽可能全面地生成所有相关制度规定和检查点\n"); + context.append("2. 公司执行情况分析需包含:查阅了哪些文件、发现了什么内容、与制度的差异点、分析判断过程\n"); + context.append("3. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n"); + context.append("4. 测试结果判定需严格,对于制度不一致、执行不到位、证据不充分的情况必须判定为不通过\n"); + context.append("5. 审计检查的证据主要按公司制度为主,政策内容和集团制度为次要\n"); + context.append("6. 审计检查的证据增加判断公司制度是否与政策内容和集团制度相符\n"); + context.append("7. 历史问题整改分析:需核查历史问题是否已整改,结合最新时间材料(如最新会议纪要)分析当前是否存在相同问题\n"); + context.append("8. 项目上会核查:涉及三重一大的项目必须核查是否按规定上会,检查有无会议纪要作为证据\n"); + context.append("9. 制度权限关系:明确分析公司制度与集团制度的关联关系,权限设置必须遵循公司权限≤集团权限的原则\n"); + context.append("10. 层级关系识别:注意识别文件中的上级单位信息,分析制度执行是否符合层级管理要求\n\n"); + + // 3. 参考数据(从常量类中获取) + context.append("## 参考数据\n"); + context.append("### 政策内容\n").append(AuditContent3TripleConstants.POLICY_CONTENTS.get(category)).append("\n\n"); + context.append("### 集团制度\n").append(AuditContent3TripleConstants.GROUP_SYSTEMS.get(category)).append("\n\n"); + context.append("### 公司制度\n").append(AuditContent3TripleConstants.COMPANY_FORMULATIONS.get(category)).append("\n\n"); + + // 4. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 5. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 6. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + return context.toString(); + } + + /** + * 三重一大相关性比较器 + */ + private int tripleOneComparator(String reg1, String reg2) { + int score1 = calculateTripleOneRelevanceScore(reg1); + int score2 = calculateTripleOneRelevanceScore(reg2); + return Integer.compare(score2, score1); + } + + /** + * 计算三重一大相关性分数 + */ + private int calculateTripleOneRelevanceScore(String content) { + return AuditContent3TripleConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 100; + case "regulation": return 80; + case "auditCase": return 60; + default: return 50; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent4TargetServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent4TargetServiceImpl.java new file mode 100644 index 0000000..d3689c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent4TargetServiceImpl.java @@ -0,0 +1,333 @@ +// AuditContent4TargetServiceImpl.java +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent4TargetConstants; +import com.gxwebsoft.ai.service.AuditContent4TargetService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent4TargetServiceImpl extends AbstractAuditContentService implements AuditContent4TargetService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-Z6YPjz3nAB46roFnZSfCTk3w"; + + @Override + public JSONObject generateTargetAuditTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成目标责任制完成情况审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 1. 检索相关知识 + Map> knowledgeSources = retrieveKnowledgeForTargetResponsibility( + kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray auditData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "目标责任制审计"); + + // 4. 后处理:确保数据格式正确 + auditData = processAuditData(auditData); + + log.info("目标责任制完成情况审计表生成成功 - 记录数: {}, 处理时间: {}ms", + auditData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(auditData, startTime, "target_audit"); + + } catch (Exception e) { + log.error("生成目标责任制完成情况审计表失败", e); + return buildErrorResponse("生成目标责任制完成情况审计表失败: " + e.getMessage()); + } + } + + /** + * 检索目标责任制相关知识 + */ + private Map> retrieveKnowledgeForTargetResponsibility(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建查询词 + List queries = buildTargetResponsibilityQueries(); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, queries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, queries, 80))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, queries, 60)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::targetResponsibilityComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("目标责任制知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建目标责任制查询词 + */ + private List buildTargetResponsibilityQueries() { + return Arrays.asList( + "目标责任 目标责任制 目标任务", + "下达文件 计划文件 考核办法", + "完成情况 未完成原因 考核结果", + "上级主管 主管部门", + "年度计划 工作目标 考核指标", + "责任书 责任状 目标考核", + "经营目标 工作计划 绩效考核", + "重点工作 专项任务 年度总结" + ); + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, + String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 核心审计任务 + context.append("## 核心审计任务\n"); + context.append("审计分类:").append(AuditContent4TargetConstants.CATEGORY_TARGET_RESPONSIBILITY).append("\n"); + context.append("审计描述:检查被审计领导干部任职期间,对照上级主管部门下达的目标责任或单位自定的目标责任,检查其完成情况\n\n"); + + // 2. 审计框架(审计规则) + context.append("## 审计框架(审计规则)\n"); + context.append("以下审计框架定义了审计范围和要点,请基于此框架开展工作:\n"); + context.append(AuditContent4TargetConstants.AUDIT_FRAMEWORK).append("\n\n"); + + // 3. 审计目标 + context.append("## 审计目标\n"); + context.append(AuditContent4TargetConstants.AUDIT_OBJECTIVE).append("\n\n"); + + // 4. 审计工作原则 + context.append("## 审计工作原则\n"); + context.append(AuditContent4TargetConstants.AUDIT_PRINCIPLES).append("\n\n"); + + // 5. 企业单位知识(主要考察内容) + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识(具体考察内容)\n"); + context.append("以下是企业单位的实际资料,请基于审计框架,结合这些具体内容生成审计记录:\n\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 审计要点提示 + context.append("## 审计要点提示\n"); + context.append(AuditContent4TargetConstants.AUDIT_KEY_POINTS).append("\n\n"); + + // 7. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("请生成JSON数组格式的审计表数据,每个审计条目包含以下字段:\n"); + context.append(AuditContent4TargetConstants.OUTPUT_FORMAT).append("\n\n"); + + // 8. 审计证据要求 + context.append("## 审计证据要求\n"); + context.append(AuditContent4TargetConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n"); + + // 9. 特别提醒(新增关键优化点) + context.append("## 特别提醒\n"); + context.append("1. 审计证据必须包含具体的查阅过程:查阅了哪些文件、文件中发现了什么内容\n"); +// context.append("2. workPaperIndex必须填写实际存在的完整文件FileId,确保能在文件夹中搜索到\n"); + context.append("2. workPaperIndex必须填写实际存在的完整文件名||FileUrl,确保能在文件夹中搜索到\n"); + context.append("3. 完成情况判定必须基于充分证据:只有证据充分且完全符合要求才能判定为已完成\n"); + context.append("4. 对于执行不到位、效果不佳、证据不充分的情况必须在备注中说明\n"); + context.append("5. 尽可能识别知识库中所有相关年度,生成对应的审计记录\n"); + context.append("6. 如果知识库中没有单位自定目标文件,请根据企业职责和上级目标合理推断单位自定计划内容\n"); + context.append("7. 不能填写简单的\"无\",要提供有意义的描述\n"); + context.append("8. 对于正在进行的年度,完成情况应填写\"年度未结束无法评估\"\n"); + context.append("9. 如果知识库中有多个目标责任文件,每个文件都应生成独立的审计记录\n"); + context.append("10. 同一年度可能有不同类型的多个目标责任,应尽可能多地生成审计记录\n"); + context.append("11. 不能填写简单的\"无\",要提供有意义的描述\n"); + context.append("12. 对于正在进行的年度,完成情况应填写\"无法评估\"\n\n"); + + // 10. 法规和案例参考 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规参考\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例参考\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 11. 历史内容(如果有) + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append(history).append("\n\n"); + } + + // 12. 用户建议(如果有) + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n\n"); + } + + log.debug("目标责任制审计知识上下文长度: {} 字符", context.length()); + + return context.toString(); + } + + /** + * 目标责任制审计相关性比较器 + */ + private int targetResponsibilityComparator(String reg1, String reg2) { + int score1 = calculateTargetResponsibilityRelevanceScore(reg1); + int score2 = calculateTargetResponsibilityRelevanceScore(reg2); + return Integer.compare(score2, score1); // 降序排序 + } + + /** + * 计算目标责任制审计相关性分数 + */ + private int calculateTargetResponsibilityRelevanceScore(String content) { + return AuditContent4TargetConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 200; // 企业知识数量 + case "regulation": return 80; // 法规数量 + case "auditCase": return 60; // 案例数量 + default: return 50; + } + } + + /** + * 后处理审计数据(优化字段处理逻辑) + */ + private JSONArray processAuditData(JSONArray auditData) { + if (auditData == null || auditData.isEmpty()) { + return auditData; + } + + JSONArray processedData = new JSONArray(); + + for (int i = 0; i < auditData.size(); i++) { + JSONObject item = auditData.getJSONObject(i); + + // 确保序号存在 + if (!item.containsKey("index") || item.getInteger("index") == null) { + item.put("index", i + 1); + } + + // 确保年度存在 + if (!item.containsKey("year") || StrUtil.isBlank(item.getString("year"))) { + item.put("year", "未知年度"); + } + + // 优化单位自定计划字段:不能是简单的"无" + String selfPlan = item.getString("selfPlan"); + if (StrUtil.isBlank(selfPlan) || "无".equals(selfPlan)) { + String year = item.getString("year"); + item.put("selfPlan", year + "年度经营工作计划"); + } + + // 优化自定完成情况:不能是简单的"无" + String selfCompletion = item.getString("selfCompletion"); + if (StrUtil.isBlank(selfCompletion) || "无".equals(selfCompletion)) { + String superiorCompletion = item.getString("superiorCompletion"); + if ("已完成".equals(superiorCompletion)) { + item.put("selfCompletion", "已完成"); + } else if ("部分完成".equals(superiorCompletion)) { + item.put("selfCompletion", "部分完成"); + } else if ("未完成".equals(superiorCompletion)) { + item.put("selfCompletion", "未完成"); + } else if (superiorCompletion != null && + (superiorCompletion.contains("尚未完成") || superiorCompletion.contains("年度未结束"))) { + item.put("selfCompletion", "年度未结束无法评估"); + } else { + item.put("selfCompletion", "未提供完成情况数据"); + } + } + + // 优化自定未完成原因:不能是简单的"无" + String selfReason = item.getString("selfReason"); + if (StrUtil.isBlank(selfReason) || "无".equals(selfReason)) { + String selfComp = item.getString("selfCompletion"); + if ("已完成".equals(selfComp)) { + item.put("selfReason", "无未完成原因"); + } else if ("部分完成".equals(selfComp)) { + item.put("selfReason", "部分指标未达预期"); + } else if ("未完成".equals(selfComp)) { + item.put("selfReason", "经营目标未实现"); + } else if ("年度未结束无法评估".equals(selfComp)) { + item.put("selfReason", "年度尚未结束,无法评估"); + } else { + item.put("selfReason", "未提供原因分析"); + } + } + + // 确保工作底稿索引是数组格式 +// if (!item.containsKey("workPaperIndex") || !(item.get("workPaperIndex") instanceof JSONArray)) { +// JSONArray workPaperIndex = new JSONArray(); +// String year = item.getString("year"); +// workPaperIndex.add(year + "年度目标责任书"); +// workPaperIndex.add(year + "年度工作总结报告"); +// item.put("workPaperIndex", workPaperIndex); +// } + + processedData.add(item); + } + + return processedData; + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetExecutionServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetExecutionServiceImpl.java new file mode 100644 index 0000000..b4b93da --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetExecutionServiceImpl.java @@ -0,0 +1,314 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent5BudgetExecutionConstants; +import com.gxwebsoft.ai.service.AuditContent5BudgetExecutionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent5BudgetExecutionServiceImpl extends AbstractAuditContentService implements AuditContent5BudgetExecutionService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-40NwSJCFSFKkOwRdGe8nqVsA"; + + @Override + public JSONObject generateBudgetExecutionTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion, Object data) { + log.info("开始生成预算执行情况审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + if (ObjectUtil.isEmpty(data)) { + JSONObject result = new JSONObject(); + log.warn("未传入预算管理审计数据,无法生成预算执行情况审计表"); + result.put("success", false); + result.put("error", "需要预算管理审计数据作为生成依据"); + return result; + } + + // 1. 检索相关知识 + Map> knowledgeSources = retrieveKnowledgeForBudgetExecution( + kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文(包含预算管理数据) + String knowledgeContext = buildCompleteKnowledgeContext(knowledgeSources, history, suggestion, data.toString()); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray auditData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "预算执行情况审计"); + + log.info("预算执行情况审计表生成成功 - 记录数: {}, 处理时间: {}ms", + auditData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(auditData, startTime, "budget_execution_audit"); + + } catch (Exception e) { + log.error("生成预算执行情况审计表失败", e); + return buildErrorResponse("生成预算执行情况审计表失败: " + e.getMessage()); + } + } + + /** + * 检索预算执行情况相关知识 + */ + private Map> retrieveKnowledgeForBudgetExecution(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + knowledgeSources.put("financial", new ArrayList<>()); + + // 构建查询词 - 具体化,提高识别率 + List queries = buildBudgetExecutionQueries(); + + // 企业单位库检索 - 增加查询深度 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, queries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, queries, 100))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, queries, 80)); + } + + // 财务知识库检索(预算执行相关) + if (StrUtil.isNotBlank(kbIds)) { + knowledgeSources.get("financial").addAll( + queryKnowledgeBase(kbIds.split(",")[0], buildFinancialExecutionQueries(), 120)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .map(String::trim) // 去除首尾空格 + .filter(str -> !StrUtil.isBlank(str)) // 过滤空字符串 + .distinct() + .sorted(this::budgetExecutionComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("预算执行情况知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条, 财务: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size(), + knowledgeSources.get("financial").size()); + + return knowledgeSources; + } + + /** + * 构建预算执行情况查询词 - 具体化 + */ + private List buildBudgetExecutionQueries() { + return Arrays.asList( + "预算执行率 执行进度 进度报表", + "实际拨款 拨款到位 资金拨付", + "预算调整 追加预算 调整审批", + "预算执行分析 执行情况分析 执行报告", + "项目执行 项目实施 项目进度", + "资金使用 资金拨付 资金到位", + "指标结余 预算结余 结转结余", + "预算执行偏差 执行差异 差异分析", + "月度执行 季度执行 年度执行", + "预算执行监督 执行检查 执行审计", + "预算执行效果 执行效益 执行成果", + "预算执行合规 执行规范 执行合法", + "预算执行台账 执行记录 执行日志", + "拨款凭证 付款凭证 资金支付", + "预算执行预警 执行风险 风险预警", + "预算执行考核 执行评价 绩效考核", + "预算执行整改 执行问题 整改措施", + "预算执行信息化 执行系统 电子台账", + "预算执行报告 执行汇报 执行总结", + "预算执行公开 执行透明 信息公开" + ); + } + + /** + * 构建财务执行相关查询词 + */ + private List buildFinancialExecutionQueries() { + return Arrays.asList( + "财务报表 财务报告 会计报表", + "资金流水 银行流水 现金流水", + "支付凭证 付款记录 银行回单", + "预算执行表 执行进度表 执行明细", + "决算报表 决算报告 决算分析", + "费用报销 报销凭证 报销记录", + "项目支出 项目付款 项目结算", + "资金监管 资金监控 资金跟踪", + "财务分析 执行分析 差异分析", + "审计报告 审计意见 审计整改" + ); + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, + String history, String suggestion, String data) { + StringBuilder context = new StringBuilder(); + + // 1. 核心审计任务 + context.append("## 核心审计任务\n"); + context.append("审计分类:").append(AuditContent5BudgetExecutionConstants.CATEGORY_BUDGET_EXECUTION).append("\n"); + context.append("审计描述:检查预算执行的进度、合规性、效果,分析预算执行偏差原因,评估预算执行效益\n\n"); + + // 2. 审计框架(审计规则) + context.append("## 审计框架(审计规则)\n"); + context.append("以下审计框架定义了审计范围和要点,请基于此框架开展工作:\n"); + context.append(AuditContent5BudgetExecutionConstants.AUDIT_FRAMEWORK).append("\n\n"); + + // 3. 审计目标 + context.append("## 审计目标\n"); + context.append(AuditContent5BudgetExecutionConstants.AUDIT_OBJECTIVE).append("\n\n"); + + // 4. 审计工作原则 + context.append("## 审计工作原则\n"); + context.append(AuditContent5BudgetExecutionConstants.AUDIT_PRINCIPLES).append("\n\n"); + + // 4.5. 预算管理审计数据(作为上下文) + if (ObjectUtil.isNotEmpty(data)) { + context.append("## 预算管理审计数据(作为参考上下文)\n"); + context.append(data).append("\n\n"); + } + + // 5. 企业单位知识(主要考察内容) + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识(具体考察内容)\n"); + context.append("以下是企业单位的实际预算执行资料,请基于审计框架,结合这些具体内容生成审计记录:\n\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 财务知识(预算执行相关) + if (!knowledgeSources.get("financial").isEmpty()) { + context.append("## 财务知识(预算执行相关)\n"); + context.append("以下是财务相关预算执行资料,包括预算执行进度、资金拨付等信息:\n\n"); + knowledgeSources.get("financial").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 审计要点提示 + context.append("## 审计要点提示\n"); + context.append(AuditContent5BudgetExecutionConstants.AUDIT_KEY_POINTS).append("\n\n"); + + // 8. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("请生成JSON数组格式的审计表数据,每个审计条目包含以下字段:\n"); + context.append(AuditContent5BudgetExecutionConstants.OUTPUT_FORMAT).append("\n\n"); + + // 9. 审计证据要求 + context.append("## 审计证据要求\n"); + context.append(AuditContent5BudgetExecutionConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n"); + + // 10. 特别提醒 - 强调全面识别 + context.append("## 特别提醒\n"); + context.append("1. 必须全面分析预算执行全过程,包括执行进度、执行效果、执行合规性\n"); + context.append("2. 重点关注预算执行率、资金到位率、预算执行偏差等关键指标\n"); + context.append("3. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"有\"或\"无\"\n"); +// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n"); + context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n"); + context.append("5. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"无\"\n"); + context.append("6. 基于预算执行全流程进行审计分析,包括月度、季度、年度执行情况\n"); + context.append("7. 重点关注预算执行偏差原因分析和整改措施\n"); + context.append("8. 尽可能多地生成审计记录,覆盖所有预算项目和执行环节\n\n"); + + // 11. 审计项目参考 + context.append("## 审计项目参考(常见类型)\n"); + AuditContent5BudgetExecutionConstants.AUDIT_PROJECT_TYPES.forEach(project -> + context.append("• ").append(project).append("\n")); + context.append("\n"); + + // 12. 法规和案例参考 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规参考\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例参考\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 13. 历史内容(如果有) + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append(history).append("\n\n"); + } + + // 14. 用户建议(如果有) + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n\n"); + } + + log.debug("预算执行情况审计知识上下文长度: {} 字符", context.length()); + + return context.toString(); + } + + /** + * 预算执行情况审计相关性比较器 + */ + private int budgetExecutionComparator(String reg1, String reg2) { + int score1 = calculateBudgetExecutionRelevanceScore(reg1); + int score2 = calculateBudgetExecutionRelevanceScore(reg2); + return Integer.compare(score2, score1); // 降序排序 + } + + /** + * 计算预算执行情况审计相关性分数 + */ + private int calculateBudgetExecutionRelevanceScore(String content) { + return AuditContent5BudgetExecutionConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 250; + case "financial": return 150; + case "regulation": return 60; + case "auditCase": return 40; + default: return 50; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetManageServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetManageServiceImpl.java new file mode 100644 index 0000000..927712a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent5BudgetManageServiceImpl.java @@ -0,0 +1,313 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent5BudgetManageConstants; +import com.gxwebsoft.ai.service.AuditContent5BudgetManageService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent5BudgetManageServiceImpl extends AbstractAuditContentService implements AuditContent5BudgetManageService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-ZGILK3nfQ9A0jHot1r4Tta0r"; + + @Override + public JSONObject generateBudgetManageTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion, Object data) { + log.info("开始生成预算管理审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + if (ObjectUtil.isEmpty(data)) { + JSONObject result = new JSONObject(); + log.warn("未传入审计内容6大数据,无法生成预算管理审计表"); + result.put("success", false); + result.put("error", "需要审计内容6数据作为生成依据"); + return result; + } + + // 1. 检索相关知识 + Map> knowledgeSources = retrieveKnowledgeForBudgetManagement( + kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext(knowledgeSources, history, suggestion, data.toString()); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray auditData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "预算管理审计"); + + log.info("预算管理审计表生成成功 - 记录数: {}, 处理时间: {}ms", + auditData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(auditData, startTime, "budget_manage_audit"); + + } catch (Exception e) { + log.error("生成预算管理审计表失败", e); + return buildErrorResponse("生成预算管理审计表失败: " + e.getMessage()); + } + } + + /** + * 检索预算管理相关知识 + */ + private Map> retrieveKnowledgeForBudgetManagement(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + knowledgeSources.put("financial", new ArrayList<>()); + + // 构建查询词 - 具体化,提高识别率 + List queries = buildBudgetManagementQueries(); + + // 企业单位库检索 - 增加查询深度 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, queries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, queries, 100))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, queries, 80)); + } + + // 财务知识库检索(预算相关) + if (StrUtil.isNotBlank(kbIds)) { + knowledgeSources.get("financial").addAll( + queryKnowledgeBase(kbIds.split(",")[0], buildFinancialQueries(), 120)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .map(String::trim) // 去除首尾空格 + .filter(str -> !StrUtil.isBlank(str)) // 过滤空字符串 + .distinct() + .sorted(this::budgetManagementComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("预算管理知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条, 财务: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size(), + knowledgeSources.get("financial").size()); + + return knowledgeSources; + } + + /** + * 构建预算管理查询词 - 具体化 + */ + private List buildBudgetManagementQueries() { + return Arrays.asList( + "部门预算 预算编制 预算报表", + "预算调整 追加预算 预算调剂", + "预算执行 预算决算 决算报表", + "指标来源 指标运用 指标结余", + "财政拨款 资金拨付 经费拨款", + "工资统发 人员经费 工资发放", + "政府采购 采购预算 采购资金", + "财政管理专户 专户管理 资金专户", + "项目预算 项目申报 项目资金", + "预算科目 科目设置 会计科目", + "上年结余 结余资金 结转资金", + "年初预算 年度预算 预算安排", + "追加预算 预算追加 调整预算", + "预算审批 审批手续 审批流程", + "预算下达 预算批复 批复文件", + "会计处理 账务处理 财务处理", + "预算调剂 科目调剂 资金调剂", + "经营收支 经营预算 企业预算", + "可行性研究 项目评审 评审报告", + "执行单位 实施单位 承办单位" + ); + } + + /** + * 构建财务相关查询词 + */ + private List buildFinancialQueries() { + return Arrays.asList( + "财务报表 财务报告 会计报表", + "会计凭证 记账凭证 原始凭证", + "会计账簿 总账 明细账", + "资金流水 银行流水 现金流水", + "收支明细 收入支出 收付实现", + "经费支出 费用报销 报销凭证", + "工资表 工资发放表 人员工资", + "采购发票 采购合同 付款凭证", + "预算执行表 执行进度 进度报表", + "决算报告 决算分析 决算说明" + ); + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, + String history, String suggestion, String data) { + StringBuilder context = new StringBuilder(); + + // 1. 核心审计任务 + context.append("## 核心审计任务\n"); + context.append("审计分类:").append(AuditContent5BudgetManageConstants.CATEGORY_BUDGET_MANAGEMENT).append("\n"); + context.append("审计描述:检查预算编制的完整准确、预算调整审批的合规,以及预算支出的真实合法合规情况\n\n"); + + // 2. 审计框架(审计规则) + context.append("## 审计框架(审计规则)\n"); + context.append("以下审计框架定义了审计范围和要点,请基于此框架开展工作:\n"); + context.append(AuditContent5BudgetManageConstants.AUDIT_FRAMEWORK).append("\n\n"); + + // 3. 审计目标 + context.append("## 审计目标\n"); + context.append(AuditContent5BudgetManageConstants.AUDIT_OBJECTIVE).append("\n\n"); + + // 4. 审计工作原则 + context.append("## 审计工作原则\n"); + context.append(AuditContent5BudgetManageConstants.AUDIT_PRINCIPLES).append("\n\n"); + + // 4.5. 国有资产的管理情况、政府采购执行情况,审计内容6数据(作为上下文) + if (StrUtil.isNotBlank(data)) { + context.append("## 国有资产的管理情况、政府采购执行情况,审计内容6分析数据(作为参考上下文)\n"); + context.append(data).append("\n\n"); + } + + // 5. 企业单位知识(主要考察内容) + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识(具体考察内容)\n"); + context.append("以下是企业单位的实际资料,请基于审计框架,结合这些具体内容生成审计记录:\n\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 财务知识(预算相关) + if (!knowledgeSources.get("financial").isEmpty()) { + context.append("## 财务知识(预算相关)\n"); + context.append("以下是财务相关资料,包括预算编制、执行、调整等信息:\n\n"); + knowledgeSources.get("financial").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 审计要点提示 + context.append("## 审计要点提示\n"); + context.append(AuditContent5BudgetManageConstants.AUDIT_KEY_POINTS).append("\n\n"); + + // 8. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("请生成JSON数组格式的审计表数据,每个审计条目包含以下字段:\n"); + context.append(AuditContent5BudgetManageConstants.OUTPUT_FORMAT).append("\n\n"); + + // 9. 审计证据要求 + context.append("## 审计证据要求\n"); + context.append(AuditContent5BudgetManageConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n"); + + // 10. 特别提醒 - 强调全面识别 + context.append("## 特别提醒\n"); + context.append("1. 必须全面识别知识库中所有预算科目,包括:基本支出、项目支出、人员经费、公用经费等\n"); + context.append("2. 每个独立的预算科目都要生成独立的审计记录,不限制数量,尽可能多地生成\n"); + context.append("3. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"有\"或\"无\"\n"); +// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n"); + context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n"); + context.append("5. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"无\"\n"); + context.append("6. 基于预算编制、调整、执行的全流程进行审计分析\n"); + context.append("7. 重点关注预算调整的合规性和预算执行的真实性\n\n"); + + // 11. 预算科目参考 + context.append("## 预算科目参考(常见类型)\n"); + AuditContent5BudgetManageConstants.BUDGET_SUBJECT_TYPES.forEach(subject -> + context.append("• ").append(subject).append("\n")); + context.append("\n"); + + // 12. 法规和案例参考 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规参考\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例参考\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 13. 历史内容(如果有) + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append(history).append("\n\n"); + } + + // 14. 用户建议(如果有) + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n\n"); + } + + log.debug("预算管理审计知识上下文长度: {} 字符", context.length()); + + return context.toString(); + } + + /** + * 预算管理审计相关性比较器 + */ + private int budgetManagementComparator(String reg1, String reg2) { + int score1 = calculateBudgetManagementRelevanceScore(reg1); + int score2 = calculateBudgetManagementRelevanceScore(reg2); + return Integer.compare(score2, score1); // 降序排序 + } + + /** + * 计算预算管理审计相关性分数 + */ + private int calculateBudgetManagementRelevanceScore(String content) { + return AuditContent5BudgetManageConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 250; + case "financial": return 150; + case "regulation": return 60; + case "auditCase": return 40; + default: return 50; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent6StateAssetsServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent6StateAssetsServiceImpl.java new file mode 100644 index 0000000..d08ca0b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent6StateAssetsServiceImpl.java @@ -0,0 +1,337 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent6StateAssetsConstants; +import com.gxwebsoft.ai.service.AuditContent6StateAssetsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent6StateAssetsServiceImpl extends AbstractAuditContentService implements AuditContent6StateAssetsService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-lsqVD2hTWYQpA89SDSr9s3dH"; + + @Override + public JSONObject generateStateAssetsAuditTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成国有资产管理审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 1. 检索相关知识 + Map> knowledgeSources = retrieveKnowledgeForStateAssetsManagement( + kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray auditData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "国资管理审计"); + + // 4. 后处理:确保数据格式正确 + auditData = processAuditData(auditData); + + log.info("国有资产管理审计表生成成功 - 记录数: {}, 处理时间: {}ms", + auditData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(auditData, startTime, "state_assets_audit"); + + } catch (Exception e) { + log.error("生成国有资产管理审计表失败", e); + return buildErrorResponse("生成国有资产管理审计表失败: " + e.getMessage()); + } + } + + /** + * 检索国资管理相关知识 + */ + private Map> retrieveKnowledgeForStateAssetsManagement(String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建查询词 - 具体化,提高识别率 + List queries = buildStateAssetsQueries(); + + // 企业单位库检索 - 增加查询深度 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, queries, 100))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, queries, 100))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, queries, 80)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .map(String::trim) // 去除首尾空格 + .filter(str -> !StrUtil.isBlank(str)) // 过滤空字符串 + .distinct() + .sorted(this::stateAssetsManagementComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("国资管理知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建国资管理查询词 - 具体化 + */ + private List buildStateAssetsQueries() { + return Arrays.asList( + "固定资产 资产台账 资产登记", + "房屋 土地 土地使用权", + "车辆 汽车 运输工具", + "机械设备 生产设备 仪器设备", + "办公设备 电脑 打印机 空调", + "仓库 厂房 办公楼 宿舍楼", + "家具 电器 电子设备", + "无形资产 软件 专利权", + "在建工程 工程项目", + "土地证 房产证 不动产权证", + "资产评估 资产价值 原值 净值", + "资产折旧 累计折旧", + "资产盘点 盘点表 盘点记录", + "资产报废 资产处置", + "资产出租 租赁合同 租金", + "资产转让 资产出售", + "政府采购 采购合同", + "招投标 投标文件", + "资产配置 资产采购", + "资产使用 资产保管" + ); + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(Map> knowledgeSources, + String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 核心审计任务 + context.append("## 核心审计任务\n"); + context.append("审计分类:").append(AuditContent6StateAssetsConstants.CATEGORY_STATE_ASSETS_MANAGEMENT).append("\n"); + context.append("审计描述:检查国有资产管理情况和政府采购执行情况,确保资产安全完整和采购规范\n\n"); + + // 2. 审计框架(审计规则) + context.append("## 审计框架(审计规则)\n"); + context.append("以下审计框架定义了审计范围和要点,请基于此框架开展工作:\n"); + context.append(AuditContent6StateAssetsConstants.AUDIT_FRAMEWORK).append("\n\n"); + + // 3. 审计目标 + context.append("## 审计目标\n"); + context.append(AuditContent6StateAssetsConstants.AUDIT_OBJECTIVE).append("\n\n"); + + // 4. 审计工作原则 + context.append("## 审计工作原则\n"); + context.append(AuditContent6StateAssetsConstants.AUDIT_PRINCIPLES).append("\n\n"); + + // 5. 企业单位知识(主要考察内容) + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识(具体考察内容)\n"); + context.append("以下是企业单位的实际资料,请基于审计框架,结合这些具体内容生成审计记录:\n\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 6. 审计要点提示 + context.append("## 审计要点提示\n"); + context.append(AuditContent6StateAssetsConstants.AUDIT_KEY_POINTS).append("\n\n"); + + // 7. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("请生成JSON数组格式的审计表数据,每个审计条目包含以下字段:\n"); + context.append(AuditContent6StateAssetsConstants.OUTPUT_FORMAT).append("\n\n"); + + // 8. 审计证据要求 + context.append("## 审计证据要求\n"); + context.append(AuditContent6StateAssetsConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n"); + + // 9. 特别提醒 - 强调全面识别 + context.append("## 特别提醒\n"); + context.append("1. 必须全面识别知识库中所有国有资产,包括:房屋、土地、车辆、机械设备、办公设备、电子设备、家具、无形资产等\n"); + context.append("2. 每个独立的资产都要生成独立的审计记录,不限制数量,尽可能多地生成\n"); + context.append("3. 即使资产信息不完整,也要基于现有信息生成审计记录\n"); +// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n"); + context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n"); + context.append("5. 对于未出租资产,承租方、合同金额等字段填写\"未出租\"\n"); + context.append("6. 备注中应详细说明资产状况、使用情况、合规性评价\n"); + context.append("7. 不能填写简单的\"无\",要提供有意义的描述\n\n"); + + // 10. 法规和案例参考 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规参考\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例参考\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append("• ").append(knowledge).append("\n")); + context.append("\n"); + } + + // 11. 历史内容(如果有) + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append(history).append("\n\n"); + } + + // 12. 用户建议(如果有) + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n\n"); + } + + log.debug("国资管理审计知识上下文长度: {} 字符", context.length()); + + return context.toString(); + } + + /** + * 国资管理审计相关性比较器 + */ + private int stateAssetsManagementComparator(String reg1, String reg2) { + int score1 = calculateStateAssetsRelevanceScore(reg1); + int score2 = calculateStateAssetsRelevanceScore(reg2); + return Integer.compare(score2, score1); // 降序排序 + } + + /** + * 计算国资管理审计相关性分数 + */ + private int calculateStateAssetsRelevanceScore(String content) { + return AuditContent6StateAssetsConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 300; // 增加企业知识数量 + case "regulation": return 60; + case "auditCase": return 40; + default: return 50; + } + } + + /** + * 后处理审计数据 + */ + private JSONArray processAuditData(JSONArray auditData) { + if (auditData == null || auditData.isEmpty()) { + return auditData; + } + + JSONArray processedData = new JSONArray(); + + for (int i = 0; i < auditData.size(); i++) { + JSONObject item = auditData.getJSONObject(i); + + // 确保序号存在 + if (!item.containsKey("index") || item.getInteger("index") == null) { + item.put("index", i + 1); + } + + // 确保国有资产名称存在 + if (!item.containsKey("assetName") || StrUtil.isBlank(item.getString("assetName"))) { + item.put("assetName", "国有资产" + (i + 1)); + } + + // 优化取得方式 + String acquisitionMethod = item.getString("acquisitionMethod"); + if (StrUtil.isBlank(acquisitionMethod) || "无".equals(acquisitionMethod)) { + item.put("acquisitionMethod", "购置"); + } + + // 优化资产价值 + String assetValue = item.getString("assetValue"); + if (StrUtil.isBlank(assetValue) || "无".equals(assetValue)) { + item.put("assetValue", "待评估"); + } + + // 优化面积 + String area = item.getString("area"); + if (StrUtil.isBlank(area) || "无".equals(area)) { + item.put("area", "未提供"); + } + + // 优化出租相关字段 + String platformLease = item.getString("platformLease"); + if (StrUtil.isBlank(platformLease) || "无".equals(platformLease)) { + item.put("platformLease", "否"); + } + + String tenant = item.getString("tenant"); + if (StrUtil.isBlank(tenant) || "无".equals(tenant) || tenant.contains("无")) { + item.put("tenant", "未出租"); + item.put("contractAmount", "未出租"); + item.put("leasePeriod", "未出租"); + item.put("rentPaymentTime", "未出租"); + item.put("approvalDoc", "未出租"); + } + + // 优化是否纳入预算 + String inBudget = item.getString("inBudget"); + if (StrUtil.isBlank(inBudget) || "无".equals(inBudget)) { + item.put("inBudget", "是"); + } + + // 确保工作底稿索引是数组格式 +// if (!item.containsKey("workPaperIndex") || !(item.get("workPaperIndex") instanceof JSONArray)) { +// JSONArray workPaperIndex = new JSONArray(); +// String assetName = item.getString("assetName"); +// workPaperIndex.add(assetName + "资产登记台账"); +// item.put("workPaperIndex", workPaperIndex); +// } + + processedData.add(item); + } + + return processedData; + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent7InvestmentServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent7InvestmentServiceImpl.java new file mode 100644 index 0000000..e86e411 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent7InvestmentServiceImpl.java @@ -0,0 +1,359 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent7Constants; +import com.gxwebsoft.ai.service.AuditContent7InvestmentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent7InvestmentServiceImpl extends AbstractAuditContentService implements AuditContent7InvestmentService { + + // Dify工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-rNhgcQkNr2JJP9lePT3ICzgW"; + + // 审计类别顺序 + private static final List CATEGORY_ORDER = AuditContent7Constants.getAllCategories(); + + @Override + public JSONObject generateInvestmentSituationTableData(String kbIds, String libraryKbIds, + String projectLibrary, String userName, + String history, String suggestion) { + log.info("开始生成重大投资情况审计表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", + userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个审计类别 + Map> futures = processCategoriesAsync( + CATEGORY_ORDER, + category -> generateCategoryDataAsync(category, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有分类的结果 + JSONArray allData = mergeCategoryResults(CATEGORY_ORDER, futures); + + log.info("重大投资情况审计表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "investment_situation_audit"); + + } catch (Exception e) { + log.error("生成重大投资情况审计表失败", e); + return buildErrorResponse("生成重大投资情况审计表失败: " + e.getMessage()); + } + } + + /** + * 异步生成单个审计类别的数据 + */ + @Async + public CompletableFuture generateCategoryDataAsync(String category, String kbIds, + String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成审计类别 {} 的数据", category); + + // 1. 为当前审计类别召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForCategory( + category, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + category, knowledgeSources, history, suggestion + ); + + // 3. 调用Dify工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray categoryData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "重大投资情况-" + category); + + log.info("审计类别 {} 数据生成完成,生成 {} 条记录", category, categoryData.size()); + return categoryData; + + } catch (Exception e) { + log.error("生成审计类别 {} 数据失败", category, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个审计类别检索相关知识 + */ + private Map> retrieveKnowledgeForCategory(String category, String kbIds, + String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + knowledgeSources.put("financial", new ArrayList<>()); // 财务数据单独处理 + + // 构建当前审计类别的查询词 + List categoryQueries = buildCategoryQueries(category); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> { + knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, categoryQueries, 120)); + // 单独查询财务相关文档 + knowledgeSources.get("financial") + .addAll(queryKnowledgeBase(kbId, + Arrays.asList("资产负债表", "利润表", "现金流量表", "会计科目", "银行流水"), 80)); + }); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, categoryQueries, 100))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, categoryQueries, 80)); + } + + // 智能去重和排序(按相关性) + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::investmentRelevanceComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("审计类别 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条, 财务: {}条", + category, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size(), + knowledgeSources.get("financial").size()); + + return knowledgeSources; + } + + /** + * 构建审计类别特定的查询词 + */ + private List buildCategoryQueries(String category) { + switch (category) { + case AuditContent7Constants.CATEGORY_MAJOR_INVESTMENT: + return Arrays.asList("对外投资 决策程序 审批手续 可行性研究 投资效益", + "长期投资 短期投资 投资合同 投资回报"); + case AuditContent7Constants.CATEGORY_MAJOR_PROJECT: + return Arrays.asList("工程建设 项目审批 安全质量 环保要求 工程效益", + "工程项目 工程管理 竣工验收"); + case AuditContent7Constants.CATEGORY_MAJOR_CAPITAL: + return Arrays.asList("资本运作 并购重组 交易对价 资金来源", + "股权交易 资产评估 资本运营"); + case AuditContent7Constants.CATEGORY_MAJOR_ASSET_DISPOSAL: + return Arrays.asList("资产处置 资产评估 交易价格 产权交易", + "资产转让 资产拍卖 资产划转"); + case AuditContent7Constants.CATEGORY_MAJOR_PROCUREMENT: + return Arrays.asList("物资采购 服务采购 招标投标 采购价格", + "采购合同 供应商 采购管理"); + case AuditContent7Constants.CATEGORY_MAJOR_GUARANTEE: + return Arrays.asList("担保借款 担保合同 借款合同 风险控制", + "对外担保 借款审批 抵押质押"); + default: + return Arrays.asList(category); + } + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(String category, Map> knowledgeSources, + String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 审计要求说明 + context.append("## 重大投资情况审计要求 - ").append(category).append("\n"); + context.append("请基于以下知识生成").append(category).append("相关的审计表格数据:\n\n"); + context.append("**审计目标:**\n"); + context.append("- 核查重大投资决策的合规性和民主性\n"); + context.append("- 检查审批手续的完备性\n"); + context.append("- 评估可行性研究和尽职调查的充分性\n"); + context.append("- 核査投资项目的效益情况\n"); + context.append("- 识别决策程序、审批过程、操作过程中的风险和问题\n\n"); + + // 2. 审计方法(从常量中获取) + context.append("## 审计方法及步骤\n"); + context.append(AuditContent7Constants.AUDIT_INSTRUCTIONS.get("GENERAL_RULE")).append("\n\n"); + + // 3. 审计指令(决策程序检查) + context.append("## 审计检查指令\n"); + context.append(AuditContent7Constants.AUDIT_INSTRUCTIONS.get("DECISION_SYSTEM_CHECK")).append("\n"); + context.append(AuditContent7Constants.AUDIT_INSTRUCTIONS.get("DECISION_PROCEDURE_CHECK")).append("\n\n"); + + // 4. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("需要生成").append(category).append("分类的数据,尽可能生成多个实例:\n"); + context.append("- ").append(category).append(":").append(AuditContent7Constants.CATEGORY_DESCRIPTIONS.get(category)).append("\n"); + context.append("\n"); + + context.append("每条记录应包含5个核心字段:\n"); + context.append("- **category**:固定为\"").append(category).append("\"\n"); + context.append("- **auditContent**:具体的审计内容,从以下模板中选择或根据知识库生成\n"); + context.append("- **checkEvidence**:详细的检查证据描述,必须包含:\n"); + context.append(" ①查阅的具体文件名称和内容\n"); + context.append(" ②检查过程和方法\n"); + context.append(" ③与制度要求的差异点\n"); + context.append(" ④明确的总结性结论(问题、风险、合规状况)\n"); + context.append("- **testResult**:测试结果(通过/不通过/待检查),严格判断:\n"); + context.append(" • 通过:证据充分且完全符合要求\n"); + context.append(" • 不通过:制度不一致、执行不到位、证据不充分\n"); + context.append(" • 待检查:无法确定,需要进一步检查\n"); +// context.append("- **workPaperIndex**:相关《工作底稿索引FileId》,必须是实际存在的完整文件FileId,确保能在文件夹中搜索到\n"); + context.append("- **workPaperIndex**:相关[\"实际存在的完整文件名||FileUrl\"],必须是实际存在的完整文件名,不能使用附表标题,确保能在文件夹中搜索到\n"); + context.append("- **fileIndex**:相关《文件索引》,关联的支持文件\n\n"); + + // 5. 审计内容模板(提供参考) + context.append("## 审计内容模板(供参考)\n"); + List templates = AuditContent7Constants.AUDIT_CONTENT_TEMPLATES.get(category); + if (templates != null && !templates.isEmpty()) { + for (int i = 0; i < Math.min(templates.size(), 5); i++) { + context.append(i + 1).append(". ").append(templates.get(i)).append("\n"); + } + } + context.append("(请根据知识库内容,从不同角度生成多个审计检查点)\n\n"); + + // 6. 文件类型参考(用于工作底稿索引) + context.append("## 相关文件类型参考\n"); + AuditContent7Constants.FILE_TYPE_KEYWORDS.forEach((type, keywords) -> { + context.append("- ").append(type).append(":").append(String.join("、", keywords)).append("\n"); + }); + context.append("\n"); + + // 7. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 8. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 9. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位制度知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 10. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 11. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 12. 财务数据知识 + if (!knowledgeSources.get("financial").isEmpty()) { + context.append("## 财务数据知识\n"); + context.append("(重点关注:资产负债表、利润表、会计科目、银行流水等)\n"); + knowledgeSources.get("financial").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + return context.toString(); + } + + /** + * 重大投资情况相关性比较器 + */ + private int investmentRelevanceComparator(String reg1, String reg2) { + int score1 = calculateInvestmentRelevanceScore(reg1); + int score2 = calculateInvestmentRelevanceScore(reg2); + return Integer.compare(score2, score1); // 降序排列 + } + + /** + * 计算重大投资情况相关性分数 + */ + private int calculateInvestmentRelevanceScore(String content) { + return AuditContent7Constants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + /** + * 根据知识来源类型获取限制数量 + */ + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 120; + case "regulation": return 100; + case "auditCase": return 80; + case "financial": return 60; + default: return 50; + } + } + + /** + * 重写callWorkflow方法,适配重大投资情况工作流的返回格式 + */ + @Override + protected JSONArray callWorkflow(String url, String token, JSONObject requestBody, String workflowName) { + try { + log.info("调用{}工作流,请求体长度: {}", workflowName, requestBody.toString().length()); + + // 调用父类方法获取工作流响应 + JSONArray result = super.callWorkflow(url, token, requestBody, workflowName); + + // 如果父类方法返回结果为空,则尝试按重大投资情况特有的格式解析 + if (result == null || result.isEmpty()) { + // 这里可以根据需要添加特定的解析逻辑 + log.warn("{}工作流返回结果为空,使用默认空数组", workflowName); + return new JSONArray(); + } + + log.info("成功获取{}工作流返回数据,记录数: {}", workflowName, result.size()); + return result; + + } catch (Exception e) { + log.error("调用{}工作流失败", workflowName, e); + throw new RuntimeException("调用" + workflowName + "工作流失败: " + e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java new file mode 100644 index 0000000..d272313 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java @@ -0,0 +1,327 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent8InternalControlConstants; +import com.gxwebsoft.ai.service.AuditContent8InternalControlService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent8InternalControlServiceImpl extends AbstractAuditContentService implements AuditContent8InternalControlService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-R1GN6zKkrfBYVMtwz8c6pyRM"; + + // 审计测试方法 + private static final List TEST_METHODS = Arrays.asList( + "符合性测试", "穿行测试", "抽样测试", "观察询问", "文档查阅" + ); + + @Override + public JSONObject generateInternalControlTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + log.info("开始生成单位层面财务管理内部控制测试表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个控制环节 + Map> futures = processCategoriesAsync( + Arrays.asList(AuditContent8InternalControlConstants.CONTROL_LINKS), + controlLink -> generateControlLinkDataAsync(controlLink, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有分类的结果 + JSONArray allData = mergeCategoryResults(Arrays.asList(AuditContent8InternalControlConstants.CONTROL_LINKS), futures); + + log.info("单位层面财务管理内部控制测试表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "internal_control_audit"); + + } catch (Exception e) { + log.error("生成单位层面财务管理内部控制测试表失败", e); + return buildErrorResponse("生成单位层面财务管理内部控制测试表失败: " + e.getMessage()); + } + } + + /** + * 异步生成单个控制环节的数据 + */ + @Async + public CompletableFuture generateControlLinkDataAsync(String controlLink, String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成控制环节 {} 的数据", controlLink); + + // 1. 为当前控制环节召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForControlLink( + controlLink, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + controlLink, knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray controlLinkData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "内部控制-" + controlLink); + + log.info("控制环节 {} 数据生成完成,生成 {} 条记录", controlLink, controlLinkData.size()); + return controlLinkData; + + } catch (Exception e) { + log.error("生成控制环节 {} 数据失败", controlLink, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个控制环节检索相关知识 + */ + private Map> retrieveKnowledgeForControlLink(String controlLink, String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建当前控制环节的查询词 + List controlQueries = buildControlLinkQueries(controlLink); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, controlQueries, 120))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, controlQueries, 100))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, controlQueries, 80)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::internalControlComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("控制环节 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + controlLink, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建控制环节特定的查询词 + */ + private List buildControlLinkQueries(String controlLink) { + // 根据控制环节构建相关查询词 + List queries = new ArrayList<>(); + + // 通用查询词 + queries.add(controlLink); + queries.add("内部控制 " + controlLink); + queries.add("财务管理 " + controlLink); + + // 根据特定控制环节添加相关关键词 + if (controlLink.contains("岗位")) { + queries.add("岗位职责 不相容职务分离"); + queries.add("岗位设置 职责划分"); + } + if (controlLink.contains("授权审批")) { + queries.add("授权审批流程 审批权限"); + queries.add("财务管理授权"); + } + if (controlLink.contains("重大事项")) { + queries.add("重大事项决策 集体决策"); + queries.add("三重一大 财务管理"); + } + if (controlLink.contains("会计")) { + queries.add("会计核算 会计档案"); + queries.add("会计凭证 会计账簿"); + } + if (controlLink.contains("信息系统")) { + queries.add("财务信息系统 数据备份"); + queries.add("信息安全 用户管理"); + } + if (controlLink.contains("风险")) { + queries.add("财务风险管理 风险识别"); + queries.add("风险评估 风险应对"); + } + if (controlLink.contains("内部审计")) { + queries.add("内审部门 审计监督"); + queries.add("审计检查 审计报告"); + } + if (controlLink.contains("反舞弊")) { + queries.add("财务舞弊 举报投诉"); + queries.add("舞弊防范 举报保护"); + } + + return queries; + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(String controlLink, Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 单位层面财务管理内部控制测试要求 + context.append("## 单位层面财务管理内部控制测试要求 - ").append(controlLink).append("\n"); + context.append("请基于以下知识生成").append(controlLink).append("相关的单位层面财务管理内部控制测试数据:\n\n"); + context.append("1. 控制环节:").append(controlLink).append("\n"); + context.append("2. 控制要求:").append(AuditContent8InternalControlConstants.CONTROL_REQUIREMENTS.get(controlLink)).append("\n"); + context.append("3. 控制活动描述:").append(AuditContent8InternalControlConstants.CONTROL_ACTIVITIES.get(controlLink)).append("\n"); + context.append("4. 控制职责岗位:").append(AuditContent8InternalControlConstants.CONTROL_POSITIONS.get(controlLink)).append("\n"); + context.append("5. 测试步骤:\n"); + AuditContent8InternalControlConstants.TEST_STEPS.get(controlLink).forEach(step -> + context.append(" ").append(step).append("\n")); + context.append("\n"); + + context.append("## 审计测试要求\n"); + context.append("1. 基于测试步骤逐项检查,评估内部控制的有效性\n"); + context.append("2. 检查证据必须具体到查阅的文件名称、内容、检查过程和发现的问题\n"); + context.append("3. 测试结果必须基于充分证据严格判断:\n"); + context.append(" - 通过:所有测试步骤均得到有效执行且证据充分\n"); + context.append(" - 不通过:任一测试步骤未执行、执行不到位或证据不足\n"); + context.append("4. 工作底稿索引必须填写实际存在的完整文件名\n"); + context.append("5. 重点关注货币资金管理、财务报销、资产管理、物资采购等关键环节\n\n"); + + // 2. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("需要生成").append(controlLink).append("控制环节的内部控制测试数据:\n\n"); + + context.append("**重要:每个测试步骤必须单独生成一条记录**\n\n"); + + context.append("每条记录应包含以下字段:\n"); + context.append("- controlLink(控制环节):固定值,不要修改\n"); + context.append("- controlRequirement(控制要求):固定值,不要修改\n"); + context.append("- controlActivity(控制活动描述):固定值,不要修改\n"); + context.append("- controlPosition(控制职责岗位):固定值,不要修改\n"); + context.append("- testSteps(测试步骤):**只包含当前步骤内容**,如\"(1)是否有制度规定\"\n"); + context.append("- checkEvidence(检查证据):针对当前测试步骤的详细检查证据\n"); + context.append("- testResult(测试结果):基于当前测试步骤的检查证据严格判断,只有证据充分且完全符合要求才能判定为\"通过\",否则必须判定为\"不通过\"\n"); +// context.append("- workPaperIndex(工作底稿索引FileId):实际存在的完整文件FileId,确保能在文件夹中搜索到\n\n"); + context.append("- workPaperIndex(工作底稿索引文件名):实际存在的完整文件名||FileUrl,确保能在文件夹中搜索到\n\n"); + + context.append("**生成规则:**\n"); + context.append("1. **每个测试步骤单独生成一条完整记录**\n"); + context.append("2. 如果测试步骤中有多个小点(如(1)、(2)、(3)),必须为每个小点生成独立记录\n"); + context.append("3. 每条记录的检查证据必须专门针对该测试步骤\n"); + context.append("4. 每条记录的测试结果必须基于该步骤的检查证据独立判断\n"); + context.append("5. 控制环节、控制要求、控制活动描述、控制职责岗位字段在所有相关记录中保持相同\n\n"); + + context.append("**注意:**\n"); + context.append("1. 检查证据必须详细描述:针对当前测试步骤查阅了哪些制度文件、检查了哪些业务凭证、访谈了哪些人员、发现了什么问题\n"); + context.append("2. 测试结果判定必须严格,对于制度不健全、执行不到位、证据不充分的情况必须判定为\"不通过\"\n"); + context.append("3. 工作底稿索引必须准确对应实际审计工作底稿文件名称,不能使用章节标题\n"); + context.append("4. 审计检查可采用:符合性测试、穿行测试、抽样测试、观察询问、文档查阅等方法\n"); + context.append("5. 重点关注内部控制制度的建立健全性和执行的有效性\n\n"); + + // 3. 审计测试方法参考 + context.append("## 审计测试方法参考\n"); + context.append("审计时可采用的测试方法:\n"); + TEST_METHODS.forEach(method -> context.append("- ").append(method).append("\n")); + context.append("\n"); + + // 4. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 5. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 6. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + return context.toString(); + } + + /** + * 内部控制相关性比较器 + */ + private int internalControlComparator(String reg1, String reg2) { + int score1 = calculateInternalControlRelevanceScore(reg1); + int score2 = calculateInternalControlRelevanceScore(reg2); + return Integer.compare(score2, score1); + } + + /** + * 计算内部控制相关性分数 + */ + private int calculateInternalControlRelevanceScore(String content) { + return AuditContent8InternalControlConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 120; + case "regulation": return 100; + case "auditCase": return 80; + default: return 50; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java new file mode 100644 index 0000000..ef5c1e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java @@ -0,0 +1,563 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent9PersonnelConstants; +import com.gxwebsoft.ai.service.AuditContent9PersonnelService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent9PersonnelServiceImpl extends AbstractAuditContentService implements AuditContent9PersonnelService { + + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-fmGhYITVQVAHaY3GJYonIGPA"; + + private static final String DIFY_WORKFLOW_TOKEN_EXT = "Bearer app-olBqOrzi1IQCWyDqthME6SYG"; + + + @Override + public JSONObject generatePersonnelTableData(String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion) { + log.info("开始生成人员编制管理审计表数据"); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个审计内容(7个大项) + Map> futures = new HashMap<>(); + + for (String auditContent : AuditContent9PersonnelConstants.AUDIT_CONTENTS) { + futures.put(auditContent, CompletableFuture.supplyAsync(() -> + generateAuditContentData(auditContent, kbIds, libraryKbIds, projectLibrary, + userName, history, suggestion) + )); + } + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有结果 + JSONArray allData = new JSONArray(); + int index = 1; + + for (String auditContent : AuditContent9PersonnelConstants.AUDIT_CONTENTS) { + JSONArray contentData = futures.get(auditContent).get(); + if (contentData != null) { + for (int i = 0; i < contentData.size(); i++) { + JSONObject item = contentData.getJSONObject(i); + item.put("index", index++); + allData.add(item); + } + } + } + + log.info("人员编制管理审计表生成完成 - 记录数: {}, 耗时: {}ms", + allData.size(), System.currentTimeMillis() - startTime); + + return buildSuccessResponse(allData, startTime, "personnel_establishment_audit"); + + } catch (Exception e) { + log.error("生成人员编制管理审计表失败", e); + return buildErrorResponse("生成失败: " + e.getMessage()); + } + } + + /** + * 生成单个审计内容(大项)下的所有子项数据 + */ + private JSONArray generateAuditContentData(String auditContent, String kbIds, String libraryKbIds, + String projectLibrary, String userName, + String history, String suggestion) { + + JSONArray results = new JSONArray(); + List subContents = + AuditContent9PersonnelConstants.AUDIT_SUB_CONTENTS.get(auditContent); + + if (subContents == null || subContents.isEmpty()) { + return results; + } + + // 为该审计内容检索相关知识 + Map> knowledgeSources = retrieveKnowledgeForAuditContent( + auditContent, subContents, kbIds, libraryKbIds, projectLibrary + ); + + // 处理每个子项 + for (AuditContent9PersonnelConstants.AuditSubContent subContent : subContents) { + try { + JSONArray subResult = generateSubItemData(auditContent, subContent, knowledgeSources, + userName, history, suggestion); + + if (subResult != null && !subResult.isEmpty()) { + results.addAll(subResult); + } + + } catch (Exception e) { + log.error("处理子项失败: {} - {}", auditContent, subContent.getAuditTarget(), e); + } + } + + return results; + } + + /** + * 为审计内容检索相关知识 + */ + private Map> retrieveKnowledgeForAuditContent( + String auditContent, List subContents, + String kbIds, String libraryKbIds, String projectLibrary) { + + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建该审计内容的关键词 + List keywords = buildAuditContentKeywords(auditContent, subContents); + + // 检索企业知识库 + if (StrUtil.isNotBlank(kbIds)) { + String[] kbIdArray = kbIds.split(","); + for (String kbId : kbIdArray) { + if (StrUtil.isNotBlank(kbId)) { + knowledgeSources.get("enterprise").addAll( + queryKnowledgeBase(kbId.trim(), keywords, 80) + ); + } + } + } + + // 检索法规库 + if (StrUtil.isNotBlank(libraryKbIds)) { + String[] libIdArray = libraryKbIds.split(","); + for (String libId : libIdArray) { + if (StrUtil.isNotBlank(libId)) { + knowledgeSources.get("regulation").addAll( + queryKnowledgeBase(libId.trim(), keywords, 60) + ); + } + } + } + + // 检索案例库 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, keywords, 40) + ); + } + + // 去重 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() +// .sorted(this::keywordRelevanceComparator) +// .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + return knowledgeSources; + } + + /** + * 构建审计内容关键词 + */ + private List buildAuditContentKeywords(String auditContent, + List subContents) { + + List keywords = new ArrayList<>(); + keywords.add(auditContent); + + // 添加审计内容相关的关键词 + if (auditContent.contains("劳务派遣")) { + keywords.addAll(Arrays.asList("劳务派遣", "派遣工", "同工同酬", "三性标准")); + } else if (auditContent.contains("外包")) { + keywords.addAll(Arrays.asList("劳务外包", "外包合同", "假外包真派遣")); + } else if (auditContent.contains("人员经费")) { + keywords.addAll(Arrays.asList("工资总额", "福利费", "劳务费")); + keywords.addAll(Arrays.asList( + "工资总额", "福利费", "劳务费", + "补贴", "慰问", "福利", + "节日", "生日", "体检", + "食堂", "交通补贴", "通讯补贴", + "误餐补贴", "职工福利", "员工福利", + "防暑降温", "取暖费", "劳保用品" + )); + } else if (auditContent.contains("借用人员")) { + keywords.addAll(Arrays.asList("借调", "借用人员", "借调程序")); + } else if (auditContent.contains("绩效管理")) { + keywords.addAll(Arrays.asList("绩效考核", "绩效管理", "绩效体系")); + } else if (auditContent.contains("机构编制")) { + keywords.addAll(Arrays.asList("机构编制", "三定方案", "编制审批", "领导职数")); + } else if (auditContent.contains("预算编制")) { + keywords.addAll(Arrays.asList("预算编制", "人员经费预算", "预算执行")); + } + + // 添加所有子项的审计目标作为关键词 + for (AuditContent9PersonnelConstants.AuditSubContent subContent : subContents) { + keywords.add(subContent.getAuditTarget()); + } + + return keywords.stream().distinct().collect(Collectors.toList()); + } + + /** + * 生成子项数据 + */ + private JSONArray generateSubItemData(String auditContent, + AuditContent9PersonnelConstants.AuditSubContent subContent, + Map> knowledgeSources, + String userName, String history, String suggestion) { + + try { + JSONArray ext = new JSONArray(); + String updatedHistory = history; // 使用新变量避免修改入参 + // 检查是否是福利费列支范围检查子项 + String extSubContent = AuditContent9PersonnelConstants.AUDIT_SUB_CONTENTS.get(AuditContent9PersonnelConstants.AUDIT_CONTENTS[2]).get(1).getAuditTarget(); + if (subContent.getAuditTarget().equals(extSubContent)) { + // 生成扩展数据(福利费明细清单) + String contextExt = buildSubItemContextExt(auditContent, subContent, knowledgeSources, history, suggestion); + JSONObject requestBodyExt = buildWorkflowRequest(contextExt, userName); + ext = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN_EXT, requestBodyExt, auditContent + "-" + subContent.getAuditTarget()); + // 更新历史内容(不影响原始入参) + if (StrUtil.isNotBlank(history)) { + updatedHistory = history + "\n《福利费超范围支出明细清单》:" + ext.toJSONString(); + } + } + + // 构建主上下文 + String context = buildSubItemContext(auditContent, subContent, knowledgeSources, updatedHistory, suggestion); + // 调用Dify工作流 + JSONObject requestBody = buildWorkflowRequest(context, userName); + JSONArray result = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, auditContent + "-" + subContent.getAuditTarget()); + + // 处理返回结果 + return processSubItemResult(result, auditContent, subContent, ext); + } catch (Exception e) { + log.error("生成子项数据失败: {} - {}", auditContent, subContent.getAuditTarget(), e); + return new JSONArray(); + } + } + + /** + * 构建子项上下文 + */ + private String buildSubItemContext(String auditContent, + AuditContent9PersonnelConstants.AuditSubContent subContent, + Map> knowledgeSources, + String history, String suggestion) { + + StringBuilder context = new StringBuilder(); + + context.append("## 审计任务\n"); + context.append("生成人员编制管理审计数据\n\n"); + + context.append("## 审计要求\n"); + context.append("1. 审计内容:").append(auditContent).append("\n"); + context.append("2. 审计目标:").append(subContent.getAuditTarget()).append("\n"); + context.append("3. 审计证据:").append(subContent.getAuditEvidence()).append("\n"); + context.append("4. 审计方法:").append(subContent.getAiTestContent()).append("\n"); + context.append("5. 制度依据:").append(subContent.getRegulationBasis()).append("\n\n"); + + context.append("## 生成结果要求\n"); + context.append(subContent.getGenerationResult()).append("\n\n"); + + context.append("## 重要要求\n"); + context.append("1. 必须使用具体单位名称,禁止使用'XX单位'等模糊词汇\n"); + context.append("2. 审计记录必须具体,包含文件名称、数据、人员等详细信息\n"); + context.append("3. 重点关注问题发现,提供具体证据和建议\n"); + context.append("4. **如果在上传资料中找不到相应证据/凭证,不要直接判定违反规定,应说明'未找到相关材料,无法判定'**\n"); + context.append("5. **除了审计证据中列出的资料清单,还需主动查找上传材料中其他涉及审计内容和目标的材料**\n"); + context.append("6. **合同与主体公司不相关时,不应判定在主体公司责任范围内,需明确区分责任主体**\n\n"); + + context.append("## 审计判断原则\n"); + context.append("1. **证据不足原则**:当缺乏关键证据时,不做出违规判定\n"); + context.append("2. **主动查找原则**:不局限于给定清单,主动识别所有相关材料\n"); + context.append("3. **责任主体原则**:明确区分合同主体,不扩大责任范围\n\n"); + + context.append("## 返回格式\n"); + context.append("返回JSON数组,每条记录包含以下字段:\n"); + context.append("- auditContent:固定为'").append(auditContent).append("'\n"); + context.append("- auditTarget:固定为'").append(subContent.getAuditTarget()).append("'\n"); + context.append("- auditEvidence:固定为'").append(subContent.getAuditEvidence()).append("'\n"); + context.append("- generationResult:审计发现和结论\n"); +// context.append("- workPaperIndex:工作底稿索引,具体的文件FileId数组\n\n"); + context.append("- workPaperIndex:工作底稿索引,具体的文件[\"实际存在的完整文件名1||FileUrl1\", \"实际存在的完整文件名2||FileUrl2\", ...]数组\n\n"); + + context.append("## generationResult格式\n"); + context.append("标题:在审计期间,[具体单位名称]存在[具体问题](如有充分证据)\n"); + context.append("或:在审计期间,[具体单位名称]未提供充分证据证明[审计事项]\n\n"); + + context.append("审计记录:\n"); + context.append("1. 核查的具体文件和内容\n"); + context.append("2. **注明是否找到审计证据清单中要求的材料**\n"); + context.append("3. **注明是否发现其他相关材料**\n"); + context.append("4. **注明合同主体是否与审计单位一致**\n\n"); + + context.append("审计发现:\n"); + context.append("1. 如有充分证据:上述行为构成[问题性质],违反了[相关规定]\n"); + context.append("2. 如证据不足:未找到相关材料,无法判定是否合规\n\n"); + + context.append("定性依据:\n"); + context.append("① [法规1];② [法规2]\n"); + context.append("**如无违规,可不填写定性依据**\n\n"); + + context.append("处理建议:\n"); + context.append("1. [建议1];2. [建议2]\n\n"); + + context.append("附件:\n"); + context.append("- [实际查阅的文件1]\n"); + context.append("- [实际查阅的文件2]\n\n"); + + // 添加历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史内容\n"); + context.append(history).append("\n\n"); + } + + // 添加用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n\n"); + } + + // 添加相关知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业信息\n"); + knowledgeSources.get("enterprise").forEach(info -> + context.append(info).append("\n")); + } + + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法规信息\n"); + knowledgeSources.get("regulation").forEach(info -> + context.append(info).append("\n")); + } + + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 案例信息\n"); + knowledgeSources.get("auditCase").forEach(info -> + context.append(info).append("\n")); + } + + return context.toString(); + } + + /** + * 构建福利费超范围支出明细清单的上下文 + */ + private String buildSubItemContextExt(String auditContent, + AuditContent9PersonnelConstants.AuditSubContent subContent, + Map> knowledgeSources, + String history, String suggestion) { + + StringBuilder context = new StringBuilder(); + + context.append("## 审计任务\n"); + context.append("生成福利费支出明细清单数据\n\n"); + + context.append("## 审计要求\n"); + context.append("1. 审计内容:").append(auditContent).append("\n"); + context.append("2. 审计目标:").append(subContent.getAuditTarget()).append("\n"); + context.append("3. 审计证据:").append(subContent.getAuditEvidence()).append("\n"); + context.append("4. 审计方法:").append(subContent.getAiTestContent()).append("\n"); + context.append("5. 制度依据:").append(subContent.getRegulationBasis()).append("\n\n"); + + context.append("## 生成结果要求\n"); + context.append("生成福利费支出明细清单,包括所有福利费支出的详细记录\n\n"); + + context.append("## 重要要求\n"); + context.append("1. **积极查找知识库中所有福利费相关支出记录**,包括但不限于:\n"); + context.append(" - 福利费支出凭证、报销单\n"); + context.append(" - 工资表中的福利费项目\n"); + context.append(" - 补贴发放记录、福利发放记录\n"); + context.append(" - 节日慰问、职工福利相关支出\n"); + context.append(" - 职工食堂、交通补贴、通讯补贴等\n"); + context.append("2. **即使信息不完整也要尽可能记录**:\n"); + context.append(" - 如果凭证号不明确,可以使用'未知'或根据内容推断\n"); + context.append(" - 如果日期不明确,可以合理推断或使用'审计期间'\n"); + context.append(" - 如果金额不明确,可以根据上下文合理估计\n"); + context.append("3. **允许合理推断**:\n"); + context.append(" - 根据支出用途推断款项性质\n"); + context.append(" - 根据文件内容推断归属部门/人员\n"); + context.append(" - 根据上下文补充缺失信息\n"); + context.append("4. **优先使用实际信息**,只有在确实无法获取时才允许推断\n\n"); + + context.append("## 审计判断原则\n"); + context.append("1. **积极查找原则**:尽可能查找所有可能的福利费相关支出\n"); + context.append("2. **信息补充原则**:对不完整信息进行合理补充\n"); + context.append("3. **分类识别原则**:识别以下类型的福利费支出:\n"); + context.append(" - 职工福利发放(节日福利、生日福利等)\n"); + context.append(" - 职工补贴(交通、通讯、餐饮补贴等)\n"); + context.append(" - 职工活动支出(文体活动、旅游活动等)\n"); + context.append(" - 职工慰问支出(生病慰问、困难补助等)\n"); + context.append(" - 其他福利性支出\n"); + context.append("4. **合规性初步判断**:\n"); + context.append(" - 对于明显超出常规福利费范围的项目进行标注\n"); + context.append(" - 对于可能违规的支出进行初步分析\n\n"); + + context.append("## 审计步骤\n"); + context.append("1. 第一步:全面搜索知识库中所有福利费相关支出\n"); + context.append("2. 第二步:尽可能多地提取福利费支出记录\n"); + context.append("3. 第三步:对每条记录进行信息补充和整理\n"); + context.append("4. 第四步:初步分析支出合规性\n"); + context.append("5. 第五步:生成完整的福利费支出明细清单\n\n"); + + context.append("## 返回格式\n"); + context.append("返回JSON数组,每条记录必须包含以下字段,严格按照字段名和格式:\n"); + context.append("- **index**:序号(数字,从1开始自动递增)\n"); + context.append("- **voucher**:凭证号(字符串,优先使用实际凭证号,如无法获取可用'未知'或合理推断)\n"); + context.append("- **expenditureDate**:支出日期(字符串,格式:YYYY-MM-DD,如无法获取可用'审计期间')\n"); + context.append("- **usage**:用途(字符串,详细说明支出用途)\n"); + context.append("- **payee**:收款方(字符串,完整收款方名称,如无法获取可用'相关单位/个人')\n"); + context.append("- **amount**:金额(数字,单位:元,保留两位小数,如无法获取可用0.00)\n"); + context.append("- **belongTo**:归属部门/人员(字符串,明确责任主体,如无法获取可用'相关部门')\n"); + context.append("- **natureDesc**:款项性质说明(字符串,详细说明款项性质和目的)\n"); + context.append("- **violationDesc**:违规说明(字符串,如果不明确是否违规,可留空或写'待进一步核实')\n\n"); + + context.append("## 重点关注福利费支出类型\n"); + context.append("1. **常规福利费**:节日慰问品、职工生日福利、职工体检等\n"); + context.append("2. **职工补贴**:交通补贴、通讯补贴、误餐补贴、住房补贴等\n"); + context.append("3. **职工活动**:文体活动、旅游活动、团建活动等\n"); + context.append("4. **职工慰问**:生病慰问、困难补助、婚丧嫁娶慰问等\n"); + context.append("5. **职工福利设施**:职工食堂、活动室、休息室等支出\n"); + context.append("6. **其他福利**:防暑降温费、取暖费、劳保用品等\n\n"); + + context.append("## 如何从知识库中提取信息\n"); + context.append("1. **搜索关键词**:福利费、补贴、慰问、福利、节日、生日、体检、活动、食堂、交通、通讯、餐饮\n"); + context.append("2. **文件类型**:财务凭证、报销单、工资表、发放记录、会议纪要、审批单\n"); + context.append("3. **提取策略**:\n"); + context.append(" - 从财务凭证中提取凭证号、金额、日期\n"); + context.append(" - 从报销单中提取用途、收款方\n"); + context.append(" - 从审批记录中提取归属部门\n"); + context.append(" - 综合分析文件内容推断款项性质\n\n"); + + context.append("## 特殊处理说明\n"); + context.append("1. **如果知识库中没有直接福利费凭证**:\n"); + context.append(" - 可以查找相关会议纪要中提到的福利费支出\n"); + context.append(" - 可以查找工资表中的福利费项目\n"); + context.append(" - 可以查找报销单中的福利相关支出\n"); + context.append("2. **信息补充方法**:\n"); + context.append(" - 通过文件名推断凭证号\n"); + context.append(" - 通过文件创建日期推断支出日期\n"); + context.append(" - 通过上下文推断款项用途\n"); + context.append("3. **生成多条记录**:\n"); + context.append(" - 一条福利费支出凭证可以生成一条记录\n"); + context.append(" - 一份工资表中的福利费项目可以生成多条记录\n"); + context.append(" - 一份会议纪要中的多项福利费决定可以生成多条记录\n\n"); + + // 添加历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史内容\n"); + context.append(history).append("\n\n"); + } + + // 添加用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户建议\n"); + context.append(suggestion).append("\n\n"); + } + + // 添加相关知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业财务信息\n"); + context.append("请仔细查找以下内容中的福利费相关信息:\n"); + knowledgeSources.get("enterprise").forEach(info -> { + context.append(info).append("\n"); + }); + } + +// if (!knowledgeSources.get("regulation").isEmpty()) { +// context.append("## 法规信息\n"); +// context.append("参考以下福利费管理规定:\n"); +// knowledgeSources.get("regulation").forEach(info -> { +// context.append(info).append("\n"); +// }); +// } + +// if (!knowledgeSources.get("auditCase").isEmpty()) { +// context.append("## 审计案例信息\n"); +// context.append("参考以下福利费审计案例:\n"); +// knowledgeSources.get("auditCase").forEach(info -> { +// context.append(info).append("\n"); +// }); +// } + + context.append("\n## 最终要求\n"); + context.append("请基于以上信息和知识库内容,尽可能多地生成福利费支出明细清单数据。\n"); + context.append("要求尽可能真实、完整,即使信息不完整也要尽量生成记录。\n"); + context.append("如果知识库中没有任何福利费相关信息,请返回空数组。\n"); + context.append("如果有福利费相关信息,请至少生成3-5条记录,尽可能多地提取信息。\n"); + + return context.toString(); + } + + /** + * 处理子项返回结果 + */ + private JSONArray processSubItemResult(JSONArray result, String auditContent, + AuditContent9PersonnelConstants.AuditSubContent subContent, JSONArray ext) { + + if (result == null || result.isEmpty()) { + JSONArray defaultResult = new JSONArray(); + JSONObject defaultItem = new JSONObject(); + defaultItem.put("auditContent", auditContent); + defaultItem.put("auditTarget", subContent.getAuditTarget()); + defaultItem.put("auditEvidence", subContent.getAuditEvidence()); + defaultItem.put("generationResult", "审计数据待生成"); + defaultItem.put("workPaperIndex", new JSONArray()); + defaultResult.add(defaultItem); + return defaultResult; + } + + // 检查是否是福利费列支范围检查子项 + String extSubContent = AuditContent9PersonnelConstants.AUDIT_SUB_CONTENTS.get(AuditContent9PersonnelConstants.AUDIT_CONTENTS[2]).get(1).getAuditTarget(); + if (ext.size() > 0 && subContent.getAuditTarget().equals(extSubContent)) { + // 将扩展数据添加到结果中 + JSONObject firstItem = result.getJSONObject(0); + firstItem.put("ext", ext); + } + return result; + } + +// /** +// * 关键词相关性比较器 +// */ +// private int keywordRelevanceComparator(String content1, String content2) { +// int score1 = calculateKeywordScore(content1); +// int score2 = calculateKeywordScore(content2); +// return Integer.compare(score2, score1); +// } +// +// /** +// * 计算关键词得分 +// */ +// private int calculateKeywordScore(String content) { +// int score = 0; +// for (Map.Entry entry : AuditContent9PersonnelConstants.KEYWORD_WEIGHTS.entrySet()) { +// if (content.contains(entry.getKey())) { +// score += entry.getValue(); +// } +// } +// return score; +// } +// +// /** +// * 获取不同来源的限制条数 +// */ +// private int getLimitBySourceType(String sourceType) { +// switch (sourceType) { +// case "enterprise": return 80; +// case "regulation": return 60; +// case "auditCase": return 40; +// default: return 30; +// } +// } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java new file mode 100644 index 0000000..5f44717 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java @@ -0,0 +1,204 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditEvidenceRequest; +import com.gxwebsoft.ai.service.AuditEvidenceService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; + +import java.util.Date; + +@Slf4j +@Service +public class AuditEvidenceServiceImpl implements AuditEvidenceService { + + // Dify工作流配置 + private static final String DIFY_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-DEhvF537rQfy6MvH9KeBKUVj"; + + @Override + public JSONObject generateAuditEvidence(AuditEvidenceRequest request) { + log.info("开始生成审计取证单 - 用户: {}, 项目: {}", request.getUserName(), request.getProjectName()); + + long startTime = System.currentTimeMillis(); + + try { + // 1. 构建完整上下文 + String knowledgeContext = buildCompleteContext(request); + + // 2. 构建工作流请求 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, request.getUserName()); + + // 3. 调用Dify工作流 + JSONObject result = callWorkflow(requestBody, "审计取证单"); + + // 4. 添加处理时间等信息 + result.put("success", true); + result.put("processing_time", (System.currentTimeMillis() - startTime) + "ms"); + result.put("generated_time", new Date().toString()); + + log.info("审计取证单生成成功 - 处理时间: {}ms", (System.currentTimeMillis() - startTime)); + + return result; + + } catch (Exception e) { + log.error("生成审计取证单失败", e); + return buildErrorResponse("生成审计取证单失败: " + e.getMessage()); + } + } + + /** + * 构建完整上下文 + */ + private String buildCompleteContext(AuditEvidenceRequest request) { + StringBuilder context = new StringBuilder(); + + // 1. 审计取证单生成要求 + context.append("## 审计取证单生成要求\n"); + context.append("请基于以下信息生成一份完整的审计取证单。审计取证单是审计工作中的重要文书,用于记录审计发现的问题、事实依据、处理建议等。\n\n"); + + // 2. 输出格式要求 + context.append("## 输出格式要求\n"); + context.append("请严格按照以下JSON格式输出,不要包含任何额外文本:\n"); + context.append("{\n"); +// context.append(" \"caseIndex\": \"案件编号,如审计-[2024]-001\",\n"); +// context.append(" \"projectName\": \"具体项目名称\",\n"); + context.append(" \"auditedTarget\": \"被审计单位或个人\",\n"); + context.append(" \"auditMatter\": \"审计事项描述\",\n"); + context.append(" \"summaryTitle\": \"核心问题标题\",\n"); + context.append(" \"auditRecord\": \"客观的审计核查事实记录,包括时间、地点、主体、行为、数据等\",\n"); + context.append(" \"auditFinding\": \"审计发现的具体问题、性质及影响\",\n"); + context.append(" \"evidenceBasis\": \"定性依据,引用法规、制度或合同条款\",\n"); + context.append(" \"handling\": \"拟采取的处理措施\",\n"); + context.append(" \"suggestion\": \"改进或整改建议\",\n"); + context.append(" \"attachment\": \"列示随附的证明材料(只出现材料中文名称,不要出现FileId,不要出现网站链接)\",\n"); + context.append(" \"auditors\": \"审计人员姓名\",\n"); + context.append(" \"compileDate\": \"编制日期,格式:YYYY-MM-DD\",\n"); + context.append(" \"providerOpinion\": \"证据提供单位或个人意见\",\n"); + context.append(" \"providerDate\": \"证据提供日期,格式:YYYY-MM-DD\",\n"); + context.append(" \"attachmentPages\": \"附件页数,如:2\",\n"); + context.append(" \"feedbackDeadline\": \"反馈期限,如:5个工作日内\"\n"); + context.append("}\n"); + + // 3. 字段内容要求 + context.append("## 字段内容具体要求\n"); + context.append("1. **审计记录**:客观记录审计核查的具体事实,包括时间、地点、主体、行为、数据等,避免主观评价。\n"); + context.append("2. **审计发现**:基于审计记录提出具体问题性质,先事实后定性,避免夸大。\n"); + context.append("3. **定性依据**:必须引用具体的法规、制度或合同条款,格式如:根据《XX法》第XX条规定...\n"); + context.append("4. **处理措施**:针对发现的问题提出具体的处理措施,要有可操作性。\n"); + context.append("5. **建议**:针对问题根源提出改进或整改建议,要具体可行。\n"); + context.append("6. **日期**:所有日期必须使用YYYY-MM-DD格式。\n\n"); + + // 4. 用户提供的基础信息 + if (StrUtil.isNotBlank(request.getProjectName()) || + StrUtil.isNotBlank(request.getAuditedTarget()) || + StrUtil.isNotBlank(request.getAuditMatter())) { + + context.append("## 用户提供的基础信息\n"); + + if (StrUtil.isNotBlank(request.getProjectName())) { + context.append("- 项目名称:").append(request.getProjectName()).append("\n"); + } + if (StrUtil.isNotBlank(request.getAuditedTarget())) { + context.append("- 被审计单位/个人:").append(request.getAuditedTarget()).append("\n"); + } + if (StrUtil.isNotBlank(request.getAuditMatter())) { + context.append("- 审计事项:").append(request.getAuditMatter()).append("\n"); + } + if (StrUtil.isNotBlank(request.getSummaryTitle())) { + context.append("- 标题:").append(request.getSummaryTitle()).append("\n"); + } + context.append("\n"); + } + + // 5. 历史内容(如果存在) + if (StrUtil.isNotBlank(request.getHistory())) { + context.append("## 历史内容参考\n"); + context.append("以下是与本次审计相关的历史内容,请参考但不完全照搬:\n"); + context.append(request.getHistory()).append("\n\n"); + } + + // 6. 额外要求 + context.append("## 额外要求\n"); + context.append("1. 内容必须真实、准确、完整,符合审计文书规范\n"); + context.append("2. 语言表述要严谨、客观、规范\n"); + context.append("3. 各字段内容要相互衔接,逻辑清晰\n"); + context.append("4. 如果某些信息不明确,请根据审计常规进行合理补充\n"); + + log.debug("构建的上下文长度: {}", context.length()); + return context.toString(); + } + + /** + * 构建工作流请求 + */ + private JSONObject buildWorkflowRequest(String knowledge, String userName) { + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + + inputs.put("knowledge", knowledge); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + requestBody.put("timeout", 300); // 5分钟超时 + + return requestBody; + } + + /** + * 调用工作流 + */ + private JSONObject callWorkflow(JSONObject requestBody, String workflowName) { + try { + log.info("调用{}工作流,请求体长度: {}", workflowName, requestBody.toString().length()); + + String result = HttpUtil.createPost(DIFY_WORKFLOW_URL) + .header("Authorization", DIFY_WORKFLOW_TOKEN) + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(5 * 60 * 1000) // 5分钟 + .execute() + .body(); + + log.info("{}工作流返回结果长度: {}", workflowName, result.length()); + + JSONObject jsonResponse = JSONObject.parseObject(result); + + // 提取输出文本 + String outputText = jsonResponse.getJSONObject("data") + .getJSONObject("outputs") + .getString("result"); + + if (StrUtil.isBlank(outputText)) { + log.warn("{}工作流返回结果为空", workflowName); + return new JSONObject(); + } + + // 解析为JSON对象 + JSONObject auditEvidence = JSONObject.parseObject(outputText); + + log.info("成功解析{}工作流返回数据", workflowName); + return auditEvidence; + + } catch (Exception e) { + log.error("调用{}工作流失败", workflowName, e); + throw new RuntimeException("调用" + workflowName + "工作流失败: " + e.getMessage(), e); + } + } + + /** + * 构建错误响应 + */ + private JSONObject buildErrorResponse(String errorMessage) { + JSONObject result = new JSONObject(); + result.put("success", false); + result.put("error", errorMessage); + result.put("timestamp", System.currentTimeMillis()); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java new file mode 100644 index 0000000..e7e1836 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java @@ -0,0 +1,347 @@ +package com.gxwebsoft.ai.service.impl; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.RetrieveResponse; +import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyDataNodes; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.service.AuditReportService; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.service.PwlProjectLibraryService; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class AuditReportServiceImpl implements AuditReportService { + + @Autowired + private KnowledgeBaseClientFactory clientFactory; + + @Autowired + private KnowledgeBaseConfig config; + + @Autowired + private PwlProjectLibraryService pwlProjectLibraryService; + + // 工作流配置 + private static final String QUESTION_GENERATION_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; + private static final String QUESTION_GENERATION_TOKEN = "Bearer app-vqbecZtTGmpGL5YhQzsy0Ctr"; + + private static final String QUESTION_ANALYSIS_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; + private static final String QUESTION_ANALYSIS_TOKEN = "Bearer app-5CQWPxctsPape0HePWA3cP4Y"; + + @Override + public JSONObject generateCompleteAuditReport(String auditContent, String fileName, + String kbId, String libraryIds, + String analysisLibrary, String projectLibrary, + String userName) { + + List idList = StrUtil.split(libraryIds, ','); + List ret = pwlProjectLibraryService.list(new LambdaQueryWrapper().in(PwlProjectLibrary::getId, idList)); + String libraryKbIds = ret.stream().map(PwlProjectLibrary::getKbId).collect(Collectors.joining(",")); + + JSONObject result = new JSONObject(); + + // 1. 从知识库召回相关规定 + List regulations = retrieveRegulations(auditContent, kbId, libraryKbIds); + + // 2. 生成审计问题列表 + JSONArray questionList = generateAuditQuestions(auditContent, regulations, userName); + + // 3. 分析每个问题 + JSONArray findings = analyzeQuestions(questionList, analysisLibrary, projectLibrary, userName); + + // 4. 构建返回结果 + result.put("source_file", fileName); + result.put("audit_content", auditContent); + result.put("total_questions", questionList.size()); + result.put("total_findings", findings.size()); + result.put("findings", findings); + result.put("generated_time", new Date().toString()); + + return result; + } + + /** + * 从知识库召回相关规定 + */ + private List retrieveRegulations(String auditContent, String kbId, String libraryKbIds) { + List regulations = new ArrayList<>(); + String query = buildRegulationQuery(auditContent); + + // 主知识库 + if (StrUtil.isNotBlank(kbId)) { + regulations.addAll(queryKnowledgeBase(kbId, query, 10)); + } + + // 多个库 + if (StrUtil.isNotBlank(libraryKbIds)) { + for (String libId : libraryKbIds.split(",")) { + if (StrUtil.isNotBlank(libId.trim())) { + regulations.addAll(queryKnowledgeBase(libId.trim(), query, 5)); + } + } + } + + return regulations.stream().distinct().collect(Collectors.toList()); + } + + /** + * 生成审计问题列表 + */ + private JSONArray generateAuditQuestions(String auditContent, List regulations, String userName) { + // 构建知识上下文 + String knowledgeContext = buildKnowledgeContext(auditContent, regulations); + + JSONObject requestBody = buildQuestionGenerationRequest(knowledgeContext, userName); + + JSONArray response = callQuestionGenerationWorkflow(requestBody); + return response; + } + + /** + * 分析每个问题 + */ + private JSONArray analyzeQuestions(JSONArray questions, String analysisLib, String projectLib, String userName) { + JSONArray findings = new JSONArray(); + + for (int i = 0; i < questions.size(); i++) { + JSONObject question = questions.getJSONObject(i); + + // 从两个库召回证据 + List analysisEvidence = retrieveEvidence(question.toJSONString(), analysisLib); + List projectEvidence = retrieveEvidence(question.toJSONString(), projectLib); + + // 调用工作流分析 + JSONArray analysisResult = analyzeSingleQuestion(question, analysisEvidence, projectEvidence, userName); + + if (analysisResult != null && !analysisResult.isEmpty()) { + JSONObject finding = analysisResult.getJSONObject(0); + // 添加索引 + finding.put("index", i + 1); + findings.add(finding); + } + } + + return findings; + } + + /** + * 召回证据 + */ + private List retrieveEvidence(String question, String libraryId) { + if (StrUtil.isBlank(libraryId)) return new ArrayList<>(); + return new ArrayList<>(queryKnowledgeBase(libraryId, question, 5)); + } + + /** + * 分析单个问题 + */ + private JSONArray analyzeSingleQuestion(JSONObject question, List analysisEvidence, + List projectEvidence, String userName) { + + String evidenceContext = buildEvidenceContext(analysisEvidence, projectEvidence); + String questionText = buildQuestionText(question); + + JSONObject requestBody = buildQuestionAnalysisRequest(questionText, evidenceContext, userName); + + return callQuestionAnalysisWorkflow(requestBody); + } + + /** + * 构建问题文本 + */ + private String buildQuestionText(JSONObject question) { + StringBuilder sb = new StringBuilder(); + sb.append("问题表述: ").append(question.getString("问题表述")).append("\n"); + sb.append("问题明细: ").append(question.getString("问题明细")).append("\n"); + sb.append("法规依据: ").append(question.getString("法规依据")); + return sb.toString(); + } + + /** + * 构建知识上下文 + */ + private String buildKnowledgeContext(String auditContent, List regulations) { + StringBuilder context = new StringBuilder(); + context.append("审计内容:\n").append(auditContent).append("\n\n"); + if (!regulations.isEmpty()) { + context.append("相关规定:\n"); + regulations.forEach(reg -> context.append("- ").append(reg).append("\n")); + } + return context.toString(); + } + + /** + * 构建证据上下文 + */ + private String buildEvidenceContext(List analysisEvidence, List projectEvidence) { + StringBuilder context = new StringBuilder(); + + if (!analysisEvidence.isEmpty()) { + context.append("分析库证据:\n"); + analysisEvidence.forEach(ev -> context.append("- ").append(ev).append("\n")); + } + + if (!projectEvidence.isEmpty()) { + context.append("项目库证据:\n"); + projectEvidence.forEach(ev -> context.append("- ").append(ev).append("\n")); + } + + return context.toString().trim(); + } + + /** + * 调用问题生成工作流 + */ + private JSONArray callQuestionGenerationWorkflow(JSONObject requestBody) { + try { + String result = HttpUtil.createPost(QUESTION_GENERATION_WORKFLOW_URL) + .header("Authorization", QUESTION_GENERATION_TOKEN) + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(600000) + .execute() + .body(); + + JSONObject jsonResponse = JSONObject.parseObject(result); + String outputText = jsonResponse.getJSONObject("data") + .getJSONObject("outputs") + .getString("result"); + + // 解析JSON数组 + return JSONArray.parseArray(outputText); + + } catch (Exception e) { + throw new RuntimeException("调用问题生成工作流失败: " + e.getMessage(), e); + } + } + + /** + * 调用问题分析工作流 + */ + private JSONArray callQuestionAnalysisWorkflow(JSONObject requestBody) { + try { + String result = HttpUtil.createPost(QUESTION_ANALYSIS_WORKFLOW_URL) + .header("Authorization", QUESTION_ANALYSIS_TOKEN) + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(600000) + .execute() + .body(); + + JSONObject jsonResponse = JSONObject.parseObject(result); + String outputText = jsonResponse.getJSONObject("data") + .getJSONObject("outputs") + .getString("result"); + + // 解析JSON数组 + return JSONArray.parseArray(outputText); + + } catch (Exception e) { + System.err.println("分析问题失败: " + e.getMessage()); + // 返回默认结构,避免整个流程中断 + return createDefaultFinding(); + } + } + + /** + * 创建默认的发现条目 + */ + private JSONArray createDefaultFinding() { + JSONObject finding = new JSONObject(); + finding.put("是否存在相关问题", "false"); + finding.put("问题表述", ""); + finding.put("问题明细", ""); + finding.put("法规依据", ""); + finding.put("涉及金额(万元)", "/"); + finding.put("具体责任界定", "/"); + finding.put("整改类型", "/"); + finding.put("整改要求", "/"); + finding.put("整改时限", "/"); + finding.put("审计建议", "/"); + + JSONArray array = new JSONArray(); + array.add(finding); + return array; + } + + /** + * 构建问题生成工作流请求 + */ + private JSONObject buildQuestionGenerationRequest(String knowledge, String userName) { + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + + inputs.put("knowledge", knowledge); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + + return requestBody; + } + + /** + * 构建问题分析工作流请求 + */ + private JSONObject buildQuestionAnalysisRequest(String query, String knowledge, String userName) { + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + + inputs.put("query", query); + inputs.put("knowledge", knowledge); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + + return requestBody; + } + + /** + * 查询知识库 + */ + private Set queryKnowledgeBase(String kbId, String query, int topK) { + Set results = new LinkedHashSet<>(); + String workspaceId = config.getWorkspaceId(); + + try { + Client client = clientFactory.createClient(); + RetrieveResponse resp = AiCloudKnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query); + + if (resp.getBody() != null && resp.getBody().getData() != null + && resp.getBody().getData().getNodes() != null) { + + for (RetrieveResponseBodyDataNodes node : resp.getBody().getData().getNodes()) { + results.add(node.getText()); + if (results.size() >= topK) break; + } + } + } catch (Exception e) { + // 记录日志但不中断流程 + System.err.println("查询知识库失败: " + e.getMessage()); + } + + return results; + } + + /** + * 构建法规查询 + */ + private String buildRegulationQuery(String auditContent) { + String[] keywords = {"制度", "办法", "规定", "流程", "标准", "规范", "要求"}; + return String.join(" ", keywords); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java new file mode 100644 index 0000000..41acd13 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java @@ -0,0 +1,221 @@ +package com.gxwebsoft.ai.service.impl; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.AddFileResponse; +import com.aliyun.bailian20231229.models.CreateIndexResponse; +import com.aliyun.bailian20231229.models.DeleteIndexDocumentResponse; +import com.aliyun.bailian20231229.models.DeleteIndexResponse; +import com.aliyun.bailian20231229.models.ListIndexDocumentsResponse; +import com.aliyun.bailian20231229.models.ListIndicesResponse; +import com.aliyun.bailian20231229.models.RetrieveResponse; +import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyDataNodes; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.constants.KnowledgeBaseConstants; +import com.gxwebsoft.ai.dto.KnowledgeBaseRequest; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.ai.util.AiCloudDataCenterUtil; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class KnowledgeBaseServiceImpl implements KnowledgeBaseService { + + @Autowired + private KnowledgeBaseConfig config; + + @Autowired + private KnowledgeBaseClientFactory clientFactory; + + @Override + public Set queryKnowledgeBase(KnowledgeBaseRequest req) { + return queryKnowledgeBase(req.getKbId(), req.getQuery(), req.getTopK(), req.getFormCommit()); + } + + @Override + public Set queryKnowledgeBase(String kbId, String query, Integer topK, Integer formCommit) { + Set result = new LinkedHashSet<>(); + String workspaceId = config.getWorkspaceId(); + List keyWords = Arrays.asList(KnowledgeBaseConstants.KEY_WORDS); + String indexId = kbId; + String searchQuery = StrUtil.isEmpty(query) ? keyWords.get(formCommit) : query; + Integer searchTopK = topK == null ? 10 : topK; + + try { + Client client = clientFactory.createClient(); + RetrieveResponse resp = AiCloudKnowledgeBaseUtil.retrieveIndex(client, workspaceId, indexId, searchQuery); + for (RetrieveResponseBodyDataNodes node : resp.getBody().getData().getNodes()) { + result.add(node.getText()); + if (result.size() >= searchTopK) { + break; + } + } + } catch (Exception e) { + throw new RuntimeException("查询知识库失败: " + e.getMessage(), e); + } + return result; + } + + @Override + public String createKnowledgeBase(String companyName, String companyCode) { + String workspaceId = config.getWorkspaceId(); + try { + String kbId = getKnowledgeBaseIdByName(companyCode); + if(StrUtil.isNotEmpty(kbId)) { + return kbId; + } + + Client client = clientFactory.createClient(); + CreateIndexResponse indexResponse = AiCloudKnowledgeBaseUtil.createIndex(client, workspaceId, companyCode, companyName); + return indexResponse.getBody().getData().getId(); + } catch (Exception e) { + throw new RuntimeException("创建知识库失败: " + e.getMessage(), e); + } + } + + @Override + public String createKnowledgeBaseTemp() { + String code = "Temp_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("MMddHHmmssSSS")); + return createKnowledgeBase(code, code); + } + + @Override + public boolean existsKnowledgeBase(String companyCode) { + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + ListIndicesResponse indicesResponse = AiCloudKnowledgeBaseUtil.listIndices(client, workspaceId); + + return indicesResponse.getBody().getData().getIndices().stream() + .anyMatch(index -> companyCode.equals(index.getName())); + } catch (Exception e) { + throw new RuntimeException("检查知识库是否存在失败: " + e.getMessage(), e); + } + } + + @Override + public String getKnowledgeBaseIdByName(String companyCode) { + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + ListIndicesResponse indicesResponse = AiCloudKnowledgeBaseUtil.listIndices(client, workspaceId); + + return indicesResponse.getBody().getData().getIndices().stream() + .filter(index -> companyCode.equals(index.getName())) + .findFirst() + .map(index -> index.getId()) + .orElse(""); + } catch (Exception e) { + throw new RuntimeException("查找知识库ID失败: " + e.getMessage(), e); + } + } + + @Override + public Map listDocuments(String kbId, Integer pageSize, Integer pageNumber) { + Map ret = new HashMap<>(); + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + ListIndexDocumentsResponse indexDocumentsResponse = AiCloudKnowledgeBaseUtil.listIndexDocuments(client, workspaceId, kbId, pageSize, pageNumber); + ret.put("data", indexDocumentsResponse.getBody().getData().getDocuments()); + ret.put("total", indexDocumentsResponse.getBody().getData().getTotalCount()); + } catch (Exception e) { + throw new RuntimeException("查询知识库下的文档列表失败: " + e.getMessage(), e); + } + return ret; + } + + @Override + public boolean deleteIndex(String kbId) { + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + DeleteIndexResponse indexDocumentResponse = AiCloudKnowledgeBaseUtil.deleteIndex(client, workspaceId, kbId); + return indexDocumentResponse.getBody().getSuccess(); + } catch (Exception e) { + throw new RuntimeException("删除知识库失败: " + e.getMessage(), e); + } + } + + @Override + public boolean deleteIndexDocument(String kbId, String fileIds) { + String workspaceId = config.getWorkspaceId(); + List ids = StrUtil.splitTrim(fileIds, ","); + try { + Client client = clientFactory.createClient(); + DeleteIndexDocumentResponse indexDocumentResponse = AiCloudKnowledgeBaseUtil.deleteIndexDocument(client, workspaceId, kbId, ids); + for(String id : ids) { + AiCloudKnowledgeBaseUtil.deleteAppDocument(client, workspaceId, id); + } + return indexDocumentResponse.getBody().getSuccess(); + } catch (Exception e) { + throw new RuntimeException("删除知识库下的文档失败: " + e.getMessage(), e); + } + } + + @Override + public List uploadDocuments(String kbId, MultipartFile[] files) { + String workspaceId = config.getWorkspaceId(); + int count = files.length; + try { + Client client = clientFactory.createClient(); + + List fileIds = new ArrayList<>(); + for(MultipartFile file : files) { + AddFileResponse addFileResponse = AiCloudDataCenterUtil.uploadFile(client, workspaceId, "cate_28190570c20043d692897701d4547401_10377381", file); + String fileId = addFileResponse.getBody().getData().getFileId(); + fileIds.add(fileId); + } +// List fileIds = AiCloudKnowledgeBaseUtil.uploadDocuments(client, workspaceId, kbId, files); + //上传切片完成后删除原文档(释放云空间) +// for(String fileId : fileIds) { +// AiCloudKnowledgeBaseUtil.deleteAppDocument(client, workspaceId, fileId); +// } + return fileIds; + } catch (Exception e) { + throw new RuntimeException("上传文档到知识库失败: " + e.getMessage(), e); + } + } + + @Async + @Override + public boolean submitDocuments(String kbId, String fileId) { + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + boolean result = AiCloudKnowledgeBaseUtil.submitIndexAddDocumentsJob(client, workspaceId, kbId, fileId).getBody().getSuccess(); + return result; + } catch (Exception e) { + throw new RuntimeException("添加文档到知识库失败: " + e.getMessage(), e); + } + } + + @Override + public boolean submitDocuments(String kbId, List fileIds) { + String workspaceId = config.getWorkspaceId(); + boolean result = true; + try { + Client client = clientFactory.createClient(); + result = result && AiCloudKnowledgeBaseUtil.submitIndexAddDocumentsJob(client, workspaceId, kbId, fileIds).getBody().getSuccess(); + } catch (Exception e) { + throw new RuntimeException("添加文档到知识库失败: " + e.getMessage(), e); + } + return result; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java b/src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java new file mode 100644 index 0000000..c942cc2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java @@ -0,0 +1,324 @@ +package com.gxwebsoft.ai.util; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.*; +import com.aliyun.teautil.models.RuntimeOptions; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.hutool.core.util.StrUtil; + +import java.util.Map; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + * 云数据中心工具类 + * @author yc + * + */ +public class AiCloudDataCenterUtil { + + /** + * 类目列表 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param categoryId 父类目ID + * @return 阿里云百炼服务的响应 + */ + public static ListCategoryResponse listCategory(Client client, String workspaceId, String parentCategoryId) throws Exception { + ListCategoryRequest listCategoryRequest = new ListCategoryRequest(); + listCategoryRequest.setCategoryType("UNSTRUCTURED"); + listCategoryRequest.setParentCategoryId(parentCategoryId); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.listCategoryWithOptions(workspaceId, listCategoryRequest, headers, runtime); + } + + /** + * 删除类目 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param categoryId 类目ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteCategoryResponse deleteCategory(Client client, String workspaceId, String categoryId) throws Exception { + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.deleteCategoryWithOptions(categoryId, workspaceId, headers, runtime); + } + + /** + * 添加类目 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param addCategoryRequest 添加类目请求对象 + * @return 阿里云百炼服务的响应 + */ + public static AddCategoryResponse addCategory(Client client, String workspaceId, String parentCategoryId, String categoryName) throws Exception { + AddCategoryRequest addCategoryRequest = new AddCategoryRequest(); + addCategoryRequest.setCategoryType("UNSTRUCTURED"); + addCategoryRequest.setParentCategoryId(parentCategoryId); + addCategoryRequest.setCategoryName(categoryName); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.addCategoryWithOptions(workspaceId, addCategoryRequest, headers, runtime); + } + + /** + * 文件列表 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @return 阿里云百炼服务的响应 + */ + public static ListFileResponse listFile(Client client, String workspaceId, String categoryId, String fileName) throws Exception { + ListFileRequest listFileRequest = new ListFileRequest(); + listFileRequest.setCategoryId(categoryId); + if(StrUtil.isNotBlank(fileName)) { + listFileRequest.setFileName(fileName); + } + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.listFileWithOptions(workspaceId, listFileRequest, headers, runtime); + } + + /** + * 添加文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param addFileRequest 添加文件请求对象 + * @return 阿里云百炼服务的响应 + */ + public static AddFileResponse addFile(Client client, String workspaceId, String categoryId, String leaseId) throws Exception { + AddFileRequest addFileRequest = new AddFileRequest(); + addFileRequest.setCategoryId(categoryId); + addFileRequest.setLeaseId(leaseId); + addFileRequest.setParser("DASHSCOPE_DOCMIND"); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.addFileWithOptions(workspaceId, addFileRequest, headers, runtime); + } + +// /** +// * 从授权OSS添加文件(弃用) +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param addFilesFromAuthorizedOssRequest 从授权OSS添加文件请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static AddFilesFromAuthorizedOssResponse addFilesFromAuthorizedOss(Client client, String workspaceId, AddFilesFromAuthorizedOssRequest addFilesFromAuthorizedOssRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.addFilesFromAuthorizedOssWithOptions(workspaceId, addFilesFromAuthorizedOssRequest, headers, runtime); +// } + + /** + * 申请文件上传租约 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param categoryId 类目ID + * @param applyFileUploadLeaseRequest 申请文件上传租约请求对象 + * @return 阿里云百炼服务的响应 + */ + public static ApplyFileUploadLeaseResponse applyFileUploadLease(Client client, String workspaceId, String categoryId, String fileName, String md5, String size) throws Exception { + ApplyFileUploadLeaseRequest applyFileUploadLeaseRequest = new ApplyFileUploadLeaseRequest(); + applyFileUploadLeaseRequest.setFileName(fileName); + applyFileUploadLeaseRequest.setMd5(md5); + applyFileUploadLeaseRequest.setSizeInBytes(size); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.applyFileUploadLeaseWithOptions(categoryId, workspaceId, applyFileUploadLeaseRequest, headers, runtime); + } + + /** + * 删除文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param fileId 文件ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteFileResponse deleteFile(Client client, String workspaceId, String fileId) throws Exception { + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.deleteFileWithOptions(fileId, workspaceId, headers, runtime); + } + + /** + * 描述文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param fileId 文件ID + * @return 阿里云百炼服务的响应 + */ + public static DescribeFileResponse describeFile(Client client, String workspaceId, String fileId) throws Exception { + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.describeFileWithOptions(workspaceId, fileId, headers, runtime); + } + + /** + * 更新文件标签 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param fileId 文件ID + * @param updateFileTagRequest 更新文件标签请求对象 + * @return 阿里云百炼服务的响应 + */ + public static UpdateFileTagResponse updateFileTag(Client client, String workspaceId, String fileId, List tags) throws Exception { + UpdateFileTagRequest updateFileTagRequest = new UpdateFileTagRequest(); + updateFileTagRequest.setTags(tags); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.updateFileTagWithOptions(workspaceId, fileId, updateFileTagRequest, headers, runtime); + } + +// /** +// * 获取解析设置 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param getParseSettingsRequest 获取解析设置请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static GetParseSettingsResponse getParseSettings(Client client, String workspaceId, GetParseSettingsRequest getParseSettingsRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.getParseSettingsWithOptions(workspaceId, getParseSettingsRequest, headers, runtime); +// } +// +// /** +// * 获取可用解析器类型 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param getAvailableParserTypesRequest 获取可用解析器类型请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static GetAvailableParserTypesResponse getAvailableParserTypes(Client client, String workspaceId, GetAvailableParserTypesRequest getAvailableParserTypesRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.getAvailableParserTypesWithOptions(workspaceId, getAvailableParserTypesRequest, headers, runtime); +// } +// +// /** +// * 更改解析设置 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param changeParseSettingRequest 更改解析设置请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static ChangeParseSettingResponse changeParseSetting(Client client, String workspaceId, ChangeParseSettingRequest changeParseSettingRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.changeParseSettingWithOptions(workspaceId, changeParseSettingRequest, headers, runtime); +// } + + + + /** + * 上传文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param addFileRequest 添加文件请求对象 + * @return 阿里云百炼服务的响应 + */ + public static AddFileResponse uploadFile(Client client, String workspaceId, String categoryId, MultipartFile file) throws Exception { + // 准备文档信息 + String fileName = file.getOriginalFilename(); + String fileMd5 = calculateMD5(file.getInputStream()); + String fileSize = String.valueOf(file.getSize()); + + ApplyFileUploadLeaseResponse applyFileUploadLeaseResponse = applyFileUploadLease(client, workspaceId, categoryId, fileName, fileMd5, fileSize); + + String leaseId = applyFileUploadLeaseResponse.getBody().getData().getFileUploadLeaseId(); + String uploadUrl = applyFileUploadLeaseResponse.getBody().getData().getParam().getUrl(); + + // 上传文件 + ObjectMapper mapper = new ObjectMapper(); + Map headers = mapper.readValue(mapper.writeValueAsString(applyFileUploadLeaseResponse.getBody().getData().getParam().getHeaders()), Map.class); + uploadFile(uploadUrl, headers, file); + + AddFileResponse addFileResponse = addFile(client, workspaceId, categoryId, leaseId); + + String fileId = addFileResponse.getBody().getData().getFileId(); + waitForFileParsing(client, workspaceId, fileId); + + // fileId的UUID部分做为标签更新 file_df12ed21b7384353bd75868444c516ae_10377381 -> df12ed21b7384353bd75868444c516ae + String tag = StrUtil.subBetween(fileId, "_", "_").substring(0, 32); + updateFileTag(client, workspaceId, fileId, Arrays.asList(tag)); + + return addFileResponse; + } + + private static void uploadFile(String preSignedUrl, Map headers, MultipartFile file) throws Exception { + HttpURLConnection conn = (HttpURLConnection) new URL(preSignedUrl).openConnection(); + conn.setRequestMethod("PUT"); + conn.setDoOutput(true); + conn.setRequestProperty("X-bailian-extra", headers.get("X-bailian-extra")); + conn.setRequestProperty("Content-Type", headers.get("Content-Type")); + + try (InputStream inputStream = file.getInputStream()) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + conn.getOutputStream().write(buffer, 0, bytesRead); + } + } + + if (conn.getResponseCode() != 200) { + throw new RuntimeException("上传失败: " + conn.getResponseCode()); + } + } + + private static String calculateMD5(InputStream inputStream) throws Exception { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + md.update(buffer, 0, bytesRead); + } + StringBuilder sb = new StringBuilder(); + for (byte b : md.digest()) { + sb.append(String.format("%02x", b & 0xff)); + } + return sb.toString(); + } + + private static void waitForFileParsing(Client client, String workspaceId, String fileId) throws Exception { + long timeout = 30 * 60 * 1000; // 30分钟超时 + long startTime = System.currentTimeMillis(); + while (true) { + // 检查超时 + if (System.currentTimeMillis() - startTime > timeout) { + throw new RuntimeException("文档解析超时"); + } + DescribeFileResponse response = describeFile(client, workspaceId, fileId); + String status = response.getBody().getData().getStatus(); + if ("PARSE_SUCCESS".equals(status)) { + break; + } else if ("PARSE_FAILED".equals(status)) { + throw new RuntimeException("文档解析失败"); + } + Thread.sleep(3000); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java b/src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java new file mode 100644 index 0000000..eac84e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java @@ -0,0 +1,210 @@ +package com.gxwebsoft.ai.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSON; +import com.aliyun.bailian20231229.models.CreateIndexResponse; +import com.aliyun.bailian20231229.models.DeleteFileResponse; +import com.aliyun.bailian20231229.models.DeleteIndexDocumentResponse; +import com.aliyun.bailian20231229.models.DeleteIndexResponse; +import com.aliyun.bailian20231229.models.ListIndexDocumentsResponse; +import com.aliyun.bailian20231229.models.ListIndicesResponse; +import com.aliyun.bailian20231229.models.RetrieveRequest; +import com.aliyun.bailian20231229.models.RetrieveResponse; +import com.aliyun.bailian20231229.models.SubmitIndexAddDocumentsJobRequest; +import com.aliyun.bailian20231229.models.SubmitIndexAddDocumentsJobResponse; +import com.aliyun.teautil.models.RuntimeOptions; + +/** + * 知识库工具类 + * @author GIIT-YC + * + */ +public class AiCloudKnowledgeBaseUtil { + + /** + * 在指定的知识库中检索信息。 + * + * @param client 客户端对象(bailian20231229Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param query 检索查询语句 + * @return 阿里云百炼服务的响应 + */ + public static RetrieveResponse retrieveIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String query) throws Exception { + RetrieveRequest retrieveRequest = new RetrieveRequest(); + retrieveRequest.setIndexId(indexId); + retrieveRequest.setQuery(query); + retrieveRequest.setDenseSimilarityTopK(100); + retrieveRequest.setSparseSimilarityTopK(100); + retrieveRequest.setEnableReranking(false);//开启耗费巨量token + RuntimeOptions runtime = new RuntimeOptions(); + return client.retrieveWithOptions(workspaceId, retrieveRequest, null, runtime); + } + + /** + * 在指定的知识库中检索信息。 + * + * @param client 客户端对象(bailian20231229Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param query 检索查询语句 + * @param filesIds 指定文件 + * @return 阿里云百炼服务的响应 + */ + public static RetrieveResponse retrieveIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String query, List filesIds) throws Exception { + RetrieveRequest retrieveRequest = new RetrieveRequest(); + retrieveRequest.setIndexId(indexId); + retrieveRequest.setQuery(query); + retrieveRequest.setDenseSimilarityTopK(100); + retrieveRequest.setSparseSimilarityTopK(100); + retrieveRequest.setEnableReranking(false);//开启耗费巨量token + List> searchFilters = new ArrayList<>(); + Map searchFiltersTags = new HashMap<>(); + searchFiltersTags.put("tags", JSON.toJSONString(filesIds)); + searchFilters.add(searchFiltersTags); + retrieveRequest.setSearchFilters(searchFilters); +// retrieveRequest.setRerankMinScore(null); + RuntimeOptions runtime = new RuntimeOptions(); + return client.retrieveWithOptions(workspaceId, retrieveRequest, null, runtime); + } + + /** + * 在阿里云百炼服务中创建知识库(初始化)。 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param name 知识库名称 + * @param desc 知识库描述 + * @return 阿里云百炼服务的响应对象 + */ + public static CreateIndexResponse createIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String name, String desc) throws Exception { + Map headers = new HashMap<>(); + com.aliyun.bailian20231229.models.CreateIndexRequest createIndexRequest = new com.aliyun.bailian20231229.models.CreateIndexRequest(); + createIndexRequest.setStructureType("unstructured"); + createIndexRequest.setName(name); + createIndexRequest.setDescription(desc); + createIndexRequest.setSinkType("DEFAULT"); + createIndexRequest.setEmbeddingModelName("text-embedding-v4"); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + return client.createIndexWithOptions(workspaceId, createIndexRequest, headers, runtime); + } + + /** + * 获取指定业务空间下一个或多个知识库的详细信息 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @return 阿里云百炼服务的响应 + */ + public static ListIndicesResponse listIndices(com.aliyun.bailian20231229.Client client, String workspaceId) throws Exception { + Map headers = new HashMap<>(); + com.aliyun.bailian20231229.models.ListIndicesRequest listIndicesRequest = new com.aliyun.bailian20231229.models.ListIndicesRequest(); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + return client.listIndicesWithOptions(workspaceId, listIndicesRequest, headers, runtime); + } + + /** + * 永久性删除指定的知识库 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteIndexResponse deleteIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId) throws Exception { + Map headers = new HashMap<>(); + com.aliyun.bailian20231229.models.DeleteIndexRequest deleteIndexRequest = new com.aliyun.bailian20231229.models.DeleteIndexRequest(); + deleteIndexRequest.setIndexId(indexId); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + return client.deleteIndexWithOptions(workspaceId, deleteIndexRequest, headers, runtime); + } + + /** + * 查询知识库下的文档列表 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @return 阿里云百炼服务的响应 + */ + public static ListIndexDocumentsResponse listIndexDocuments(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, Integer pageSize, Integer pageNumber) throws Exception { + com.aliyun.bailian20231229.models.ListIndexDocumentsRequest listIndexDocumentsRequest = new com.aliyun.bailian20231229.models.ListIndexDocumentsRequest(); + listIndexDocumentsRequest.setIndexId(indexId); + listIndexDocumentsRequest.setPageSize(pageSize); + listIndexDocumentsRequest.setPageNumber(pageNumber); + return client.listIndexDocuments(workspaceId, listIndexDocumentsRequest); + } + + /** + * 删除知识库下的文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param ids 删除文件ID列表 + * @return 阿里云百炼服务的响应 + */ + public static DeleteIndexDocumentResponse deleteIndexDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, List ids) throws Exception { + com.aliyun.bailian20231229.models.DeleteIndexDocumentRequest deleteIndexDocumentRequest = new com.aliyun.bailian20231229.models.DeleteIndexDocumentRequest(); + deleteIndexDocumentRequest.setIndexId(indexId); + deleteIndexDocumentRequest.setDocumentIds(ids); + return client.deleteIndexDocument(workspaceId, deleteIndexDocumentRequest); + } + + /** + * 删除阿里云应用数据文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param fileId 删除文件ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteFileResponse deleteAppDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String fileId) throws Exception { + return client.deleteFile(fileId, workspaceId); + } + + /** + * 向一个非结构化知识库追加导入已解析的文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param fileId 文档ID + * @param sourceType 数据类型 + * @return 阿里云百炼服务的响应 + */ + public static SubmitIndexAddDocumentsJobResponse submitIndexAddDocumentsJob(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String fileId) throws Exception { + Map headers = new HashMap<>(); + SubmitIndexAddDocumentsJobRequest submitIndexAddDocumentsJobRequest = new SubmitIndexAddDocumentsJobRequest(); + submitIndexAddDocumentsJobRequest.setIndexId(indexId); + submitIndexAddDocumentsJobRequest.setDocumentIds(Collections.singletonList(fileId)); + submitIndexAddDocumentsJobRequest.setSourceType("DATA_CENTER_FILE"); + RuntimeOptions runtime = new RuntimeOptions(); + return client.submitIndexAddDocumentsJobWithOptions(workspaceId, submitIndexAddDocumentsJobRequest, headers, runtime); + } + + /** + * 向一个非结构化知识库追加导入已解析的文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param fileId 文档ID + * @param sourceType 数据类型 + * @return 阿里云百炼服务的响应 + */ + public static SubmitIndexAddDocumentsJobResponse submitIndexAddDocumentsJob(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, List fileIds) throws Exception { + Map headers = new HashMap<>(); + SubmitIndexAddDocumentsJobRequest submitIndexAddDocumentsJobRequest = new SubmitIndexAddDocumentsJobRequest(); + submitIndexAddDocumentsJobRequest.setIndexId(indexId); + submitIndexAddDocumentsJobRequest.setDocumentIds(fileIds); + submitIndexAddDocumentsJobRequest.setSourceType("DATA_CENTER_FILE"); + RuntimeOptions runtime = new RuntimeOptions(); + return client.submitIndexAddDocumentsJobWithOptions(workspaceId, submitIndexAddDocumentsJobRequest, headers, runtime); + } +} diff --git a/src/main/java/com/gxwebsoft/ai/util/AuditReportUtil.java b/src/main/java/com/gxwebsoft/ai/util/AuditReportUtil.java new file mode 100644 index 0000000..422b03f --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/AuditReportUtil.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.ai.util; + +import java.util.List; + +import com.gxwebsoft.ai.dto.AuditReportRequest; + +public class AuditReportUtil { + + public static String generateReportContent(AuditReportRequest request) { + // 创建模板字符串 + String template = + "# \n" + request.getFrom00() + "\n\n" + + "## 一、审计依据\n" + request.getFrom10() + "\n\n" + + "## 二、审计目标\n" + request.getFrom20() + "\n\n" + + "## 三、审计对象和范围\n" + request.getFrom30() + "\n\n" + + "## 四、被审计单位基本情况\n" + + "### (一)单位概况\n" + request.getFrom41() + "\n" + + "### (二)机构和人员相关情况\n" + request.getFrom42() + "\n" + + "### (三)财务会计、重大会计政策选用及变动情况\n" + request.getFrom43() + "\n" + listToString(request.getFrom44()) + "\n" + + "### (五)相关内部控制\n" + request.getFrom45() + "\n\n" + + "## 五、审计内容和重点及审计方法\n" + + "### (一)贯彻执行党和国家有关经济方针和上级决策部署情况\n" + request.getFrom51() + "\n" + + "### (二)公司发展战略规划的制定、执行和效果情况以及年度责任目标完成情况\n" + request.getFrom52() + "\n" + + "### (三)重大经济事项的决策、执行和效果情况\n" + request.getFrom53() + "\n" + + "### (四)公司法人治理结构的建立、健全和运行情况,内部控制制度的制定和执行情况\n" + request.getFrom54() + "\n" + + "### (五)公司财务的真实合法效益情况,风险管控情况,境外资产管理情况\n" + request.getFrom55() + "\n" + + "### (六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况\n" + request.getFrom56() + "\n" + + "### (七)对以往审计中发现问题的整改情况\n" + request.getFrom57() + "\n" + + "### (八)其他需要审计的事项\n" + request.getFrom58() + "\n\n" + + "## 六、重要风险的识别及应对\n" + + "### (一)重要风险的识别\n" + request.getFrom61() + "\n" + + "### (二)风险的应对策略\n" + request.getFrom62() + "\n\n" + + "## 七、审计技术方法\n" + request.getFrom70() + "\n\n" + + "## 八、工作步骤与时间安排\n" + request.getFrom80() + "\n\n" + + "## 九、审计工作的组织实施\n" + request.getFrom90(); + + return template; + } + + private static String listToString(List list) { + if (list == null || list.isEmpty()) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + for (String item : list) { + sb.append(item).append("\n"); + } + return sb.toString(); + } + + public static String generateReportContentByFormCommit(AuditReportRequest request) { + int formCommit = (request.getFormCommit() >= 10) ? request.getFormCommit() / 10 : request.getFormCommit(); + // 创建模板字符串 + String template = ""; + if(formCommit == 5) { + template = + "## 四、被审计单位基本情况\n" + + "### (一)单位概况\n" + request.getFrom41() + "\n" + + "### (二)机构和人员相关情况\n" + request.getFrom42() + "\n" + + "### (三)财务会计、重大会计政策选用及变动情况\n" + request.getFrom43() + "\n" + listToString(request.getFrom44()) + "\n" + + "### (五)相关内部控制\n" + request.getFrom45() + "\n\n" + + "## 五、审计内容和重点及审计方法\n" + + "### (一)贯彻执行党和国家有关经济方针和上级决策部署情况\n" + request.getFrom51() + "\n" + + "### (二)公司发展战略规划的制定、执行和效果情况以及年度责任目标完成情况\n" + request.getFrom52() + "\n" + + "### (三)重大经济事项的决策、执行和效果情况\n" + request.getFrom53() + "\n" + + "### (四)公司法人治理结构的建立、健全和运行情况,内部控制制度的制定和执行情况\n" + request.getFrom54() + "\n" + + "### (五)公司财务的真实合法效益情况,风险管控情况,境外资产管理情况\n" + request.getFrom55() + "\n" + + "### (六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况\n" + request.getFrom56() + "\n" + + "### (七)对以往审计中发现问题的整改情况\n" + request.getFrom57() + "\n" + + "### (八)其他需要审计的事项\n" + request.getFrom58() + "\n\n"; + }else if(formCommit == 6) { + template = + "## 五、审计内容和重点及审计方法\n" + + "### (一)贯彻执行党和国家有关经济方针和上级决策部署情况\n" + request.getFrom51() + "\n" + + "### (二)公司发展战略规划的制定、执行和效果情况以及年度责任目标完成情况\n" + request.getFrom52() + "\n" + + "### (三)重大经济事项的决策、执行和效果情况\n" + request.getFrom53() + "\n" + + "### (四)公司法人治理结构的建立、健全和运行情况,内部控制制度的制定和执行情况\n" + request.getFrom54() + "\n" + + "### (五)公司财务的真实合法效益情况,风险管控情况,境外资产管理情况\n" + request.getFrom55() + "\n" + + "### (六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况\n" + request.getFrom56() + "\n" + + "### (七)对以往审计中发现问题的整改情况\n" + request.getFrom57() + "\n" + + "### (八)其他需要审计的事项\n" + request.getFrom58() + "\n\n" + + "## 六、重要风险的识别及应对\n" + + "### (一)重要风险的识别\n" + request.getFrom61() + "\n" + + "### (二)风险的应对策略\n" + request.getFrom62() + "\n\n"; + }else { + template = request.getHistory(); + } + return template.replaceAll("#", ""); + } +} diff --git a/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseCreate.java b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseCreate.java new file mode 100644 index 0000000..ca3afde --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseCreate.java @@ -0,0 +1,384 @@ +//package com.gxwebsoft.ai.util; +// +//import com.aliyun.bailian20231229.models.*; +//import com.fasterxml.jackson.databind.ObjectMapper; +// +//import java.io.File; +//import java.io.FileInputStream; +//import java.net.HttpURLConnection; +//import java.net.URL; +//import java.security.MessageDigest; +//import java.util.*; +//import java.util.concurrent.TimeUnit; +// +///** +// * 创建知识库 +// * @author GIIT-YC +// * +// */ +//public class KnowledgeBaseCreate { +// +// String ALIBABA_CLOUD_ACCESS_KEY_ID = "LTAI5tD5YRKuxWz6Eg7qrM4P"; +// String ALIBABA_CLOUD_ACCESS_KEY_SECRET = "bO8TBDXflOwbtSKimPpG8XrJnyzgTk"; +// String WORKSPACE_ID = "llm-4pf5auwewoz34zqu"; +// +// /** +// * 检查并提示设置必要的环境变量。 +// * +// * @return true 如果所有必需的环境变量都已设置,否则 false +// */ +// public static boolean checkEnvironmentVariables() { +// Map requiredVars = new HashMap<>(); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_ID", "阿里云访问密钥ID"); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_SECRET", "阿里云访问密钥密码"); +// requiredVars.put("WORKSPACE_ID", "阿里云百炼业务空间ID"); +// +// List missingVars = new ArrayList<>(); +// for (Map.Entry entry : requiredVars.entrySet()) { +// String value = System.getenv(entry.getKey()); +// if (value == null || value.isEmpty()) { +// missingVars.add(entry.getKey()); +// System.out.println("错误:请设置 " + entry.getKey() + " 环境变量 (" + entry.getValue() + ")"); +// } +// } +// +// return missingVars.isEmpty(); +// } +// +// /** +// * 计算文档的MD5值。 +// * +// * @param filePath 文档本地路径 +// * @return 文档的MD5值 +// * @throws Exception 如果计算过程中发生错误 +// */ +// public static String calculateMD5(String filePath) throws Exception { +// MessageDigest md = MessageDigest.getInstance("MD5"); +// try (FileInputStream fis = new FileInputStream(filePath)) { +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = fis.read(buffer)) != -1) { +// md.update(buffer, 0, bytesRead); +// } +// } +// StringBuilder sb = new StringBuilder(); +// for (byte b : md.digest()) { +// sb.append(String.format("%02x", b & 0xff)); +// } +// return sb.toString(); +// } +// +// /** +// * 获取文档大小(以字节为单位)。 +// * +// * @param filePath 文档本地路径 +// * @return 文档大小(以字节为单位) +// */ +// public static String getFileSize(String filePath) { +// File file = new File(filePath); +// long fileSize = file.length(); +// return String.valueOf(fileSize); +// } +// +// /** +// * 初始化客户端(Client)。 +// * +// * @return 配置好的客户端对象 +// */ +// public static com.aliyun.bailian20231229.Client createClient() throws Exception { +// com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() +// .setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")) +// .setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")); +// // 下方接入地址以公有云的公网接入地址为例,可按需更换接入地址。 +// config.endpoint = "bailian.cn-beijing.aliyuncs.com"; +// return new com.aliyun.bailian20231229.Client(config); +// } +// +// /** +// * 申请文档上传租约。 +// * +// * @param client 客户端对象 +// * @param categoryId 类目ID +// * @param fileName 文档名称 +// * @param fileMd5 文档的MD5值 +// * @param fileSize 文档大小(以字节为单位) +// * @param workspaceId 业务空间ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static ApplyFileUploadLeaseResponse applyLease(com.aliyun.bailian20231229.Client client, String categoryId, +// String fileName, String fileMd5, String fileSize, String workspaceId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.ApplyFileUploadLeaseRequest applyFileUploadLeaseRequest = new com.aliyun.bailian20231229.models.ApplyFileUploadLeaseRequest(); +// applyFileUploadLeaseRequest.setFileName(fileName); +// applyFileUploadLeaseRequest.setMd5(fileMd5); +// applyFileUploadLeaseRequest.setSizeInBytes(fileSize); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// ApplyFileUploadLeaseResponse applyFileUploadLeaseResponse = null; +// applyFileUploadLeaseResponse = client.applyFileUploadLeaseWithOptions(categoryId, workspaceId, +// applyFileUploadLeaseRequest, headers, runtime); +// return applyFileUploadLeaseResponse; +// } +// +// /** +// * 上传文档到临时存储。 +// * +// * @param preSignedUrl 上传租约中的 URL +// * @param headers 上传请求的头部 +// * @param filePath 文档本地路径 +// * @throws Exception 如果上传过程中发生错误 +// */ +// public static void uploadFile(String preSignedUrl, Map headers, String filePath) throws Exception { +// File file = new File(filePath); +// if (!file.exists() || !file.isFile()) { +// throw new IllegalArgumentException("文件不存在或不是普通文件: " + filePath); +// } +// +// try (FileInputStream fis = new FileInputStream(file)) { +// URL url = new URL(preSignedUrl); +// HttpURLConnection conn = (HttpURLConnection) url.openConnection(); +// conn.setRequestMethod("PUT"); +// conn.setDoOutput(true); +// +// // 设置上传请求头 +// conn.setRequestProperty("X-bailian-extra", headers.get("X-bailian-extra")); +// conn.setRequestProperty("Content-Type", headers.get("Content-Type")); +// +// // 分块读取并上传文档 +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = fis.read(buffer)) != -1) { +// conn.getOutputStream().write(buffer, 0, bytesRead); +// } +// +// int responseCode = conn.getResponseCode(); +// if (responseCode != 200) { +// throw new RuntimeException("上传失败: " + responseCode); +// } +// } +// } +// +// /** +// * 将文档添加到类目中。 +// * +// * @param client 客户端对象 +// * @param leaseId 租约ID +// * @param parser 用于文档的解析器 +// * @param categoryId 类目ID +// * @param workspaceId 业务空间ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static AddFileResponse addFile(com.aliyun.bailian20231229.Client client, String leaseId, String parser, +// String categoryId, String workspaceId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.AddFileRequest addFileRequest = new com.aliyun.bailian20231229.models.AddFileRequest(); +// addFileRequest.setLeaseId(leaseId); +// addFileRequest.setParser(parser); +// addFileRequest.setCategoryId(categoryId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.addFileWithOptions(workspaceId, addFileRequest, headers, runtime); +// } +// +// /** +// * 查询文档的基本信息。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param fileId 文档ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static DescribeFileResponse describeFile(com.aliyun.bailian20231229.Client client, String workspaceId, +// String fileId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.describeFileWithOptions(workspaceId, fileId, headers, runtime); +// } +// +// /** +// * 在阿里云百炼服务中创建知识库(初始化)。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param fileId 文档ID +// * @param name 知识库名称 +// * @param structureType 知识库的数据类型 +// * @param sourceType 应用数据的数据类型,支持类目类型和文档类型 +// * @param sinkType 知识库的向量存储类型 +// * @return 阿里云百炼服务的响应对象 +// */ +// public static CreateIndexResponse createIndex(com.aliyun.bailian20231229.Client client, String workspaceId, +// String fileId, String name, String structureType, String sourceType, String sinkType) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.CreateIndexRequest createIndexRequest = new com.aliyun.bailian20231229.models.CreateIndexRequest(); +// createIndexRequest.setStructureType(structureType); +// createIndexRequest.setName(name); +// createIndexRequest.setSourceType(sourceType); +// createIndexRequest.setSinkType(sinkType); +// createIndexRequest.setDocumentIds(Collections.singletonList(fileId)); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.createIndexWithOptions(workspaceId, createIndexRequest, headers, runtime); +// } +// +// /** +// * 向阿里云百炼服务提交索引任务。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static SubmitIndexJobResponse submitIndex(com.aliyun.bailian20231229.Client client, String workspaceId, +// String indexId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.SubmitIndexJobRequest submitIndexJobRequest = new com.aliyun.bailian20231229.models.SubmitIndexJobRequest(); +// submitIndexJobRequest.setIndexId(indexId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.submitIndexJobWithOptions(workspaceId, submitIndexJobRequest, headers, runtime); +// } +// +// /** +// * 查询索引任务状态。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param jobId 任务ID +// * @param indexId 知识库ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static GetIndexJobStatusResponse getIndexJobStatus(com.aliyun.bailian20231229.Client client, +// String workspaceId, String jobId, String indexId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.GetIndexJobStatusRequest getIndexJobStatusRequest = new com.aliyun.bailian20231229.models.GetIndexJobStatusRequest(); +// getIndexJobStatusRequest.setIndexId(indexId); +// getIndexJobStatusRequest.setJobId(jobId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// GetIndexJobStatusResponse getIndexJobStatusResponse = null; +// getIndexJobStatusResponse = client.getIndexJobStatusWithOptions(workspaceId, getIndexJobStatusRequest, headers, +// runtime); +// return getIndexJobStatusResponse; +// } +// +// /** +// * 使用阿里云百炼服务创建知识库。 +// * +// * @param filePath 文档本地路径 +// * @param workspaceId 业务空间ID +// * @param name 知识库名称 +// * @return 如果成功,返回知识库ID;否则返回 null +// */ +// public static String createKnowledgeBase(String filePath, String workspaceId, String name) { +// // 设置默认值 +// String categoryId = "default"; +// String parser = "DASHSCOPE_DOCMIND"; +// String sourceType = "DATA_CENTER_FILE"; +// String structureType = "unstructured"; +// String sinkType = "DEFAULT"; +// try { +// // 步骤1:初始化客户端(Client) +// System.out.println("步骤1:初始化Client"); +// com.aliyun.bailian20231229.Client client = createClient(); +// +// // 步骤2:准备文档信息 +// System.out.println("步骤2:准备文档信息"); +// String fileName = new File(filePath).getName(); +// String fileMd5 = calculateMD5(filePath); +// String fileSize = getFileSize(filePath); +// +// // 步骤3:申请上传租约 +// System.out.println("步骤3:向阿里云百炼申请上传租约"); +// ApplyFileUploadLeaseResponse leaseResponse = applyLease(client, categoryId, fileName, fileMd5, fileSize, +// workspaceId); +// String leaseId = leaseResponse.getBody().getData().getFileUploadLeaseId(); +// String uploadUrl = leaseResponse.getBody().getData().getParam().getUrl(); +// Object uploadHeaders = leaseResponse.getBody().getData().getParam().getHeaders(); +// +// // 步骤4:上传文档 +// System.out.println("步骤4:上传文档到阿里云百炼"); +// // 请自行安装jackson-databind +// // 将上一步的uploadHeaders转换为Map(Key-Value形式) +// ObjectMapper mapper = new ObjectMapper(); +// Map uploadHeadersMap = (Map) mapper +// .readValue(mapper.writeValueAsString(uploadHeaders), Map.class); +// uploadFile(uploadUrl, uploadHeadersMap, filePath); +// +// // 步骤5:将文档添加到服务器 +// System.out.println("步骤5:将文档添加到阿里云百炼服务器"); +// AddFileResponse addResponse = addFile(client, leaseId, parser, categoryId, workspaceId); +// String fileId = addResponse.getBody().getData().getFileId(); +// +// // 步骤6:检查文档状态 +// System.out.println("步骤6:检查阿里云百炼中的文档状态"); +// while (true) { +// DescribeFileResponse describeResponse = describeFile(client, workspaceId, fileId); +// String status = describeResponse.getBody().getData().getStatus(); +// System.out.println("当前文档状态:" + status); +// +// if (status.equals("INIT")) { +// System.out.println("文档待解析,请稍候..."); +// } else if (status.equals("PARSING")) { +// System.out.println("文档解析中,请稍候..."); +// } else if (status.equals("PARSE_SUCCESS")) { +// System.out.println("文档解析完成!"); +// break; +// } else { +// System.out.println("未知的文档状态:" + status + ",请联系技术支持。"); +// return null; +// } +// TimeUnit.SECONDS.sleep(5); +// } +// +// // 步骤7:初始化知识库 +// System.out.println("步骤7:在阿里云百炼中创建知识库"); +// CreateIndexResponse indexResponse = createIndex(client, workspaceId, fileId, name, structureType, +// sourceType, sinkType); +// String indexId = indexResponse.getBody().getData().getId(); +// +// // 步骤8:提交索引任务 +// System.out.println("步骤8:向阿里云百炼提交索引任务"); +// SubmitIndexJobResponse submitResponse = submitIndex(client, workspaceId, indexId); +// String jobId = submitResponse.getBody().getData().getId(); +// +// // 步骤9:获取索引任务状态 +// System.out.println("步骤9:获取阿里云百炼索引任务状态"); +// while (true) { +// GetIndexJobStatusResponse getStatusResponse = getIndexJobStatus(client, workspaceId, jobId, indexId); +// String status = getStatusResponse.getBody().getData().getStatus(); +// System.out.println("当前索引任务状态:" + status); +// +// if (status.equals("COMPLETED")) { +// break; +// } +// TimeUnit.SECONDS.sleep(5); +// } +// +// System.out.println("阿里云百炼知识库创建成功!"); +// return indexId; +// +// } catch (Exception e) { +// System.out.println("发生错误:" + e.getMessage()); +// e.printStackTrace(); +// return null; +// } +// } +// +// /** +// * 主函数。 +// */ +// public static void main(String[] args) { +// Scanner scanner = new Scanner(System.in); +// if (!checkEnvironmentVariables()) { +// return; +// } +// +// System.out.print("请输入您需要上传文档的实际本地路径(以Linux为例:/xxx/xxx/阿里云百炼系列手机产品介绍.docx):"); +// String filePath = scanner.nextLine(); +// +// System.out.print("请为您的知识库输入一个名称:"); +// String kbName = scanner.nextLine(); +// +// String workspaceId = System.getenv("WORKSPACE_ID"); +// String result = createKnowledgeBase(filePath, workspaceId, kbName); +// if (result != null) { +// System.out.println("知识库ID: " + result); +// } +// } +//} diff --git a/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseManage.java b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseManage.java new file mode 100644 index 0000000..5232d65 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseManage.java @@ -0,0 +1,145 @@ +//package com.gxwebsoft.ai.util; +// +//import com.aliyun.bailian20231229.models.DeleteIndexResponse; +//import com.aliyun.bailian20231229.models.ListIndicesResponse; +//import com.fasterxml.jackson.databind.ObjectMapper; +// +//import java.util.*; +// +///** +// * 管理知识库 +// * @author GIIT-YC +// * +// */ +//public class KnowledgeBaseManage { +// +// String ALIBABA_CLOUD_ACCESS_KEY_ID = "LTAI5tD5YRKuxWz6Eg7qrM4P"; +// String ALIBABA_CLOUD_ACCESS_KEY_SECRET = "bO8TBDXflOwbtSKimPpG8XrJnyzgTk"; +// String WORKSPACE_ID = "llm-4pf5auwewoz34zqu"; +// +// /** +// * 检查并提示设置必要的环境变量。 +// * +// * @return true 如果所有必需的环境变量都已设置,否则 false +// */ +// public static boolean checkEnvironmentVariables() { +// Map requiredVars = new HashMap<>(); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_ID", "阿里云访问密钥ID"); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_SECRET", "阿里云访问密钥密码"); +// requiredVars.put("WORKSPACE_ID", "阿里云百炼业务空间ID"); +// +// List missingVars = new ArrayList<>(); +// for (Map.Entry entry : requiredVars.entrySet()) { +// String value = System.getenv(entry.getKey()); +// if (value == null || value.isEmpty()) { +// missingVars.add(entry.getKey()); +// System.out.println("错误:请设置 " + entry.getKey() + " 环境变量 (" + entry.getValue() + ")"); +// } +// } +// +// return missingVars.isEmpty(); +// } +// +// /** +// * 创建并配置客户端(Client) +// * +// * @return 配置好的客户端(Client) +// */ +// public static com.aliyun.bailian20231229.Client createClient() throws Exception { +// com.aliyun.credentials.Client credential = new com.aliyun.credentials.Client(); +// com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() +// .setCredential(credential); +// // 下方接入地址以公有云的公网接入地址为例,可按需更换接入地址。 +// config.endpoint = "bailian.cn-beijing.aliyuncs.com"; +// return new com.aliyun.bailian20231229.Client(config); +// } +// +// /** +// * 获取指定业务空间下一个或多个知识库的详细信息 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @return 阿里云百炼服务的响应 +// */ +// public static ListIndicesResponse listIndices(com.aliyun.bailian20231229.Client client, String workspaceId) +// throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.ListIndicesRequest listIndicesRequest = new com.aliyun.bailian20231229.models.ListIndicesRequest(); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.listIndicesWithOptions(workspaceId, listIndicesRequest, headers, runtime); +// } +// +// /** +// * 永久性删除指定的知识库 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @return 阿里云百炼服务的响应 +// */ +// public static DeleteIndexResponse deleteIndex(com.aliyun.bailian20231229.Client client, String workspaceId, +// String indexId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.DeleteIndexRequest deleteIndexRequest = new com.aliyun.bailian20231229.models.DeleteIndexRequest(); +// deleteIndexRequest.setIndexId(indexId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.deleteIndexWithOptions(workspaceId, deleteIndexRequest, headers, runtime); +// } +// +// /** +// * 主函数 +// */ +// public static void main(String[] args) { +// if (!checkEnvironmentVariables()) { +// System.out.println("环境变量校验未通过。"); +// return; +// } +// +// try { +// Scanner scanner = new Scanner(System.in); +// System.out.print("请选择要执行的操作:\n1. 查看知识库\n2. 删除知识库\n请输入选项(1或2):"); +// String startOption = scanner.nextLine(); +// com.aliyun.bailian20231229.Client client = createClient(); +// if (startOption.equals("1")) { +// // 查看知识库 +// System.out.println("\n执行查看知识库"); +// String workspaceId = System.getenv("WORKSPACE_ID"); +// ListIndicesResponse response = listIndices(client, workspaceId); +// // 请自行安装jackson-databind。将响应转换为 JSON 字符串 +// ObjectMapper mapper = new ObjectMapper(); +// String result = mapper.writeValueAsString(response.getBody().getData()); +// System.out.println(result); +// } else if (startOption.equals("2")) { +// System.out.println("\n执行删除知识库"); +// String workspaceId = System.getenv("WORKSPACE_ID"); +// System.out.print("请输入知识库ID:"); // 即 CreateIndex 接口返回的 Data.Id,您也可以在阿里云百炼控制台的知识库页面获取。 +// String indexId = scanner.nextLine(); +// // 删除前二次确认 +// boolean confirm = false; +// while (!confirm) { +// System.out.print("您确定要永久性删除该知识库 " + indexId + " 吗?(y/n): "); +// String input = scanner.nextLine().trim().toLowerCase(); +// if (input.equals("y")) { +// confirm = true; +// } else if (input.equals("n")) { +// System.out.println("已取消删除操作。"); +// return; +// } else { +// System.out.println("无效输入,请输入 y 或 n。"); +// } +// } +// DeleteIndexResponse resp = deleteIndex(client, workspaceId, indexId); +// if (resp.getBody().getStatus().equals("200")) { +// System.out.println("知识库" + indexId + "删除成功!"); +// } else { +// ObjectMapper mapper = new ObjectMapper(); +// System.out.println("发生错误:" + mapper.writeValueAsString(resp.getBody())); +// } +// } else { +// System.out.println("无效的选项,程序退出。"); +// } +// } catch (Exception e) { +// System.out.println("发生错误:" + e.getMessage()); +// } +// } +//} diff --git a/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseRetrieve.java b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseRetrieve.java new file mode 100644 index 0000000..74c2b73 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseRetrieve.java @@ -0,0 +1,110 @@ +//package com.gxwebsoft.ai.util; +// +//import com.aliyun.bailian20231229.models.RetrieveRequest; +//import com.aliyun.bailian20231229.models.RetrieveResponse; +//import com.aliyun.teautil.models.RuntimeOptions; +//import com.fasterxml.jackson.databind.ObjectMapper; +// +//import java.util.*; +// +///** +// * 检索知识库 +// * @author GIIT-YC +// * +// */ +//public class KnowledgeBaseRetrieve { +// +// static String ALIBABA_CLOUD_ACCESS_KEY_ID = "LTAI5tD5YRKuxWz6Eg7qrM4P"; +// static String ALIBABA_CLOUD_ACCESS_KEY_SECRET = "bO8TBDXflOwbtSKimPpG8XrJnyzgTk"; +// static String WORKSPACE_ID = "llm-4pf5auwewoz34zqu"; +// +// +// /** +// * 检查并提示设置必要的环境变量。 +// * +// * @return true 如果所有必需的环境变量都已设置,否则 false +// */ +// public static boolean checkEnvironmentVariables() { +// Map requiredVars = new HashMap<>(); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_ID", "阿里云访问密钥ID"); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_SECRET", "阿里云访问密钥密码"); +// requiredVars.put("WORKSPACE_ID", "阿里云百炼业务空间ID"); +// +// List missingVars = new ArrayList<>(); +// for (Map.Entry entry : requiredVars.entrySet()) { +// String value = System.getenv(entry.getKey()); +// if (value == null || value.isEmpty()) { +// missingVars.add(entry.getKey()); +// System.out.println("错误:请设置 " + entry.getKey() + " 环境变量 (" + entry.getValue() + ")"); +// } +// } +// +// return missingVars.isEmpty(); +// } +// +// /** +// * 初始化客户端(Client)。 +// * +// * @return 配置好的客户端对象 +// */ +// public static com.aliyun.bailian20231229.Client createClient() throws Exception { +// com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() +// .setAccessKeyId(ALIBABA_CLOUD_ACCESS_KEY_ID) +// .setAccessKeySecret(ALIBABA_CLOUD_ACCESS_KEY_SECRET); +// // 下方接入地址以公有云的公网接入地址为例,可按需更换接入地址。 +// config.endpoint = "bailian.cn-beijing.aliyuncs.com"; +// return new com.aliyun.bailian20231229.Client(config); +// } +// +// /** +// * 在指定的知识库中检索信息。 +// * +// * @param client 客户端对象(bailian20231229Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param query 检索查询语句 +// * @return 阿里云百炼服务的响应 +// */ +// public static RetrieveResponse retrieveIndex(com.aliyun.bailian20231229.Client client, String workspaceId, +// String indexId, String query) throws Exception { +// RetrieveRequest retrieveRequest = new RetrieveRequest(); +// retrieveRequest.setIndexId(indexId); +// retrieveRequest.setQuery(query); +// retrieveRequest.setDenseSimilarityTopK(null); +// RuntimeOptions runtime = new RuntimeOptions(); +// return client.retrieveWithOptions(workspaceId, retrieveRequest, null, runtime); +// } +// +// /** +// * 使用阿里云百炼服务检索知识库。 +// */ +// public static void main(String[] args) { +//// if (!checkEnvironmentVariables()) { +//// System.out.println("环境变量校验未通过。"); +//// return; +//// } +// +// try { +// // 步骤1:初始化客户端(Client) +// System.out.println("步骤1:创建Client"); +// com.aliyun.bailian20231229.Client client = createClient(); +// +// // 步骤2:检索知识库 +// System.out.println("步骤2:检索知识库"); +// Scanner scanner = new Scanner(System.in); +// System.out.print("请输入知识库ID:"); // 即 CreateIndex 接口返回的 Data.Id,您也可以在阿里云百炼控制台的知识库页面获取。 +// String indexId = scanner.nextLine(); +// System.out.print("请输入检索query:"); +// String query = scanner.nextLine(); +// String workspaceId = WORKSPACE_ID; +// RetrieveResponse resp = retrieveIndex(client, workspaceId, indexId, query); +// +// // 请自行安装jackson-databind。将响应体responsebody转换为 JSON 字符串 +// ObjectMapper mapper = new ObjectMapper(); +// String result = mapper.writeValueAsString(resp.getBody()); +// System.out.println(result); +// } catch (Exception e) { +// System.out.println("发生错误:" + e.getMessage()); +// } +// } +//} diff --git a/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUpdate.java b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUpdate.java new file mode 100644 index 0000000..753809d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUpdate.java @@ -0,0 +1,384 @@ +//package com.gxwebsoft.ai.util; +// +//import com.aliyun.bailian20231229.models.*; +//import com.aliyun.teautil.models.RuntimeOptions; +//import com.fasterxml.jackson.databind.ObjectMapper; +// +//import java.io.File; +//import java.io.FileInputStream; +//import java.net.HttpURLConnection; +//import java.net.URL; +//import java.nio.file.Paths; +//import java.security.MessageDigest; +//import java.util.*; +// +///** +// * 更新知识库 +// * @author GIIT-YC +// * +// */ +//public class KnowledgeBaseUpdate { +// +// String ALIBABA_CLOUD_ACCESS_KEY_ID = "LTAI5tD5YRKuxWz6Eg7qrM4P"; +// String ALIBABA_CLOUD_ACCESS_KEY_SECRET = "bO8TBDXflOwbtSKimPpG8XrJnyzgTk"; +// String WORKSPACE_ID = "llm-4pf5auwewoz34zqu"; +// +// /** +// * 检查并提示设置必要的环境变量。 +// * +// * @return true 如果所有必需的环境变量都已设置,否则 false +// */ +// public static boolean checkEnvironmentVariables() { +// Map requiredVars = new HashMap<>(); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_ID", "阿里云访问密钥ID"); +// requiredVars.put("ALIBABA_CLOUD_ACCESS_KEY_SECRET", "阿里云访问密钥密码"); +// requiredVars.put("WORKSPACE_ID", "阿里云百炼业务空间ID"); +// +// List missingVars = new ArrayList<>(); +// for (Map.Entry entry : requiredVars.entrySet()) { +// String value = System.getenv(entry.getKey()); +// if (value == null || value.isEmpty()) { +// missingVars.add(entry.getKey()); +// System.out.println("错误:请设置 " + entry.getKey() + " 环境变量 (" + entry.getValue() + ")"); +// } +// } +// +// return missingVars.isEmpty(); +// } +// +// /** +// * 创建并配置客户端(Client) +// * +// * @return 配置好的客户端(Client) +// */ +// public static com.aliyun.bailian20231229.Client createClient() throws Exception { +// com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() +// .setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")) +// .setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")); +// // 下方接入地址以公有云的公网接入地址为例,可按需更换接入地址。 +// config.endpoint = "bailian.cn-beijing.aliyuncs.com"; +// return new com.aliyun.bailian20231229.Client(config); +// } +// +// /** +// * 计算文档的MD5值 +// * +// * @param filePath 文档本地路径 +// * @return 文档的MD5值 +// */ +// public static String calculateMD5(String filePath) throws Exception { +// MessageDigest md = MessageDigest.getInstance("MD5"); +// try (FileInputStream fis = new FileInputStream(filePath)) { +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = fis.read(buffer)) != -1) { +// md.update(buffer, 0, bytesRead); +// } +// } +// StringBuilder sb = new StringBuilder(); +// for (byte b : md.digest()) { +// sb.append(String.format("%02x", b & 0xff)); +// } +// return sb.toString(); +// } +// +// /** +// * 获取文档大小(以字节为单位) +// * +// * @param filePath 文档本地路径 +// * @return 文档大小(以字节为单位) +// */ +// public static String getFileSize(String filePath) { +// File file = new File(filePath); +// long fileSize = file.length(); +// return String.valueOf(fileSize); +// } +// +// /** +// * 申请文档上传租约。 +// * +// * @param client 客户端对象 +// * @param categoryId 类目ID +// * @param fileName 文档名称 +// * @param fileMd5 文档的MD5值 +// * @param fileSize 文档大小(以字节为单位) +// * @param workspaceId 业务空间ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static ApplyFileUploadLeaseResponse applyLease(com.aliyun.bailian20231229.Client client, String categoryId, +// String fileName, String fileMd5, String fileSize, String workspaceId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.ApplyFileUploadLeaseRequest applyFileUploadLeaseRequest = new com.aliyun.bailian20231229.models.ApplyFileUploadLeaseRequest(); +// applyFileUploadLeaseRequest.setFileName(fileName); +// applyFileUploadLeaseRequest.setMd5(fileMd5); +// applyFileUploadLeaseRequest.setSizeInBytes(fileSize); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// ApplyFileUploadLeaseResponse applyFileUploadLeaseResponse = null; +// applyFileUploadLeaseResponse = client.applyFileUploadLeaseWithOptions(categoryId, workspaceId, +// applyFileUploadLeaseRequest, headers, runtime); +// return applyFileUploadLeaseResponse; +// } +// +// /** +// * 上传文档到临时存储。 +// * +// * @param preSignedUrl 上传租约中的 URL +// * @param headers 上传请求的头部 +// * @param filePath 文档本地路径 +// * @throws Exception 如果上传过程中发生错误 +// */ +// public static void uploadFile(String preSignedUrl, Map headers, String filePath) throws Exception { +// File file = new File(filePath); +// if (!file.exists() || !file.isFile()) { +// throw new IllegalArgumentException("文件不存在或不是普通文件: " + filePath); +// } +// +// try (FileInputStream fis = new FileInputStream(file)) { +// URL url = new URL(preSignedUrl); +// HttpURLConnection conn = (HttpURLConnection) url.openConnection(); +// conn.setRequestMethod("PUT"); +// conn.setDoOutput(true); +// +// // 设置上传请求头 +// conn.setRequestProperty("X-bailian-extra", headers.get("X-bailian-extra")); +// conn.setRequestProperty("Content-Type", headers.get("Content-Type")); +// +// // 分块读取并上传文档 +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = fis.read(buffer)) != -1) { +// conn.getOutputStream().write(buffer, 0, bytesRead); +// } +// +// int responseCode = conn.getResponseCode(); +// if (responseCode != 200) { +// throw new RuntimeException("上传失败: " + responseCode); +// } +// } +// } +// +// /** +// * 将文档添加到类目中。 +// * +// * @param client 客户端对象 +// * @param leaseId 租约ID +// * @param parser 用于文档的解析器 +// * @param categoryId 类目ID +// * @param workspaceId 业务空间ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static AddFileResponse addFile(com.aliyun.bailian20231229.Client client, String leaseId, String parser, +// String categoryId, String workspaceId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.AddFileRequest addFileRequest = new com.aliyun.bailian20231229.models.AddFileRequest(); +// addFileRequest.setLeaseId(leaseId); +// addFileRequest.setParser(parser); +// addFileRequest.setCategoryId(categoryId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.addFileWithOptions(workspaceId, addFileRequest, headers, runtime); +// } +// +// /** +// * 查询文档的基本信息。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param fileId 文档ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static DescribeFileResponse describeFile(com.aliyun.bailian20231229.Client client, String workspaceId, +// String fileId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.describeFileWithOptions(workspaceId, fileId, headers, runtime); +// } +// +// /** +// * 向一个非结构化知识库追加导入已解析的文档 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param fileId 文档ID +// * @param sourceType 数据类型 +// * @return 阿里云百炼服务的响应 +// */ +// public static SubmitIndexAddDocumentsJobResponse submitIndexAddDocumentsJob( +// com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String fileId, +// String sourceType) throws Exception { +// Map headers = new HashMap<>(); +// SubmitIndexAddDocumentsJobRequest submitIndexAddDocumentsJobRequest = new SubmitIndexAddDocumentsJobRequest(); +// submitIndexAddDocumentsJobRequest.setIndexId(indexId); +// submitIndexAddDocumentsJobRequest.setDocumentIds(Collections.singletonList(fileId)); +// submitIndexAddDocumentsJobRequest.setSourceType(sourceType); +// RuntimeOptions runtime = new RuntimeOptions(); +// return client.submitIndexAddDocumentsJobWithOptions(workspaceId, submitIndexAddDocumentsJobRequest, headers, +// runtime); +// } +// +// /** +// * 查询索引任务状态。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param jobId 任务ID +// * @param indexId 知识库ID +// * @return 阿里云百炼服务的响应对象 +// */ +// public static GetIndexJobStatusResponse getIndexJobStatus(com.aliyun.bailian20231229.Client client, +// String workspaceId, String jobId, String indexId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.GetIndexJobStatusRequest getIndexJobStatusRequest = new com.aliyun.bailian20231229.models.GetIndexJobStatusRequest(); +// getIndexJobStatusRequest.setIndexId(indexId); +// getIndexJobStatusRequest.setJobId(jobId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// GetIndexJobStatusResponse getIndexJobStatusResponse = null; +// getIndexJobStatusResponse = client.getIndexJobStatusWithOptions(workspaceId, getIndexJobStatusRequest, headers, +// runtime); +// return getIndexJobStatusResponse; +// } +// +// /** +// * 从指定的非结构化知识库中永久删除一个或多个文档 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param fileId 文档ID +// * @return 阿里云百炼服务的响应 +// */ +// public static DeleteIndexDocumentResponse deleteIndexDocument(com.aliyun.bailian20231229.Client client, +// String workspaceId, String indexId, String fileId) throws Exception { +// Map headers = new HashMap<>(); +// DeleteIndexDocumentRequest deleteIndexDocumentRequest = new DeleteIndexDocumentRequest(); +// deleteIndexDocumentRequest.setIndexId(indexId); +// deleteIndexDocumentRequest.setDocumentIds(Collections.singletonList(fileId)); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.deleteIndexDocumentWithOptions(workspaceId, deleteIndexDocumentRequest, headers, runtime); +// } +// +// /** +// * 使用阿里云百炼服务更新知识库 +// * +// * @param filePath 文档(更新后的)的实际本地路径 +// * @param workspaceId 业务空间ID +// * @param indexId 需要更新的知识库ID +// * @param oldFileId 需要更新的文档的FileID +// * @return 如果成功,返回知识库ID;否则返回 null +// */ +// public static String updateKnowledgeBase(String filePath, String workspaceId, String indexId, String oldFileId) { +// // 设置默认值 +// String categoryId = "default"; +// String parser = "DASHSCOPE_DOCMIND"; +// String sourceType = "DATA_CENTER_FILE"; +// try { +// // 步骤1:初始化客户端(Client) +// System.out.println("步骤1:创建Client"); +// com.aliyun.bailian20231229.Client client = createClient(); +// +// // 步骤2:准备文档信息(更新后的文档) +// System.out.println("步骤2:准备文档信息"); +// String fileName = Paths.get(filePath).getFileName().toString(); +// String fileMd5 = calculateMD5(filePath); +// String fileSize = getFileSize(filePath); +// +// // 步骤3:申请上传租约 +// System.out.println("步骤3:向阿里云百炼申请上传租约"); +// ApplyFileUploadLeaseResponse leaseResponse = applyLease(client, categoryId, fileName, fileMd5, fileSize, +// workspaceId); +// String leaseId = leaseResponse.getBody().getData().getFileUploadLeaseId(); +// String uploadUrl = leaseResponse.getBody().getData().getParam().getUrl(); +// Object uploadHeaders = leaseResponse.getBody().getData().getParam().getHeaders(); +// +// // 步骤4:上传文档到临时存储 +// System.out.println("步骤4:上传文档到临时存储"); +// // 请自行安装jackson-databind +// // 将上一步的uploadHeaders转换为Map(Key-Value形式) +// ObjectMapper mapper = new ObjectMapper(); +// Map uploadHeadersMap = (Map) mapper +// .readValue(mapper.writeValueAsString(uploadHeaders), Map.class); +// uploadFile(uploadUrl, uploadHeadersMap, filePath); +// +// // 步骤5:添加文档到类目中 +// System.out.println("步骤5:添加文档到类目中"); +// AddFileResponse addResponse = addFile(client, leaseId, parser, categoryId, workspaceId); +// String fileId = addResponse.getBody().getData().getFileId(); +// +// // 步骤6:检查更新后的文档状态 +// System.out.println("步骤6:检查阿里云百炼中的文档状态"); +// while (true) { +// DescribeFileResponse describeResponse = describeFile(client, workspaceId, fileId); +// String status = describeResponse.getBody().getData().getStatus(); +// System.out.println("当前文档状态:" + status); +// if ("INIT".equals(status)) { +// System.out.println("文档待解析,请稍候..."); +// } else if ("PARSING".equals(status)) { +// System.out.println("文档解析中,请稍候..."); +// } else if ("PARSE_SUCCESS".equals(status)) { +// System.out.println("文档解析完成!"); +// break; +// } else { +// System.out.println("未知的文档状态:" + status + ",请联系技术支持。"); +// return null; +// } +// Thread.sleep(5000); +// } +// +// // 步骤7:提交追加文档任务 +// System.out.println("步骤7:提交追加文档任务"); +// SubmitIndexAddDocumentsJobResponse indexAddResponse = submitIndexAddDocumentsJob(client, workspaceId, +// indexId, fileId, sourceType); +// String jobId = indexAddResponse.getBody().getData().getId(); +// +// // 步骤8:等待追加任务完成 +// System.out.println("步骤8:等待追加任务完成"); +// while (true) { +// GetIndexJobStatusResponse jobStatusResponse = getIndexJobStatus(client, workspaceId, jobId, indexId); +// String status = jobStatusResponse.getBody().getData().getStatus(); +// System.out.println("当前索引任务状态:" + status); +// if ("COMPLETED".equals(status)) { +// break; +// } +// Thread.sleep(5000); +// } +// +// // 步骤9:删除旧文档 +// System.out.println("步骤9:删除旧文档"); +// deleteIndexDocument(client, workspaceId, indexId, oldFileId); +// +// System.out.println("阿里云百炼知识库更新成功!"); +// return indexId; +// } catch (Exception e) { +// System.out.println("发生错误:" + e.getMessage()); +// return null; +// } +// } +// +// /** +// * 主函数。 +// */ +// public static void main(String[] args) { +// if (!checkEnvironmentVariables()) { +// System.out.println("环境变量校验未通过。"); +// return; +// } +// +// Scanner scanner = new Scanner(System.in); +// System.out.print("请输入您需要上传文档(更新后的)的实际本地路径(以Linux为例:/xxx/xxx/阿里云百炼系列手机产品介绍.docx):"); +// String filePath = scanner.nextLine(); +// +// System.out.print("请输入需要更新的知识库ID:"); // 即 CreateIndex 接口返回的 Data.Id,您也可以在阿里云百炼控制台的知识库页面获取。 +// String indexId = scanner.nextLine(); // 即 AddFile 接口返回的 FileId。您也可以在阿里云百炼控制台的应用数据页面,单击文件名称旁的 ID 图标获取。 +// +// System.out.print("请输入需要更新的文档的 FileID:"); +// String oldFileId = scanner.nextLine(); +// +// String workspaceId = System.getenv("WORKSPACE_ID"); +// String result = updateKnowledgeBase(filePath, workspaceId, indexId, oldFileId); +// if (result != null) { +// System.out.println("知识库更新成功,返回知识库ID: " + result); +// } else { +// System.out.println("知识库更新失败。"); +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUploader.java b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUploader.java new file mode 100644 index 0000000..2afe79c --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUploader.java @@ -0,0 +1,303 @@ +//package com.gxwebsoft.ai.util; +// +//import com.aliyun.bailian20231229.Client; +//import com.aliyun.bailian20231229.models.*; +//import com.aliyun.teautil.models.RuntimeOptions; +//import com.fasterxml.jackson.databind.ObjectMapper; +// +//import java.io.File; +//import java.io.FileInputStream; +//import java.io.InputStream; +//import java.net.HttpURLConnection; +//import java.net.URL; +//import java.nio.file.Paths; +//import java.security.MessageDigest; +//import java.util.*; +// +//import org.springframework.web.multipart.MultipartFile; +// +///** +// * 知识库上传工具类 +// * @author GIIT-YC +// * +// */ +//public class KnowledgeBaseUploader { +// +// /** +// * 上传文档到知识库(直接处理MultipartFile) +// * +// * @param client 阿里云客户端 +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param file 上传的文件 +// * @return 新文档的FileID,失败返回null +// */ +// public static String uploadDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, MultipartFile file) { +// try { +// // 准备文档信息 +// String fileName = file.getOriginalFilename(); +// String fileMd5 = calculateMD5(file.getInputStream()); +// String fileSize = String.valueOf(file.getSize()); +// +// // 申请上传租约 +// ApplyFileUploadLeaseRequest leaseRequest = new ApplyFileUploadLeaseRequest() +// .setFileName(fileName) +// .setMd5(fileMd5) +// .setSizeInBytes(fileSize); +// +// ApplyFileUploadLeaseResponse leaseResponse = client.applyFileUploadLeaseWithOptions( +// "default", workspaceId, leaseRequest, new HashMap<>(), new RuntimeOptions()); +// +// String leaseId = leaseResponse.getBody().getData().getFileUploadLeaseId(); +// String uploadUrl = leaseResponse.getBody().getData().getParam().getUrl(); +// +// // 上传文件 +// ObjectMapper mapper = new ObjectMapper(); +// Map headers = mapper.readValue(mapper.writeValueAsString(leaseResponse.getBody().getData().getParam().getHeaders()), Map.class); +// +// uploadFile(uploadUrl, headers, file); +// +// // 添加文件到类目 +// AddFileRequest addRequest = new AddFileRequest() +// .setLeaseId(leaseId) +// .setParser("DASHSCOPE_DOCMIND") +// .setCategoryId("default"); +// +// AddFileResponse addResponse = client.addFileWithOptions(workspaceId, addRequest, new HashMap<>(), new RuntimeOptions()); +// +// String fileId = addResponse.getBody().getData().getFileId(); +// +// // 等待文件解析完成 +// waitForFileParsing(client, workspaceId, fileId); +// +// // 添加到知识库 +// SubmitIndexAddDocumentsJobRequest indexRequest = new SubmitIndexAddDocumentsJobRequest() +// .setIndexId(indexId) +// .setDocumentIds(Collections.singletonList(fileId)) +// .setSourceType("DATA_CENTER_FILE"); +// +// SubmitIndexAddDocumentsJobResponse indexResponse = client.submitIndexAddDocumentsJobWithOptions(workspaceId, indexRequest, new HashMap<>(), new RuntimeOptions()); +// +// // 等待索引完成 +// waitForIndexJob(client, workspaceId, indexResponse.getBody().getData().getId(), indexId); +// +// return fileId; +// +// } catch (Exception e) { +// e.printStackTrace(); +// return null; +// } +// } +// +// /** +// * 批量上传文档到知识库 +// */ +// public static List uploadDocuments(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, MultipartFile[] files) { +// List fileIds = new ArrayList<>(); +// for (MultipartFile file : files) { +// String fileId = uploadDocument(client, workspaceId, indexId, file); +// if (fileId != null) { +// fileIds.add(fileId); +// } +// } +// return fileIds; +// } +// +// /** +// * 上传文档到知识库 +// * +// * @param client 阿里云客户端 +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param filePath 文档本地路径 +// * @return 新文档的FileID,失败返回null +// */ +// public static String uploadDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String filePath) { +// try { +// // 准备文档信息 +// String fileName = Paths.get(filePath).getFileName().toString(); +// String fileMd5 = calculateMD5(filePath); +// String fileSize = String.valueOf(new File(filePath).length()); +// +// // 申请上传租约 +// ApplyFileUploadLeaseRequest leaseRequest = new ApplyFileUploadLeaseRequest() +// .setFileName(fileName) +// .setMd5(fileMd5) +// .setSizeInBytes(fileSize); +// +// ApplyFileUploadLeaseResponse leaseResponse = client.applyFileUploadLeaseWithOptions( +// "default", workspaceId, leaseRequest, new HashMap<>(), new RuntimeOptions()); +// +// String leaseId = leaseResponse.getBody().getData().getFileUploadLeaseId(); +// String uploadUrl = leaseResponse.getBody().getData().getParam().getUrl(); +// +// // 上传文件 +// ObjectMapper mapper = new ObjectMapper(); +// Map headers = mapper.readValue( +// mapper.writeValueAsString(leaseResponse.getBody().getData().getParam().getHeaders()), +// Map.class); +// +// uploadFile(uploadUrl, headers, filePath); +// +// // 添加文件到类目 +// AddFileRequest addRequest = new AddFileRequest() +// .setLeaseId(leaseId) +// .setParser("DASHSCOPE_DOCMIND") +// .setCategoryId("default"); +// +// AddFileResponse addResponse = client.addFileWithOptions( +// workspaceId, addRequest, new HashMap<>(), new RuntimeOptions()); +// +// String fileId = addResponse.getBody().getData().getFileId(); +// +// // 等待文件解析完成 +// waitForFileParsing(client, workspaceId, fileId); +// +// // 添加到知识库 +// SubmitIndexAddDocumentsJobRequest indexRequest = new SubmitIndexAddDocumentsJobRequest() +// .setIndexId(indexId) +// .setDocumentIds(Collections.singletonList(fileId)) +// .setSourceType("DATA_CENTER_FILE"); +// +// SubmitIndexAddDocumentsJobResponse indexResponse = client.submitIndexAddDocumentsJobWithOptions( +// workspaceId, indexRequest, new HashMap<>(), new RuntimeOptions()); +// +// // 等待索引完成 +// waitForIndexJob(client, workspaceId, indexResponse.getBody().getData().getId(), indexId); +// +// return fileId; +// +// } catch (Exception e) { +// e.printStackTrace(); +// return null; +// } +// } +// +// private static String calculateMD5(String filePath) throws Exception { +// MessageDigest md = MessageDigest.getInstance("MD5"); +// try (FileInputStream fis = new FileInputStream(filePath)) { +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = fis.read(buffer)) != -1) { +// md.update(buffer, 0, bytesRead); +// } +// } +// StringBuilder sb = new StringBuilder(); +// for (byte b : md.digest()) { +// sb.append(String.format("%02x", b & 0xff)); +// } +// return sb.toString(); +// } +// +// private static void uploadFile(String preSignedUrl, Map headers, +// String filePath) throws Exception { +// try (FileInputStream fis = new FileInputStream(filePath)) { +// HttpURLConnection conn = (HttpURLConnection) new URL(preSignedUrl).openConnection(); +// conn.setRequestMethod("PUT"); +// conn.setDoOutput(true); +// conn.setRequestProperty("X-bailian-extra", headers.get("X-bailian-extra")); +// conn.setRequestProperty("Content-Type", headers.get("Content-Type")); +// +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = fis.read(buffer)) != -1) { +// conn.getOutputStream().write(buffer, 0, bytesRead); +// } +// +// if (conn.getResponseCode() != 200) { +// throw new RuntimeException("上传失败: " + conn.getResponseCode()); +// } +// } +// } +// +// private static void waitForFileParsing(com.aliyun.bailian20231229.Client client, +// String workspaceId, String fileId) throws Exception { +// while (true) { +// DescribeFileResponse response = client.describeFileWithOptions( +// workspaceId, fileId, new HashMap<>(), new RuntimeOptions()); +// +// String status = response.getBody().getData().getStatus(); +// if ("PARSE_SUCCESS".equals(status)) break; +// if ("PARSE_FAILED".equals(status)) throw new RuntimeException("文档解析失败"); +// Thread.sleep(5000); +// } +// } +// +// private static void waitForIndexJob(com.aliyun.bailian20231229.Client client, +// String workspaceId, String jobId, String indexId) throws Exception { +// while (true) { +// GetIndexJobStatusRequest request = new GetIndexJobStatusRequest() +// .setIndexId(indexId) +// .setJobId(jobId); +// +// GetIndexJobStatusResponse response = client.getIndexJobStatusWithOptions( +// workspaceId, request, new HashMap<>(), new RuntimeOptions()); +// +// String status = response.getBody().getData().getStatus(); +// if ("COMPLETED".equals(status)) break; +// if ("FAILED".equals(status)) throw new RuntimeException("索引任务失败"); +// Thread.sleep(5000); +// } +// } +// +// private static String calculateMD5(InputStream inputStream) throws Exception { +// MessageDigest md = MessageDigest.getInstance("MD5"); +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = inputStream.read(buffer)) != -1) { +// md.update(buffer, 0, bytesRead); +// } +// StringBuilder sb = new StringBuilder(); +// for (byte b : md.digest()) { +// sb.append(String.format("%02x", b & 0xff)); +// } +// return sb.toString(); +// } +// +// private static void uploadFile(String preSignedUrl, Map headers, +// MultipartFile file) throws Exception { +// HttpURLConnection conn = (HttpURLConnection) new URL(preSignedUrl).openConnection(); +// conn.setRequestMethod("PUT"); +// conn.setDoOutput(true); +// conn.setRequestProperty("X-bailian-extra", headers.get("X-bailian-extra")); +// conn.setRequestProperty("Content-Type", headers.get("Content-Type")); +// +// try (InputStream inputStream = file.getInputStream()) { +// byte[] buffer = new byte[4096]; +// int bytesRead; +// while ((bytesRead = inputStream.read(buffer)) != -1) { +// conn.getOutputStream().write(buffer, 0, bytesRead); +// } +// } +// +// if (conn.getResponseCode() != 200) { +// throw new RuntimeException("上传失败: " + conn.getResponseCode()); +// } +// } +// +// /** +// * 初始化客户端(Client)。 +// * +// * @return 配置好的客户端对象 +// */ +// public static com.aliyun.bailian20231229.Client createClient(String accessKeyId, String accessKeySecret) throws Exception { +// com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() +// .setAccessKeyId(accessKeyId) +// .setAccessKeySecret(accessKeySecret); +// // 下方接入地址以公有云的公网接入地址为例,可按需更换接入地址。 +// config.endpoint = "bailian.cn-beijing.aliyuncs.com"; +// return new com.aliyun.bailian20231229.Client(config); +// } +// +// public static void main(String[] args) throws Exception { +// String ALIBABA_CLOUD_ACCESS_KEY_ID = "LTAI5tD5YRKuxWz6Eg7qrM4P"; +// String ALIBABA_CLOUD_ACCESS_KEY_SECRET = "bO8TBDXflOwbtSKimPpG8XrJnyzgTk"; +// String WORKSPACE_ID = "llm-4pf5auwewoz34zqu"; +// String indexId = "b9pvwfqp3d"; +// String filePath = "D:\\公司经济责任审计方案模板.docx"; +// +// Client client = createClient(ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET); +// +// uploadDocument(client, WORKSPACE_ID, indexId, filePath); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUtil.java b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUtil.java new file mode 100644 index 0000000..09bfe49 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/KnowledgeBaseUtil.java @@ -0,0 +1,156 @@ +//package com.gxwebsoft.ai.util; +// +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +// +//import com.alibaba.fastjson.JSON; +//import com.aliyun.bailian20231229.models.CreateIndexResponse; +//import com.aliyun.bailian20231229.models.DeleteFileResponse; +//import com.aliyun.bailian20231229.models.DeleteIndexDocumentResponse; +//import com.aliyun.bailian20231229.models.DeleteIndexResponse; +//import com.aliyun.bailian20231229.models.ListIndexDocumentsResponse; +//import com.aliyun.bailian20231229.models.ListIndicesResponse; +//import com.aliyun.bailian20231229.models.RetrieveRequest; +//import com.aliyun.bailian20231229.models.RetrieveResponse; +//import com.aliyun.teautil.models.RuntimeOptions; +// +///** +// * 知识库工具类 +// * @author GIIT-YC +// * +// */ +//public class KnowledgeBaseUtil { +// +// /** +// * 在指定的知识库中检索信息。 +// * +// * @param client 客户端对象(bailian20231229Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param query 检索查询语句 +// * @return 阿里云百炼服务的响应 +// */ +// public static RetrieveResponse retrieveIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String query) throws Exception { +// RetrieveRequest retrieveRequest = new RetrieveRequest(); +// retrieveRequest.setIndexId(indexId); +// retrieveRequest.setQuery(query); +// retrieveRequest.setDenseSimilarityTopK(100); +// retrieveRequest.setSparseSimilarityTopK(100); +// retrieveRequest.setEnableReranking(false);//开启耗费巨量token +// RuntimeOptions runtime = new RuntimeOptions(); +// return client.retrieveWithOptions(workspaceId, retrieveRequest, null, runtime); +// } +// +// public static RetrieveResponse retrieveIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String query, List filesIds) throws Exception { +// RetrieveRequest retrieveRequest = new RetrieveRequest(); +// retrieveRequest.setIndexId(indexId); +// retrieveRequest.setQuery(query); +// retrieveRequest.setDenseSimilarityTopK(100); +// retrieveRequest.setSparseSimilarityTopK(100); +// retrieveRequest.setEnableReranking(false);//开启耗费巨量token +// List> searchFilters = new ArrayList<>(); +// Map searchFiltersTags = new HashMap<>(); +// searchFiltersTags.put("tags", JSON.toJSONString(filesIds)); +// searchFilters.add(searchFiltersTags); +// retrieveRequest.setSearchFilters(searchFilters); +// RuntimeOptions runtime = new RuntimeOptions(); +// return client.retrieveWithOptions(workspaceId, retrieveRequest, null, runtime); +// } +// +// /** +// * 在阿里云百炼服务中创建知识库(初始化)。 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param name 知识库名称 +// * @param desc 知识库描述 +// * @return 阿里云百炼服务的响应对象 +// */ +// public static CreateIndexResponse createIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String name, String desc) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.CreateIndexRequest createIndexRequest = new com.aliyun.bailian20231229.models.CreateIndexRequest(); +// createIndexRequest.setStructureType("unstructured"); +// createIndexRequest.setName(name); +// createIndexRequest.setDescription(desc); +// createIndexRequest.setSinkType("DEFAULT"); +// createIndexRequest.setEmbeddingModelName("text-embedding-v4"); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.createIndexWithOptions(workspaceId, createIndexRequest, headers, runtime); +// } +// +// /** +// * 获取指定业务空间下一个或多个知识库的详细信息 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @return 阿里云百炼服务的响应 +// */ +// public static ListIndicesResponse listIndices(com.aliyun.bailian20231229.Client client, String workspaceId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.ListIndicesRequest listIndicesRequest = new com.aliyun.bailian20231229.models.ListIndicesRequest(); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.listIndicesWithOptions(workspaceId, listIndicesRequest, headers, runtime); +// } +// +// /** +// * 永久性删除指定的知识库 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @return 阿里云百炼服务的响应 +// */ +// public static DeleteIndexResponse deleteIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId) throws Exception { +// Map headers = new HashMap<>(); +// com.aliyun.bailian20231229.models.DeleteIndexRequest deleteIndexRequest = new com.aliyun.bailian20231229.models.DeleteIndexRequest(); +// deleteIndexRequest.setIndexId(indexId); +// com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); +// return client.deleteIndexWithOptions(workspaceId, deleteIndexRequest, headers, runtime); +// } +// +// /** +// * 查询知识库下的文档列表 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @return 阿里云百炼服务的响应 +// */ +// public static ListIndexDocumentsResponse listIndexDocuments(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, Integer pageSize, Integer pageNumber) throws Exception { +// com.aliyun.bailian20231229.models.ListIndexDocumentsRequest listIndexDocumentsRequest = new com.aliyun.bailian20231229.models.ListIndexDocumentsRequest(); +// listIndexDocumentsRequest.setIndexId(indexId); +// listIndexDocumentsRequest.setPageSize(pageSize); +// listIndexDocumentsRequest.setPageNumber(pageNumber); +// return client.listIndexDocuments(workspaceId, listIndexDocumentsRequest); +// } +// +// /** +// * 删除知识库下的文档 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param indexId 知识库ID +// * @param ids 删除文件ID列表 +// * @return 阿里云百炼服务的响应 +// */ +// public static DeleteIndexDocumentResponse deleteIndexDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, List ids) throws Exception { +// com.aliyun.bailian20231229.models.DeleteIndexDocumentRequest deleteIndexDocumentRequest = new com.aliyun.bailian20231229.models.DeleteIndexDocumentRequest(); +// deleteIndexDocumentRequest.setIndexId(indexId); +// deleteIndexDocumentRequest.setDocumentIds(ids); +// return client.deleteIndexDocument(workspaceId, deleteIndexDocumentRequest); +// } +// +// /** +// * 删除阿里云应用数据文档 +// * +// * @param client 客户端(Client) +// * @param workspaceId 业务空间ID +// * @param fileId 删除文件ID +// * @return 阿里云百炼服务的响应 +// */ +// public static DeleteFileResponse deleteAppDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String fileId) throws Exception { +// return client.deleteFile(fileId, workspaceId); +// } +//} diff --git a/src/main/java/com/gxwebsoft/ai/utils/ExcelExportTool.java b/src/main/java/com/gxwebsoft/ai/utils/ExcelExportTool.java new file mode 100644 index 0000000..1185fd8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/utils/ExcelExportTool.java @@ -0,0 +1,139 @@ +package com.gxwebsoft.ai.utils; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import com.gxwebsoft.ai.config.AuditExcelStyle; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import javax.servlet.http.HttpServletResponse; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Excel导出工具类 + */ +@Slf4j +public class ExcelExportTool { + + /** + * 通用Excel导出方法 + * + * @param dataList 数据列表 + * @param entityClass 实体类Class + * @param fileName 文件名(不含扩展名) + * @param sheetName 工作表名 + * @param title 标题 + * @param response HttpServletResponse + */ + public static void exportExcel(List dataList, + Class entityClass, + String fileName, + String sheetName, + String title, + HttpServletResponse response) { + exportExcel(dataList, entityClass, fileName, sheetName, title, response, true); + } + + /** + * 通用Excel导出方法(完整参数) + * + * @param dataList 数据列表 + * @param entityClass 实体类Class + * @param fileName 文件名(不含扩展名) + * @param sheetName 工作表名 + * @param title 标题 + * @param response HttpServletResponse + * @param autoHeight 是否自适应行高 + */ + public static void exportExcel(List dataList, + Class entityClass, + String fileName, + String sheetName, + String title, + HttpServletResponse response, + boolean autoHeight) { + try { + if (dataList == null || dataList.isEmpty()) { + throw new RuntimeException("没有可导出的数据"); + } + + log.info("开始导出Excel - 文件: {}, 数据条数: {}", fileName, dataList.size()); + + // 设置响应头 + setupResponse(response, fileName); + + // 创建导出参数 + ExportParams exportParams = createExportParams(sheetName, title); + + // 创建Workbook并导出 + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityClass, dataList); + + // 应用样式 + if (autoHeight) { + applyAutoStyle(workbook); + } + + // 写入响应流 + writeToResponse(workbook, response); + + log.info("成功导出Excel文件: {}, 共{}条数据", fileName, dataList.size()); + + } catch (Exception e) { + log.error("导出Excel失败: {}", fileName, e); + throw new RuntimeException("导出失败: " + e.getMessage()); + } + } + + /** + * 设置响应头 + */ + private static void setupResponse(HttpServletResponse response, String fileName) throws Exception { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + String encodedFileName = URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8.toString()); + response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName); + response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + } + + /** + * 创建导出参数 + */ + private static ExportParams createExportParams(String sheetName, String title) { + ExportParams exportParams = new ExportParams(); + exportParams.setSheetName(sheetName); + exportParams.setTitle(title); + exportParams.setType(ExcelType.XSSF); // 使用xlsx格式 + exportParams.setStyle(AuditExcelStyle.class); + return exportParams; + } + + /** + * 应用自动样式 + */ + private static void applyAutoStyle(Workbook workbook) { + Sheet sheet = workbook.getSheetAt(0); + + // 设置自适应行高 + for (int i = 1; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row != null) { + row.setHeight((short) -1); // 设置为自动行高 + } + } + } + + /** + * 写入响应流 + */ + private static void writeToResponse(Workbook workbook, HttpServletResponse response) throws Exception { + try (OutputStream out = response.getOutputStream()) { + workbook.write(out); + out.flush(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java new file mode 100644 index 0000000..d296d82 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java @@ -0,0 +1,104 @@ +package com.gxwebsoft.auto.controller; + +import com.gxwebsoft.auto.dto.QrLoginConfirmRequest; +import com.gxwebsoft.auto.dto.QrLoginGenerateResponse; +import com.gxwebsoft.auto.dto.QrLoginStatusResponse; +import com.gxwebsoft.auto.service.QrLoginService; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.ApiResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * 认证模块 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "认证模块") +@RestController +@RequestMapping("/api/qr-login") +public class QrLoginController extends BaseController { + + @Autowired + private QrLoginService qrLoginService; + + /** + * 生成扫码登录token + */ + @Operation(summary = "生成扫码登录token") + @PostMapping("/generate") + public ApiResult generateQrLoginToken() { + try { + QrLoginGenerateResponse response = qrLoginService.generateQrLoginToken(); + return success("生成成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 检查扫码登录状态 + */ + @Operation(summary = "检查扫码登录状态") + @GetMapping("/status/{token}") + public ApiResult checkQrLoginStatus( + @Parameter(description = "扫码登录token") @PathVariable String token) { + try { + QrLoginStatusResponse response = qrLoginService.checkQrLoginStatus(token); + return success("查询成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 确认扫码登录 + */ + @Operation(summary = "确认扫码登录") + @PostMapping("/confirm") + public ApiResult confirmQrLogin(@Valid @RequestBody QrLoginConfirmRequest request) { + try { + QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request); + return success("确认成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 扫码操作(可选接口,用于移动端扫码后更新状态) + */ + @Operation(summary = "扫码操作") + @PostMapping("/scan/{token}") + public ApiResult scanQrCode(@Parameter(description = "扫码登录token") @PathVariable String token) { + try { + boolean result = qrLoginService.scanQrCode(token); + return success("操作成功", result); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 微信小程序扫码登录确认(便捷接口) + */ + @Operation(summary = "微信小程序扫码登录确认") + @PostMapping("/wechat-confirm") + public ApiResult wechatMiniProgramConfirm(@Valid @RequestBody QrLoginConfirmRequest request) { + try { + // 设置平台为微信小程序 + request.setPlatform("miniprogram"); + QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request); + return success("微信小程序登录确认成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java new file mode 100644 index 0000000..f3b423e --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.auto.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 扫码登录确认请求 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@Schema(description = "扫码登录确认请求") +public class QrLoginConfirmRequest { + + @Schema(description = "扫码登录token") + @NotBlank(message = "token不能为空") + private String token; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "登录平台: web-网页端, app-移动应用, miniprogram-微信小程序") + private String platform; + + @Schema(description = "微信小程序相关信息") + private WechatMiniProgramInfo wechatInfo; + + /** + * 微信小程序信息 + */ + @Data + @Schema(description = "微信小程序信息") + public static class WechatMiniProgramInfo { + @Schema(description = "微信openid") + private String openid; + + @Schema(description = "微信unionid") + private String unionid; + + @Schema(description = "微信昵称") + private String nickname; + + @Schema(description = "微信头像") + private String avatar; + } + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java new file mode 100644 index 0000000..563bf1d --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.auto.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 扫码登录数据模型 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class QrLoginData { + + /** + * 扫码登录token + */ + private String token; + + /** + * 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期 + */ + private String status; + + /** + * 用户ID(扫码确认后设置) + */ + private Integer userId; + + /** + * 用户名(扫码确认后设置) + */ + private String username; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 过期时间 + */ + private LocalDateTime expireTime; + + /** + * JWT访问令牌(确认后生成) + */ + private String accessToken; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java new file mode 100644 index 0000000..f0b69e5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.auto.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 扫码登录生成响应 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "扫码登录生成响应") +public class QrLoginGenerateResponse { + + @Schema(description = "扫码登录token") + private String token; + + @Schema(description = "二维码内容") + private String qrCode; + + @Schema(description = "过期时间(秒)") + private Long expiresIn; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java new file mode 100644 index 0000000..1eb0d4a --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java @@ -0,0 +1,32 @@ +package com.gxwebsoft.auto.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 扫码登录状态响应 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "扫码登录状态响应") +public class QrLoginStatusResponse { + + @Schema(description = "状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期") + private String status; + + @Schema(description = "JWT访问令牌(仅在confirmed状态时返回)") + private String accessToken; + + @Schema(description = "用户信息(仅在confirmed状态时返回)") + private Object userInfo; + + @Schema(description = "剩余过期时间(秒)") + private Long expiresIn; + +} diff --git a/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java new file mode 100644 index 0000000..85ed28f --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.auto.service; + +import com.gxwebsoft.auto.dto.QrLoginConfirmRequest; +import com.gxwebsoft.auto.dto.QrLoginGenerateResponse; +import com.gxwebsoft.auto.dto.QrLoginStatusResponse; + +/** + * 扫码登录服务接口 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +public interface QrLoginService { + + /** + * 生成扫码登录token + * + * @return QrLoginGenerateResponse + */ + QrLoginGenerateResponse generateQrLoginToken(); + + /** + * 检查扫码登录状态 + * + * @param token 扫码登录token + * @return QrLoginStatusResponse + */ + QrLoginStatusResponse checkQrLoginStatus(String token); + + /** + * 确认扫码登录 + * + * @param request 确认请求 + * @return QrLoginStatusResponse + */ + QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request); + + /** + * 扫码操作(更新状态为已扫码) + * + * @param token 扫码登录token + * @return boolean + */ + boolean scanQrCode(String token); + +} diff --git a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java new file mode 100644 index 0000000..34658e8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java @@ -0,0 +1,239 @@ +package com.gxwebsoft.auto.service.impl; + +import cn.hutool.core.lang.UUID; +import cn.hutool.core.util.StrUtil; +import com.gxwebsoft.auto.dto.*; +import com.gxwebsoft.auto.service.QrLoginService; +import com.gxwebsoft.common.core.security.JwtSubject; +import com.gxwebsoft.common.core.security.JwtUtil; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +import static com.gxwebsoft.common.core.constants.RedisConstants.*; + +/** + * 扫码登录服务实现 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Slf4j +@Service +public class QrLoginServiceImpl implements QrLoginService { + + @Autowired + private RedisUtil redisUtil; + + @Autowired + private UserService userService; + + @Value("${config.jwt.secret:websoft-jwt-secret-key-2025}") + private String jwtSecret; + + @Value("${config.jwt.expire:86400}") + private Long jwtExpire; + + @Override + public QrLoginGenerateResponse generateQrLoginToken() { + // 生成唯一的扫码登录token + String token = UUID.randomUUID().toString(true); + + // 创建扫码登录数据 + QrLoginData qrLoginData = new QrLoginData(); + qrLoginData.setToken(token); + qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING); + qrLoginData.setCreateTime(LocalDateTime.now()); + qrLoginData.setExpireTime(LocalDateTime.now().plusSeconds(QR_LOGIN_TOKEN_TTL)); + + // 存储到Redis,设置过期时间 + String redisKey = QR_LOGIN_TOKEN_KEY + token; + redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS); + + log.info("生成扫码登录token: {}", token); + + // 构造二维码内容(这里可以是前端登录页面的URL + token参数) + String qrCodeContent = "qr-login:" + token; + + return new QrLoginGenerateResponse(token, qrCodeContent, QR_LOGIN_TOKEN_TTL); + } + + @Override + public QrLoginStatusResponse checkQrLoginStatus(String token) { + if (StrUtil.isBlank(token)) { + return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L); + } + + String redisKey = QR_LOGIN_TOKEN_KEY + token; + QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class); + + if (qrLoginData == null) { + return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L); + } + + // 检查是否过期 + if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) { + // 删除过期的token + redisUtil.delete(redisKey); + return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L); + } + + // 计算剩余过期时间 + long expiresIn = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime()); + + QrLoginStatusResponse response = new QrLoginStatusResponse(); + response.setStatus(qrLoginData.getStatus()); + response.setExpiresIn(expiresIn); + + // 如果已确认,返回token和用户信息 + if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())) { + response.setAccessToken(qrLoginData.getAccessToken()); + + // 获取用户信息 + if (qrLoginData.getUserId() != null) { + User user = userService.getByIdRel(qrLoginData.getUserId()); + if (user != null) { + // 清除敏感信息 + user.setPassword(null); + response.setUserInfo(user); + } + } + + // 确认后删除token,防止重复使用 + redisUtil.delete(redisKey); + } + + return response; + } + + @Override + public QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request) { + String token = request.getToken(); + Integer userId = request.getUserId(); + String platform = request.getPlatform(); + + if (StrUtil.isBlank(token) || userId == null) { + throw new RuntimeException("参数不能为空"); + } + + String redisKey = QR_LOGIN_TOKEN_KEY + token; + QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class); + + if (qrLoginData == null) { + throw new RuntimeException("扫码登录token不存在或已过期"); + } + + // 检查是否过期 + if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) { + redisUtil.delete(redisKey); + throw new RuntimeException("扫码登录token已过期"); + } + + // 获取用户信息 + User user = userService.getByIdRel(userId); + if (user == null) { + throw new RuntimeException("用户不存在"); + } + + // 检查用户状态 + if (user.getStatus() != null && user.getStatus() != 0) { + throw new RuntimeException("用户已被冻结"); + } + + // 如果是微信小程序登录,处理微信相关信息 + if ("miniprogram".equals(platform) && request.getWechatInfo() != null) { + handleWechatMiniProgramLogin(user, request.getWechatInfo()); + } + + // 生成JWT token + JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId()); + String accessToken = JwtUtil.buildToken(jwtSubject, jwtExpire, jwtSecret); + + // 更新扫码登录数据 + qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED); + qrLoginData.setUserId(userId); + qrLoginData.setUsername(user.getUsername()); + qrLoginData.setAccessToken(accessToken); + + // 更新Redis中的数据 + redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS); // 给前端60秒时间获取token + + log.info("用户 {} 通过 {} 平台确认扫码登录,token: {}", user.getUsername(), + platform != null ? platform : "unknown", token); + + // 清除敏感信息 + user.setPassword(null); + + return new QrLoginStatusResponse(QR_LOGIN_STATUS_CONFIRMED, accessToken, user, 60L); + } + + /** + * 处理微信小程序登录相关逻辑 + */ + private void handleWechatMiniProgramLogin(User user, QrLoginConfirmRequest.WechatMiniProgramInfo wechatInfo) { + // 更新用户的微信信息 + if (StrUtil.isNotBlank(wechatInfo.getOpenid())) { + user.setOpenid(wechatInfo.getOpenid()); + } + if (StrUtil.isNotBlank(wechatInfo.getUnionid())) { + user.setUnionid(wechatInfo.getUnionid()); + } + if (StrUtil.isNotBlank(wechatInfo.getNickname()) && StrUtil.isBlank(user.getNickname())) { + user.setNickname(wechatInfo.getNickname()); + } + if (StrUtil.isNotBlank(wechatInfo.getAvatar()) && StrUtil.isBlank(user.getAvatar())) { + user.setAvatar(wechatInfo.getAvatar()); + } + + // 更新用户信息到数据库 + try { + userService.updateById(user); + log.info("更新用户 {} 的微信小程序信息成功", user.getUsername()); + } catch (Exception e) { + log.warn("更新用户 {} 的微信小程序信息失败: {}", user.getUsername(), e.getMessage()); + } + } + + @Override + public boolean scanQrCode(String token) { + if (StrUtil.isBlank(token)) { + return false; + } + + String redisKey = QR_LOGIN_TOKEN_KEY + token; + QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class); + + if (qrLoginData == null) { + return false; + } + + // 检查是否过期 + if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) { + redisUtil.delete(redisKey); + return false; + } + + // 只有pending状态才能更新为scanned + if (QR_LOGIN_STATUS_PENDING.equals(qrLoginData.getStatus())) { + qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED); + + // 计算剩余过期时间 + long remainingSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime()); + redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS); + + log.info("扫码登录token {} 状态更新为已扫码", token); + return true; + } + + return false; + } +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxBmController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxBmController.java new file mode 100644 index 0000000..995c6fb --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxBmController.java @@ -0,0 +1,166 @@ +package com.gxwebsoft.bszx.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.cms.service.CmsArticleService; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxBmService; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.param.BszxBmParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 百色中学-报名记录控制器 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "百色中学-报名记录管理") +@RestController +@RequestMapping("/api/bszx/bszx-bm") +public class BszxBmController extends BaseController { + @Resource + private BszxBmService bszxBmService; + @Resource + @Lazy + private CmsArticleService cmsArticleService; + + @PreAuthorize("hasAuthority('bszx:bszxBm:list')") + @Operation(summary = "分页查询百色中学-报名记录") + @GetMapping("/page") + public ApiResult> page(BszxBmParam param) { + // 使用关联查询 + return success(bszxBmService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('bszx:bszxBm:list')") + @Operation(summary = "查询全部百色中学-报名记录") + @GetMapping() + public ApiResult> list(BszxBmParam param) { + // 使用关联查询 + return success(bszxBmService.listRel(param)); + } + + @PreAuthorize("hasAuthority('bszx:bszxBm:list')") + @Operation(summary = "根据id查询百色中学-报名记录") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxBmService.getByIdRel(id)); + } + + @OperationLog + @Operation(summary = "申请报名生成邀请函") + @PostMapping() + public ApiResult save(@RequestBody BszxBm bszxBm) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (bszxBm.getName() == null) { + return fail("请填写姓名"); + } + if (loginUser != null) { + bszxBm.setUserId(loginUser.getUserId()); + if (bszxBmService.count(new LambdaQueryWrapper().eq(BszxBm::getUserId,loginUser.getUserId())) > 0) { + return fail("您已经报名过了",null); + } + if (bszxBmService.save(bszxBm)) { + cmsArticleService.saveInc(bszxBm.getFormId()); + return success("报名成功"); + } + } + return fail("添加失败"); + } + + @OperationLog + @Operation(summary = "修改报名信息") + @PutMapping() + public ApiResult update(@RequestBody BszxBm bszxBm) { + final User loginUser = getLoginUser(); + if(loginUser == null){ + return fail("请先登录"); + } + if (bszxBmService.updateById(bszxBm)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBm:remove')") + @OperationLog + @Operation(summary = "删除报名记录") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxBmService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBm:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-报名记录") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxBmService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBm:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-报名记录") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxBmService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBm:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-报名记录") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxBmService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "查询我的报名记录") + @GetMapping("/myPage") + public ApiResult> myPage(BszxBmParam param) { + // 使用关联查询 + if (getLoginUser() != null) { + param.setUserId(getLoginUserId()); + return success(bszxBmService.pageRel(param)); + } + return fail("请先登录",null); + } + + @Operation(summary = "获取海报地址") + @GetMapping("/generatePoster") + public ApiResult generatePoster() throws Exception { + if (getLoginUser() == null) { + return fail("请先登录",null); + } + final BszxBm bm = bszxBmService.getOne(new LambdaQueryWrapper().eq(BszxBm::getUserId, getLoginUser().getUserId()).last("limit 1")); + return success("生成宣传海报",bszxBmService.generatePoster(bm)); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxBranchController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxBranchController.java new file mode 100644 index 0000000..6a24686 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxBranchController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.bszx.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxBranchService; +import com.gxwebsoft.bszx.entity.BszxBranch; +import com.gxwebsoft.bszx.param.BszxBranchParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 百色中学-分部控制器 + * + * @author 科技小王子 + * @since 2025-03-17 17:18:22 + */ +@Tag(name = "百色中学-分部管理") +@RestController +@RequestMapping("/api/bszx/bszx-branch") +public class BszxBranchController extends BaseController { + @Resource + private BszxBranchService bszxBranchService; + + @Operation(summary = "分页查询百色中学-分部") + @GetMapping("/page") + public ApiResult> page(BszxBranchParam param) { + // 使用关联查询 + return success(bszxBranchService.pageRel(param)); + } + + @Operation(summary = "查询全部百色中学-分部") + @GetMapping() + public ApiResult> list(BszxBranchParam param) { + // 使用关联查询 + return success(bszxBranchService.listRel(param)); + } + + @Operation(summary = "根据id查询百色中学-分部") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxBranchService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('bszx:bszxBranch:save')") + @OperationLog + @Operation(summary = "添加百色中学-分部") + @PostMapping() + public ApiResult save(@RequestBody BszxBranch bszxBranch) { + if (bszxBranchService.save(bszxBranch)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBranch:update')") + @OperationLog + @Operation(summary = "修改百色中学-分部") + @PutMapping() + public ApiResult update(@RequestBody BszxBranch bszxBranch) { + if (bszxBranchService.updateById(bszxBranch)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBranch:remove')") + @OperationLog + @Operation(summary = "删除百色中学-分部") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxBranchService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBranch:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-分部") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxBranchService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBranch:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-分部") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxBranchService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxBranch:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-分部") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxBranchService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxClassController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxClassController.java new file mode 100644 index 0000000..ceb251c --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxClassController.java @@ -0,0 +1,156 @@ +package com.gxwebsoft.bszx.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.bszx.entity.BszxBranch; +import com.gxwebsoft.bszx.entity.BszxEra; +import com.gxwebsoft.bszx.entity.BszxGrade; +import com.gxwebsoft.bszx.param.BszxGradeParam; +import com.gxwebsoft.bszx.service.BszxBranchService; +import com.gxwebsoft.bszx.service.BszxEraService; +import com.gxwebsoft.bszx.service.BszxGradeService; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxClassService; +import com.gxwebsoft.bszx.entity.BszxClass; +import com.gxwebsoft.bszx.param.BszxClassParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 百色中学-班级控制器 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "百色中学-班级管理") +@RestController +@RequestMapping("/api/bszx/bszx-class") +public class BszxClassController extends BaseController { + @Resource + private BszxClassService bszxClassService; + @Resource + private BszxGradeService bszxGradeService; + @Resource + private BszxBranchService bszxBranchService; + + @Operation(summary = "分页查询百色中学-班级") + @GetMapping("/page") + public ApiResult> page(BszxClassParam param) { + // 使用关联查询 + return success(bszxClassService.pageRel(param)); + } + + @Operation(summary = "查询全部百色中学-班级") + @GetMapping() + public ApiResult> list(BszxClassParam param) { + // 使用关联查询 + return success(bszxClassService.listRel(param)); + } + + @Operation(summary = "根据id查询百色中学-班级") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxClassService.getByIdRel(id)); + } + + @Operation(summary = "百色中学-年级班级数据") + @GetMapping("/tree") + public ApiResult> tree() { + final List list = bszxBranchService.list(); + final BszxGradeParam bszxGradeParam = new BszxGradeParam(); + final List gradeList = bszxGradeService.listRel(bszxGradeParam); + final BszxClassParam bszxClassParam = new BszxClassParam(); + final List bszxClasseList = bszxClassService.listRel(bszxClassParam); + final Map> collectClass = bszxClasseList.stream().collect(Collectors.groupingBy(BszxClass::getGradeId)); + gradeList.forEach(d -> { + d.setChildren(collectClass.get(d.getId())); + }); + final Map> collectGrade = gradeList.stream().collect(Collectors.groupingBy(BszxGrade::getBranch)); + + list.forEach(d -> { + d.setChildren(collectGrade.get(d.getId())); + }); + + return success(list); + } + + @PreAuthorize("hasAuthority('bszx:bszxClass:save')") + @OperationLog + @Operation(summary = "添加百色中学-班级") + @PostMapping() + public ApiResult save(@RequestBody BszxClass bszxClass) { + if (bszxClassService.save(bszxClass)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxClass:update')") + @OperationLog + @Operation(summary = "修改百色中学-班级") + @PutMapping() + public ApiResult update(@RequestBody BszxClass bszxClass) { + if (bszxClassService.updateById(bszxClass)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxClass:remove')") + @OperationLog + @Operation(summary = "删除百色中学-班级") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxClassService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxClass:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-班级") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxClassService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxClass:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-班级") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxClassService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxClass:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-班级") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxClassService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxEraController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxEraController.java new file mode 100644 index 0000000..c827039 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxEraController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.bszx.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxEraService; +import com.gxwebsoft.bszx.entity.BszxEra; +import com.gxwebsoft.bszx.param.BszxEraParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 百色中学-年代控制器 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "百色中学-年代管理") +@RestController +@RequestMapping("/api/bszx/bszx-era") +public class BszxEraController extends BaseController { + @Resource + private BszxEraService bszxEraService; + + @Operation(summary = "分页查询百色中学-年代") + @GetMapping("/page") + public ApiResult> page(BszxEraParam param) { + // 使用关联查询 + return success(bszxEraService.pageRel(param)); + } + + @Operation(summary = "查询全部百色中学-年代") + @GetMapping() + public ApiResult> list(BszxEraParam param) { + // 使用关联查询 + return success(bszxEraService.listRel(param)); + } + + @Operation(summary = "根据id查询百色中学-年代") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxEraService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('bszx:bszxEra:save')") + @OperationLog + @Operation(summary = "添加百色中学-年代") + @PostMapping() + public ApiResult save(@RequestBody BszxEra bszxEra) { + if (bszxEraService.save(bszxEra)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxEra:update')") + @OperationLog + @Operation(summary = "修改百色中学-年代") + @PutMapping() + public ApiResult update(@RequestBody BszxEra bszxEra) { + if (bszxEraService.updateById(bszxEra)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxEra:remove')") + @OperationLog + @Operation(summary = "删除百色中学-年代") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxEraService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxEra:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-年代") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxEraService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxEra:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-年代") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxEraService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxEra:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-年代") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxEraService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxGradeController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxGradeController.java new file mode 100644 index 0000000..3280d0f --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxGradeController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.bszx.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxGradeService; +import com.gxwebsoft.bszx.entity.BszxGrade; +import com.gxwebsoft.bszx.param.BszxGradeParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 百色中学-年级控制器 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "百色中学-年级管理") +@RestController +@RequestMapping("/api/bszx/bszx-grade") +public class BszxGradeController extends BaseController { + @Resource + private BszxGradeService bszxGradeService; + + @Operation(summary = "分页查询百色中学-年级") + @GetMapping("/page") + public ApiResult> page(BszxGradeParam param) { + // 使用关联查询 + return success(bszxGradeService.pageRel(param)); + } + + @Operation(summary = "查询全部百色中学-年级") + @GetMapping() + public ApiResult> list(BszxGradeParam param) { + // 使用关联查询 + return success(bszxGradeService.listRel(param)); + } + + @Operation(summary = "根据id查询百色中学-年级") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxGradeService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('bszx:bszxGrade:save')") + @OperationLog + @Operation(summary = "添加百色中学-年级") + @PostMapping() + public ApiResult save(@RequestBody BszxGrade bszxGrade) { + if (bszxGradeService.save(bszxGrade)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxGrade:update')") + @OperationLog + @Operation(summary = "修改百色中学-年级") + @PutMapping() + public ApiResult update(@RequestBody BszxGrade bszxGrade) { + if (bszxGradeService.updateById(bszxGrade)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxGrade:remove')") + @OperationLog + @Operation(summary = "删除百色中学-年级") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxGradeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxGrade:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-年级") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxGradeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxGrade:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-年级") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxGradeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxGrade:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-年级") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxGradeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxOrderController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxOrderController.java new file mode 100644 index 0000000..01ba5c2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxOrderController.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.bszx.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.param.BszxPayParam; +import com.gxwebsoft.bszx.service.BszxBmService; +import com.gxwebsoft.bszx.service.BszxPayService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.param.ShopOrderParam; +import com.gxwebsoft.shop.service.ShopOrderService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 百色中学-订单管理 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "百色中学-订单管理") +@RestController +@RequestMapping("/api/bszx/bszx-order") +public class BszxOrderController extends BaseController { + @Resource + private BszxPayService bszxPayService; + @Resource + private BszxBmService bszxBmService; + @Resource + private ShopOrderService shopOrderService; + + @Operation(summary = "分页查询百色中学-订单列表") + @GetMapping("/page") + public ApiResult> page(ShopOrderParam param) { + // 使用关联查询 + final PageResult result = shopOrderService.pageRel(param); + if(!CollectionUtils.isEmpty(result.getList())){ + final Set userIds = result.getList().stream().map(ShopOrder::getUserId).collect(Collectors.toSet()); + final List bmList = bszxBmService.list(new LambdaQueryWrapper().in(BszxBm::getUserId, userIds).isNotNull(BszxBm::getName)); + final Map> collect = bmList.stream().collect(Collectors.groupingBy(BszxBm::getUserId)); + final Set orderNos = result.getList().stream().map(ShopOrder::getOrderNo).collect(Collectors.toSet()); + final BszxPayParam bszxPayParam = new BszxPayParam(); + bszxPayParam.setOrderNos(orderNos); + final List bszxPays = bszxPayService.listRel(bszxPayParam); + final Map> collectByOrderNo = bszxPays.stream().collect(Collectors.groupingBy(BszxPay::getOrderNo)); + + result.getList().forEach(d -> { + final List pays = collectByOrderNo.get(d.getOrderNo()); + if(!CollectionUtils.isEmpty(pays)){ + d.setDeliveryStatus(20); + } + final List bmList1 = collect.get(d.getUserId()); + if(!CollectionUtils.isEmpty(bmList1)){ + final BszxBm bm = bmList1.get(0); + d.setBm(bm); + d.setRealName(bm.getName()); + if(bm.getPhone() != null){ + d.setPhone(bm.getPhone()); + } + } + }); + } + return success(result); + } + + + @Operation(summary = "统计订单总金额") + @GetMapping("/total") + public ApiResult total() { + try { + BigDecimal totalAmount = bszxPayService.total(); + return success(totalAmount); + } catch (Exception e) { + // 异常时返回0,保持接口稳定性 + return success(BigDecimal.ZERO); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxPayController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxPayController.java new file mode 100644 index 0000000..10b238d --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxPayController.java @@ -0,0 +1,343 @@ +package com.gxwebsoft.bszx.controller; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.service.BszxBmService; +import com.wechat.pay.java.core.notification.*; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.security.JwtUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxPayService; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.param.BszxPayParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.service.ShopOrderService; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService; +import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; + +/** + * 百色中学-捐款记录控制器 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "百色中学-捐款记录管理") +@RestController +@RequestMapping("/api/bszx/bszx-pay") +public class BszxPayController extends BaseController { + public static JsapiService service; + @Resource + private BszxPayService bszxPayService; + @Resource + private BszxBmService bszxBmService; + @Resource + private RedisUtil redisUtil; + @Resource + private ShopOrderService shopOrderService; + @Resource + private ConfigProperties conf; + @Value("${spring.profiles.active}") + String active; + + @PreAuthorize("hasAuthority('bszx:bszxPay:list')") + @Operation(summary = "分页查询百色中学-捐款记录") + @GetMapping("/page") + public ApiResult> page(BszxPayParam param) { + // 使用关联查询 + return success(bszxPayService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:list')") + @Operation(summary = "查询全部百色中学-捐款记录") + @GetMapping() + public ApiResult> list(BszxPayParam param) { + // 使用关联查询 + return success(bszxPayService.listRel(param)); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:list')") + @Operation(summary = "根据id查询百色中学-捐款记录") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxPayService.getByIdRel(id)); + } + + @OperationLog + @Operation(summary = "活动捐款") + @PostMapping() + public ApiResult save(@RequestBody BszxPay bszxPay, HttpServletRequest request) { + if (bszxPay.getPrice().compareTo(BigDecimal.ZERO) == 0) { + return fail("金额不能为0"); + } + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + String access_token = JwtUtil.getAccessToken(request); + bszxPay.setUserId(loginUser.getUserId()); + // 微信openid(必填) + if (StrUtil.isBlank(loginUser.getOpenid())) { + return fail("微信openid(必填)"); + } + final BszxBm bmInfo = bszxBmService.getByUserId(loginUser.getUserId()); + bszxPay.setName(bmInfo.getName()); + bszxPay.setSex(bmInfo.getSex()); + bszxPay.setPhone(bmInfo.getPhone()); + bszxPay.setBranchName(bmInfo.getBranchName()); + bszxPay.setGradeName(bmInfo.getGradeName()); + bszxPay.setClassName(bmInfo.getClassName()); + bszxPay.setAddress(bmInfo.getAddress()); + bszxPay.setWorkUnit(bmInfo.getWorkUnit()); + bszxPay.setPosition(bmInfo.getPosition()); + bszxPay.setAge(bmInfo.getAge()); + bszxPay.setNumber(bmInfo.getNumber()); + } + if (bszxPayService.save(bszxPay)) { + // 调起支付 + return success("下单成功", bszxPay); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:update')") + @OperationLog + @Operation(summary = "修改百色中学-捐款记录") + @PutMapping() + public ApiResult update(@RequestBody BszxPay bszxPay) { + if (bszxPayService.updateById(bszxPay)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:remove')") + @OperationLog + @Operation(summary = "删除百色中学-捐款记录") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxPayService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-捐款记录") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxPayService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-捐款记录") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxPayService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPay:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-捐款记录") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxPayService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "查询我的报名记录") + @GetMapping("/myPage") + public ApiResult> myPage(BszxPayParam param) { + // 使用关联查询 + if (getLoginUser() != null) { + param.setUserId(getLoginUserId()); + return success(bszxPayService.pageRel(param)); + } + return fail("请先登录", null); + } + + @Operation(summary = "统计捐款总金额与人次") + @GetMapping("/getCount") + public ApiResult getCount() { + final HashMap map = new HashMap<>(); + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + final BigDecimal bigDecimal = bszxPayService.sumMoney(wrapper); + Long count = (long) bszxPayService.count(new LambdaQueryWrapper()); + map.put("numbers", count); + map.put("totalMoney", bigDecimal); + return success(map); + } + + @Schema(description = "异步通知") + @PostMapping("/notify/{tenantId}") + public String wxNotify(@RequestHeader Map header, @RequestBody String body,HttpServletRequest request, @PathVariable("tenantId") Integer tenantId) { + // 获取支付配置信息用于解密 - 优先使用 Payment:1* 格式 + String key = "Payment:11"; // 微信支付类型为1,使用 Payment:11 格式 + Payment payment = redisUtil.get(key, Payment.class); + + // 如果 Payment:1* 格式不存在,尝试原有格式 + if (payment == null) { + String fallbackKey = "Payment:1:".concat(tenantId.toString()); + payment = redisUtil.get(fallbackKey, Payment.class); + } + String uploadPath = conf.getUploadPath(); + + // 开发环境 + String mid = "1242289702"; + String apiV3Key = "0b2996803383c3e3391abd9183b54key"; + String serialNumber = "3B458EB14A28160DC094431A21C0508EFA712D1C"; + String privateKey = "/Users/gxwebsoft/JAVA/site-java/cert/bszx/apiclient_key.pem"; + String apiclientCert = "/Users/gxwebsoft/JAVA/site-java/cert/bszx/apiclient_cert.pem"; + String pubKey = "/Users/gxwebsoft/JAVA/site-java/cert/bszx/0f65a8517c284acb90aa83dd0c23e8f6.pem"; + String pubId = "PUB_KEY_ID_0112422897022025011300326200001208"; + // 生产环境 + if (ObjectUtil.isNotEmpty(payment)) { + // 检查 payment 字段是否为空,并避免直接解析为数字 + mid = payment.getMchId(); + apiV3Key = payment.getApiKey(); + serialNumber = payment.getMerchantSerialNumber(); + // 生产环境使用容器证书路径 /www/wwwroot/file.ws + privateKey = "/www/wwwroot/file.ws" + payment.getApiclientKey(); + apiclientCert = "/www/wwwroot/file.ws" + payment.getApiclientCert(); + pubKey = "/www/wwwroot/file.ws" + payment.getPubKey(); + pubId = payment.getPubKeyId(); + } + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(header.get("wechatpay-serial")) + .nonce(header.get("wechatpay-nonce")) + .signature(header.get("wechatpay-signature")) + .timestamp(header.get("wechatpay-timestamp")) + .body(body) + .build(); + + +// NotificationConfig config = new RSAPublicKeyConfig.Builder() +// .merchantId(mid) +// .publicKeyFromPath(pubKey) +// .publicKeyId(pubId) +// .privateKeyFromPath(privateKey) +// .merchantSerialNumber(serialNumber) +// .apiV3Key(apiV3Key) +// .build(); + + NotificationConfig config = new RSAPublicKeyNotificationConfig.Builder() + .publicKeyFromPath(pubKey) + .publicKeyId(pubId) + .apiV3Key(apiV3Key) + .build(); + + + // 初始化 NotificationParser + NotificationParser parser = new NotificationParser(config); + + // 以支付通知回调为例,验签、解密并转换成 Transaction + try { + Transaction transaction = parser.parse(requestParam, Transaction.class); + final String outTradeNo = transaction.getOutTradeNo(); + final String transactionId = transaction.getTransactionId(); + final Integer total = transaction.getAmount().getTotal(); + final String tradeStateDesc = transaction.getTradeStateDesc(); + final Transaction.TradeStateEnum tradeState = transaction.getTradeState(); + final Transaction.TradeTypeEnum tradeType = transaction.getTradeType(); + System.out.println("transaction = " + transaction); + System.out.println("tradeStateDesc = " + tradeStateDesc); + System.out.println("tradeType = " + tradeType); + System.out.println("tradeState = " + tradeState); + System.out.println("outTradeNo = " + outTradeNo); + System.out.println("amount = " + total); + + if (StrUtil.equals("支付成功", tradeStateDesc)) { + // 1. 查询要处理的订单 + ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo); + // 2. 已支付则跳过 + if (order.getPayStatus().equals(true)) { + return "SUCCESS"; + } + // 2. 未支付则处理更新订单状态 + if (order.getPayStatus().equals(false)) { + // 5. TODO 处理订单状态 + order.setPayTime(LocalDateTime.now()); + order.setPayStatus(true); + order.setTransactionId(transactionId); + order.setPayPrice(new BigDecimal(NumberUtil.decimalFormat("0.00", total * 0.01))); + order.setExpirationTime(LocalDateTime.now().plusYears(10)); + System.out.println("实际付款金额 = " + order.getPayPrice()); + return "SUCCESS"; + } + } + } catch (Exception $e) { + System.out.println($e.getMessage()); + System.out.println(Arrays.toString($e.getStackTrace())); + } + + return "fail"; + } + + + @PreAuthorize("hasAuthority('shop:shopOrder:update')") + @Operation(summary = "修复订单") + @PutMapping("/repair") + public ApiResult repair(@RequestBody ShopOrder shopOrder) { + if (shopOrderService.queryOrderByOutTradeNo(shopOrder)) { + if (bszxPayService.count(new LambdaQueryWrapper().eq(BszxPay::getOrderNo, shopOrder.getOrderNo())) == 0) { + final BszxPay bszxPay = new BszxPay(); + final BszxBm bm = shopOrder.getBm(); + if (ObjectUtil.isNotEmpty(bm)) { + bszxPay.setName(bm.getName()); + bszxPay.setSex(bm.getSex()); + bszxPay.setClassName(bm.getClassName()); + bszxPay.setGradeName(bm.getGradeName()); + bszxPay.setAddress(bm.getAddress()); + bszxPay.setWorkUnit(bm.getWorkUnit()); + bszxPay.setPosition(bm.getPosition()); + bszxPay.setPrice(shopOrder.getPayPrice()); + bszxPay.setOrderNo(shopOrder.getOrderNo()); + bszxPay.setUserId(shopOrder.getUserId()); + bszxPay.setFormId(shopOrder.getFormId()); + bszxPay.setComments(shopOrder.getComments()); + bszxPayService.save(bszxPay); + } + } + return success("修复成功"); + } + return fail("修复失败"); + } + + @Operation(summary = "获取捐款证书") + @GetMapping("/generatePayCert/{id}") + public ApiResult generatePayCert(@PathVariable("id") Integer id) throws Exception { + return success("获取捐款证书", bszxPayService.generatePayCert(id)); + } +} diff --git a/src/main/java/com/gxwebsoft/bszx/controller/BszxPayRankingController.java b/src/main/java/com/gxwebsoft/bszx/controller/BszxPayRankingController.java new file mode 100644 index 0000000..6458b32 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/controller/BszxPayRankingController.java @@ -0,0 +1,198 @@ +package com.gxwebsoft.bszx.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.bszx.entity.BszxClass; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.param.BszxClassParam; +import com.gxwebsoft.bszx.service.BszxClassService; +import com.gxwebsoft.bszx.service.BszxPayService; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.service.CmsArticleService; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.bszx.service.BszxPayRankingService; +import com.gxwebsoft.bszx.entity.BszxPayRanking; +import com.gxwebsoft.bszx.param.BszxPayRankingParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 百色中学-捐款排行控制器 + * + * @author 科技小王子 + * @since 2025-03-25 08:54:09 + */ +@Tag(name = "百色中学-捐款排行管理") +@RestController +@RequestMapping("/api/bszx/bszx-pay-ranking") +public class BszxPayRankingController extends BaseController { + @Resource + private BszxPayRankingService bszxPayRankingService; + @Resource + private CmsArticleService cmsArticleService; + @Resource + private BszxPayService bszxPayService; + @Resource + private BszxClassService bszxClassService; + @Resource + private RedisUtil redisUtil; + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:list')") + @Operation(summary = "分页查询百色中学-捐款排行") + @GetMapping("/page") + public ApiResult> page(BszxPayRankingParam param) { + // 使用关联查询 + return success(bszxPayRankingService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:list')") + @Operation(summary = "查询全部百色中学-捐款排行") + @GetMapping() + public ApiResult> list(BszxPayRankingParam param) { + // 使用关联查询 + return success(bszxPayRankingService.listRel(param)); + } + + @Operation(summary = "查询全部百色中学-捐款排行榜") + @GetMapping("/ranking") + public ApiResult> ranking(BszxPayRankingParam param) { + final ArrayList rankings = new ArrayList<>(); + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + final List list = cmsArticleService.list(new LambdaQueryWrapper().eq(CmsArticle::getCategoryId, 2444)); + + list.forEach(item -> { + final BszxPayRanking ranking = new BszxPayRanking(); + wrapper.clear(); + // 按时间段查询 + if(param.getCreateTimeStart() != null && param.getCreateTimeEnd() != null){ + final String timeStart = param.getCreateTimeStart(); + final String timeEnd = param.getCreateTimeEnd(); + wrapper.ge(BszxPay::getCreateTime, timeStart); + wrapper.le(BszxPay::getCreateTime, timeEnd); + } + wrapper.eq(BszxPay::getFormId, item.getArticleId()); + ranking.setFormId(item.getArticleId()); + ranking.setFormName(item.getTitle()); + ranking.setNumber((long) bszxPayService.count(wrapper)); + ranking.setTotalPrice(bszxPayService.sumMoney(wrapper)); + rankings.add(ranking); + }); + // totalPrice按大到小排序 + rankings.sort((o1, o2) -> o2.getTotalPrice().compareTo(o1.getTotalPrice())); + return success(rankings); + } + + + @Operation(summary = "查询全部百色中学-千班万元") + @GetMapping("/ranking2") + public ApiResult> ranking2(BszxClassParam param) { + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + final List list = bszxClassService.listRel(param); + + String key = "BSZX:UpdateRanking2"; + final String isTimeOut = redisUtil.get(key); + if(StrUtil.isNotBlank(isTimeOut)){ + list.sort((o1, o2) -> o2.getTotalMoney().compareTo(o1.getTotalMoney())); + return success(list); + } + list.forEach(item -> { + wrapper.clear(); + wrapper.eq(BszxPay::getGradeName,item.getGradeName()); + wrapper.eq(BszxPay::getClassName, item.getName()); + item.setTotalMoney(bszxPayService.sumMoney(wrapper)); + bszxClassService.updateById(item); + }); + // totalPrice按大到小排序 + list.sort((o1, o2) -> o2.getTotalMoney().compareTo(o1.getTotalMoney())); + redisUtil.set(key, 1,1L, TimeUnit.DAYS); + return success(list); + } + + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:list')") + @Operation(summary = "根据id查询百色中学-捐款排行") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(bszxPayRankingService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:save')") + @OperationLog + @Operation(summary = "添加百色中学-捐款排行") + @PostMapping() + public ApiResult save(@RequestBody BszxPayRanking bszxPayRanking) { + if (bszxPayRankingService.save(bszxPayRanking)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:update')") + @OperationLog + @Operation(summary = "修改百色中学-捐款排行") + @PutMapping() + public ApiResult update(@RequestBody BszxPayRanking bszxPayRanking) { + if (bszxPayRankingService.updateById(bszxPayRanking)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:remove')") + @OperationLog + @Operation(summary = "删除百色中学-捐款排行") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (bszxPayRankingService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:save')") + @OperationLog + @Operation(summary = "批量添加百色中学-捐款排行") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (bszxPayRankingService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:update')") + @OperationLog + @Operation(summary = "批量修改百色中学-捐款排行") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(bszxPayRankingService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('bszx:bszxPayRanking:remove')") + @OperationLog + @Operation(summary = "批量删除百色中学-捐款排行") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (bszxPayRankingService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxBm.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxBm.java new file mode 100644 index 0000000..c4dee60 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxBm.java @@ -0,0 +1,151 @@ +package com.gxwebsoft.bszx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import com.gxwebsoft.cms.entity.CmsArticle; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-报名记录 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxBm对象", description = "百色中学-报名记录") +public class BszxBm implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "类型 0校友 1单位 2爱心人士") + private Integer type; + + @Schema(description = "性别 1男 2女") + private String sex; + + @Schema(description = "性别名称") + @TableField(exist = false) + private String sexName; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "手机号码") + @TableField(exist = false) + private String mobile; + + @Schema(description = "班级ID") + private Integer classId; + + @Schema(description = "班级") + private String className; + + @Schema(description = "年级") + private String gradeName; + + @Schema(description = "分部ID") + private Integer branchId; + + @Schema(description = "分部名称") + @TableField(exist = false) + private String branchName; + + @Schema(description = "居住地址") + private String address; + + @Schema(description = "工作单位") + private String workUnit; + + @Schema(description = "职务") + private String position; + + @Schema(description = "是否能到场") + private String present; + + @Schema(description = "年龄") + private Integer age; + + @Schema(description = "人数") + private Integer number; + + @Schema(description = "额外信息") + private String extra; + + @Schema(description = "生成的邀请函存放路径") + private String certificate; + + @Schema(description = "预定日期") + private LocalDate dateTime; + + @Schema(description = "表单数据") + private String formData; + + @Schema(description = "表单ID") + private Integer formId; + + @Schema(description = "活动名称") + @TableField(exist = false) + private String formName; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "文章对象") + @TableField(exist = false) + private CmsArticle article; + + public String getSexName() { + if (this.sex == null) { + return ""; + } + return this.sex.equals("1") ? "男" : "女"; + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxBranch.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxBranch.java new file mode 100644 index 0000000..f8ffe72 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxBranch.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.bszx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-分部 + * + * @author 科技小王子 + * @since 2025-03-17 17:18:22 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxBranch对象", description = "百色中学-分部") +public class BszxBranch implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "分部名称 ") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "子分类") + @TableField(exist = false) + private List children; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxClass.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxClass.java new file mode 100644 index 0000000..e7340cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxClass.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.bszx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-班级 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxClass对象", description = "百色中学-班级") +public class BszxClass implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "时代ID") + private Integer eraId; + + @Schema(description = "时代名称") + @TableField(exist = false) + private String eraName; + + @Schema(description = "年级ID") + private Integer gradeId; + + @Schema(description = "年级名称") + @TableField(exist = false) + private String gradeName; + + @Schema(description = "班级") + private String name; + + @Schema(description = "累计捐款金额") + private BigDecimal totalMoney; + + @Schema(description = "分部") + private Integer branch; + + @Schema(description = "分部名称") + @TableField(exist = false) + private String branchName; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "子分类") + @TableField(exist = false) + private List children; +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxEra.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxEra.java new file mode 100644 index 0000000..5b7be7a --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxEra.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.bszx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-年代 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxEra对象", description = "百色中学-年代") +public class BszxEra implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "年代") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "子分类") + @TableField(exist = false) + private List children; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxGrade.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxGrade.java new file mode 100644 index 0000000..6eacb08 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxGrade.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.bszx.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-年级 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxGrade对象", description = "百色中学-年级") +public class BszxGrade implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "年级") + private String name; + + @Schema(description = "年代") + private Integer eraId; + + @Schema(description = "分部") + private Integer branch; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "子分类") + @TableField(exist = false) + private List children; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxPay.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxPay.java new file mode 100644 index 0000000..a61e6b8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxPay.java @@ -0,0 +1,143 @@ +package com.gxwebsoft.bszx.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.shop.entity.ShopOrder; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-捐款记录 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxPay对象", description = "百色中学-捐款记录") +public class BszxPay implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "年龄") + private Integer age; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "性别 1男 2女") + private String sex; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "手机号码") + @TableField(exist = false) + private String mobile; + + @Schema(description = "分部") + private String branchName; + + @Schema(description = "班级") + private String className; + + @Schema(description = "年级") + private String gradeName; + + @Schema(description = "居住地址") + private String address; + + @Schema(description = "工作单位") + private String workUnit; + + @Schema(description = "职务") + private String position; + + @Schema(description = "数量") + private Integer number; + + @Schema(description = "付费金额") + private BigDecimal price; + + @Schema(description = "额外信息") + private String extra; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "预定日期") + private LocalDate dateTime; + + @Schema(description = "捐赠证书") + private String certificate; + + @Schema(description = "表单数据") + private String formData; + + @Schema(description = "来源表ID") + private Integer formId; + + @Schema(description = "活动名称") + @TableField(exist = false) + private String formName; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "文章") + @TableField(exist = false) + private CmsArticle article; + + @Schema(description = "订单") + @TableField(exist = false) + private ShopOrder shopOrder; + + public String getSexName() { + return this.sex.equals("1") ? "男" : "女"; + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/entity/BszxPayRanking.java b/src/main/java/com/gxwebsoft/bszx/entity/BszxPayRanking.java new file mode 100644 index 0000000..6956822 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/entity/BszxPayRanking.java @@ -0,0 +1,67 @@ +package com.gxwebsoft.bszx.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-捐款排行 + * + * @author 科技小王子 + * @since 2025-03-25 08:54:09 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "BszxPayRanking对象", description = "百色中学-捐款排行") +public class BszxPayRanking implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "来源表ID(文章ID)") + private Integer formId; + + @Schema(description = "项目名称") + @TableField(exist = false) + private String formName; + + @Schema(description = "数量") + private Long number; + + @Schema(description = "获得捐款总金额") + private BigDecimal totalPrice; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxBmMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxBmMapper.java new file mode 100644 index 0000000..7c63575 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxBmMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.param.BszxBmParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 百色中学-报名记录Mapper + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxBmMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxBmParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxBmParam param); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxBranchMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxBranchMapper.java new file mode 100644 index 0000000..d94fd0b --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxBranchMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxBranch; +import com.gxwebsoft.bszx.param.BszxBranchParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 百色中学-分部Mapper + * + * @author 科技小王子 + * @since 2025-03-17 17:18:22 + */ +public interface BszxBranchMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxBranchParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxBranchParam param); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxClassMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxClassMapper.java new file mode 100644 index 0000000..81d251f --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxClassMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxClass; +import com.gxwebsoft.bszx.param.BszxClassParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 百色中学-班级Mapper + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxClassMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxClassParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxClassParam param); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxEraMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxEraMapper.java new file mode 100644 index 0000000..17d83c9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxEraMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxEra; +import com.gxwebsoft.bszx.param.BszxEraParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 百色中学-年代Mapper + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxEraMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxEraParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxEraParam param); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxGradeMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxGradeMapper.java new file mode 100644 index 0000000..1e566bb --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxGradeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxGrade; +import com.gxwebsoft.bszx.param.BszxGradeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 百色中学-年级Mapper + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxGradeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxGradeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxGradeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxPayMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxPayMapper.java new file mode 100644 index 0000000..020c7ea --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxPayMapper.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.param.BszxPayParam; +import org.apache.ibatis.annotations.Param; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 百色中学-捐款记录Mapper + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxPayMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxPayParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxPayParam param); + + BigDecimal selectSumMoney(@Param("ew") Wrapper wrapper); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/BszxPayRankingMapper.java b/src/main/java/com/gxwebsoft/bszx/mapper/BszxPayRankingMapper.java new file mode 100644 index 0000000..c6e14b0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/BszxPayRankingMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.bszx.entity.BszxPayRanking; +import com.gxwebsoft.bszx.param.BszxPayRankingParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 百色中学-捐款排行Mapper + * + * @author 科技小王子 + * @since 2025-03-25 08:54:09 + */ +public interface BszxPayRankingMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") BszxPayRankingParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") BszxPayRankingParam param); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBmMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBmMapper.xml new file mode 100644 index 0000000..74d0099 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBmMapper.xml @@ -0,0 +1,113 @@ + + + + + + + SELECT a.*,b.title as formName, c.name as branchName, u.phone as mobile,u.avatar,u.nickname + FROM bszx_bm a + LEFT JOIN cms_article b ON a.form_id = b.article_id + LEFT JOIN bszx_branch c ON a.branch_id = c.id + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.type = #{param.type} + + + AND a.sex = #{param.sex} + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.branch_id = #{param.branchId} + + + AND a.class_name LIKE CONCAT('%', #{param.className}, '%') + + + AND a.grade_name LIKE CONCAT('%', #{param.gradeName}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.work_unit LIKE CONCAT('%', #{param.workUnit}, '%') + + + AND a.position LIKE CONCAT('%', #{param.position}, '%') + + + AND a.present = #{param.present} + + + AND a.age = #{param.age} + + + AND a.number = #{param.number} + + + AND a.extra LIKE CONCAT('%', #{param.extra}, '%') + + + AND a.certificate LIKE CONCAT('%', #{param.certificate}, '%') + + + AND a.date_time LIKE CONCAT('%', #{param.dateTime}, '%') + + + AND a.form_data LIKE CONCAT('%', #{param.formData}, '%') + + + AND a.form_id = #{param.formId} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.phone = #{param.keywords} + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBranchMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBranchMapper.xml new file mode 100644 index 0000000..c9c7fa0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxBranchMapper.xml @@ -0,0 +1,36 @@ + + + + + + + SELECT a.* + FROM bszx_branch a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxClassMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxClassMapper.xml new file mode 100644 index 0000000..8f07436 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxClassMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.*,b.name as gradeName, c.name as eraName, d.name as branchName + FROM bszx_class a + LEFT JOIN bszx_grade b ON a.grade_id = b.id + LEFT JOIN bszx_era c ON a.era_id = c.id + LEFT JOIN bszx_branch d ON a.branch = d.id + + + AND a.id = #{param.id} + + + AND a.era_id = #{param.eraId} + + + AND a.grade_id = #{param.gradeId} + + + AND b.name = #{param.gradeName} + + + AND a.name = #{param.name} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.branch = #{param.branch} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxEraMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxEraMapper.xml new file mode 100644 index 0000000..867fdf4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxEraMapper.xml @@ -0,0 +1,36 @@ + + + + + + + SELECT a.* + FROM bszx_era a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxGradeMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxGradeMapper.xml new file mode 100644 index 0000000..df9419e --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxGradeMapper.xml @@ -0,0 +1,54 @@ + + + + + + + SELECT a.* + FROM bszx_grade a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.era_id = #{param.eraId} + + + AND a.branch = #{param.branch} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayMapper.xml new file mode 100644 index 0000000..fde31b8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayMapper.xml @@ -0,0 +1,126 @@ + + + + + + + SELECT a.*,b.title as formName,u.phone as mobile,u.avatar,u.nickname + FROM bszx_pay a + LEFT JOIN cms_article b ON a.form_id = b.article_id + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.age = #{param.age} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.sex = #{param.sex} + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.class_name = #{param.className} + + + AND a.grade_name LIKE CONCAT('%', #{param.gradeName}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.work_unit LIKE CONCAT('%', #{param.workUnit}, '%') + + + AND a.position LIKE CONCAT('%', #{param.position}, '%') + + + AND a.number = #{param.number} + + + AND a.price = #{param.price} + + + AND a.extra LIKE CONCAT('%', #{param.extra}, '%') + + + AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%') + + + AND a.date_time LIKE CONCAT('%', #{param.dateTime}, '%') + + + AND a.certificate LIKE CONCAT('%', #{param.certificate}, '%') + + + AND a.form_data LIKE CONCAT('%', #{param.formData}, '%') + + + AND a.form_id = #{param.formId} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.order_no IN + + #{item} + + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR u.phone = #{param.keywords} + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.order_no = #{param.keywords} + ) + + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayRankingMapper.xml b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayRankingMapper.xml new file mode 100644 index 0000000..806e26f --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/mapper/xml/BszxPayRankingMapper.xml @@ -0,0 +1,61 @@ + + + + + + + SELECT a.*,b.title as formName + FROM bszx_pay_ranking a + LEFT JOIN cms_article b ON a.form_id = b.article_id + + + AND a.id = #{param.id} + + + AND a.form_id = #{param.formId} + + + AND a.number = #{param.number} + + + AND a.total_price = #{param.totalPrice} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxBmParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxBmParam.java new file mode 100644 index 0000000..b8b665c --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxBmParam.java @@ -0,0 +1,114 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-报名记录查询参数 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxBmParam对象", description = "百色中学-报名记录查询参数") +public class BszxBmParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "类型 0校友 1单位") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "性别 1男 2女") + @QueryField(type = QueryType.EQ) + private Integer sex; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "班级") + private String className; + + @Schema(description = "年级") + private String gradeName; + + @Schema(description = "分部ID") + @QueryField(type = QueryType.EQ) + private Integer branchId; + + @Schema(description = "居住地址") + private String address; + + @Schema(description = "工作单位") + private String workUnit; + + @Schema(description = "职务") + private String position; + + @Schema(description = "是否能到场") + @QueryField(type = QueryType.EQ) + private Boolean present; + + @Schema(description = "年龄") + @QueryField(type = QueryType.EQ) + private Integer age; + + @Schema(description = "人数") + @QueryField(type = QueryType.EQ) + private Integer number; + + @Schema(description = "额外信息") + private String extra; + + @Schema(description = "生成的邀请函存放路径") + private String certificate; + + @Schema(description = "预定日期") + private String dateTime; + + @Schema(description = "表单数据") + private String formData; + + @Schema(description = "表单ID") + @QueryField(type = QueryType.EQ) + private Integer formId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "订单编号") + @QueryField(type = QueryType.LIKE) + private String orderNo; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxBranchParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxBranchParam.java new file mode 100644 index 0000000..642988c --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxBranchParam.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-分部查询参数 + * + * @author 科技小王子 + * @since 2025-03-17 17:18:22 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxBranchParam对象", description = "百色中学-分部查询参数") +public class BszxBranchParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "分部名称 ") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxClassParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxClassParam.java new file mode 100644 index 0000000..5145480 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxClassParam.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-班级查询参数 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxClassParam对象", description = "百色中学-班级查询参数") +public class BszxClassParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "时代ID") + @QueryField(type = QueryType.EQ) + private Integer eraId; + + @Schema(description = "年级ID") + @QueryField(type = QueryType.EQ) + private Integer gradeId; + + @Schema(description = "年级") + @QueryField(type = QueryType.EQ) + private String gradeName; + + @Schema(description = "累计捐款金额") + @QueryField(type = QueryType.EQ) + private BigDecimal totalMoney; + + @Schema(description = "班级") + private String name; + + @Schema(description = "分部") + @QueryField(type = QueryType.EQ) + private Integer branch; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxEraParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxEraParam.java new file mode 100644 index 0000000..4fb6ffe --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxEraParam.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-年代查询参数 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxEraParam对象", description = "百色中学-年代查询参数") +public class BszxEraParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "年代") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxGradeParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxGradeParam.java new file mode 100644 index 0000000..3d40762 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxGradeParam.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-年级查询参数 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxGradeParam对象", description = "百色中学-年级查询参数") +public class BszxGradeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "年级") + private String name; + + @Schema(description = "年代") + @QueryField(type = QueryType.EQ) + private Integer eraId; + + @Schema(description = "分部") + @QueryField(type = QueryType.EQ) + private Integer branch; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxPayParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxPayParam.java new file mode 100644 index 0000000..95ff991 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxPayParam.java @@ -0,0 +1,118 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import java.util.Set; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-捐款记录查询参数 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxPayParam对象", description = "百色中学-捐款记录查询参数") +public class BszxPayParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "年龄") + @QueryField(type = QueryType.EQ) + private Integer age; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "性别 1男 2女") + @QueryField(type = QueryType.EQ) + private Integer sex; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "班级") + private String className; + + @Schema(description = "年级") + private String gradeName; + + @Schema(description = "居住地址") + private String address; + + @Schema(description = "工作单位") + private String workUnit; + + @Schema(description = "职务") + private String position; + + @Schema(description = "数量") + @QueryField(type = QueryType.EQ) + private Integer number; + + @Schema(description = "付费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "额外信息") + private String extra; + + @Schema(description = "订单编号") + @QueryField(type = QueryType.EQ) + private String orderNo; + + @Schema(description = "订单编号") + @QueryField(type = QueryType.IN) + private Set orderNos; + + @Schema(description = "预定日期") + private String dateTime; + + @Schema(description = "捐赠证书") + private String certificate; + + @Schema(description = "表单数据") + private String formData; + + @Schema(description = "来源表ID") + @QueryField(type = QueryType.EQ) + private Integer formId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "登录用户") + @TableField(exist = false) + private User loginUser; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/param/BszxPayRankingParam.java b/src/main/java/com/gxwebsoft/bszx/param/BszxPayRankingParam.java new file mode 100644 index 0000000..51a37a6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/param/BszxPayRankingParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.bszx.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 百色中学-捐款排行查询参数 + * + * @author 科技小王子 + * @since 2025-03-25 08:54:09 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "BszxPayRankingParam对象", description = "百色中学-捐款排行查询参数") +public class BszxPayRankingParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "来源表ID(项目名称)") + @QueryField(type = QueryType.EQ) + private Integer formId; + + @Schema(description = "数量") + @QueryField(type = QueryType.EQ) + private Integer number; + + @Schema(description = "获得捐款总金额") + @QueryField(type = QueryType.EQ) + private BigDecimal totalPrice; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxBmService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxBmService.java new file mode 100644 index 0000000..f8caaa3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxBmService.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.param.BszxBmParam; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 百色中学-报名记录Service + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxBmService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxBmParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxBmParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return BszxBm + */ + BszxBm getByIdRel(Integer id); + + /** + * 生成海报 + */ + String generatePoster(BszxBm bm) throws Exception; + + BszxBm getByUserId(Integer userId); +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxBranchService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxBranchService.java new file mode 100644 index 0000000..c7fe0ac --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxBranchService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxBranch; +import com.gxwebsoft.bszx.param.BszxBranchParam; + +import java.util.List; + +/** + * 百色中学-分部Service + * + * @author 科技小王子 + * @since 2025-03-17 17:18:22 + */ +public interface BszxBranchService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxBranchParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxBranchParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return BszxBranch + */ + BszxBranch getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxClassService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxClassService.java new file mode 100644 index 0000000..7871918 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxClassService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxClass; +import com.gxwebsoft.bszx.param.BszxClassParam; + +import java.util.List; + +/** + * 百色中学-班级Service + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxClassService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxClassParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxClassParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return BszxClass + */ + BszxClass getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxEraService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxEraService.java new file mode 100644 index 0000000..efff9da --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxEraService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxEra; +import com.gxwebsoft.bszx.param.BszxEraParam; + +import java.util.List; + +/** + * 百色中学-年代Service + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxEraService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxEraParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxEraParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return BszxEra + */ + BszxEra getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxGradeService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxGradeService.java new file mode 100644 index 0000000..17b5dfd --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxGradeService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxGrade; +import com.gxwebsoft.bszx.param.BszxGradeParam; + +import java.util.List; + +/** + * 百色中学-年级Service + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxGradeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxGradeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxGradeParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return BszxGrade + */ + BszxGrade getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxPayRankingService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxPayRankingService.java new file mode 100644 index 0000000..962ff2b --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxPayRankingService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxPayRanking; +import com.gxwebsoft.bszx.param.BszxPayRankingParam; + +import java.util.List; + +/** + * 百色中学-捐款排行Service + * + * @author 科技小王子 + * @since 2025-03-25 08:54:09 + */ +public interface BszxPayRankingService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxPayRankingParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxPayRankingParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return BszxPayRanking + */ + BszxPayRanking getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/BszxPayService.java b/src/main/java/com/gxwebsoft/bszx/service/BszxPayService.java new file mode 100644 index 0000000..20c8cfc --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/BszxPayService.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.bszx.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.param.BszxPayParam; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 百色中学-捐款记录Service + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +public interface BszxPayService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(BszxPayParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(BszxPayParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return BszxPay + */ + BszxPay getByIdRel(Integer id); + + /** + * 生成捐款证书 + */ + String generatePayCert(Integer id) throws Exception; + + BigDecimal sumMoney(LambdaQueryWrapper between); + + /** + * 统计捐款总金额 + * + * @return 捐款总金额 + */ + BigDecimal total(); +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxBmServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxBmServiceImpl.java new file mode 100644 index 0000000..65fef9d --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxBmServiceImpl.java @@ -0,0 +1,161 @@ +package com.gxwebsoft.bszx.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.freewayso.image.combiner.ImageCombiner; +import com.freewayso.image.combiner.enums.OutputFormat; +import com.gxwebsoft.bszx.entity.BszxClass; +import com.gxwebsoft.bszx.mapper.BszxBmMapper; +import com.gxwebsoft.bszx.param.BszxClassParam; +import com.gxwebsoft.bszx.service.BszxBmService; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.param.BszxBmParam; +import com.gxwebsoft.bszx.service.BszxClassService; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.service.CmsArticleService; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.utils.FileServerUtil; +import com.gxwebsoft.common.core.utils.ImageUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 百色中学-报名记录Service实现 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Service +public class BszxBmServiceImpl extends ServiceImpl implements BszxBmService { + @Value("${config.upload-path}") + private String uploadPath; + @Value("${config.file-server}") + private String fileServer; + @Resource + private ConfigProperties config; + @Resource + @Lazy + private CmsArticleService cmsArticleService; + @Resource + private BszxClassService bszxClassService; + + @Override + public PageResult pageRel(BszxBmParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("id desc"); + List list = baseMapper.selectPageRel(page, param); + list.forEach(d -> { + if(d.getClassId().equals(0)){ + final BszxClassParam classParam = new BszxClassParam(); + classParam.setGradeName(d.getGradeName()); + classParam.setName(d.getClassName()); + final List bszxClasses = bszxClassService.listRel(classParam); + if (!CollectionUtils.isEmpty(bszxClasses)) { + BszxClass bszxClass = bszxClasses.get(0); + d.setClassId(bszxClass.getId()); + d.setBranchId(bszxClass.getBranch()); + updateById(d); + } + } + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxBmParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("id desc"); + return page.sortRecords(list); + } + + @Override + public BszxBm getByIdRel(Integer id) { + BszxBmParam param = new BszxBmParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + + /** + * 生成捐款证书 ... + * + * @return + * @throws Exception + */ + @Override + public String generatePoster(BszxBm item) throws Exception { + final CmsArticle article = cmsArticleService.getById(7859); + if (ObjectUtil.isEmpty(article)) { + return null; + } + if (ObjectUtil.isNotEmpty(item)) { + // Font font = new Font("阿里巴巴普惠体", Font.PLAIN, 40); + //合成器(指定背景图和输出格式,整个图片的宽高和相关计算依赖于背景图,所以背景图的大小是个基准) + ImageCombiner combiner = new ImageCombiner(article.getAddress(), OutputFormat.JPG); + //加文本元素:姓名 +// if (item.getType().equals(0)) { +// combiner.addTextElement(item.getName().concat(" 校友"), 40, 220, 540); +// } else { +// combiner.addTextElement(item.getName(), 40, 220, 540); +// } + +// combiner.addTextElement(DateUtil.format(DateUtil.date(), "yyyy年MM月"), 28,650, 1566); + //加图片元素:盖章 +// combiner.addImageElement("https://oss.wsdns.cn/20250304/6936b109b09b4919a3498ac5027e728b.png", 600, 1420); + + + if (item.getType().equals(0)) { + combiner.addTextElement(item.getName().concat(" 校友"), 30, 160, 1008); + } else { + combiner.addTextElement(item.getName(), 30, 160, 1008); + } + +// combiner.addTextElement(DateUtil.format(DateUtil.date(), "yyyy年MM月"), 28,650, 1566); + //加图片元素:盖章 +// combiner.addImageElement("https://oss.wsdns.cn/20250304/6936b109b09b4919a3498ac5027e728b.png", 600, 1420); + //执行图片合并 + combiner.combine(); + + if (!FileUtil.exist(uploadPath + "/file/poster/" + item.getTenantId() + "/bm")) { + FileUtil.mkdir(uploadPath + "/file/poster/" + item.getTenantId() + "/bm"); + } + String basePath = "/poster/" + item.getTenantId() + "/bm/big-" + item.getId() + ".jpg"; + String smallPath = "/poster/" + item.getTenantId() + "/bm/" + item.getId() + ".jpg"; + String filename = uploadPath + "/file" + basePath; + String smallFileName = uploadPath + "/file" + smallPath; + combiner.save(filename); + + File input = new File(filename); + File output = new File(smallFileName); + ImageUtil.adjustQuality(input, output, 0.8f); + if(input.exists()){ + input.delete(); + } + return fileServer + smallPath + "?r=" + RandomUtil.randomNumbers(4); + } + return null; + } + + @Override + public BszxBm getByUserId(Integer userId) { + return getOne(new LambdaQueryWrapper().eq(BszxBm::getUserId, userId).last("limit 1")); + } +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxBranchServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxBranchServiceImpl.java new file mode 100644 index 0000000..7e12499 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxBranchServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.bszx.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.bszx.mapper.BszxBranchMapper; +import com.gxwebsoft.bszx.service.BszxBranchService; +import com.gxwebsoft.bszx.entity.BszxBranch; +import com.gxwebsoft.bszx.param.BszxBranchParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 百色中学-分部Service实现 + * + * @author 科技小王子 + * @since 2025-03-17 17:18:22 + */ +@Service +public class BszxBranchServiceImpl extends ServiceImpl implements BszxBranchService { + + @Override + public PageResult pageRel(BszxBranchParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxBranchParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public BszxBranch getByIdRel(Integer id) { + BszxBranchParam param = new BszxBranchParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxClassServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxClassServiceImpl.java new file mode 100644 index 0000000..20ea2f7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxClassServiceImpl.java @@ -0,0 +1,68 @@ +package com.gxwebsoft.bszx.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.mapper.BszxClassMapper; +import com.gxwebsoft.bszx.service.BszxClassService; +import com.gxwebsoft.bszx.entity.BszxClass; +import com.gxwebsoft.bszx.param.BszxClassParam; +import com.gxwebsoft.bszx.service.BszxPayService; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 百色中学-班级Service实现 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Service +public class BszxClassServiceImpl extends ServiceImpl implements BszxClassService { + @Resource + private RedisUtil redisUtil; + @Resource + private BszxPayService bszxPayService; + + @Override + public PageResult pageRel(BszxClassParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, id asc"); + List list = baseMapper.selectPageRel(page, param); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (param.getLimit() == null) { + list.forEach(item -> { + wrapper.clear(); +// wrapper.eq(BszxPay::getBranchName,item.getBranchName()); + wrapper.eq(BszxPay::getGradeName,item.getGradeName()); + wrapper.eq(BszxPay::getClassName, item.getName()); + item.setTotalMoney(bszxPayService.sumMoney(wrapper)); + updateById(item); + }); + } + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxClassParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, id asc"); + return page.sortRecords(list); + } + + @Override + public BszxClass getByIdRel(Integer id) { + BszxClassParam param = new BszxClassParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxEraServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxEraServiceImpl.java new file mode 100644 index 0000000..ad39481 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxEraServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.bszx.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.bszx.mapper.BszxEraMapper; +import com.gxwebsoft.bszx.service.BszxEraService; +import com.gxwebsoft.bszx.entity.BszxEra; +import com.gxwebsoft.bszx.param.BszxEraParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 百色中学-年代Service实现 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Service +public class BszxEraServiceImpl extends ServiceImpl implements BszxEraService { + + @Override + public PageResult pageRel(BszxEraParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxEraParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public BszxEra getByIdRel(Integer id) { + BszxEraParam param = new BszxEraParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxGradeServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxGradeServiceImpl.java new file mode 100644 index 0000000..1dded74 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxGradeServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.bszx.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.bszx.mapper.BszxGradeMapper; +import com.gxwebsoft.bszx.service.BszxGradeService; +import com.gxwebsoft.bszx.entity.BszxGrade; +import com.gxwebsoft.bszx.param.BszxGradeParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 百色中学-年级Service实现 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Service +public class BszxGradeServiceImpl extends ServiceImpl implements BszxGradeService { + + @Override + public PageResult pageRel(BszxGradeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, id asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxGradeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, id asc"); + return page.sortRecords(list); + } + + @Override + public BszxGrade getByIdRel(Integer id) { + BszxGradeParam param = new BszxGradeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayRankingServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayRankingServiceImpl.java new file mode 100644 index 0000000..22cee64 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayRankingServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.bszx.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.bszx.mapper.BszxPayRankingMapper; +import com.gxwebsoft.bszx.service.BszxPayRankingService; +import com.gxwebsoft.bszx.entity.BszxPayRanking; +import com.gxwebsoft.bszx.param.BszxPayRankingParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 百色中学-捐款排行Service实现 + * + * @author 科技小王子 + * @since 2025-03-25 08:54:09 + */ +@Service +public class BszxPayRankingServiceImpl extends ServiceImpl implements BszxPayRankingService { + + @Override + public PageResult pageRel(BszxPayRankingParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxPayRankingParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public BszxPayRanking getByIdRel(Integer id) { + BszxPayRankingParam param = new BszxPayRankingParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayServiceImpl.java b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayServiceImpl.java new file mode 100644 index 0000000..eb2c3d0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/bszx/service/impl/BszxPayServiceImpl.java @@ -0,0 +1,169 @@ +package com.gxwebsoft.bszx.service.impl; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.freewayso.image.combiner.ImageCombiner; +import com.freewayso.image.combiner.enums.OutputFormat; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.bszx.mapper.BszxPayMapper; +import com.gxwebsoft.bszx.service.BszxBmService; +import com.gxwebsoft.bszx.service.BszxPayService; +import com.gxwebsoft.bszx.entity.BszxPay; +import com.gxwebsoft.bszx.param.BszxPayParam; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.service.CmsArticleService; +import com.gxwebsoft.common.core.utils.ImageUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.File; +import java.math.BigDecimal; +import java.util.List; + +/** + * 百色中学-捐款记录Service实现 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Service +public class BszxPayServiceImpl extends ServiceImpl implements BszxPayService { + @Value("${config.upload-path}") + private String uploadPath; + @Value("${config.file-server}") + private String fileServer; + + @Resource + private CmsArticleService cmsArticleService; + @Resource + public BszxBmService bszxBmService; + @Resource + private BszxPayService bszxPayService; + + @Override + public PageResult pageRel(BszxPayParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("price desc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + list.forEach(item -> { + if(item.getId().equals(2088)){ + item.setFormName("捐款用于设立阙里校友奖学金"); + } + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(BszxPayParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("id desc"); + return page.sortRecords(list); + } + + @Override + public BszxPay getByIdRel(Integer id) { + BszxPayParam param = new BszxPayParam(); + param.setId(id); + final BszxPay item = param.getOne(baseMapper.selectListRel(param)); + final CmsArticle article = cmsArticleService.getById(item.getFormId()); + if (ObjectUtil.isNotEmpty(article)) { + item.setArticle(article); + } + return item; + } + + /** + * 生成捐款证书 ... + */ + @Override + public String generatePayCert(Integer id) throws Exception { + final BszxPay payCert = getByIdRel(id); + final CmsArticle item = cmsArticleService.getById(payCert.getFormId()); + final BszxBm bm = bszxBmService.getOne(new LambdaQueryWrapper().eq(BszxBm::getUserId, payCert.getUserId()).last("limit 1")); + final BigDecimal totalMoney = bszxPayService.sumMoney(new LambdaQueryWrapper().eq(BszxPay::getUserId, payCert.getUserId())); + if (StrUtil.isBlank(item.getAddress())) { + return null; + } + if (ObjectUtil.isNotEmpty(payCert)) { + //合成器(指定背景图和输出格式,整个图片的宽高和相关计算依赖于背景图,所以背景图的大小是个基准) + ImageCombiner combiner = new ImageCombiner("https://oss.wsdns.cn/20250420/811a380e8e124097aa0940a7c68a1f72.jpeg", OutputFormat.JPG); + //加图片元素:盖章 +// combiner.addImageElement("https://oss.wsdns.cn/20250304/6936b109b09b4919a3498ac5027e728b.png", 550, 926); + //加文本元素:姓名 + String str; + if (bm.getType().equals(0)) { + str = bm.getName().concat(" 校友"); + combiner.addTextElement(str, 32, 930, 450); + } else { + str = bm.getName(); + combiner.addTextElement(str, 22, 880, 450); + } +// combiner.addTextElement(bm.getName(), 32,900, 450); + //加文本元素:捐款证书内容 +// combiner.addTextElement(" 承您慷慨解囊,襄助百色市百色中学", 32,200, 650); +// combiner.addTextElement("百廿校庆“" + item.getTitle() + "”项目,捐赠人民币", 32,200, 700); + combiner.addTextElement(totalMoney + "", 32, 1330, 600); +// combiner.addTextElement(" 您对学校的支持,为我们共同教育理", 32,200, 800); +// combiner.addTextElement("想的实现增添了一份动力。", 32,200, 850); +// combiner.addTextElement(" 承蒙惠赠,隆情铭感,特颁此证,以资谢旌!", 32, 200, 900); +// combiner.addTextElement("百色市百色中学", 32,560, 1015); +// final Date createTime = payCert.getCreateTime(); +// combiner.addTextElement(DateUtil.format(createTime, "yyyy年MM月"), 28,586, 1060); +// combiner.addTextElement("2025年4月15日", 28,580, 1060); + + //执行图片合并 + combiner.combine(); + + if (!FileUtil.exist(uploadPath + "/file/poster/" + payCert.getTenantId() + "/pay")) { + FileUtil.mkdir(uploadPath + "/file/poster/" + payCert.getTenantId() + "/pay"); + } + String basePath = "/poster/" + payCert.getTenantId() + "/pay/big-" + id + ".jpg"; + String smallPath = "/poster/" + payCert.getTenantId() + "/pay/" + id + ".jpg"; + String filename = uploadPath + "/file" + basePath; + String smallFileName = uploadPath + "/file" + smallPath; + combiner.save(filename); + + File input = new File(filename); + File output = new File(smallFileName); + ImageUtil.adjustQuality(input, output, 0.8f); + if (input.exists()) { + input.delete(); + } + return fileServer + smallPath + "?r=" + RandomUtil.randomNumbers(4); + } + return null; + } + + @Override + public BigDecimal sumMoney(LambdaQueryWrapper wrapper) { + return baseMapper.selectSumMoney(wrapper); + } + + @Override + public BigDecimal total() { + try { + // 使用数据库聚合查询统计捐款总金额,性能更高 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + BigDecimal total = baseMapper.selectSumMoney(wrapper); + + if (total == null) { + total = BigDecimal.ZERO; + } + + return total; + + } catch (Exception e) { + // 异常时返回0,确保接口稳定性 + return BigDecimal.ZERO; + } + } +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsAdController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsAdController.java new file mode 100644 index 0000000..64a979b --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsAdController.java @@ -0,0 +1,119 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsAdService; +import com.gxwebsoft.cms.entity.CmsAd; +import com.gxwebsoft.cms.param.CmsAdParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 广告位控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "广告位管理") +@RestController +@RequestMapping("/api/cms/cms-ad") +public class CmsAdController extends BaseController { + @Resource + private CmsAdService cmsAdService; + + @Operation(summary = "分页查询广告位") + @GetMapping("/page") + public ApiResult> page(CmsAdParam param) { + // 使用关联查询 + return success(cmsAdService.pageRel(param)); + } + + @Operation(summary = "查询全部广告位") + @GetMapping() + public ApiResult> list(CmsAdParam param) { + // 使用关联查询 + return success(cmsAdService.listRel(param)); + } + + @Operation(summary = "根据id查询广告位") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + final CmsAd ad = cmsAdService.getByIdRel(id); + return success(ad); + } + + @Operation(summary = "根据code查询广告位") + @GetMapping("/getByCode/{code}") + public ApiResult getByCode(@PathVariable("code") String code) { + final CmsAd ad = cmsAdService.getByIdCode(code); + return success(ad); + } + + @Operation(summary = "添加广告位") + @PostMapping() + public ApiResult save(@RequestBody CmsAd cmsAd) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsAd.setUserId(loginUser.getUserId()); + } + if (cmsAdService.save(cmsAd)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改广告位") + @PutMapping() + public ApiResult update(@RequestBody CmsAd cmsAd) { + if (cmsAdService.updateById(cmsAd)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除广告位") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsAdService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加广告位") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsAdService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改广告位") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsAdService, "ad_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除广告位") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsAdService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsAdRecordController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsAdRecordController.java new file mode 100644 index 0000000..4188ead --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsAdRecordController.java @@ -0,0 +1,114 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsAdRecordService; +import com.gxwebsoft.cms.entity.CmsAdRecord; +import com.gxwebsoft.cms.param.CmsAdRecordParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 广告图片控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "广告图片管理") +@RestController +@RequestMapping("/api/cms/cms-ad-record") +public class CmsAdRecordController extends BaseController { + @Resource + private CmsAdRecordService cmsAdRecordService; + + @Operation(summary = "分页查询广告图片") + @GetMapping("/page") + public ApiResult> page(CmsAdRecordParam param) { + // 使用关联查询 + return success(cmsAdRecordService.pageRel(param)); + } + + @Operation(summary = "查询全部广告图片") + @GetMapping() + public ApiResult> list(CmsAdRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsAdRecordService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsAdRecordService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsAdRecord:list')") + @OperationLog + @Operation(summary = "根据id查询广告图片") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsAdRecordService.getById(id)); + // 使用关联查询 + //return success(cmsAdRecordService.getByIdRel(id)); + } + + @Operation(summary = "添加广告图片") + @PostMapping() + public ApiResult save(@RequestBody CmsAdRecord cmsAdRecord) { + if (cmsAdRecordService.save(cmsAdRecord)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改广告图片") + @PutMapping() + public ApiResult update(@RequestBody CmsAdRecord cmsAdRecord) { + if (cmsAdRecordService.updateById(cmsAdRecord)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除广告图片") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsAdRecordService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加广告图片") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsAdRecordService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改广告图片") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsAdRecordService, "ad_record_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除广告图片") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsAdRecordService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCategoryController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCategoryController.java new file mode 100644 index 0000000..1e540a0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCategoryController.java @@ -0,0 +1,111 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsArticleCategoryService; +import com.gxwebsoft.cms.entity.CmsArticleCategory; +import com.gxwebsoft.cms.param.CmsArticleCategoryParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 文章分类表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "文章分类表管理") +@RestController +@RequestMapping("/api/cms/cms-article-category") +public class CmsArticleCategoryController extends BaseController { + @Resource + private CmsArticleCategoryService cmsArticleCategoryService; + + @Operation(summary = "分页查询文章分类表") + @GetMapping("/page") + public ApiResult> page(CmsArticleCategoryParam param) { + // 使用关联查询 + return success(cmsArticleCategoryService.pageRel(param)); + } + + @Operation(summary = "查询全部文章分类表") + @GetMapping() + public ApiResult> list(CmsArticleCategoryParam param) { + // 使用关联查询 + return success(cmsArticleCategoryService.listRel(param)); + } + + @Operation(summary = "根据id查询文章分类表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsArticleCategoryService.getByIdRel(id)); + } + + @Operation(summary = "添加文章分类表") + @PostMapping() + public ApiResult save(@RequestBody CmsArticleCategory cmsArticleCategory) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsArticleCategory.setUserId(loginUser.getUserId()); + } + if (cmsArticleCategoryService.save(cmsArticleCategory)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改文章分类表") + @PutMapping() + public ApiResult update(@RequestBody CmsArticleCategory cmsArticleCategory) { + if (cmsArticleCategoryService.updateById(cmsArticleCategory)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除文章分类表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsArticleCategoryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加文章分类表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsArticleCategoryService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改文章分类表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsArticleCategoryService, "category_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除文章分类表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsArticleCategoryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCommentController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCommentController.java new file mode 100644 index 0000000..51ed99e --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCommentController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsArticleCommentService; +import com.gxwebsoft.cms.entity.CmsArticleComment; +import com.gxwebsoft.cms.param.CmsArticleCommentParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 文章评论表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "文章评论表管理") +@RestController +@RequestMapping("/api/cms/cms-article-comment") +public class CmsArticleCommentController extends BaseController { + @Resource + private CmsArticleCommentService cmsArticleCommentService; + + @Operation(summary = "分页查询文章评论表") + @GetMapping("/page") + public ApiResult> page(CmsArticleCommentParam param) { + // 使用关联查询 + return success(cmsArticleCommentService.pageRel(param)); + } + + @Operation(summary = "查询全部文章评论表") + @GetMapping() + public ApiResult> list(CmsArticleCommentParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsArticleCommentService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsArticleCommentService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsArticleComment:list')") + @OperationLog + @Operation(summary = "根据id查询文章评论表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsArticleCommentService.getById(id)); + // 使用关联查询 + //return success(cmsArticleCommentService.getByIdRel(id)); + } + + @Operation(summary = "添加文章评论表") + @PostMapping() + public ApiResult save(@RequestBody CmsArticleComment cmsArticleComment) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsArticleComment.setUserId(loginUser.getUserId()); + } + if (cmsArticleCommentService.save(cmsArticleComment)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改文章评论表") + @PutMapping() + public ApiResult update(@RequestBody CmsArticleComment cmsArticleComment) { + if (cmsArticleCommentService.updateById(cmsArticleComment)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除文章评论表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsArticleCommentService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加文章评论表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsArticleCommentService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改文章评论表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsArticleCommentService, "comment_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除文章评论表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsArticleCommentService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsArticleContentController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleContentController.java new file mode 100644 index 0000000..4f3e3d6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleContentController.java @@ -0,0 +1,113 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsArticleContentService; +import com.gxwebsoft.cms.entity.CmsArticleContent; +import com.gxwebsoft.cms.param.CmsArticleContentParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 文章记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "文章记录表管理") +@RestController +@RequestMapping("/api/cms/cms-article-content") +public class CmsArticleContentController extends BaseController { + @Resource + private CmsArticleContentService cmsArticleContentService; + + @Operation(summary = "分页查询文章记录表") + @GetMapping("/page") + public ApiResult> page(CmsArticleContentParam param) { + // 使用关联查询 + return success(cmsArticleContentService.pageRel(param)); + } + + @Operation(summary = "查询全部文章记录表") + @GetMapping() + public ApiResult> list(CmsArticleContentParam param) { +// PageParam page = new PageParam<>(param); +// page.setDefaultOrder("create_time desc"); +// return success(cmsArticleContentService.list(page.getOrderWrapper())); + // 使用关联查询 + return success(cmsArticleContentService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsArticleContent:list')") + @OperationLog + @Operation(summary = "根据id查询文章记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { +// return success(cmsArticleContentService.getById(id)); + // 使用关联查询 + return success(cmsArticleContentService.getByIdRel(id)); + } + + @Operation(summary = "添加文章记录表") + @PostMapping() + public ApiResult save(@RequestBody CmsArticleContent cmsArticleContent) { + if (cmsArticleContentService.save(cmsArticleContent)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改文章记录表") + @PutMapping() + public ApiResult update(@RequestBody CmsArticleContent cmsArticleContent) { + if (cmsArticleContentService.updateById(cmsArticleContent)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除文章记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsArticleContentService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加文章记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsArticleContentService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改文章记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsArticleContentService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除文章记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsArticleContentService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsArticleController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleController.java new file mode 100644 index 0000000..5da719a --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleController.java @@ -0,0 +1,362 @@ +package com.gxwebsoft.cms.controller; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.gxwebsoft.cms.entity.*; +import com.gxwebsoft.cms.param.CmsArticleImportParam; +import com.gxwebsoft.cms.service.*; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.param.CmsArticleParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.*; + +import static com.gxwebsoft.common.core.constants.ArticleConstants.CACHE_KEY_ARTICLE; + +/** + * 文章控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Slf4j +@Validated +@Tag(name = "文章管理") +@RestController +@RequestMapping("/api/cms/cms-article") +public class CmsArticleController extends BaseController { + @Resource + private CmsArticleService cmsArticleService; + @Resource + private CmsArticleContentService articleContentService; + @Resource + @Lazy + private CmsNavigationService cmsNavigationService; + @Resource + private CmsModelService cmsModelService; + @Resource + private UserService userService; + @Resource + private RedisUtil redisUtil; + + private static final long CACHE_MINUTES = 30L; + + @Operation(summary = "分页查询文章") + @GetMapping("/page") + public ApiResult> page(CmsArticleParam param) { + // 使用关联查询 + return success(cmsArticleService.pageRel(param)); + } + + @Operation(summary = "查询全部文章") + @GetMapping() + public ApiResult> list(CmsArticleParam param) { + // 使用关联查询 + return success(cmsArticleService.listRel(param)); + } + + @Operation(summary = "根据id查询文章") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") @NotNull Integer id) { + final CmsArticle article = cmsArticleService.getByIdRel(id); + if (ObjectUtil.isNotEmpty(article)) { + return success(article); + } + return fail("文章ID不存在",null); + } + + @PreAuthorize("hasAuthority('cms:cmsArticle:save')") + @Operation(summary = "添加文章") + @PostMapping() + public ApiResult save(@RequestBody @Valid CmsArticle article) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + article.setUserId(loginUser.getUserId()); + article.setAuthor(loginUser.getNickname()); + article.setMerchantId(loginUser.getMerchantId()); + if (cmsArticleService.saveRel(article)) { + return success("添加成功"); + } + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsArticle:update')") + @Operation(summary = "修改文章") + @PutMapping() + public ApiResult update(@RequestBody CmsArticle article) { + if (cmsArticleService.updateByIdRel(article)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsArticle:remove')") + @Operation(summary = "删除文章") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsArticleService.removeById(id)) { + redisUtil.delete(CACHE_KEY_ARTICLE + id); + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsArticle:save')") + @Operation(summary = "批量添加文章") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsArticleService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsArticle:update')") + @Operation(summary = "批量修改文章") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsArticleService, "article_id")) { + // 删除缓存 + final List ids = batchParam.getIds(); + ids.forEach(id -> { + redisUtil.delete(CACHE_KEY_ARTICLE + id); + }); + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsArticle:remove')") + @Operation(summary = "批量删除文章") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsArticleService.removeByIds(ids)) { + // 删除缓存 + ids.forEach(id -> { + redisUtil.delete(CACHE_KEY_ARTICLE + id); + }); + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "读取上一篇") + @GetMapping("/getPrevious/{id}") + public ApiResult getPrevious(@PathVariable("id") Integer id) { + final CmsArticle item = cmsArticleService.getById(id); + if (ObjectUtil.isEmpty(item)) { + return success("没有找到上一篇文章",null); + } + CmsArticle article; + // TODO 按排序号规则 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.lt(CmsArticle::getSortNumber, item.getSortNumber()); + wrapper.eq(CmsArticle::getStatus, 0); + wrapper.eq(CmsArticle::getType, 0); + wrapper.eq(CmsArticle::getCategoryId, item.getCategoryId()); + wrapper.orderByDesc(CmsArticle::getSortNumber); + wrapper.last("limit 1"); + article = cmsArticleService.getOne(wrapper); + if (ObjectUtil.isNotEmpty(article)) { + return success(article); + } + // TODO 按ID排序 + LambdaQueryWrapper wrapper2 = new LambdaQueryWrapper<>(); + wrapper2.lt(CmsArticle::getArticleId, item.getArticleId()); + wrapper2.eq(CmsArticle::getStatus, 0); + wrapper2.eq(CmsArticle::getCategoryId, item.getCategoryId()); + wrapper2.last("limit 1"); + wrapper2.orderByDesc(CmsArticle::getArticleId); + article = cmsArticleService.getOne(wrapper2); + return success(article); + } + + @Operation(summary = "读取下一篇") + @GetMapping("/getNext/{id}") + public ApiResult getNext(@PathVariable("id") Integer id) { + CmsArticle item = cmsArticleService.getById(id); + if (ObjectUtil.isEmpty(item)) { + return success("没有找到下一篇文章",null); + } + CmsArticle article; + // TODO 按排序号规则 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.gt(CmsArticle::getSortNumber, item.getSortNumber()); + wrapper.eq(CmsArticle::getStatus, 0); + wrapper.eq(CmsArticle::getType, 0); + wrapper.eq(CmsArticle::getCategoryId, item.getCategoryId()); + wrapper.orderByAsc(CmsArticle::getSortNumber); + wrapper.last("limit 1"); + article = cmsArticleService.getOne(wrapper); + if (ObjectUtil.isNotEmpty(article)) { + return success(article); + } + // TODO 按ID排序 + LambdaQueryWrapper wrapper2 = new LambdaQueryWrapper<>(); + wrapper2.gt(CmsArticle::getArticleId, item.getArticleId()); + wrapper2.eq(CmsArticle::getStatus, 0); + wrapper2.eq(CmsArticle::getCategoryId, item.getCategoryId()); + wrapper2.last("limit 1"); + wrapper2.orderByAsc(CmsArticle::getArticleId); + article = cmsArticleService.getOne(wrapper2); + return success(article); + } + + @Operation(summary = "统计信息") + @GetMapping("/data") + public ApiResult> data(CmsArticleParam param) { + Map data = new HashMap<>(); + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (param.getMerchantId() != null) { + wrapper.eq(CmsArticle::getMerchantId, param.getMerchantId()); + } + + long totalNum = cmsArticleService.count( + wrapper.eq(CmsArticle::getDeleted, 0).eq(CmsArticle::getStatus, 0) + ); + data.put("totalNum", Math.toIntExact(totalNum)); + + long totalNum2 = cmsArticleService.count( + wrapper.eq(CmsArticle::getStatus, 1) + ); + data.put("totalNum2", Math.toIntExact(totalNum2)); + + long totalNum3 = cmsArticleService.count( + wrapper.gt(CmsArticle::getStatus, 1) + ); + data.put("totalNum3", Math.toIntExact(totalNum3)); + + return success(data); + } + + @Operation(summary = "密码校验") + @GetMapping("/checkArticlePassword") + public ApiResult checkArticlePassword(CmsArticle param) { + if (!userService.comparePassword(param.getPassword(), param.getPassword2())) { + return fail("密码不正确"); + } + return success("密码正确"); + } + + /** + * excel批量导入文章 + */ + @PreAuthorize("hasAuthority('cms:cmsArticle:save')") + @Operation(summary = "批量导入文章") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/import") + public ApiResult> importBatch(MultipartFile file) { + ImportParams importParams = new ImportParams(); + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), CmsArticleImportParam.class, importParams); + list.forEach(d -> { + CmsArticle item = JSONUtil.parseObject(JSONUtil.toJSONString(d), CmsArticle.class); + assert item != null; + if (ObjectUtil.isNotEmpty(item)) { + if (item.getStatus() == null) { + item.setStatus(1); + } + if (cmsArticleService.save(item)) { + CmsArticleContent content = new CmsArticleContent(); + content.setArticleId(item.getArticleId()); + content.setContent(item.getContent()); + articleContentService.save(content); + } + } + }); + return success("成功导入" + list.size() + "条", null); + } catch (Exception e) { + e.printStackTrace(); + } + return fail("导入失败", null); + } + + @Operation(summary = "按标签分页查询") + @GetMapping("/findTags") + public ApiResult> findTags(CmsArticleParam param) { + final String tags = param.getTags(); + if (StringUtils.isNotBlank(tags)) { + final String[] split = tags.split(","); + final List list = Arrays.asList(split); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); + if (StrUtil.isNotBlank(tags)) { + for (String s : list) { + queryWrapper.or().like(CmsArticle::getTags, s); +// queryWrapper.or().apply("LOCATE(" + "'" + s + "'," + "tags" + ") > 0"); + } + } + if (param.getCategoryId() != null) { + queryWrapper.eq(CmsArticle::getCategoryId, param.getCategoryId()); + } + if (param.getDetail() != null) { + queryWrapper.eq(CmsArticle::getDetail, param.getDetail()); + } + queryWrapper.last("limit 8"); + List articles = cmsArticleService.list(queryWrapper); + return success(articles); + } + return success("", null); + } + + @Operation(summary = "按标签分页查询") + @GetMapping("/pageTags") + public ApiResult> pageTags(CmsArticleParam param) { + final String tags = param.getTags(); + if (StringUtils.isNotBlank(tags)) { + final String[] split = tags.split(","); + final List list = Arrays.asList(split); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + for (String s : list) { + queryWrapper.or().like(CmsArticle::getTags, s); + } + queryWrapper.orderByDesc(CmsArticle::getCreateTime); + queryWrapper.last("limit 100"); + List articles = cmsArticleService.list(queryWrapper); + if (!articles.isEmpty()) { + List navigationList = cmsNavigationService.listByIds(articles.stream().map(CmsArticle::getCategoryId).toList()); + for (CmsArticle article : articles) { + for (CmsNavigation navigation : navigationList) { + if (article.getCategoryId().equals(navigation.getNavigationId())) { + article.setCategoryName(navigation.getTitle()); + } + } + } + } + return success(articles); + } + return success("", null); + } + + @Operation(summary = "按IDS查询") + @GetMapping("/getByIds") + public ApiResult> getByIds(CmsArticleParam param) { + // 使用关联查询 + return success(cmsArticleService.list(new LambdaQueryWrapper().in(CmsArticle::getArticleId, param.getArticleIds()))); + } +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCountController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCountController.java new file mode 100644 index 0000000..f80a684 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleCountController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsArticleCountService; +import com.gxwebsoft.cms.entity.CmsArticleCount; +import com.gxwebsoft.cms.param.CmsArticleCountParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 点赞文章控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "点赞文章管理") +@RestController +@RequestMapping("/api/cms/cms-article-count") +public class CmsArticleCountController extends BaseController { + @Resource + private CmsArticleCountService cmsArticleCountService; + + @Operation(summary = "分页查询点赞文章") + @GetMapping("/page") + public ApiResult> page(CmsArticleCountParam param) { + // 使用关联查询 + return success(cmsArticleCountService.pageRel(param)); + } + + @Operation(summary = "查询全部点赞文章") + @GetMapping() + public ApiResult> list(CmsArticleCountParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsArticleCountService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsArticleCountService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsArticleCount:list')") + @OperationLog + @Operation(summary = "根据id查询点赞文章") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsArticleCountService.getById(id)); + // 使用关联查询 + //return success(cmsArticleCountService.getByIdRel(id)); + } + + @Operation(summary = "添加点赞文章") + @PostMapping() + public ApiResult save(@RequestBody CmsArticleCount cmsArticleCount) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsArticleCount.setUserId(loginUser.getUserId()); + } + if (cmsArticleCountService.save(cmsArticleCount)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改点赞文章") + @PutMapping() + public ApiResult update(@RequestBody CmsArticleCount cmsArticleCount) { + if (cmsArticleCountService.updateById(cmsArticleCount)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除点赞文章") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsArticleCountService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加点赞文章") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsArticleCountService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改点赞文章") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsArticleCountService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除点赞文章") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsArticleCountService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsArticleLikeController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleLikeController.java new file mode 100644 index 0000000..2b9d2ef --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsArticleLikeController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsArticleLikeService; +import com.gxwebsoft.cms.entity.CmsArticleLike; +import com.gxwebsoft.cms.param.CmsArticleLikeParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 点赞文章控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "点赞文章管理") +@RestController +@RequestMapping("/api/cms/cms-article-like") +public class CmsArticleLikeController extends BaseController { + @Resource + private CmsArticleLikeService cmsArticleLikeService; + + @Operation(summary = "分页查询点赞文章") + @GetMapping("/page") + public ApiResult> page(CmsArticleLikeParam param) { + // 使用关联查询 + return success(cmsArticleLikeService.pageRel(param)); + } + + @Operation(summary = "查询全部点赞文章") + @GetMapping() + public ApiResult> list(CmsArticleLikeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsArticleLikeService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsArticleLikeService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsArticleLike:list')") + @OperationLog + @Operation(summary = "根据id查询点赞文章") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsArticleLikeService.getById(id)); + // 使用关联查询 + //return success(cmsArticleLikeService.getByIdRel(id)); + } + + @Operation(summary = "添加点赞文章") + @PostMapping() + public ApiResult save(@RequestBody CmsArticleLike cmsArticleLike) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsArticleLike.setUserId(loginUser.getUserId()); + } + if (cmsArticleLikeService.save(cmsArticleLike)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改点赞文章") + @PutMapping() + public ApiResult update(@RequestBody CmsArticleLike cmsArticleLike) { + if (cmsArticleLikeService.updateById(cmsArticleLike)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除点赞文章") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsArticleLikeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加点赞文章") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsArticleLikeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改点赞文章") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsArticleLikeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除点赞文章") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsArticleLikeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsDesignController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsDesignController.java new file mode 100644 index 0000000..e69d81b --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsDesignController.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.cms.controller; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsDesignService; +import com.gxwebsoft.cms.entity.CmsDesign; +import com.gxwebsoft.cms.param.CmsDesignParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 页面管理记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "页面管理记录表管理") +@RestController +@RequestMapping("/api/cms/cms-design") +public class CmsDesignController extends BaseController { + @Resource + private CmsDesignService cmsDesignService; + @Resource + private CmsNavigationService cmsNavigationService; + + @Operation(summary = "分页查询页面管理记录表") + @GetMapping("/page") + public ApiResult> page(CmsDesignParam param) { + // 使用关联查询 + return success(cmsDesignService.pageRel(param)); + } + + @Operation(summary = "查询全部页面管理记录表") + @GetMapping() + public ApiResult> list(CmsDesignParam param) { + // 使用关联查询 + return success(cmsDesignService.listRel(param)); + } + + @Operation(summary = "根据id查询页面管理记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsDesignService.getByIdRel(id)); + } + + @Operation(summary = "添加页面管理记录表") + @PostMapping() + public ApiResult save(@RequestBody CmsDesign cmsDesign) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsDesign.setUserId(loginUser.getUserId()); + } + if (cmsDesignService.save(cmsDesign)) { + try { + cmsNavigationService.update(new LambdaUpdateWrapper().set(CmsNavigation::getBanner, cmsDesign.getPhoto()).eq(CmsNavigation::getNavigationId,cmsDesign.getCategoryId())); + // 同步翻译英文版 + cmsDesignService.translate(cmsDesign); + return success("添加成功"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return fail("添加失败"); + } + + @Operation(summary = "修改页面管理记录表") + @PutMapping() + public ApiResult update(@RequestBody CmsDesign cmsDesign) { + if (cmsDesignService.updateById(cmsDesign)) { + // 同步翻译英文版 + cmsDesignService.translate(cmsDesign); + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除页面管理记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsDesignService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加页面管理记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsDesignService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改页面管理记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsDesignService, "page_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除页面管理记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsDesignService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsDesignRecordController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsDesignRecordController.java new file mode 100644 index 0000000..d921d01 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsDesignRecordController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsDesignRecordService; +import com.gxwebsoft.cms.entity.CmsDesignRecord; +import com.gxwebsoft.cms.param.CmsDesignRecordParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 页面组件表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "页面组件表管理") +@RestController +@RequestMapping("/api/cms/cms-design-record") +public class CmsDesignRecordController extends BaseController { + @Resource + private CmsDesignRecordService cmsDesignRecordService; + + @Operation(summary = "分页查询页面组件表") + @GetMapping("/page") + public ApiResult> page(CmsDesignRecordParam param) { + // 使用关联查询 + return success(cmsDesignRecordService.pageRel(param)); + } + + @Operation(summary = "查询全部页面组件表") + @GetMapping() + public ApiResult> list(CmsDesignRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsDesignRecordService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsDesignRecordService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsDesignRecord:list')") + @OperationLog + @Operation(summary = "根据id查询页面组件表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsDesignRecordService.getById(id)); + // 使用关联查询 + //return success(cmsDesignRecordService.getByIdRel(id)); + } + + @Operation(summary = "添加页面组件表") + @PostMapping() + public ApiResult save(@RequestBody CmsDesignRecord cmsDesignRecord) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsDesignRecord.setUserId(loginUser.getUserId()); + } + if (cmsDesignRecordService.save(cmsDesignRecord)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改页面组件表") + @PutMapping() + public ApiResult update(@RequestBody CmsDesignRecord cmsDesignRecord) { + if (cmsDesignRecordService.updateById(cmsDesignRecord)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除页面组件表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsDesignRecordService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加页面组件表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsDesignRecordService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改页面组件表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsDesignRecordService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除页面组件表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsDesignRecordService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsDomainController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsDomainController.java new file mode 100644 index 0000000..5487036 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsDomainController.java @@ -0,0 +1,166 @@ +package com.gxwebsoft.cms.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.cms.mapper.CmsDomainMapper; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsDomainService; +import com.gxwebsoft.cms.entity.CmsDomain; +import com.gxwebsoft.cms.param.CmsDomainParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 网站域名记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Tag(name = "网站域名记录表管理") +@RestController +@RequestMapping("/api/cms/cms-domain") +public class CmsDomainController extends BaseController { + @Resource + private CmsDomainService cmsDomainService; + @Resource + private CmsDomainMapper cmsDomainMapper; + @Resource + private RedisUtil redisUtil; + + @Operation(summary = "分页查询网站域名记录表") + @GetMapping("/page") + public ApiResult> page(CmsDomainParam param) { + // 使用关联查询 + return success(cmsDomainService.pageRel(param)); + } + + @Operation(summary = "查询全部网站域名记录表") + @GetMapping() + public ApiResult> list(CmsDomainParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsDomainService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsDomainService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsDomain:list')") + @OperationLog + @Operation(summary = "根据id查询网站域名记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsDomainService.getById(id)); + // 使用关联查询 + //return success(cmsDomainService.getByIdRel(id)); + } + + @Operation(summary = "添加网站域名记录表") + @PostMapping() + public ApiResult save(@RequestBody CmsDomain cmsDomain) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsDomain.setUserId(loginUser.getUserId()); + } + if (cmsDomainService.save(cmsDomain)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改网站域名记录表") + @PutMapping() + public ApiResult update(@RequestBody CmsDomain cmsDomain) { + if (cmsDomainService.updateById(cmsDomain)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除网站域名记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsDomainService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加网站域名记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsDomainService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改网站域名记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsDomainService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除网站域名记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsDomainService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "查询授权域名信息") + @GetMapping("/getTenantIdByDomain") + public ApiResult getTenantIdByDomain(CmsDomainParam param) { + final CmsDomain domain = cmsDomainService.getOne(new LambdaQueryWrapper().eq(CmsDomain::getDomain, param.getDomain()).last("limit 1")); + return success(domain); + } + + @Operation(summary = "授权二级域名") + @PostMapping("/domain") + public ApiResult domain(@RequestBody CmsDomain cmsDomain) { + final User loginUser = getLoginUser(); + String key = "Domain:" + cmsDomain.getDomain(); + final Integer tenantId = loginUser.getTenantId(); + final CmsDomain domain = cmsDomainService.getOne(new LambdaQueryWrapper() + .eq(CmsDomain::getWebsiteId, cmsDomain.getWebsiteId()).last("limit 1")); + if (ObjectUtil.isNotEmpty(domain)) { + // 重写缓存 + redisUtil.set(key,tenantId); + domain.setDomain(cmsDomain.getDomain()); + cmsDomainService.updateById(domain); + return success("授权成功"); + } + if(ObjectUtil.isEmpty(domain)){ + cmsDomain.setUserId(loginUser.getUserId()); + cmsDomain.setSortNumber(100); + cmsDomain.setStatus(1); + cmsDomain.setHostName("@"); + cmsDomain.setWebsiteId(cmsDomain.getWebsiteId()); + cmsDomain.setTenantId(tenantId); + if(cmsDomainService.save(cmsDomain)){ + // 重写缓存 + redisUtil.set(key,tenantId); + return success("授权成功"); + } + } + return fail("授权失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsFormController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsFormController.java new file mode 100644 index 0000000..ce9ab34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsFormController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsFormService; +import com.gxwebsoft.cms.entity.CmsForm; +import com.gxwebsoft.cms.param.CmsFormParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 表单设计表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "表单设计表管理") +@RestController +@RequestMapping("/api/cms/cms-form") +public class CmsFormController extends BaseController { + @Resource + private CmsFormService cmsFormService; + + @Operation(summary = "分页查询表单设计表") + @GetMapping("/page") + public ApiResult> page(CmsFormParam param) { + // 使用关联查询 + return success(cmsFormService.pageRel(param)); + } + + @Operation(summary = "查询全部表单设计表") + @GetMapping() + public ApiResult> list(CmsFormParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsFormService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsFormService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsForm:list')") + @OperationLog + @Operation(summary = "根据id查询表单设计表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsFormService.getById(id)); + // 使用关联查询 + //return success(cmsFormService.getByIdRel(id)); + } + + @Operation(summary = "添加表单设计表") + @PostMapping() + public ApiResult save(@RequestBody CmsForm cmsForm) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsForm.setUserId(loginUser.getUserId()); + } + if (cmsFormService.save(cmsForm)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改表单设计表") + @PutMapping() + public ApiResult update(@RequestBody CmsForm cmsForm) { + if (cmsFormService.updateById(cmsForm)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除表单设计表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsFormService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加表单设计表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsFormService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改表单设计表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsFormService, "form_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除表单设计表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsFormService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsFormRecordController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsFormRecordController.java new file mode 100644 index 0000000..0aedacc --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsFormRecordController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsFormRecordService; +import com.gxwebsoft.cms.entity.CmsFormRecord; +import com.gxwebsoft.cms.param.CmsFormRecordParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 表单数据记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "表单数据记录表管理") +@RestController +@RequestMapping("/api/cms/cms-form-record") +public class CmsFormRecordController extends BaseController { + @Resource + private CmsFormRecordService cmsFormRecordService; + + @Operation(summary = "分页查询表单数据记录表") + @GetMapping("/page") + public ApiResult> page(CmsFormRecordParam param) { + // 使用关联查询 + return success(cmsFormRecordService.pageRel(param)); + } + + @Operation(summary = "查询全部表单数据记录表") + @GetMapping() + public ApiResult> list(CmsFormRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(cmsFormRecordService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(cmsFormRecordService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsFormRecord:list')") + @OperationLog + @Operation(summary = "根据id查询表单数据记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(cmsFormRecordService.getById(id)); + // 使用关联查询 + //return success(cmsFormRecordService.getByIdRel(id)); + } + + @Operation(summary = "添加表单数据记录表") + @PostMapping() + public ApiResult save(@RequestBody CmsFormRecord cmsFormRecord) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsFormRecord.setUserId(loginUser.getUserId()); + } + if (cmsFormRecordService.save(cmsFormRecord)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改表单数据记录表") + @PutMapping() + public ApiResult update(@RequestBody CmsFormRecord cmsFormRecord) { + if (cmsFormRecordService.updateById(cmsFormRecord)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除表单数据记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsFormRecordService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加表单数据记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsFormRecordService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改表单数据记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsFormRecordService, "form_record_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除表单数据记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsFormRecordService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsLangController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsLangController.java new file mode 100644 index 0000000..475e9cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsLangController.java @@ -0,0 +1,113 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsLangService; +import com.gxwebsoft.cms.entity.CmsLang; +import com.gxwebsoft.cms.param.CmsLangParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 国际化控制器 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Tag(name = "国际化管理") +@RestController +@RequestMapping("/api/cms/cms-lang") +public class CmsLangController extends BaseController { + @Resource + private CmsLangService cmsLangService; + + @Operation(summary = "分页查询国际化") + @GetMapping("/page") + public ApiResult> page(CmsLangParam param) { + // 使用关联查询 + return success(cmsLangService.pageRel(param)); + } + + @Operation(summary = "查询全部国际化") + @GetMapping() + public ApiResult> list(CmsLangParam param) { + // 使用关联查询 + return success(cmsLangService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:list')") + @Operation(summary = "根据id查询国际化") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsLangService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:save')") + @Operation(summary = "添加国际化") + @PostMapping() + public ApiResult save(@RequestBody CmsLang cmsLang) { + if (cmsLangService.save(cmsLang)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:update')") + @Operation(summary = "修改国际化") + @PutMapping() + public ApiResult update(@RequestBody CmsLang cmsLang) { + if (cmsLangService.updateById(cmsLang)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:remove')") + @Operation(summary = "删除国际化") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsLangService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:save')") + @Operation(summary = "批量添加国际化") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsLangService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:update')") + @Operation(summary = "批量修改国际化") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsLangService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLang:reomve')") + @Operation(summary = "批量删除国际化") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsLangService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsLangLogController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsLangLogController.java new file mode 100644 index 0000000..a881156 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsLangLogController.java @@ -0,0 +1,113 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsLangLogService; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.param.CmsLangLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 国际化记录启用控制器 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Tag(name = "国际化记录启用管理") +@RestController +@RequestMapping("/api/cms/cms-lang-log") +public class CmsLangLogController extends BaseController { + @Resource + private CmsLangLogService cmsLangLogService; + + @Operation(summary = "分页查询国际化记录启用") + @GetMapping("/page") + public ApiResult> page(CmsLangLogParam param) { + // 使用关联查询 + return success(cmsLangLogService.pageRel(param)); + } + + @Operation(summary = "查询全部国际化记录启用") + @GetMapping() + public ApiResult> list(CmsLangLogParam param) { + // 使用关联查询 + return success(cmsLangLogService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:list')") + @Operation(summary = "根据id查询国际化记录启用") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsLangLogService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:save')") + @Operation(summary = "添加国际化记录启用") + @PostMapping() + public ApiResult save(@RequestBody CmsLangLog cmsLangLog) { + if (cmsLangLogService.save(cmsLangLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:update')") + @Operation(summary = "修改国际化记录启用") + @PutMapping() + public ApiResult update(@RequestBody CmsLangLog cmsLangLog) { + if (cmsLangLogService.updateById(cmsLangLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:remove')") + @Operation(summary = "删除国际化记录启用") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsLangLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:save')") + @Operation(summary = "批量添加国际化记录启用") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsLangLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:update')") + @Operation(summary = "批量修改国际化记录启用") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsLangLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsLangLog:remove')") + @Operation(summary = "批量删除国际化记录启用") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsLangLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsLinkController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsLinkController.java new file mode 100644 index 0000000..03f798c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsLinkController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsLinkService; +import com.gxwebsoft.cms.entity.CmsLink; +import com.gxwebsoft.cms.param.CmsLinkParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 常用链接控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "常用链接管理") +@RestController +@RequestMapping("/api/cms/cms-link") +public class CmsLinkController extends BaseController { + @Resource + private CmsLinkService cmsLinkService; + + @Operation(summary = "分页查询常用链接") + @GetMapping("/page") + public ApiResult> page(CmsLinkParam param) { + // 使用关联查询 + return success(cmsLinkService.pageRel(param)); + } + + @Operation(summary = "查询全部常用链接") + @GetMapping() + public ApiResult> list(CmsLinkParam param) { + // 使用关联查询 + return success(cmsLinkService.listRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsLink:list')") + @OperationLog + @Operation(summary = "根据id查询常用链接") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsLinkService.getByIdRel(id)); + } + + @Operation(summary = "添加常用链接") + @PostMapping() + public ApiResult save(@RequestBody CmsLink cmsLink) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsLink.setUserId(loginUser.getUserId()); + } + if (cmsLinkService.save(cmsLink)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改常用链接") + @PutMapping() + public ApiResult update(@RequestBody CmsLink cmsLink) { + if (cmsLinkService.updateById(cmsLink)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除常用链接") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsLinkService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加常用链接") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsLinkService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改常用链接") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsLinkService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除常用链接") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsLinkService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsMainController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsMainController.java new file mode 100644 index 0000000..4242c80 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsMainController.java @@ -0,0 +1,25 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.common.core.web.BaseController; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 网站应用主入口 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Slf4j +@Tag(name = "网站应用") +@RestController +@RequestMapping("/api/cms") +public class CmsMainController extends BaseController { + @Resource + private CmsWebsiteService cmsWebsiteService; + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsModelController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsModelController.java new file mode 100644 index 0000000..c139eaa --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsModelController.java @@ -0,0 +1,118 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsModelService; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.param.CmsModelParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 模型控制器 + * + * @author 科技小王子 + * @since 2024-11-26 15:44:53 + */ +@Tag(name = "模型管理") +@RestController +@RequestMapping("/api/cms/cms-model") +public class CmsModelController extends BaseController { + @Resource + private CmsModelService cmsModelService; + + @Operation(summary = "分页查询模型") + @GetMapping("/page") + public ApiResult> page(CmsModelParam param) { + // 使用关联查询 + return success(cmsModelService.pageRel(param)); + } + + @Operation(summary = "查询全部模型") + @GetMapping() + public ApiResult> list(CmsModelParam param) { + // 使用关联查询 + return success(cmsModelService.listRel(param)); + } + + @Operation(summary = "根据id查询模型") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsModelService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('cms:cmsModel:save')") + @Operation(summary = "添加模型") + @PostMapping() + public ApiResult save(@RequestBody CmsModel cmsModel) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsModel.setUserId(loginUser.getUserId()); + } + if (cmsModelService.save(cmsModel)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsModel:update')") + @Operation(summary = "修改模型") + @PutMapping() + public ApiResult update(@RequestBody CmsModel cmsModel) { + if (cmsModelService.updateById(cmsModel)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsModel:remove')") + @Operation(summary = "删除模型") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsModelService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsModel:save')") + @Operation(summary = "批量添加模型") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsModelService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsModel:update')") + @Operation(summary = "批量修改模型") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsModelService, "model_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsModel:remvoe')") + @Operation(summary = "批量删除模型") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsModelService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsNavigationController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsNavigationController.java new file mode 100644 index 0000000..10707c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsNavigationController.java @@ -0,0 +1,190 @@ +package com.gxwebsoft.cms.controller; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.cms.entity.CmsDesign; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.service.CmsDesignService; +import com.gxwebsoft.cms.service.CmsModelService; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.param.CmsNavigationParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; + + +import javax.annotation.Resource; +import java.util.List; + +/** + * 网站导航记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Tag(name = "网站导航记录表管理") +@RestController +@RequestMapping("/api/cms/cms-navigation") +public class CmsNavigationController extends BaseController { + @Resource + private CmsNavigationService cmsNavigationService; + @Resource + private CmsModelService cmsModelService; + @Resource + private CmsDesignService cmsDesignService; + @Resource + private UserService userService; + @Resource + private RedisUtil redisUtil; + + + private static final String SITE_INFO_KEY_PREFIX = "SiteInfo:"; + + @Operation(summary = "分页查询网站导航记录表") + @GetMapping("/page") + public ApiResult> page(CmsNavigationParam param) { + // 使用关联查询 + return success(cmsNavigationService.pageRel(param)); + } + + @Operation(summary = "查询全部网站导航记录表") + @GetMapping() + public ApiResult> list(CmsNavigationParam param) { + // 使用关联查询 + return success(cmsNavigationService.listRel(param)); + } + + @Operation(summary = "根据id查询网站导航记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsNavigationService.getByIdRel(id)); + } + + @Operation(summary = "添加网站导航记录表") + @PostMapping() + public ApiResult save(@RequestBody CmsNavigation cmsNavigation) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsNavigation.setUserId(loginUser.getUserId()); + cmsNavigation.setTenantId(loginUser.getTenantId()); + } + // 去除前面空格 + cmsNavigation.setTitle(StrUtil.trimStart(cmsNavigation.getTitle())); + if (cmsNavigationService.save(cmsNavigation)) { + // 添加成功事务处理 + cmsNavigationService.saveAsync(cmsNavigation); + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改网站导航记录表") + @PutMapping() + public ApiResult update(@RequestBody CmsNavigation cmsNavigation) { + if (cmsNavigationService.updateById(cmsNavigation)) { + // 修改成功事务处理 + cmsNavigationService.saveAsync(cmsNavigation); + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除网站导航记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsNavigationService.removeById(id)) { + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加网站导航记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsNavigationService.saveBatch(list)) { + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改网站导航记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsNavigationService, "navigation_id")) { + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除网站导航记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsNavigationService.removeByIds(ids)) { + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "获取树形结构的网站导航数据") + @GetMapping("/tree") + public ApiResult> tree(CmsNavigationParam param) { + param.setHide(0); + final List navigations = cmsNavigationService.listRel(param); + return success(CommonUtil.toTreeData(navigations, 0, CmsNavigation::getParentId, CmsNavigation::getNavigationId, CmsNavigation::setChildren)); + } + + @Operation(summary = "根据path获取导航") + @GetMapping("/getNavigationByPath") + public ApiResult getNavigationByPath(CmsNavigationParam param) { + final CmsNavigation navigation = cmsNavigationService.getOne(new LambdaUpdateWrapper().eq(CmsNavigation::getModel, param.getModel()).last("limit 1")); + if (ObjectUtil.isNotEmpty(navigation)) { + // 页面元素 + final CmsDesign design = cmsDesignService.getOne(new LambdaUpdateWrapper().eq(CmsDesign::getCategoryId, navigation.getNavigationId()).last("limit 1")); + // 模型信息 + final CmsModel model = cmsModelService.getOne(new LambdaQueryWrapper().eq(CmsModel::getModel, navigation.getModel()).last("limit 1")); + navigation.setBanner(model.getBanner()); + // 上级导航 + if (!navigation.getParentId().equals(0)) { + final CmsNavigation parent = cmsNavigationService.getById(navigation.getParentId()); + navigation.setParentPath(parent.getPath()); + navigation.setParentName(parent.getTitle()); + } + // 页面信息 + navigation.setDesign(design); + // 页面布局 + if (ObjectUtil.isNotEmpty(design)) { + navigation.setLayout(design.getLayout()); + } + } + return success(navigation); + } + + @Operation(summary = "密码校验") + @GetMapping("/checkNavigationPassword") + public ApiResult checkNavigationPassword(CmsNavigationParam param) { + if (!userService.comparePassword(param.getPassword(), param.getPassword2())) { + return fail("密码不正确"); + } + return success("密码正确"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsStatisticsController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsStatisticsController.java new file mode 100644 index 0000000..b15e8c4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsStatisticsController.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsStatisticsService; +import com.gxwebsoft.cms.entity.CmsStatistics; +import com.gxwebsoft.cms.param.CmsStatisticsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 站点统计信息表控制器 + * + * @author 科技小王子 + * @since 2025-07-25 12:32:06 + */ +@Tag(name = "站点统计信息表管理") +@RestController +@RequestMapping("/api/cms/cms-statistics") +public class CmsStatisticsController extends BaseController { + @Resource + private CmsStatisticsService cmsStatisticsService; + + @Operation(summary = "分页查询站点统计信息表") + @GetMapping("/page") + public ApiResult> page(CmsStatisticsParam param) { + // 使用关联查询 + return success(cmsStatisticsService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:list')") + @Operation(summary = "查询全部站点统计信息表") + @GetMapping() + public ApiResult> list(CmsStatisticsParam param) { + // 使用关联查询 + return success(cmsStatisticsService.listRel(param)); + } + + @Operation(summary = "根据id查询站点统计信息表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsStatisticsService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:save')") + @OperationLog + @Operation(summary = "添加站点统计信息表") + @PostMapping() + public ApiResult save(@RequestBody CmsStatistics cmsStatistics) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsStatistics.setUserId(loginUser.getUserId()); + } + if (cmsStatisticsService.save(cmsStatistics)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:update')") + @OperationLog + @Operation(summary = "修改站点统计信息表") + @PutMapping() + public ApiResult update(@RequestBody CmsStatistics cmsStatistics) { + if (cmsStatisticsService.updateById(cmsStatistics)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:remove')") + @OperationLog + @Operation(summary = "删除站点统计信息表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsStatisticsService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:save')") + @OperationLog + @Operation(summary = "批量添加站点统计信息表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsStatisticsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:update')") + @OperationLog + @Operation(summary = "批量修改站点统计信息表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsStatisticsService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsStatistics:remove')") + @OperationLog + @Operation(summary = "批量删除站点统计信息表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsStatisticsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsTemplateController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsTemplateController.java new file mode 100644 index 0000000..1b3fe9e --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsTemplateController.java @@ -0,0 +1,118 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsTemplateService; +import com.gxwebsoft.cms.entity.CmsTemplate; +import com.gxwebsoft.cms.param.CmsTemplateParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 网站模版控制器 + * + * @author 科技小王子 + * @since 2025-01-21 14:21:16 + */ +@Tag(name = "网站模版管理") +@RestController +@RequestMapping("/api/cms/cms-template") +public class CmsTemplateController extends BaseController { + @Resource + private CmsTemplateService cmsTemplateService; + + @Operation(summary = "分页查询网站模版") + @GetMapping("/page") + public ApiResult> page(CmsTemplateParam param) { + // 使用关联查询 + return success(cmsTemplateService.pageRel(param)); + } + + @Operation(summary = "查询全部网站模版") + @GetMapping() + public ApiResult> list(CmsTemplateParam param) { + // 使用关联查询 + return success(cmsTemplateService.listRel(param)); + } + + @Operation(summary = "根据id查询网站模版") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsTemplateService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('cms:cmsTemplate:save')") + @Operation(summary = "添加网站模版") + @PostMapping() + public ApiResult save(@RequestBody CmsTemplate cmsTemplate) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsTemplate.setUserId(loginUser.getUserId()); + } + if (cmsTemplateService.save(cmsTemplate)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsTemplate:update')") + @Operation(summary = "修改网站模版") + @PutMapping() + public ApiResult update(@RequestBody CmsTemplate cmsTemplate) { + if (cmsTemplateService.updateById(cmsTemplate)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsTemplate:remove')") + @Operation(summary = "删除网站模版") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsTemplateService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsTemplate:save')") + @Operation(summary = "批量添加网站模版") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsTemplateService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsTemplate:update')") + @Operation(summary = "批量修改网站模版") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsTemplateService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsTemplate:remove')") + @Operation(summary = "批量删除网站模版") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsTemplateService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteController.java new file mode 100644 index 0000000..1073846 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteController.java @@ -0,0 +1,526 @@ +package com.gxwebsoft.cms.controller; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.cms.entity.*; +import com.gxwebsoft.cms.param.CmsNavigationParam; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.cms.service.CmsWebsiteFieldService; +import com.gxwebsoft.cms.service.CmsWebsiteSettingService; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.cms.param.CmsWebsiteParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.*; + +import java.util.concurrent.TimeUnit; + +/** + * 网站信息记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Slf4j +@Tag(name = "网站信息记录表管理") +@RestController +@RequestMapping("/api/cms/cms-website") +public class CmsWebsiteController extends BaseController { + @Resource + private CmsWebsiteService cmsWebsiteService; + @Resource + private RedisUtil redisUtil; + @Resource + private CmsWebsiteFieldService cmsWebsiteFieldService; + @Resource + private CmsNavigationService cmsNavigationService; + @Resource + private CmsWebsiteSettingService cmsWebsiteSettingService; + + private static final String SITE_INFO_KEY_PREFIX = "SiteInfo:"; + private static final String MP_INFO_KEY_PREFIX = "MpInfo:"; + private static final String SELECT_PAYMENT_KEY_PREFIX = "SelectPayment:"; + private static final String SYS_DOMAIN_SUFFIX = ".websoft.top"; + private static final String DOMAIN_SUFFIX = ".wsdns.cn"; + + @Operation(summary = "分页查询网站信息记录表") + @GetMapping("/page") + public ApiResult> page(CmsWebsiteParam param) { + // 使用关联查询 + return success(cmsWebsiteService.pageRel(param)); + } + + @Operation(summary = "查询全部网站信息记录表") + @GetMapping() + public ApiResult> list(CmsWebsiteParam param) { + // 使用关联查询 + return success(cmsWebsiteService.listRel(param)); + } + + @Operation(summary = "分页查询网站信息记录表") + @GetMapping("/pageAll") + public ApiResult> pageAll(CmsWebsiteParam param) { + return success(cmsWebsiteService.pageRelAll(param)); + } + + @Operation(summary = "根据id查询网站信息记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsWebsiteService.getByIdRel(id)); + } + + @Operation(summary = "根据id查询网站信息记录表") + @GetMapping("/getAll/{id}") + public ApiResult getAll(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsWebsiteService.getByIdRelAll(id)); + } + + @PreAuthorize("hasAuthority('cms:website:save')") + @Operation(summary = "添加网站信息记录表") + @PostMapping() + public ApiResult save(@RequestBody CmsWebsite cmsWebsite) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + cmsWebsite.setLoginUser(loginUser); + return success("创建成功", cmsWebsiteService.create(cmsWebsite)); + } + return fail("创建失败"); + } + + @PreAuthorize("hasAuthority('cms:website:update')") + @Operation(summary = "修改网站信息记录表") + @PutMapping() + public ApiResult update(@RequestBody CmsWebsite cmsWebsite) { + if (cmsWebsiteService.updateById(cmsWebsite)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:website:update')") + @Operation(summary = "修改网站信息记录表") + @PutMapping("/updateAll") + public ApiResult updateAll(@RequestBody CmsWebsite cmsWebsite) { + if (cmsWebsiteService.updateByIdAll(cmsWebsite)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:website:remove')") + @Operation(summary = "删除网站信息记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsWebsiteService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:website:remove')") + @Operation(summary = "删除网站信息记录表") + @DeleteMapping("/removeAll/{id}") + public ApiResult removeAll(@PathVariable("id") Integer id) { + if (cmsWebsiteService.removeByIdAll(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:website:save')") + @Operation(summary = "批量添加网站信息记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsWebsiteService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:website:update')") + @Operation(summary = "批量修改网站信息记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsWebsiteService, "website_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:website:remove')") + @Operation(summary = "批量删除网站信息记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsWebsiteService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "网站基本信息") + @GetMapping("/getSiteInfo") + public ApiResult getSiteInfo() { + try { + Integer tenantId = getTenantId(); + if (ObjectUtil.isEmpty(tenantId)) { + return fail("租户ID不能为空", null); + } + + String key = SITE_INFO_KEY_PREFIX + tenantId; + + // 尝试从缓存获取 + try { + final String siteInfo = redisUtil.get(key); + if (StrUtil.isNotBlank(siteInfo)) { + log.info("从缓存获取网站信息: = {}", key); + // 可以启用缓存返回,但先注释掉确保数据最新 + // return success(JSONUtil.parseObject(siteInfo, CmsWebsite.class)); + } + } catch (Exception e) { + log.warn("获取缓存失败: {}", e.getMessage()); + } + + // 获取站点信息 + CmsWebsite website = null; + try { + website = cmsWebsiteService.getOne(new LambdaQueryWrapper() + .eq(CmsWebsite::getTenantId, tenantId) + .eq(CmsWebsite::getDeleted, 0) + .last("limit 1")); + } catch (Exception e) { + log.error("查询站点信息失败: {}", e.getMessage(), e); + return fail("查询站点信息失败", null); + } + + // 创建默认站点 + if (ObjectUtil.isEmpty(website)) { + return success("请先创建站点...", null); + } + + // 安全地构建网站信息 + try { + buildSafeWebsiteInfo(website); + } catch (Exception e) { + log.error("构建网站信息失败: {}", e.getMessage(), e); + return fail("构建网站信息失败", null); + } + + // 缓存结果 + try { + redisUtil.set(key, website, 1L, TimeUnit.DAYS); + } catch (Exception e) { + log.warn("缓存网站信息失败: {}", e.getMessage()); + } + + return success(website); + + } catch (Exception e) { + log.error("获取网站信息异常: {}", e.getMessage(), e); + return fail("获取网站信息失败: " + e.getMessage(), null); + } + } + + /** + * 安全地构建网站信息 + */ + private void buildSafeWebsiteInfo(CmsWebsite website) { + if (website == null) { + throw new IllegalArgumentException("网站对象不能为空"); + } + + // 1. 设置网站状态 + try { + setWebsiteStatus(website); + } catch (Exception e) { + log.warn("设置网站状态失败: {}", e.getMessage()); + website.setStatus(0); // 默认状态 + website.setStatusText("状态未知"); + } + + // 2. 构建配置信息 + try { + HashMap config = buildSafeWebsiteConfig(website); + website.setConfig(config); + } catch (Exception e) { + log.warn("构建网站配置失败: {}", e.getMessage()); + website.setConfig(new HashMap<>()); + } + + // 3. 设置导航信息 + try { + setSafeWebsiteNavigation(website); + } catch (Exception e) { + log.warn("设置网站导航失败: {}", e.getMessage()); + website.setTopNavs(new ArrayList<>()); + website.setBottomNavs(new ArrayList<>()); + } + + // 4. 设置网站设置信息 + try { + setWebsiteSetting(website); + } catch (Exception e) { + log.warn("设置网站设置失败: {}", e.getMessage()); + // 设置为null,让前端知道没有设置信息 + } + + // 5. 构建服务器时间(使用LocalDateTime) + try { + HashMap serverTime = buildSafeServerTime(); + website.setServerTime(serverTime); + } catch (Exception e) { + log.warn("构建服务器时间失败: {}", e.getMessage()); + HashMap defaultTime = new HashMap<>(); + defaultTime.put("now", java.time.LocalDateTime.now().toString()); + website.setServerTime(defaultTime); + } + } + + private void setWebsiteStatus(CmsWebsite website) { + if (!website.getRunning().equals(1)) { + // 未开通 + if (website.getRunning().equals(0)) { + website.setStatusIcon("error"); + website.setStatusText("该站点未开通"); + } + // 维护中 + if (website.getRunning().equals(2)) { + website.setStatusIcon("warning"); + } + // 已关闭 + if (website.getRunning().equals(3)) { + website.setStatusIcon("error"); + website.setStatusText("已关闭"); + } + // 已欠费停机 + if (website.getRunning().equals(4)) { + website.setStatusIcon("error"); + website.setStatusText("已欠费停机"); + } + // 违规关停 + if (website.getRunning().equals(5)) { + website.setStatusIcon("error"); + website.setStatusText("违规关停"); + } + } + } + + private HashMap buildWebsiteConfig(CmsWebsite website) { + return buildSafeWebsiteConfig(website); + } + + private HashMap buildSafeWebsiteConfig(CmsWebsite website) { + HashMap config = new HashMap<>(); + + try { + // 获取网站字段配置 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CmsWebsiteField::getDeleted, 0); + final List fields = cmsWebsiteFieldService.list(wrapper); + + if (fields != null && !fields.isEmpty()) { + fields.forEach(field -> { + if (field != null && StrUtil.isNotBlank(field.getName())) { + config.put(field.getName(), field.getValue() != null ? field.getValue() : ""); + } + }); + } + } catch (Exception e) { + log.warn("获取网站字段配置失败: {}", e.getMessage()); + } + + // 安全地设置域名信息 + try { + config.put("SysDomain", getSafeSysDomain(website)); + config.put("Domain", getSafeDomain(website)); + } catch (Exception e) { + log.warn("设置域名信息失败: {}", e.getMessage()); + config.put("SysDomain", website.getTenantId() + ".websoft.top"); + config.put("Domain", website.getTenantId() + ".wsdns.cn"); + } + + return config; + } + + private String getSysDomain(CmsWebsite website) { + return getSafeSysDomain(website); + } + + private String getDomain(CmsWebsite website) { + return getSafeDomain(website); + } + + private String getSafeSysDomain(CmsWebsite website) { + if (website == null || website.getTenantId() == null) { + return "unknown.websoft.top"; + } + return StrUtil.isNotBlank(website.getWebsiteCode()) ? + website.getWebsiteCode() + SYS_DOMAIN_SUFFIX : + website.getTenantId() + SYS_DOMAIN_SUFFIX; + } + + private String getSafeDomain(CmsWebsite website) { + if (website == null || website.getTenantId() == null) { + return "unknown.wsdns.cn"; + } + + if (StrUtil.isNotBlank(website.getDomain())) { + return website.getDomain(); + } + if (StrUtil.isNotBlank(website.getWebsiteCode())) { + return website.getWebsiteCode() + DOMAIN_SUFFIX; + } + return website.getTenantId() + DOMAIN_SUFFIX; + } + + private void setWebsiteNavigation(CmsWebsite website) { + setSafeWebsiteNavigation(website); + } + + private void setSafeWebsiteNavigation(CmsWebsite website) { + if (website == null) { + return; + } + + // 设置顶部导航 + try { + final CmsNavigationParam topParam = new CmsNavigationParam(); + topParam.setHide(0); + topParam.setTop(0); + topParam.setBottom(null); + final List topNavs = cmsNavigationService.listRel(topParam); + + if (topNavs != null && !topNavs.isEmpty()) { + try { + website.setTopNavs(CommonUtil.toTreeData(topNavs, 0, + CmsNavigation::getParentId, + CmsNavigation::getNavigationId, + CmsNavigation::setChildren)); + } catch (Exception e) { + log.warn("构建顶部导航树失败: {}", e.getMessage()); + website.setTopNavs(new ArrayList<>(topNavs)); + } + } else { + website.setTopNavs(new ArrayList<>()); + } + } catch (Exception e) { + log.warn("获取顶部导航失败: {}", e.getMessage()); + website.setTopNavs(new ArrayList<>()); + } + + // 设置底部导航 + try { + final CmsNavigationParam bottomParam = new CmsNavigationParam(); + bottomParam.setHide(0); + bottomParam.setTop(null); + bottomParam.setBottom(0); + final List bottomNavs = cmsNavigationService.listRel(bottomParam); + + if (bottomNavs != null && !bottomNavs.isEmpty()) { + try { + website.setBottomNavs(CommonUtil.toTreeData(bottomNavs, 0, + CmsNavigation::getParentId, + CmsNavigation::getNavigationId, + CmsNavigation::setChildren)); + } catch (Exception e) { + log.warn("构建底部导航树失败: {}", e.getMessage()); + website.setBottomNavs(new ArrayList<>(bottomNavs)); + } + } else { + website.setBottomNavs(new ArrayList<>()); + } + } catch (Exception e) { + log.warn("获取底部导航失败: {}", e.getMessage()); + website.setBottomNavs(new ArrayList<>()); + } + } + + private void setWebsiteSetting(CmsWebsite website) { + final CmsWebsiteSetting setting = cmsWebsiteSettingService.getOne(new LambdaQueryWrapper().eq(CmsWebsiteSetting::getWebsiteId, website.getWebsiteId())); + if (ObjectUtil.isNotEmpty(setting)) { + website.setSetting(setting); + } + } + + private HashMap buildServerTime() { + return buildSafeServerTime(); + } + + private HashMap buildSafeServerTime() { + HashMap serverTime = new HashMap<>(); + + try { + // 使用 LocalDateTime 替代 DateTime + java.time.LocalDateTime now = java.time.LocalDateTime.now(); + java.time.LocalDate today = java.time.LocalDate.now(); + + // 当前时间 + serverTime.put("now", now.toString()); + + // 今天日期 + serverTime.put("today", today.toString()); + + // 明天日期 + java.time.LocalDate tomorrow = today.plusDays(1); + serverTime.put("tomorrow", tomorrow.toString()); + + // 后天日期 + java.time.LocalDate afterDay = today.plusDays(2); + serverTime.put("afterDay", afterDay.toString()); + + // 今天星期几 (1=Monday, 7=Sunday) + int week = today.getDayOfWeek().getValue(); + serverTime.put("week", week); + + // 下周同一天 + java.time.LocalDate nextWeek = today.plusWeeks(1); + serverTime.put("nextWeek", nextWeek.toString()); + + // 时间戳 + serverTime.put("timestamp", System.currentTimeMillis()); + + } catch (Exception e) { + log.error("构建服务器时间失败: {}", e.getMessage(), e); + // 提供最基本的时间信息 + serverTime.put("now", java.time.LocalDateTime.now().toString()); + serverTime.put("today", java.time.LocalDate.now().toString()); + serverTime.put("timestamp", System.currentTimeMillis()); + } + + return serverTime; + } + + @Operation(summary = "清除缓存") + @DeleteMapping("/clearSiteInfo/{key}") + public ApiResult clearSiteInfo(@PathVariable("key") String key) { + log.info("清除缓存开始,key: {}", key); + // 清除指定key + redisUtil.delete(key); + // 清除缓存 + redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + log.info("清除缓存结束,key: {}", SITE_INFO_KEY_PREFIX.concat(getTenantId().toString())); + // 清除小程序缓存 + redisUtil.delete(MP_INFO_KEY_PREFIX.concat(getTenantId().toString())); + // 选择支付方式 + redisUtil.delete(SELECT_PAYMENT_KEY_PREFIX.concat(getTenantId().toString())); + return success("清除成功"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteFieldController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteFieldController.java new file mode 100644 index 0000000..a121ef6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteFieldController.java @@ -0,0 +1,118 @@ +package com.gxwebsoft.cms.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsWebsiteFieldService; +import com.gxwebsoft.cms.entity.CmsWebsiteField; +import com.gxwebsoft.cms.param.CmsWebsiteFieldParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; + +/** + * 应用参数控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Tag(name = "应用参数管理") +@RestController +@RequestMapping("/api/cms/cms-website-field") +public class CmsWebsiteFieldController extends BaseController { + @Resource + private CmsWebsiteFieldService cmsWebsiteFieldService; + + @Operation(summary = "分页查询应用参数") + @GetMapping("/page") + public ApiResult> page(CmsWebsiteFieldParam param) { + // 使用关联查询 + return success(cmsWebsiteFieldService.pageRel(param)); + } + + @Operation(summary = "查询全部应用参数") + @GetMapping() + public ApiResult> list(CmsWebsiteFieldParam param) { + // 使用关联查询 + return success(cmsWebsiteFieldService.listRel(param)); + } + + @Operation(summary = "根据id查询应用参数") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(cmsWebsiteFieldService.getByIdRel(id)); + } + + @Operation(summary = "添加应用参数") + @PostMapping() + public ApiResult save(@RequestBody CmsWebsiteField cmsWebsiteField) { + if (cmsWebsiteFieldService.save(cmsWebsiteField)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改应用参数") + @PutMapping() + public ApiResult update(@RequestBody CmsWebsiteField cmsWebsiteField) { + if (cmsWebsiteFieldService.updateById(cmsWebsiteField)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除应用参数") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsWebsiteFieldService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加应用参数") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsWebsiteFieldService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改应用参数") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsWebsiteFieldService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除应用参数") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsWebsiteFieldService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "获取网站配置参数-对象形式") + @GetMapping("/config") + public ApiResult getConfig(CmsWebsiteFieldParam param) { + // 使用关联查询 + final List fields = cmsWebsiteFieldService.listRel(param); + + HashMap config = new HashMap<>(); + fields.forEach(d -> { + config.put(d.getName(), d.getValue()); + }); + return success(config); + } +} diff --git a/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteSettingController.java b/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteSettingController.java new file mode 100644 index 0000000..49424de --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/controller/CmsWebsiteSettingController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.cms.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.cms.service.CmsWebsiteSettingService; +import com.gxwebsoft.cms.entity.CmsWebsiteSetting; +import com.gxwebsoft.cms.param.CmsWebsiteSettingParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 网站设置控制器 + * + * @author 科技小王子 + * @since 2025-02-19 01:35:44 + */ +@Tag(name = "网站设置管理") +@RestController +@RequestMapping("/api/cms/cms-website-setting") +public class CmsWebsiteSettingController extends BaseController { + @Resource + private CmsWebsiteSettingService cmsWebsiteSettingService; + + @Operation(summary = "分页查询网站设置") + @GetMapping("/page") + public ApiResult> page(CmsWebsiteSettingParam param) { + // 使用关联查询 + return success(cmsWebsiteSettingService.pageRel(param)); + } + + @Operation(summary = "查询全部网站设置") + @GetMapping() + public ApiResult> list(CmsWebsiteSettingParam param) { + // 使用关联查询 + return success(cmsWebsiteSettingService.listRel(param)); + } + + @Operation(summary = "根据id查询网站设置") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + final CmsWebsiteSetting cmsWebsiteSetting = cmsWebsiteSettingService.getOne(new LambdaQueryWrapper().eq(CmsWebsiteSetting::getWebsiteId, id)); + if(ObjectUtil.isEmpty(cmsWebsiteSetting)){ + final CmsWebsiteSetting setting = new CmsWebsiteSetting(); + setting.setWebsiteId(id); + cmsWebsiteSettingService.save(setting); + return success(cmsWebsiteSettingService.getOne(new LambdaQueryWrapper().eq(CmsWebsiteSetting::getWebsiteId, id))); + } + return success(cmsWebsiteSetting); + } + + @PreAuthorize("hasAuthority('cms:cmsWebsiteSetting:save')") + @Operation(summary = "添加网站设置") + @PostMapping() + public ApiResult save(@RequestBody CmsWebsiteSetting cmsWebsiteSetting) { + if (cmsWebsiteSettingService.save(cmsWebsiteSetting)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:website:update')") + @Operation(summary = "修改网站设置") + @PutMapping() + public ApiResult update(@RequestBody CmsWebsiteSetting cmsWebsiteSetting) { + if (cmsWebsiteSettingService.updateById(cmsWebsiteSetting)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsWebsiteSetting:remove')") + @Operation(summary = "删除网站设置") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (cmsWebsiteSettingService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsWebsiteSetting:save')") + @Operation(summary = "批量添加网站设置") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (cmsWebsiteSettingService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsWebsiteSetting:update')") + @Operation(summary = "批量修改网站设置") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(cmsWebsiteSettingService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('cms:cmsWebsiteSetting:remove')") + @Operation(summary = "批量删除网站设置") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (cmsWebsiteSettingService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsAd.java b/src/main/java/com/gxwebsoft/cms/entity/CmsAd.java new file mode 100644 index 0000000..1f6996a --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsAd.java @@ -0,0 +1,106 @@ +package com.gxwebsoft.cms.entity; + +import cn.hutool.core.util.DesensitizedUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.gxwebsoft.common.core.utils.JSONUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 广告位 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsAd对象", description = "广告位") +public class CmsAd implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "ad_id", type = IdType.AUTO) + private Integer adId; + + @Schema(description = "类型") + private Integer type; + + @Schema(description = "唯一标识") + private String code; + + @Schema(description = "栏目ID") + private Integer categoryId; + + @Schema(description = "栏目名称") + @TableField(exist = false) + private String categoryName; + + @Schema(description = "广告位名称") + private String name; + + @Schema(description = "宽") + private String width; + + @Schema(description = "高") + private String height; + + @Schema(description = "边框") + private String border; + + @Schema(description = "CSS样式") + private String style; + + @Schema(description = "广告图片") + private String images; + + @Schema(description = "广告图片") + @TableField(exist = false) + private JSONArray imageList; + + @Schema(description = "路由/链接地址") + private String path; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + public JSONArray getImageList() { + return JSON.parseArray(this.images); + } +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsAdRecord.java b/src/main/java/com/gxwebsoft/cms/entity/CmsAdRecord.java new file mode 100644 index 0000000..8b4cab4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsAdRecord.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 广告图片 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsAdRecord对象", description = "广告图片") +public class CmsAdRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "ad_record_id", type = IdType.AUTO) + private Integer adRecordId; + + @Schema(description = "广告标题") + private String title; + + @Schema(description = "图片地址") + private String path; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "广告位ID") + private Integer adId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsAdVo.java b/src/main/java/com/gxwebsoft/cms/entity/CmsAdVo.java new file mode 100644 index 0000000..d86e281 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsAdVo.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "图片DTO", description = "图片DTO") +public class CmsAdVo implements Serializable { + + @Schema(description = "ID") + @TableField(exist = false) + private Integer uid; + + @Schema(description = "名称") + @TableField(exist = false) + private String title; + + @Schema(description = "图片路径") + @TableField(exist = false) + private String url; + + @Schema(description = "视频地址") + @TableField(exist = false) + private String video; + + @Schema(description = "状态") + @TableField(exist = false) + private String status; + + @Schema(description = "图片宽") + @TableField(exist = false) + private Integer width; + + @Schema(description = "图片高") + @TableField(exist = false) + private Integer height; +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsArticle.java b/src/main/java/com/gxwebsoft/cms/entity/CmsArticle.java new file mode 100644 index 0000000..cad1e40 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsArticle.java @@ -0,0 +1,264 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsArticle对象", description = "文章") +public class CmsArticle implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "文章ID") + @TableId(value = "article_id", type = IdType.AUTO) + private Integer articleId; + + @Schema(description = "文章标题") + private String title; + + @Schema(description = "文章类型 0常规 1视频") + private Integer type; + + @Schema(description = "文章模型") + private String model; + + @Schema(description = "内容模板页面") + private String detail; + + @Schema(description = "banner") + @TableField(exist = false) + private String banner; + + @Schema(description = "访问路径") + @TableField(exist = false) + private String path; + + @Schema(description = "列表显示方式(10小图展示 20大图展示)") + private Integer showType; + + @Schema(description = "话题") + private String topic; + + @Schema(description = "标签") + private String tags; + + @Schema(description = "文章分类ID") + private Integer categoryId; + + @Schema(description = "当前分类") + @TableField(exist = false) + private String categoryName; + + @Schema(description = "父级分类ID") + private Integer parentId; + + @Schema(description = "父级分类") + @TableField(exist = false) + private String parentName; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "封面图宽度") + private Integer imageWidth; + + @Schema(description = "封面图高度") + private Integer imageHeight; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "来源") + private String source; + + @Schema(description = "产品概述") + private String overview; + + @Schema(description = "虚拟阅读量(仅用作展示)") + private Integer virtualViews; + + @Schema(description = "实际阅读量") + private Integer actualViews; + + @Schema(description = "评分") + private BigDecimal rate; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + private Integer permission; + + @Schema(description = "访问密码") + private String password; + + @Schema(description = "验证密码(前端回传)") + @TableField(exist = false) + private String password2; + + @Schema(description = "发布来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "文章附件") + private String files; + + @Schema(description = "视频地址") + private String video; + + @Schema(description = "接受的文件类型") + private String accept; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "点赞数") + private Integer likes; + + @Schema(description = "评论数") + private Integer commentNumbers; + + @Schema(description = "提醒谁看") + private String toUsers; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "作者") + private String author; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "关联默认语言的文章ID,用于同步翻译更新") + private Integer langArticleId; + + @Schema(description = "是否启用自动翻译") + private Boolean translation; + + @Schema(description = "编辑器类型 1 富文本编辑器 2 Markdown编辑器") + private Integer editor; + + @Schema(description = "PDF地址") + private String pdfUrl; + + @Schema(description = "版本号") + private Integer version; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "项目ID") + private Long projectId; + + @Schema(description = "商户名称") + @TableField(exist = false) + private String merchantName; + + @Schema(description = "商户名称") + @TableField(exist = false) + private String merchantAvatar; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + private Integer status; + + @Schema(description = "状态文本") + @TableField(exist = false) + private String statusText; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "是否更新") + @TableField(exist = false) + private Boolean isUpdate; + + @Schema(description = "文章内容") + @TableField(exist = false) + private String content; + + @Schema(description = "已报名人数") + private Integer bmUsers; + + public String getStatusText() { + if (this.status == 0) { + return "已发布"; + } + if (this.status == 1) { + return "待审核"; + } + if (this.status == 2) { + return "已驳回"; + } + if (this.status == 3) { + return "违规内容"; + } + return ""; + } +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsArticleCategory.java b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleCategory.java new file mode 100644 index 0000000..1f03126 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleCategory.java @@ -0,0 +1,94 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章分类表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsArticleCategory对象", description = "文章分类表") +public class CmsArticleCategory implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "文章分类ID") + @TableId(value = "category_id", type = IdType.AUTO) + private Integer categoryId; + + @Schema(description = "分类标识") + private String categoryCode; + + @Schema(description = "分类名称") + private String title; + + @Schema(description = "类型 0列表 1单页 2外链") + private Integer type; + + @Schema(description = "分类图片") + private String image; + + @Schema(description = "上级分类ID") + private Integer parentId; + + @Schema(description = "路由/链接地址") + private String path; + + @Schema(description = "组件路径") + private String component; + + @Schema(description = "绑定的页面") + private Integer pageId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "文章数量") + private Integer count; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "是否显示在首页") + private Integer showIndex; + + @Schema(description = "状态, 0正常, 1禁用") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsArticleComment.java b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleComment.java new file mode 100644 index 0000000..3afd431 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleComment.java @@ -0,0 +1,79 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章评论表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsArticleComment对象", description = "文章评论表") +public class CmsArticleComment implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "评价ID") + @TableId(value = "comment_id", type = IdType.AUTO) + private Integer commentId; + + @Schema(description = "文章ID") + private Integer articleId; + + @Schema(description = "评分 (10好评 20中评 30差评)") + private Integer score; + + @Schema(description = "评价内容") + private String content; + + @Schema(description = "是否为图片评价") + private Integer isPicture; + + @Schema(description = "评论者ID") + private Integer userId; + + @Schema(description = "被评价者ID") + private Integer toUserId; + + @Schema(description = "回复的评论ID") + private Integer replyCommentId; + + @Schema(description = "回复者ID") + private Integer replyUserId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0未读, 1已读") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsArticleContent.java b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleContent.java new file mode 100644 index 0000000..7001e06 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleContent.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsArticleContent对象", description = "文章记录表") +public class CmsArticleContent implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "文章ID") + private Integer articleId; + + @Schema(description = "文章内容") + private String content; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsArticleCount.java b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleCount.java new file mode 100644 index 0000000..c145531 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleCount.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 点赞文章 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsArticleCount对象", description = "点赞文章") +public class CmsArticleCount implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "文章ID") + private Integer articleId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsArticleLike.java b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleLike.java new file mode 100644 index 0000000..1e1137a --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsArticleLike.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 点赞文章 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsArticleLike对象", description = "点赞文章") +public class CmsArticleLike implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "文章ID") + private Integer articleId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsDesign.java b/src/main/java/com/gxwebsoft/cms/entity/CmsDesign.java new file mode 100644 index 0000000..ce90519 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsDesign.java @@ -0,0 +1,123 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 页面管理记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsDesign对象", description = "页面管理记录表") +public class CmsDesign implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "page_id", type = IdType.AUTO) + private Integer pageId; + + @Schema(description = "页面") + private String name; + + @Schema(description = "所属栏目ID") + private Integer categoryId; + + @Schema(description = "所属栏目") + @TableField(exist = false) + private String categoryName; + + @Schema(description = "页面模型") + private String model; + + @Schema(description = "页面关键词") + private String keywords; + + @Schema(description = "页面描述") + private String description; + + @Schema(description = "路由地址") + @TableField(exist = false) + private String path; + + @Schema(description = "组件路径") + @TableField(exist = false) + private String component; + + @Schema(description = "缩列图") + private String photo; + + @Schema(description = "购买链接") + private String buyUrl; + + @Schema(description = "页面样式") + private String style; + + @Schema(description = "页面内容") + private String content; + + @Schema(description = "是否开启布局") + private Boolean showLayout; + + @Schema(description = "页面布局") + @TableField(exist = false) + private String layout; + + @Schema(description = "是否显示banner") + private Boolean showBanner; + + @Schema(description = "是否显示Button") + private Boolean showButton; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "用于同步翻译内容") + @TableField(exist = false) + private Integer langCategoryId; + + @Schema(description = "是否启用自动翻译") + private Boolean translation; + + @Schema(description = "设为首页") + private Integer home; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsDesignRecord.java b/src/main/java/com/gxwebsoft/cms/entity/CmsDesignRecord.java new file mode 100644 index 0000000..1b5552b --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsDesignRecord.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 页面组件表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsDesignRecord对象", description = "页面组件表") +public class CmsDesignRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "关联导航ID") + private Integer navigationId; + + @Schema(description = "组件") + private String title; + + @Schema(description = "组件标识") + private String dictCode; + + @Schema(description = "组件样式") + private String styles; + + @Schema(description = "卡片阴影显示时机") + private String shadow; + + @Schema(description = "页面关键词") + private String keywords; + + @Schema(description = "页面描述") + private String description; + + @Schema(description = "页面路由地址") + private String path; + + @Schema(description = "缩列图") + private String photo; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsDomain.java b/src/main/java/com/gxwebsoft/cms/entity/CmsDomain.java new file mode 100644 index 0000000..7d81bc0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsDomain.java @@ -0,0 +1,73 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站域名记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsDomain对象", description = "网站域名记录表") +public class CmsDomain implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "类型 0赠送域名 1绑定域名 ") + private Integer type; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "主机记录") + private String hostName; + + @Schema(description = "记录值") + private String hostValue; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "网站ID") + private Integer websiteId; + + @Schema(description = "租户ID") + private Integer appId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsForm.java b/src/main/java/com/gxwebsoft/cms/entity/CmsForm.java new file mode 100644 index 0000000..f989863 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsForm.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.cms.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单设计表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsForm对象", description = "表单设计表") +public class CmsForm implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "form_id", type = IdType.AUTO) + private Integer formId; + + @Schema(description = "表单标题") + private String name; + + @Schema(description = "顶部图片") + private String photo; + + @Schema(description = "背景图片") + private String background; + + @Schema(description = "视频文件") + private String video; + + @Schema(description = "提交次数") + private Integer submitNumber; + + @Schema(description = "页面布局") + private String layout; + + @Schema(description = "是否隐藏顶部图片") + private Integer hidePhoto; + + @Schema(description = "是否隐藏背景图片") + private Integer hideBackground; + + @Schema(description = "是否隐藏视频") + private Integer hideVideo; + + @Schema(description = "背景图片透明度") + private BigDecimal opacity; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsFormRecord.java b/src/main/java/com/gxwebsoft/cms/entity/CmsFormRecord.java new file mode 100644 index 0000000..3040320 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsFormRecord.java @@ -0,0 +1,69 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单数据记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsFormRecord对象", description = "表单数据记录表") +public class CmsFormRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "form_record_id", type = IdType.AUTO) + private Integer formRecordId; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "表单数据") + private String formData; + + @Schema(description = "表单ID") + private Integer formId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsLang.java b/src/main/java/com/gxwebsoft/cms/entity/CmsLang.java new file mode 100644 index 0000000..5df6393 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsLang.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 国际化 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsLang对象", description = "国际化") +public class CmsLang implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "名称") + private String name; + + @Schema(description = "编码") + private String code; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsLangLog.java b/src/main/java/com/gxwebsoft/cms/entity/CmsLangLog.java new file mode 100644 index 0000000..a590e5d --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsLangLog.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 国际化记录启用 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsLangLog对象", description = "国际化记录启用") +public class CmsLangLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "名称") + private String lang; + + @Schema(description = "关联ID") + private Integer langId; + + @Schema(description = "编码") + private String code; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsLink.java b/src/main/java/com/gxwebsoft/cms/entity/CmsLink.java new file mode 100644 index 0000000..93e61bb --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsLink.java @@ -0,0 +1,80 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 常用链接 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsLink对象", description = "常用链接") +public class CmsLink implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "链接名称") + private String name; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "栏目ID") + private Integer categoryId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "栏目名称") + @TableField(exist = false) + private String categoryName; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsModel.java b/src/main/java/com/gxwebsoft/cms/entity/CmsModel.java new file mode 100644 index 0000000..97c5943 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsModel.java @@ -0,0 +1,97 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 模型 + * + * @author 科技小王子 + * @since 2024-11-26 15:44:53 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsModel对象", description = "模型") +public class CmsModel implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "model_id", type = IdType.AUTO) + private Integer modelId; + + @Schema(description = "模型名称") + private String name; + + @Schema(description = "唯一标识") + private String model; + + @Schema(description = "列表页路径") + private String component; + + @Schema(description = "详情页路径") + private String componentDetail; + + @Schema(description = "模型banner图片") + private String banner; + + @Schema(description = "文章后缀") + private String suffix; + + @Schema(description = "拇指图片") + private String thumb; + + @Schema(description = "封面图宽") + private String imageWidth; + + @Schema(description = "封面图高") + private String imageHeight; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "Banner上的标题") + private String title; + + @Schema(description = "列表显示方式(10小图展示 20大图展示)") + private Integer showType; + + @Schema(description = "是否禁用") + private Boolean disabled; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsNavigation.java b/src/main/java/com/gxwebsoft/cms/entity/CmsNavigation.java new file mode 100644 index 0000000..1570a9a --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsNavigation.java @@ -0,0 +1,241 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站导航记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsNavigation对象", description = "网站导航记录表") +public class CmsNavigation implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "navigation_id", type = IdType.AUTO) + private Integer navigationId; + + @Schema(description = "类型, 0列表 1图文") + private Integer type; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "模型") + private String model; + + @Schema(description = "标识") + private String code; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "文件后缀") + @TableField(exist = false) + private String suffix; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "banner") + @TableField(exist = false) + private String banner; + + @Schema(description = "移动端banner") + @TableField(exist = false) + private String mpBanner; + + @Schema(description = "图标颜色") + private String color; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + private Integer permission; + + @Schema(description = "访问密码") + private String password; + + @Schema(description = "验证密码(前端回传)") + @TableField(exist = false) + private String password2; + + @Schema(description = "位置 0不限 1顶部 2底部") + private Integer position; + + @Schema(description = "仅在顶部显示") + private Integer top; + + @Schema(description = "仅在底部显示") + private Integer bottom; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "父级栏目路由") + private String parentPath; + + @Schema(description = "父级栏目名称") + private String parentName; + + @Schema(description = "父级栏目位置") + @TableField(exist = false) + private Integer parentPosition; + + @Schema(description = "模型名称") + private String modelName; + + @Schema(description = "绑定的页面(已废弃)") + private Integer pageId; + + @Schema(description = "详情页ID") + private Integer itemId; + + @Schema(description = "是否微信小程序菜单") + private Boolean isMpWeixin; + + @Schema(description = "菜单间距") + private Integer gutter; + + @Schema(description = "菜单宽度") + private Integer span; + + @Schema(description = "阅读量") + private Integer readNum; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "用于同步翻译内容") + private Integer langCategoryId; + + @Schema(description = "设为首页") + private Integer home; + + @Schema(description = "是否推荐") + private Boolean recommend; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonIgnore // 导航的创建时间前端不需要 + private LocalDateTime createTime; + + + @Schema(description = "页面名称") + @TableField(exist = false) + private String pageName; + + @Schema(description = "子菜单") + @TableField(exist = false) + private List children; + + @Schema(description = "页面布局") + @TableField(exist = false) + private String layout; + + @Schema(description = "关联的页面") + @TableField(exist = false) + private CmsDesign design; + + @Schema(description = "所属模型") + @TableField(exist = false) + private CmsModel modelInfo; + + @Schema(description = "父级栏目") + @TableField(exist = false) + private CmsNavigation parent; + + @Schema(description = "当前栏目名称") + @TableField(exist = false) + private String categoryName; + + @Schema(description = "当前栏目链接") + @TableField(exist = false) + private String categoryPath; + + @Schema(description = "栏目图片") + @TableField(exist = false) + private String photo; + + @Schema(description = "是否开启布局") + @TableField(exist = false) + private Boolean showLayout; + + @Schema(description = "单页内容") + @TableField(exist = false) + private String content; + + @Schema(description = "是否显示banner") + @TableField(exist = false) + private Boolean showBanner; + + @Schema(description = "菜单标题") + @TableField(exist = false) + private String text; + + public String getCategoryName() { + return this.title; + } + + public String getCategoryPath() { + return this.path; + } + + public String getText() { + return this.title; + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsStatistics.java b/src/main/java/com/gxwebsoft/cms/entity/CmsStatistics.java new file mode 100644 index 0000000..9ff97d4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsStatistics.java @@ -0,0 +1,126 @@ +package com.gxwebsoft.cms.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 站点统计信息表 + * + * @author 科技小王子 + * @since 2025-07-25 12:32:06 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsStatistics对象", description = "站点统计信息表") +public class CmsStatistics implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "站点ID") + private Integer websiteId; + + @Schema(description = "用户总数") + private Integer userCount; + + @Schema(description = "订单总数") + private Integer orderCount; + + @Schema(description = "商品总数") + private Integer productCount; + + @Schema(description = "总销售额") + private BigDecimal totalSales; + + @Schema(description = "本月销售额") + private BigDecimal monthSales; + + @Schema(description = "今日销售额") + private BigDecimal todaySales; + + @Schema(description = "昨日销售额") + private BigDecimal yesterdaySales; + + @Schema(description = "本周销售额") + private BigDecimal weekSales; + + @Schema(description = "本年销售额") + private BigDecimal yearSales; + + @Schema(description = "今日订单数") + private Integer todayOrders; + + @Schema(description = "本月订单数") + private Integer monthOrders; + + @Schema(description = "今日新增用户") + private Integer todayUsers; + + @Schema(description = "本月新增用户") + private Integer monthUsers; + + @Schema(description = "今日访问量") + private Integer todayVisits; + + @Schema(description = "总访问量") + private Integer totalVisits; + + @Schema(description = "商户总数") + private Integer merchantCount; + + @Schema(description = "活跃用户数") + private Integer activeUsers; + + @Schema(description = "转化率(%)") + private BigDecimal conversionRate; + + @Schema(description = "平均订单金额") + private BigDecimal avgOrderAmount; + + @Schema(description = "统计日期") + private LocalDate statisticsDate; + + @Schema(description = "统计类型: 1日统计, 2月统计, 3年统计") + private Integer statisticsType; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "操作用户ID") + private Integer userId; + + @Schema(description = "商户ID") + private Integer merchantId; + + @Schema(description = "状态: 0禁用, 1启用") + private Boolean status; + + @Schema(description = "是否删除: 0否, 1是") + @TableLogic + private Boolean deleted; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsTemplate.java b/src/main/java/com/gxwebsoft/cms/entity/CmsTemplate.java new file mode 100644 index 0000000..6af939d --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsTemplate.java @@ -0,0 +1,98 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站模版 + * + * @author 科技小王子 + * @since 2025-01-21 14:21:16 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsTemplate对象", description = "网站模版") +public class CmsTemplate implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "模版名称") + private String name; + + @Schema(description = "模版标识") + private String code; + + @Schema(description = "缩列图") + private String image; + + @Schema(description = "类型 1企业官网 2其他") + private Integer type; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "域名前缀") + private String prefix; + + @Schema(description = "预览地址") + private String domain; + + @Schema(description = "模版下载地址") + private String downUrl; + + @Schema(description = "色系") + private String color; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + private Integer version; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + private Boolean recommend; + + @Schema(description = "是否共享模板") + private Boolean share; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsWebsite.java b/src/main/java/com/gxwebsoft/cms/entity/CmsWebsite.java new file mode 100644 index 0000000..73c3842 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsWebsite.java @@ -0,0 +1,322 @@ +package com.gxwebsoft.cms.entity; + +import cn.hutool.core.util.DesensitizedUtil; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站信息记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsWebsite对象", description = "网站信息记录表") +public class CmsWebsite implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "站点ID") + @TableId(value = "website_id", type = IdType.AUTO) + private Integer websiteId; + + @Schema(description = "网站名称") + private String websiteName; + + @Schema(description = "网站标识") + private String websiteCode; + + @Schema(description = "网站LOGO") + private String websiteIcon; + + @Schema(description = "网站LOGO") + private String websiteLogo; + + @Schema(description = "网站LOGO(深色模式)") + private String websiteDarkLogo; + + @Schema(description = "网站类型") + private String websiteType; + + @Schema(description = "栏目ID") + private Integer categoryId; + + @Schema(description = "应用ID") + @TableField(exist = false) + private Integer appId; + + @Schema(description = "网站截图") + private String files; + + @Schema(description = "网站类型 10企业官网 20微信小程序 30APP 40其他") + private Integer type; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "域名前缀") + private String prefix; + + @Schema(description = "绑定域名") + private String domain; + + @Schema(description = "全局样式") + private String style; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + private Integer version; + + @Schema(description = "服务到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "是否到期") + @TableField(exist = false) + private Integer expired; + + @Schema(description = "剩余天数") + @TableField(exist = false) + private Long expiredDays; + + @Schema(description = "服务器ID") + private Integer assetsId; + + @Schema(description = "服务器ID") + private String assetsName; + + @Schema(description = "模版ID(存克隆的租户UID)") + private Integer templateId; + + @Schema(description = "模版名称") + @TableField(exist = false) + private String templateName; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "开发者名称") + private String developer; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "电子邮箱") + private String email; + + @Schema(description = "ICP备案号") + private String icpNo; + + @Schema(description = "公安备案") + private String policeNo; + + @Schema(description = "网站描述") + private String content; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "管理员备注") + private String remarks; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "是否官方产品") + private Boolean official; + + @Schema(description = "允许展示到插件市场") + private Boolean market; + + @Schema(description = "是否插件类型 0应用 1插件") + private Boolean plugin; + + @Schema(description = "允许被搜索") + private Boolean search; + + @Schema(description = "主题色") + private String color; + + @Schema(description = "运行状态 0运行中 1已关闭 2维护中") + private Integer running; + + @Schema(description = "即将过期") + private Integer soon; + + @Schema(description = "评分") + private BigDecimal rate; + + @Schema(description = "点赞数量") + private Integer likes; + + @Schema(description = "点击数量") + private Integer clicks; + + @Schema(description = "购买数量") + private Integer buys; + + @Schema(description = "下载数量") + private Integer downloads; + + @Schema(description = "销售价格") + private BigDecimal price; + + @Schema(description = "交付方式") + private Integer deliveryMethod; + + @Schema(description = "计费方式") + private Integer chargingMethod; + + @Schema(description = "状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停") + private Integer status; + + @Schema(description = "状态图标") + @TableField(exist = false) + private String statusIcon; + + @Schema(description = "维护说明") + private String statusText; + + @Schema(description = "关闭说明") + private String statusClose; + + @Schema(description = "全局样式") + private String styles; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "租户名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "预设字段") + @TableField(exist = false) + private List fields; + + @Schema(description = "小程序导航图标") + @TableField(exist = false) + private Map> mpMenus; + + @Schema(description = "网站导航栏") + @TableField(exist = false) + private List navigations; + + @Schema(description = "顶部菜单") + @TableField(exist = false) + private List topNavs; + + @Schema(description = "底部菜单") + @TableField(exist = false) + private List bottomNavs; + + @Schema(description = "幻灯片广告") + @TableField(exist = false) + private CmsAd slide; + + @Schema(description = "站点广告") + @TableField(exist = false) + private List ads; + + @Schema(description = "首页布局") + @TableField(exist = false) + private String layout; + + @Schema(description = "友情链接") + @TableField(exist = false) + private List links; + + @Schema(description = "配置信息") + @TableField(exist = false) + private Object config; + + @Schema(description = "服务器时间") + @TableField(exist = false) + private HashMap serverTime; + + @Schema(description = "当前登录用户") + @TableField(exist = false) + private User loginUser; + + @Schema(description = "超管账号") + @TableField(exist = false) + private String superAdminPhone; + + @Schema(description = "是否登录") + @TableField(exist = false) + private Boolean isLogin; + + @Schema(description = "网站设置") + @TableField(exist = false) + private CmsWebsiteSetting setting; + + public String getPhone(){ + return DesensitizedUtil.mobilePhone(this.phone); + } +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteField.java b/src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteField.java new file mode 100644 index 0000000..35b2a4c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteField.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsWebsiteField对象", description = "应用参数") +public class CmsWebsiteField implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "类型,0文本 1图片 2其他") + private Integer type; + + @Schema(description = "名称") + private String name; + + @Schema(description = "默认值") + private String defaultValue; + + @Schema(description = "可修改的值 [on|off]") + private String modifyRange; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "名称") + private String value; + + @Schema(description = "国际化语言") + private String lang; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteSetting.java b/src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteSetting.java new file mode 100644 index 0000000..8e750c5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/CmsWebsiteSetting.java @@ -0,0 +1,89 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站设置 + * + * @author 科技小王子 + * @since 2025-02-19 01:35:44 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CmsWebsiteSetting对象", description = "网站设置") +public class CmsWebsiteSetting implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "关联网站ID") + private Integer websiteId; + + @Schema(description = "是否官方插件") + private Boolean official; + + @Schema(description = "是否展示在插件市场") + private Boolean market; + + @Schema(description = "是否允许被搜索") + private Boolean search; + + @Schema(description = "是否共享") + private Boolean share; + + @Schema(description = "文章是否需要审核") + private Boolean articleReview; + + @Schema(description = "是否插件 0应用1 插件 ") + private Boolean plugin; + + @Schema(description = "编辑器类型 1 富文本编辑器 2 Markdown编辑器") + private Integer editor; + + @Schema(description = "显示站内搜索") + private Boolean searchBtn; + + @Schema(description = "显示登录注册功能") + private Boolean loginBtn; + + @Schema(description = "显示悬浮客服工具") + private Boolean floatTool; + + @Schema(description = "显示版权链接") + private Boolean copyrightLink; + + @Schema(description = "导航栏最多显示数量") + private Boolean maxMenuNum; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/TranslateDataVo.java b/src/main/java/com/gxwebsoft/cms/entity/TranslateDataVo.java new file mode 100644 index 0000000..43e9ba7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/TranslateDataVo.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "翻译结果", description = "翻译结果") +public class TranslateDataVo implements Serializable { + + @Schema(description = "文本格式") + private String FormatType; + + @Schema(description = "原文语言") + private String SourceLanguage; + + @Schema(description = "译文语言") + private String TargetLanguage; + + @Schema(description = "待翻译内容") + private String SourceText; + + @Schema(description = "场景可选取值:商品标题(title),商品描述(description),商品沟通(communication),医疗(medical),社交(social),金融(finance)") + private String Scene; + + @Schema(description = "上下文信息") + private String Context; + + @Schema(description = "翻译结果") + private String translated; + + @Schema(description = "总单词数") + private String wordCount; + + @Schema(description = "源语言传入 auto 时,语种识别后的源语言代码") + private String detectedLanguage; + +} diff --git a/src/main/java/com/gxwebsoft/cms/entity/TranslateVo.java b/src/main/java/com/gxwebsoft/cms/entity/TranslateVo.java new file mode 100644 index 0000000..1a988a2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/entity/TranslateVo.java @@ -0,0 +1,30 @@ +package com.gxwebsoft.cms.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "阿里云机器翻译", description = "阿里云机器翻译") +public class TranslateVo implements Serializable { + + @Schema(description = "错误码") + private String Code; + + @Schema(description = "错误信息") + @JsonIgnoreProperties(ignoreUnknown = true) + private String Message; + + @Schema(description = "请求ID") + private String RequestId; + + @Schema(description = "返回数据") + private TranslateDataVo Data; + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsAdMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsAdMapper.java new file mode 100644 index 0000000..836fa83 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsAdMapper.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsAd; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.param.CmsAdParam; +import com.gxwebsoft.cms.param.CmsLangLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 广告位Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsAdMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsAdParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsAdParam param); + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsAdParam param); +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsAdRecordMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsAdRecordMapper.java new file mode 100644 index 0000000..329956e --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsAdRecordMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsAdRecord; +import com.gxwebsoft.cms.param.CmsAdRecordParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 广告图片Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsAdRecordMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsAdRecordParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsAdRecordParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCategoryMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCategoryMapper.java new file mode 100644 index 0000000..d540f29 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCategoryMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsArticleCategory; +import com.gxwebsoft.cms.param.CmsArticleCategoryParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 文章分类表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleCategoryMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsArticleCategoryParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsArticleCategoryParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCommentMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCommentMapper.java new file mode 100644 index 0000000..ed20748 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCommentMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsArticleComment; +import com.gxwebsoft.cms.param.CmsArticleCommentParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 文章评论表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleCommentMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsArticleCommentParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsArticleCommentParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleContentMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleContentMapper.java new file mode 100644 index 0000000..2a39ca9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleContentMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsArticleContent; +import com.gxwebsoft.cms.param.CmsArticleContentParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 文章记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleContentMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsArticleContentParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsArticleContentParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCountMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCountMapper.java new file mode 100644 index 0000000..596a640 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleCountMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsArticleCount; +import com.gxwebsoft.cms.param.CmsArticleCountParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 点赞文章Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleCountMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsArticleCountParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsArticleCountParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleLikeMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleLikeMapper.java new file mode 100644 index 0000000..f6eba4b --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleLikeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsArticleLike; +import com.gxwebsoft.cms.param.CmsArticleLikeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 点赞文章Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleLikeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsArticleLikeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsArticleLikeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleMapper.java new file mode 100644 index 0000000..18c2506 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsArticleMapper.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.param.CmsArticleParam; +import com.gxwebsoft.cms.param.CmsLangLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 文章Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsArticleParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsArticleParam param); + + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsArticleParam param); +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsDesignMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsDesignMapper.java new file mode 100644 index 0000000..5542ff7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsDesignMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsDesign; +import com.gxwebsoft.cms.param.CmsDesignParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 页面管理记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsDesignMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsDesignParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsDesignParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsDesignRecordMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsDesignRecordMapper.java new file mode 100644 index 0000000..dd25171 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsDesignRecordMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsDesignRecord; +import com.gxwebsoft.cms.param.CmsDesignRecordParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 页面组件表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsDesignRecordMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsDesignRecordParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsDesignRecordParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsDomainMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsDomainMapper.java new file mode 100644 index 0000000..e561b20 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsDomainMapper.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsDomain; +import com.gxwebsoft.cms.param.CmsDomainParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 网站域名记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +public interface CmsDomainMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsDomainParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsDomainParam param); + + @InterceptorIgnore(tenantLine = "true") + CmsDomain getDomain(@Param("domain") String domain); +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsFormMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsFormMapper.java new file mode 100644 index 0000000..ab12d1a --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsFormMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsForm; +import com.gxwebsoft.cms.param.CmsFormParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 表单设计表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsFormMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsFormParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsFormParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsFormRecordMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsFormRecordMapper.java new file mode 100644 index 0000000..b766f7b --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsFormRecordMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsFormRecord; +import com.gxwebsoft.cms.param.CmsFormRecordParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 表单数据记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsFormRecordMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsFormRecordParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsFormRecordParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsLangLogMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsLangLogMapper.java new file mode 100644 index 0000000..70c8678 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsLangLogMapper.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.param.CmsLangLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 国际化记录启用Mapper + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +public interface CmsLangLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsLangLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsLangLogParam param); + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsLangLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsLangMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsLangMapper.java new file mode 100644 index 0000000..b8705df --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsLangMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsLang; +import com.gxwebsoft.cms.param.CmsLangParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 国际化Mapper + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +public interface CmsLangMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsLangParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsLangParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsLinkMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsLinkMapper.java new file mode 100644 index 0000000..915b1f1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsLinkMapper.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.entity.CmsLink; +import com.gxwebsoft.cms.param.CmsLangLogParam; +import com.gxwebsoft.cms.param.CmsLinkParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 常用链接Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsLinkMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsLinkParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsLinkParam param); + + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsLinkParam param); +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsModelMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsModelMapper.java new file mode 100644 index 0000000..6fe0a36 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsModelMapper.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.param.CmsModelParam; +import com.gxwebsoft.cms.param.CmsWebsiteFieldParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 模型Mapper + * + * @author 科技小王子 + * @since 2024-11-26 15:44:53 + */ +public interface CmsModelMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsModelParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsModelParam param); + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsModelParam param); +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsNavigationMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsNavigationMapper.java new file mode 100644 index 0000000..e0fcfac --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsNavigationMapper.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.param.CmsModelParam; +import com.gxwebsoft.cms.param.CmsNavigationParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 网站导航记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsNavigationMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsNavigationParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsNavigationParam param); + + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsNavigationParam param); + + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsStatisticsMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsStatisticsMapper.java new file mode 100644 index 0000000..160d5be --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsStatisticsMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsStatistics; +import com.gxwebsoft.cms.param.CmsStatisticsParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 站点统计信息表Mapper + * + * @author 科技小王子 + * @since 2025-07-25 12:32:06 + */ +public interface CmsStatisticsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsStatisticsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsStatisticsParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsTemplateMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsTemplateMapper.java new file mode 100644 index 0000000..7cd4485 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsTemplateMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsTemplate; +import com.gxwebsoft.cms.param.CmsTemplateParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 网站模版Mapper + * + * @author 科技小王子 + * @since 2025-01-21 14:21:16 + */ +public interface CmsTemplateMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsTemplateParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsTemplateParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteFieldMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteFieldMapper.java new file mode 100644 index 0000000..e4c7e79 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteFieldMapper.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsWebsiteField; +import com.gxwebsoft.cms.param.CmsWebsiteFieldParam; +import org.apache.ibatis.annotations.Param; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 应用参数Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +public interface CmsWebsiteFieldMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsWebsiteFieldParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsWebsiteFieldParam param); + + + @InterceptorIgnore(tenantLine = "true") + List selectListAllRel(@Param("param") CmsWebsiteFieldParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteMapper.java new file mode 100644 index 0000000..c24e345 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteMapper.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.cms.param.CmsWebsiteParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 网站信息记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +public interface CmsWebsiteMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsWebsiteParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsWebsiteParam param); + + @InterceptorIgnore(tenantLine = "true") + List selectPageRelAll(@Param("page") IPage page, + @Param("param") CmsWebsiteParam param); + + @InterceptorIgnore(tenantLine = "true") + CmsWebsite getByIdRelAll(@Param("websiteId") Integer id); + + @InterceptorIgnore(tenantLine = "true") + boolean updateByIdAll(@Param("param") CmsWebsite cmsWebsite); + + @InterceptorIgnore(tenantLine = "true") + boolean removeByIdAll(@Param("websiteId") Integer id); + + @InterceptorIgnore(tenantLine = "true") + CmsWebsite getByTenantId(@Param("tenantId") Integer tenantId); +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteSettingMapper.java b/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteSettingMapper.java new file mode 100644 index 0000000..dc468fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/CmsWebsiteSettingMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsWebsiteSetting; +import com.gxwebsoft.cms.param.CmsWebsiteSettingParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 网站设置Mapper + * + * @author 科技小王子 + * @since 2025-02-19 01:35:44 + */ +public interface CmsWebsiteSettingMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CmsWebsiteSettingParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CmsWebsiteSettingParam param); + +} diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdMapper.xml new file mode 100644 index 0000000..6ee7cbf --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdMapper.xml @@ -0,0 +1,92 @@ + + + + + + + SELECT a.*, b.user_id as websiteUserId, c.title as categoryName + FROM cms_ad a + LEFT JOIN cms_website b ON a.tenant_id = b.tenant_id + LEFT JOIN cms_navigation c ON a.category_id = c.navigation_id + + + AND a.ad_id = #{param.adId} + + + AND a.type = #{param.type} + + + AND a.code = #{param.code} + + + AND a.category_id = #{param.categoryId} + + + AND a.lang = #{param.lang} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.width LIKE CONCAT('%', #{param.width}, '%') + + + AND a.height LIKE CONCAT('%', #{param.height}, '%') + + + AND a.images LIKE CONCAT('%', #{param.images}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.user_id = #{param.userId} + + + AND b.user_id = #{param.websiteUserId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.ad_id = #{param.keywords} + ) + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdRecordMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdRecordMapper.xml new file mode 100644 index 0000000..959667a --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAdRecordMapper.xml @@ -0,0 +1,53 @@ + + + + + + + SELECT a.* + FROM cms_ad_record a + + + AND a.ad_record_id = #{param.adRecordId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.url LIKE CONCAT('%', #{param.url}, '%') + + + AND a.ad_id = #{param.adId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCategoryMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCategoryMapper.xml new file mode 100644 index 0000000..f548eb4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCategoryMapper.xml @@ -0,0 +1,86 @@ + + + + + + + SELECT a.* + FROM cms_article_category a + + + AND a.category_id = #{param.categoryId} + + + AND a.category_code LIKE CONCAT('%', #{param.categoryCode}, '%') + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.type = #{param.type} + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.parent_id = #{param.parentId} + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.page_id = #{param.pageId} + + + AND a.user_id = #{param.userId} + + + AND a.count = #{param.count} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.hide = #{param.hide} + + + AND a.recommend = #{param.recommend} + + + AND a.show_index = #{param.showIndex} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCommentMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCommentMapper.xml new file mode 100644 index 0000000..8d82a95 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCommentMapper.xml @@ -0,0 +1,71 @@ + + + + + + + SELECT a.* + FROM cms_article_comment a + + + AND a.comment_id = #{param.commentId} + + + AND a.article_id = #{param.articleId} + + + AND a.score = #{param.score} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.is_picture = #{param.isPicture} + + + AND a.user_id = #{param.userId} + + + AND a.to_user_id = #{param.toUserId} + + + AND a.reply_comment_id = #{param.replyCommentId} + + + AND a.reply_user_id = #{param.replyUserId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleContentMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleContentMapper.xml new file mode 100644 index 0000000..de251b4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleContentMapper.xml @@ -0,0 +1,38 @@ + + + + + + + SELECT a.* + FROM cms_article_content a + + + AND a.id = #{param.id} + + + AND a.article_id = #{param.articleId} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCountMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCountMapper.xml new file mode 100644 index 0000000..d714ed3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleCountMapper.xml @@ -0,0 +1,38 @@ + + + + + + + SELECT a.* + FROM cms_article_count a + + + AND a.id = #{param.id} + + + AND a.article_id = #{param.articleId} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleLikeMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleLikeMapper.xml new file mode 100644 index 0000000..b33ff0c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleLikeMapper.xml @@ -0,0 +1,38 @@ + + + + + + + SELECT a.* + FROM cms_article_like a + + + AND a.id = #{param.id} + + + AND a.article_id = #{param.articleId} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleMapper.xml new file mode 100644 index 0000000..8409288 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsArticleMapper.xml @@ -0,0 +1,184 @@ + + + + + + + SELECT a.*,b.title as categoryName,b.parent_Id as parentId, b.model,c.title as parentName,u.nickname,u.avatar + FROM cms_article a + LEFT JOIN cms_navigation b ON a.category_id = b.navigation_id + LEFT JOIN cms_navigation c ON b.parent_id = c.navigation_id + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + + + AND a.article_id = #{param.articleId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.type = #{param.type} + + + AND a.lang = #{param.lang} + + + AND a.model = #{param.model} + + + AND a.detail = #{param.detail} + + + AND a.show_type = #{param.showType} + + + AND a.topic LIKE CONCAT('%', #{param.topic}, '%') + + + AND a.category_id = #{param.categoryId} + + + AND a.category_id IN + + #{item} + + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.image != '' + + + AND a.source LIKE CONCAT('%', #{param.source}, '%') + + + AND a.virtual_views = #{param.virtualViews} + + + AND a.actual_views = #{param.actualViews} + + + AND a.platform LIKE CONCAT('%', #{param.platform}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.video LIKE CONCAT('%', #{param.video}, '%') + + + AND a.accept LIKE CONCAT('%', #{param.accept}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.likes = #{param.likes} + + + AND a.comment_numbers = #{param.commentNumbers} + + + AND a.to_users LIKE CONCAT('%', #{param.toUsers}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.project_id = #{param.projectId} + + + AND a.tags LIKE CONCAT('%', #{param.tags}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.article_id IN + + #{item} + + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.article_id = #{param.keywords} + OR a.detail = #{param.keywords} + OR a.title LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignMapper.xml new file mode 100644 index 0000000..def6547 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignMapper.xml @@ -0,0 +1,93 @@ + + + + + + + SELECT a.*,b.lang_category_id, b.title as categoryName + FROM cms_design a + LEFT JOIN cms_navigation b ON a.category_id = b.navigation_id + + + AND a.page_id = #{param.pageId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.lang = #{param.lang} + + + AND a.category_id = #{param.categoryId} + + + AND a.model = #{param.model} + + + AND a.keywords LIKE CONCAT('%', #{param.keywords}, '%') + + + AND a.description LIKE CONCAT('%', #{param.description}, '%') + + + AND a.photo LIKE CONCAT('%', #{param.photo}, '%') + + + AND a.buy_url LIKE CONCAT('%', #{param.buyUrl}, '%') + + + AND a.style LIKE CONCAT('%', #{param.style}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.show_layout = #{param.showLayout} + + + AND a.layout LIKE CONCAT('%', #{param.layout}, '%') + + + AND a.parent_id = #{param.parentId} + + + AND a.user_id = #{param.userId} + + + AND a.home = #{param.home} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignRecordMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignRecordMapper.xml new file mode 100644 index 0000000..0ff62ef --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDesignRecordMapper.xml @@ -0,0 +1,71 @@ + + + + + + + SELECT a.* + FROM cms_design_record a + + + AND a.id = #{param.id} + + + AND a.navigation_id = #{param.navigationId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.dict_code LIKE CONCAT('%', #{param.dictCode}, '%') + + + AND a.styles LIKE CONCAT('%', #{param.styles}, '%') + + + AND a.shadow LIKE CONCAT('%', #{param.shadow}, '%') + + + AND a.keywords LIKE CONCAT('%', #{param.keywords}, '%') + + + AND a.description LIKE CONCAT('%', #{param.description}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.photo LIKE CONCAT('%', #{param.photo}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDomainMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDomainMapper.xml new file mode 100644 index 0000000..dc39215 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsDomainMapper.xml @@ -0,0 +1,65 @@ + + + + + + + SELECT a.* + FROM cms_domain a + + + AND a.id = #{param.id} + + + AND a.type = #{param.type} + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.host_name LIKE CONCAT('%', #{param.hostName}, '%') + + + AND a.host_value LIKE CONCAT('%', #{param.hostValue}, '%') + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.website_id = #{param.websiteId} + + + AND a.app_id = #{param.appId} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormMapper.xml new file mode 100644 index 0000000..5b2d2b1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormMapper.xml @@ -0,0 +1,83 @@ + + + + + + + SELECT a.* + FROM cms_form a + + + AND a.form_id = #{param.formId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.photo LIKE CONCAT('%', #{param.photo}, '%') + + + AND a.background LIKE CONCAT('%', #{param.background}, '%') + + + AND a.video LIKE CONCAT('%', #{param.video}, '%') + + + AND a.submit_number = #{param.submitNumber} + + + AND a.layout LIKE CONCAT('%', #{param.layout}, '%') + + + AND a.hide_photo = #{param.hidePhoto} + + + AND a.hide_background = #{param.hideBackground} + + + AND a.hide_video = #{param.hideVideo} + + + AND a.opacity = #{param.opacity} + + + AND a.user_id = #{param.userId} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormRecordMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormRecordMapper.xml new file mode 100644 index 0000000..f646c1e --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsFormRecordMapper.xml @@ -0,0 +1,65 @@ + + + + + + + SELECT a.* + FROM cms_form_record a + + + AND a.form_record_id = #{param.formRecordId} + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.form_data LIKE CONCAT('%', #{param.formData}, '%') + + + AND a.form_id = #{param.formId} + + + AND a.user_id = #{param.userId} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangLogMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangLogMapper.xml new file mode 100644 index 0000000..97d9bcb --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangLogMapper.xml @@ -0,0 +1,54 @@ + + + + + + + SELECT a.*, b.user_id as websiteUserId + FROM cms_lang_log a + LEFT JOIN cms_website b ON a.tenant_id = b.tenant_id + + + AND a.id = #{param.id} + + + AND a.lang LIKE CONCAT('%', #{param.lang}, '%') + + + AND a.lang_id = #{param.langId} + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND b.user_id = #{param.websiteUserId} + + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangMapper.xml new file mode 100644 index 0000000..0dc0bed --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLangMapper.xml @@ -0,0 +1,57 @@ + + + + + + + SELECT a.* + FROM cms_lang a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLinkMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLinkMapper.xml new file mode 100644 index 0000000..6102242 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsLinkMapper.xml @@ -0,0 +1,86 @@ + + + + + + + SELECT a.*, b.user_id as websiteUserId,c.title as categoryName + FROM cms_link a + LEFT JOIN cms_website b ON a.tenant_id = b.tenant_id + LEFT JOIN cms_navigation c ON a.category_id = c.navigation_id + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.lang = #{param.lang} + + + AND a.icon LIKE CONCAT('%', #{param.icon}, '%') + + + AND a.url LIKE CONCAT('%', #{param.url}, '%') + + + AND a.category_id LIKE CONCAT('%', #{param.categoryId}, '%') + + + AND a.app_id = #{param.appId} + + + AND a.user_id = #{param.userId} + + + AND b.user_id = #{param.websiteUserId} + + + AND a.recommend = #{param.recommend} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.id = #{param.keywords} + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsModelMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsModelMapper.xml new file mode 100644 index 0000000..a948d9c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsModelMapper.xml @@ -0,0 +1,89 @@ + + + + + + + SELECT a.*, b.user_id as websiteUserId + FROM cms_model a + LEFT JOIN cms_website b ON a.tenant_id = b.tenant_id + + + AND a.model_id = #{param.modelId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.model LIKE CONCAT('%', #{param.model}, '%') + + + AND a.component_detail LIKE CONCAT('%', #{param.componentDetail}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.banner LIKE CONCAT('%', #{param.banner}, '%') + + + AND a.image_width = #{param.imageWidth} + + + AND a.image_height = #{param.imageHeight} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.show_type = #{param.showType} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND b.user_id = #{param.websiteUserId} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsNavigationMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsNavigationMapper.xml new file mode 100644 index 0000000..837a948 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsNavigationMapper.xml @@ -0,0 +1,161 @@ + + + + + + + SELECT a.*, b.title as parentName, b.position as parentPosition + FROM cms_navigation a + LEFT JOIN cms_navigation b ON a.parent_id = b.navigation_id + + + AND a.navigation_id = #{param.navigationId} + + + AND a.parent_id = #{param.parentId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.model LIKE CONCAT('%', #{param.model}, '%') + + + AND a.lang = #{param.lang} + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.target LIKE CONCAT('%', #{param.target}, '%') + + + AND a.icon LIKE CONCAT('%', #{param.icon}, '%') + + + AND a.color LIKE CONCAT('%', #{param.color}, '%') + + + AND a.hide = #{param.hide} + + + AND a.permission = #{param.permission} + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.position = #{param.position} + + + AND a.top = #{param.top} + + + AND a.bottom = #{param.bottom} + + + AND a.active LIKE CONCAT('%', #{param.active}, '%') + + + AND a.meta LIKE CONCAT('%', #{param.meta}, '%') + + + AND a.style LIKE CONCAT('%', #{param.style}, '%') + + + AND a.parent_path LIKE CONCAT('%', #{param.parentPath}, '%') + + + AND a.parent_name LIKE CONCAT('%', #{param.parentName}, '%') + + + AND a.model_name LIKE CONCAT('%', #{param.modelName}, '%') + + + AND a.type = #{param.type} + + + AND a.page_id = #{param.pageId} + + + AND a.is_mp_weixin = #{param.isMpWeixin} + + + AND a.user_id = #{param.userId} + + + AND a.home = #{param.home} + + + AND a.recommend = #{param.recommend} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.path LIKE CONCAT('%', #{param.keywords}, '%') + OR a.title LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsStatisticsMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsStatisticsMapper.xml new file mode 100644 index 0000000..6662a86 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsStatisticsMapper.xml @@ -0,0 +1,120 @@ + + + + + + + SELECT a.* + FROM cms_statistics a + + + AND a.id = #{param.id} + + + AND a.website_id = #{param.websiteId} + + + AND a.user_count = #{param.userCount} + + + AND a.order_count = #{param.orderCount} + + + AND a.product_count = #{param.productCount} + + + AND a.total_sales = #{param.totalSales} + + + AND a.month_sales = #{param.monthSales} + + + AND a.today_sales = #{param.todaySales} + + + AND a.yesterday_sales = #{param.yesterdaySales} + + + AND a.week_sales = #{param.weekSales} + + + AND a.year_sales = #{param.yearSales} + + + AND a.today_orders = #{param.todayOrders} + + + AND a.month_orders = #{param.monthOrders} + + + AND a.today_users = #{param.todayUsers} + + + AND a.month_users = #{param.monthUsers} + + + AND a.today_visits = #{param.todayVisits} + + + AND a.total_visits = #{param.totalVisits} + + + AND a.merchant_count = #{param.merchantCount} + + + AND a.active_users = #{param.activeUsers} + + + AND a.conversion_rate = #{param.conversionRate} + + + AND a.avg_order_amount = #{param.avgOrderAmount} + + + AND a.statistics_date LIKE CONCAT('%', #{param.statisticsDate}, '%') + + + AND a.statistics_type = #{param.statisticsType} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsTemplateMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsTemplateMapper.xml new file mode 100644 index 0000000..c7319c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsTemplateMapper.xml @@ -0,0 +1,93 @@ + + + + + + + SELECT a.* + FROM cms_template a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.type = #{param.type} + + + AND a.keywords LIKE CONCAT('%', #{param.keywords}, '%') + + + AND a.prefix LIKE CONCAT('%', #{param.prefix}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.down_url LIKE CONCAT('%', #{param.downUrl}, '%') + + + AND a.color LIKE CONCAT('%', #{param.color}, '%') + + + AND a.version = #{param.version} + + + AND a.industry_parent LIKE CONCAT('%', #{param.industryParent}, '%') + + + AND a.industry_child LIKE CONCAT('%', #{param.industryChild}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.share = #{param.share} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteFieldMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteFieldMapper.xml new file mode 100644 index 0000000..dc903bc --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteFieldMapper.xml @@ -0,0 +1,82 @@ + + + + + + + SELECT a.*, b.user_id + FROM cms_website_field a + LEFT JOIN cms_website b ON a.tenant_id = b.tenant_id + + + AND a.id = #{param.id} + + + AND a.type = #{param.type} + + + AND a.lang = #{param.lang} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.default_value LIKE CONCAT('%', #{param.defaultValue}, '%') + + + AND a.modify_range LIKE CONCAT('%', #{param.modifyRange}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.style LIKE CONCAT('%', #{param.style}, '%') + + + AND a.value LIKE CONCAT('%', #{param.value}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND b.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.value LIKE CONCAT('%', #{param.keywords}, '%') + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteMapper.xml new file mode 100644 index 0000000..577c1c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteMapper.xml @@ -0,0 +1,454 @@ + + + + + + + SELECT a.*, b.tenant_name as tenantName + FROM cms_website a + LEFT JOIN gxwebsoft_core.sys_tenant b ON a.tenant_id = b.tenant_id + + + AND a.website_id = #{param.websiteId} + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.website_name LIKE CONCAT('%', #{param.websiteName}, '%') + + + AND a.website_code LIKE CONCAT('%', #{param.websiteCode}, '%') + + + AND a.website_icon LIKE CONCAT('%', #{param.websiteIcon}, '%') + + + AND a.website_logo LIKE CONCAT('%', #{param.websiteLogo}, '%') + + + AND a.website_dark_logo LIKE CONCAT('%', #{param.websiteDarkLogo}, '%') + + + AND a.website_type LIKE CONCAT('%', #{param.websiteType}, '%') + + + AND a.prefix LIKE CONCAT('%', #{param.prefix}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.style LIKE CONCAT('%', #{param.style}, '%') + + + AND a.admin_url LIKE CONCAT('%', #{param.adminUrl}, '%') + + + AND a.version = #{param.version} + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.template_id = #{param.templateId} + + + AND a.industry_parent LIKE CONCAT('%', #{param.industryParent}, '%') + + + AND a.industry_child LIKE CONCAT('%', #{param.industryChild}, '%') + + + AND a.category_id = #{param.categoryId} + + + AND a.company_id = #{param.companyId} + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.email LIKE CONCAT('%', #{param.email}, '%') + + + AND a.icp_no LIKE CONCAT('%', #{param.icpNo}, '%') + + + AND a.police_no LIKE CONCAT('%', #{param.policeNo}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.official = #{param.official} + + + AND a.market = #{param.market} + + + AND a.plugin = #{param.plugin} + + + AND a.search = #{param.search} + + + AND a.color = #{param.color} + + + AND a.status = #{param.status} + + + AND a.status_text LIKE CONCAT('%', #{param.statusText}, '%') + + + AND a.status_close LIKE CONCAT('%', #{param.statusClose}, '%') + + + AND a.styles LIKE CONCAT('%', #{param.styles}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND (a.website_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.website_code LIKE CONCAT('%', #{param.keywords}, '%') + OR a.domain LIKE CONCAT('%', #{param.keywords}, '%') + OR a.tenant_id = #{param.keywords} + ) + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + + + + + + + UPDATE cms_website + + + type = #{param.type}, + + + website_name = #{param.websiteName}, + + + website_logo = #{param.websiteLogo}, + + + website_code = #{param.websiteCode}, + + + website_type = #{param.websiteType}, + + + template_id = #{param.templateId}, + + + company_id = #{param.companyId}, + + + admin_url = #{param.adminUrl}, + + + running = #{param.running}, + + + domain = #{param.domain}, + + + market = #{param.market}, + + + plugin = #{param.plugin}, + + + `search` = #{param.search}, + + + developer = #{param.developer}, + + + color = #{param.color}, + + + recommend = #{param.recommend}, + + + official = #{param.official}, + + + prefix = #{param.prefix}, + + + version = #{param.version}, + + + files = #{param.files}, + + + price = #{param.price}, + + + delivery_method = #{param.deliveryMethod}, + + + charging_method = #{param.chargingMethod}, + + + keywords = #{param.keywords}, + + + comments = #{param.comments}, + + + content = #{param.content}, + + + deleted = #{param.deleted}, + + + deleted = 0, + + + + website_id = #{param.websiteId} + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteSettingMapper.xml b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteSettingMapper.xml new file mode 100644 index 0000000..2164b44 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/mapper/xml/CmsWebsiteSettingMapper.xml @@ -0,0 +1,81 @@ + + + + + + + SELECT a.* + FROM cms_website_setting a + + + AND a.id = #{param.id} + + + AND a.website_id = #{param.websiteId} + + + AND a.official = #{param.official} + + + AND a.market = #{param.market} + + + AND a.search = #{param.search} + + + AND a.share = #{param.share} + + + AND a.plugin = #{param.plugin} + + + AND a.editor = #{param.editor} + + + AND a.search_btn = #{param.searchBtn} + + + AND a.login_btn = #{param.loginBtn} + + + AND a.float_tool = #{param.floatTool} + + + AND a.copyright_link = #{param.copyrightLink} + + + AND a.max_menu_num = #{param.maxMenuNum} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsAdParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsAdParam.java new file mode 100644 index 0000000..59d077f --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsAdParam.java @@ -0,0 +1,92 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 广告位查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsAdParam对象", description = "广告位查询参数") +public class CmsAdParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer adId; + + @Schema(description = "类型") + private Integer type; + + @Schema(description = "唯一标识") + @QueryField(type = QueryType.EQ) + private String code; + + @Schema(description = "栏目ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "页面ID") + @QueryField(type = QueryType.EQ) + private Integer designId; + + @Schema(description = "广告类型(废弃)") + private String adType; + + @Schema(description = "广告位名称") + private String name; + + @Schema(description = "宽") + private String width; + + @Schema(description = "高") + private String height; + + @Schema(description = "广告图片") + private String images; + + @Schema(description = "路由/链接地址") + private String path; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "页面ID") + @QueryField(type = QueryType.EQ) + private Integer pageId; + + @Schema(description = "页面名称") + private String pageName; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "网站创建者ID") + @QueryField(type = QueryType.EQ) + private Integer websiteUserId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsAdRecordParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsAdRecordParam.java new file mode 100644 index 0000000..dd125c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsAdRecordParam.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 广告图片查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsAdRecordParam对象", description = "广告图片查询参数") +public class CmsAdRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer adRecordId; + + @Schema(description = "广告标题") + private String title; + + @Schema(description = "图片地址") + private String path; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "广告位ID") + @QueryField(type = QueryType.EQ) + private Integer adId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleCategoryParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleCategoryParam.java new file mode 100644 index 0000000..601236c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleCategoryParam.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章分类表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsArticleCategoryParam对象", description = "文章分类表查询参数") +public class CmsArticleCategoryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "文章分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "分类标识") + private String categoryCode; + + @Schema(description = "分类名称") + private String title; + + @Schema(description = "类型 0列表 1单页 2外链") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "分类图片") + private String image; + + @Schema(description = "上级分类ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "路由/链接地址") + private String path; + + @Schema(description = "组件路径") + private String component; + + @Schema(description = "绑定的页面") + @QueryField(type = QueryType.EQ) + private Integer pageId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "文章数量") + @QueryField(type = QueryType.EQ) + private Integer count; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "是否显示在首页") + @QueryField(type = QueryType.EQ) + private Integer showIndex; + + @Schema(description = "状态, 0正常, 1禁用") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleCommentParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleCommentParam.java new file mode 100644 index 0000000..e185eaf --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleCommentParam.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章评论表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsArticleCommentParam对象", description = "文章评论表查询参数") +public class CmsArticleCommentParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "评价ID") + @QueryField(type = QueryType.EQ) + private Integer commentId; + + @Schema(description = "文章ID") + @QueryField(type = QueryType.EQ) + private Integer articleId; + + @Schema(description = "评分 (10好评 20中评 30差评)") + @QueryField(type = QueryType.EQ) + private Integer score; + + @Schema(description = "评价内容") + private String content; + + @Schema(description = "是否为图片评价") + @QueryField(type = QueryType.EQ) + private Integer isPicture; + + @Schema(description = "评论者ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "被评价者ID") + @QueryField(type = QueryType.EQ) + private Integer toUserId; + + @Schema(description = "回复的评论ID") + @QueryField(type = QueryType.EQ) + private Integer replyCommentId; + + @Schema(description = "回复者ID") + @QueryField(type = QueryType.EQ) + private Integer replyUserId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0未读, 1已读") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleContentParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleContentParam.java new file mode 100644 index 0000000..4976eff --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleContentParam.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文章记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsArticleContentParam对象", description = "文章记录表查询参数") +public class CmsArticleContentParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "文章ID") + @QueryField(type = QueryType.EQ) + private Integer articleId; + + @Schema(description = "文章内容") + private String content; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleCountParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleCountParam.java new file mode 100644 index 0000000..9878abe --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleCountParam.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 点赞文章查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsArticleCountParam对象", description = "点赞文章查询参数") +public class CmsArticleCountParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "文章ID") + @QueryField(type = QueryType.EQ) + private Integer articleId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleImportParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleImportParam.java new file mode 100644 index 0000000..d39650f --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleImportParam.java @@ -0,0 +1,122 @@ +package com.gxwebsoft.cms.param; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户导入参数 + * + * @author WebSoft + * @since 2011-10-15 17:33:34 + */ +@Data +public class CmsArticleImportParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Excel(name = "文章ID") + private Integer articleId; + + @Excel(name = "文章标题") + private String title; + + @Excel(name = "封面图片") + private String image; + + @Excel(name = "所属栏目") + @TableField(exist = false) + private String categoryName; + + @Excel(name = "栏目ID") + private String categoryId; + + @Excel(name = "父级栏目名称") + private String parentName; + + @Excel(name = "父级栏目ID") + private Integer parentId; + + @Excel(name = "文章内容") + private String content; + + @Excel(name = "摘要") + private String comments; + + @Excel(name = "文章来源") + private String source; + + @Excel(name = "实际阅读量") + private String actualViews; + + @Excel(name = "作者") + private String author; + + @Excel(name = "发布时间") + private LocalDateTime createTime; + + @Excel(name = "类型") + private Integer type; + + @Excel(name = "模型") + private String model; + + @Excel(name = "详情页模板") + private String detail; + + @Excel(name = "话题") + private String topic; + + @Excel(name = "关键词") + private String tags; + + @Excel(name = "产品概述") + private String overview; + + @Excel(name = "显示方式") + private Integer showType; + + @Excel(name = "客户端") + private String platform; + + @Excel(name = "文件列表") + private String files; + + @Excel(name = "视频地址") + private String video; + + @Excel(name = "点赞数") + private Integer likes; + + @Excel(name = "评论数") + private Integer commentNumbers; + + @Excel(name = "推荐") + private Integer recommend; + + @Excel(name = "查看密码") + private String password; + + @Excel(name = "权限") + private Integer permission; + + @Excel(name = "用户ID") + private Integer userId; + + @Excel(name = "商户ID") + private Long merchantId; + + @Excel(name = "语言") + private String lang; + + @Excel(name = "排序") + private Integer sortNumber; + + @Excel(name = "状态") + private Integer status; + + @Excel(name = "租户ID") + private Integer tenantId; +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleLikeParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleLikeParam.java new file mode 100644 index 0000000..435d6da --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleLikeParam.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 点赞文章查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsArticleLikeParam对象", description = "点赞文章查询参数") +public class CmsArticleLikeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "文章ID") + @QueryField(type = QueryType.EQ) + private Integer articleId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsArticleParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsArticleParam.java new file mode 100644 index 0000000..479f771 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsArticleParam.java @@ -0,0 +1,185 @@ +package com.gxwebsoft.cms.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Set; + +/** + * 文章查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsArticleParam对象", description = "文章查询参数") +public class CmsArticleParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "文章ID") + @QueryField(type = QueryType.EQ) + private Integer articleId; + + @Schema(description = "父级栏目ID") + @TableField(exist = false) + private Set categoryIds; + + @Schema(description = "文章标题") + private String title; + + @Schema(description = "文章类型 0常规 1视频") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "文章模型") + @QueryField(type = QueryType.EQ) + private String model; + + @Schema(description = "详情页标识") + @QueryField(type = QueryType.EQ) + private String detail; + + @Schema(description = "列表显示方式(10小图展示 20大图展示)") + @QueryField(type = QueryType.EQ) + private Integer showType; + + @Schema(description = "话题") + @QueryField(type = QueryType.LIKE) + private String topic; + + @Schema(description = "标签") + @QueryField(type = QueryType.EQ) + private String tags; + + @Schema(description = "栏目ID") + @QueryField(type = QueryType.EQ) + private Integer navigationId; + + @Schema(description = "文章分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "父级栏目ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "是否包含封面图") + @QueryField(type = QueryType.EQ) + private Boolean hasImage; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "来源") + private String source; + + @Schema(description = "虚拟阅读量(仅用作展示)") + @QueryField(type = QueryType.EQ) + private Integer virtualViews; + + @Schema(description = "实际阅读量") + @QueryField(type = QueryType.EQ) + private Integer actualViews; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + @QueryField(type = QueryType.EQ) + private Integer permission; + + @Schema(description = "访问密码") + @QueryField(type = QueryType.EQ) + private String password; + + @Schema(description = "访问密码") + @QueryField(type = QueryType.EQ) + private String password2; + + @Schema(description = "发布来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "文章附件") + private String files; + + @Schema(description = "视频地址") + private String video; + + @Schema(description = "接受的文件类型") + private String accept; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "点赞数") + @QueryField(type = QueryType.EQ) + private Integer likes; + + @Schema(description = "评论数") + @QueryField(type = QueryType.EQ) + private Integer commentNumbers; + + @Schema(description = "提醒谁看") + private String toUsers; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "项目ID") + @QueryField(type = QueryType.EQ) + private Long projectId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "文章ID集查询") + @TableField(exist = false) + private Set articleIds; + + @Schema(description = "网站创建者ID") + @QueryField(type = QueryType.EQ) + private Integer websiteUserId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsDesignParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsDesignParam.java new file mode 100644 index 0000000..3a131b6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsDesignParam.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 页面管理记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsDesignParam对象", description = "页面管理记录表查询参数") +public class CmsDesignParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer pageId; + + @Schema(description = "页面标题") + private String name; + + @Schema(description = "所属栏目ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "页面模型") + private String model; + + @Schema(description = "页面关键词") + private String keywords; + + @Schema(description = "页面描述") + private String description; + + @Schema(description = "缩列图") + private String photo; + + @Schema(description = "购买链接") + private String buyUrl; + + @Schema(description = "页面样式") + private String style; + + @Schema(description = "页面内容") + private String content; + + @Schema(description = "是否开启布局") + @QueryField(type = QueryType.EQ) + private Integer showLayout; + + @Schema(description = "页面布局") + private String layout; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "设为首页") + @QueryField(type = QueryType.EQ) + private Integer home; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsDesignRecordParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsDesignRecordParam.java new file mode 100644 index 0000000..eaeb6f4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsDesignRecordParam.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 页面组件表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsDesignRecordParam对象", description = "页面组件表查询参数") +public class CmsDesignRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "关联导航ID") + @QueryField(type = QueryType.EQ) + private Integer navigationId; + + @Schema(description = "组件") + private String title; + + @Schema(description = "组件标识") + private String dictCode; + + @Schema(description = "组件样式") + private String styles; + + @Schema(description = "卡片阴影显示时机") + private String shadow; + + @Schema(description = "页面关键词") + private String keywords; + + @Schema(description = "页面描述") + private String description; + + @Schema(description = "页面路由地址") + private String path; + + @Schema(description = "缩列图") + private String photo; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsDomainParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsDomainParam.java new file mode 100644 index 0000000..d1cacfb --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsDomainParam.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站域名记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsDomainParam对象", description = "网站域名记录表查询参数") +public class CmsDomainParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "类型 0赠送域名 1绑定域名 ") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "主机记录") + private String hostName; + + @Schema(description = "记录值") + private String hostValue; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "网站ID") + @QueryField(type = QueryType.EQ) + private Integer websiteId; + + @Schema(description = "租户ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsFormParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsFormParam.java new file mode 100644 index 0000000..1ce4c88 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsFormParam.java @@ -0,0 +1,89 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 表单设计表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsFormParam对象", description = "表单设计表查询参数") +public class CmsFormParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer formId; + + @Schema(description = "表单标题") + private String name; + + @Schema(description = "顶部图片") + private String photo; + + @Schema(description = "背景图片") + private String background; + + @Schema(description = "视频文件") + private String video; + + @Schema(description = "提交次数") + @QueryField(type = QueryType.EQ) + private Integer submitNumber; + + @Schema(description = "页面布局") + private String layout; + + @Schema(description = "是否隐藏顶部图片") + @QueryField(type = QueryType.EQ) + private Integer hidePhoto; + + @Schema(description = "是否隐藏背景图片") + @QueryField(type = QueryType.EQ) + private Integer hideBackground; + + @Schema(description = "是否隐藏视频") + @QueryField(type = QueryType.EQ) + private Integer hideVideo; + + @Schema(description = "背景图片透明度") + @QueryField(type = QueryType.EQ) + private BigDecimal opacity; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsFormRecordParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsFormRecordParam.java new file mode 100644 index 0000000..d070843 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsFormRecordParam.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单数据记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsFormRecordParam对象", description = "表单数据记录表查询参数") +public class CmsFormRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer formRecordId; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "表单数据") + private String formData; + + @Schema(description = "表单ID") + @QueryField(type = QueryType.EQ) + private Integer formId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsLangLogParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsLangLogParam.java new file mode 100644 index 0000000..4145cee --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsLangLogParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.cms.param; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 国际化记录启用查询参数 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsLangLogParam对象", description = "国际化记录启用查询参数") +public class CmsLangLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "名称") + private String lang; + + @Schema(description = "关联ID") + @QueryField(type = QueryType.EQ) + private Integer langId; + + @Schema(description = "编码") + private String code; + + @Schema(description = "名称") + private String name; + + @Schema(description = "创建者UID") + @TableField(exist = false) + private Integer websiteUserId; +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsLangParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsLangParam.java new file mode 100644 index 0000000..a6e1d56 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsLangParam.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.cms.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 国际化查询参数 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsLangParam对象", description = "国际化查询参数") +public class CmsLangParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "名称") + private String name; + + @Schema(description = "编码") + private String code; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsLinkParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsLinkParam.java new file mode 100644 index 0000000..c27da58 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsLinkParam.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 常用链接查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsLinkParam对象", description = "常用链接查询参数") +public class CmsLinkParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "链接名称") + private String name; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "栏目ID") + private Integer categoryId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "网站创建者ID") + @QueryField(type = QueryType.EQ) + private Integer websiteUserId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsModelParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsModelParam.java new file mode 100644 index 0000000..345c253 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsModelParam.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.cms.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 模型查询参数 + * + * @author 科技小王子 + * @since 2024-11-26 15:44:53 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsModelParam对象", description = "模型查询参数") +public class CmsModelParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer modelId; + + @Schema(description = "模型名称") + private String name; + + @Schema(description = "唯一标识") + private String model; + + @Schema(description = "菜单路由地址") + private String componentDetail; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "模型banner图片") + private String banner; + + @Schema(description = "封面图宽") + @QueryField(type = QueryType.EQ) + private String imageWidth; + + @Schema(description = "封面图高") + @QueryField(type = QueryType.EQ) + private String imageHeight; + + @Schema(description = "Banner上的标题") + private String title; + + @Schema(description = "列表显示方式(10小图展示 20大图展示)") + @QueryField(type = QueryType.EQ) + private Integer showType; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "创建者用户ID") + @QueryField(type = QueryType.EQ) + private Integer websiteUserId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsNavigationParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsNavigationParam.java new file mode 100644 index 0000000..83f9e07 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsNavigationParam.java @@ -0,0 +1,154 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站导航记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsNavigationParam对象", description = "网站导航记录表查询参数") +public class CmsNavigationParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer navigationId; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "模型") + private String model; + + @Schema(description = "标识") + private String code; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "图标颜色") + private String color; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + @QueryField(type = QueryType.EQ) + private Integer permission; + + @Schema(description = "访问密码") + @QueryField(type = QueryType.EQ) + private String password; + + @Schema(description = "访问密码") + @QueryField(type = QueryType.EQ) + private String password2; + + @Schema(description = "位置 0不限 1顶部 2底部") + @QueryField(type = QueryType.EQ) + private Integer position; + + @Schema(description = "仅在顶部显示") + @QueryField(type = QueryType.EQ) + private Integer top; + + @Schema(description = "仅在底部显示") + @QueryField(type = QueryType.EQ) + private Integer bottom; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "父级栏目路由") + private String parentPath; + + @Schema(description = "父级栏目名称") + private String parentName; + + @Schema(description = "模型名称") + private String modelName; + + @Schema(description = "类型(已废弃)") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "绑定的页面(已废弃)") + @QueryField(type = QueryType.EQ) + private Integer pageId; + + @Schema(description = "是否微信小程序菜单") + @QueryField(type = QueryType.EQ) + private Boolean isMpWeixin; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "设为首页") + @QueryField(type = QueryType.EQ) + private Integer home; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Boolean recommend; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否开启布局") + @QueryField(type = QueryType.EQ) + private Boolean showLayout; + + @Schema(description = "是否查询单页内容") + @QueryField(type = QueryType.EQ) + private Boolean queryContent; + + @Schema(description = "网站创建者用户ID") + @QueryField(type = QueryType.EQ) + private Integer websiteUserId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsStatisticsParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsStatisticsParam.java new file mode 100644 index 0000000..a15d63e --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsStatisticsParam.java @@ -0,0 +1,137 @@ +package com.gxwebsoft.cms.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 站点统计信息表查询参数 + * + * @author 科技小王子 + * @since 2025-07-25 12:32:06 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsStatisticsParam对象", description = "站点统计信息表查询参数") +public class CmsStatisticsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "站点ID") + @QueryField(type = QueryType.EQ) + private Integer websiteId; + + @Schema(description = "用户总数") + @QueryField(type = QueryType.EQ) + private Integer userCount; + + @Schema(description = "订单总数") + @QueryField(type = QueryType.EQ) + private Integer orderCount; + + @Schema(description = "商品总数") + @QueryField(type = QueryType.EQ) + private Integer productCount; + + @Schema(description = "总销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal totalSales; + + @Schema(description = "本月销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal monthSales; + + @Schema(description = "今日销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal todaySales; + + @Schema(description = "昨日销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal yesterdaySales; + + @Schema(description = "本周销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal weekSales; + + @Schema(description = "本年销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal yearSales; + + @Schema(description = "今日订单数") + @QueryField(type = QueryType.EQ) + private Integer todayOrders; + + @Schema(description = "本月订单数") + @QueryField(type = QueryType.EQ) + private Integer monthOrders; + + @Schema(description = "今日新增用户") + @QueryField(type = QueryType.EQ) + private Integer todayUsers; + + @Schema(description = "本月新增用户") + @QueryField(type = QueryType.EQ) + private Integer monthUsers; + + @Schema(description = "今日访问量") + @QueryField(type = QueryType.EQ) + private Integer todayVisits; + + @Schema(description = "总访问量") + @QueryField(type = QueryType.EQ) + private Integer totalVisits; + + @Schema(description = "商户总数") + @QueryField(type = QueryType.EQ) + private Integer merchantCount; + + @Schema(description = "活跃用户数") + @QueryField(type = QueryType.EQ) + private Integer activeUsers; + + @Schema(description = "转化率(%)") + @QueryField(type = QueryType.EQ) + private BigDecimal conversionRate; + + @Schema(description = "平均订单金额") + @QueryField(type = QueryType.EQ) + private BigDecimal avgOrderAmount; + + @Schema(description = "统计日期") + private String statisticsDate; + + @Schema(description = "统计类型: 1日统计, 2月统计, 3年统计") + @QueryField(type = QueryType.EQ) + private Integer statisticsType; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "操作用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "状态: 0禁用, 1启用") + @QueryField(type = QueryType.EQ) + private Boolean status; + + @Schema(description = "是否删除: 0否, 1是") + @QueryField(type = QueryType.EQ) + private Boolean deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsTemplateParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsTemplateParam.java new file mode 100644 index 0000000..176e0a9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsTemplateParam.java @@ -0,0 +1,91 @@ +package com.gxwebsoft.cms.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站模版查询参数 + * + * @author 科技小王子 + * @since 2025-01-21 14:21:16 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsTemplateParam对象", description = "网站模版查询参数") +public class CmsTemplateParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "模版名称") + private String name; + + @Schema(description = "模版标识") + private String code; + + @Schema(description = "缩列图") + private String image; + + @Schema(description = "类型 1企业官网 2其他") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "域名前缀") + private String prefix; + + @Schema(description = "预览地址") + private String domain; + + @Schema(description = "模版下载地址") + private String downUrl; + + @Schema(description = "色系") + private String color; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Boolean recommend; + + @Schema(description = "是否共享模板") + @QueryField(type = QueryType.EQ) + private Boolean share; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteFieldParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteFieldParam.java new file mode 100644 index 0000000..6355104 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteFieldParam.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.cms.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsWebsiteFieldParam对象", description = "应用参数查询参数") +public class CmsWebsiteFieldParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "类型,0文本 1图片 2其他") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "名称") + private String name; + + @Schema(description = "默认值") + private String defaultValue; + + @Schema(description = "可修改的值 [on|off]") + private String modifyRange; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "名称") + private String value; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "创建者") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteParam.java new file mode 100644 index 0000000..423a59c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteParam.java @@ -0,0 +1,219 @@ +package com.gxwebsoft.cms.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Set; + +/** + * 网站信息记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsWebsiteParam对象", description = "网站信息记录表查询参数") +public class CmsWebsiteParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "站点ID") + @QueryField(type = QueryType.EQ) + private Integer websiteId; + + @Schema(description = "站点类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "网站名称") + private String websiteName; + + @Schema(description = "网站标识") + private String websiteCode; + + @Schema(description = "网站LOGO") + private String websiteIcon; + + @Schema(description = "网站LOGO") + private String websiteLogo; + + @Schema(description = "网站LOGO(深色模式)") + private String websiteDarkLogo; + + @Schema(description = "网站类型") + private String websiteType; + + @Schema(description = "栏目ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "网站截图") + private String files; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "域名前缀") + private String prefix; + + @Schema(description = "绑定域名") + private String domain; + + @Schema(description = "全局样式") + private String style; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "服务到期时间") + private String expirationTime; + + @Schema(description = "模版ID") + @QueryField(type = QueryType.EQ) + private Integer templateId; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "开发者名称") + private String developer; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "电子邮箱") + private String email; + + @Schema(description = "ICP备案号") + private String icpNo; + + @Schema(description = "公安备案") + private String policeNo; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "是否官方产品") + @QueryField(type = QueryType.EQ) + private Boolean official; + + @Schema(description = "是否查询超管账号") + @QueryField(type = QueryType.EQ) + private Boolean showAdminPhone; + + @Schema(description = "允许展示到插件市场") + @QueryField(type = QueryType.EQ) + private Boolean market; + + @Schema(description = "是否插件类型 0应用 1插件") + @QueryField(type = QueryType.EQ) + private Boolean plugin; + + @Schema(description = "允许被搜索") + @QueryField(type = QueryType.EQ) + private Boolean search; + + @Schema(description = "主题色") + private String color; + + @Schema(description = "点赞数量") + private Integer likes; + + @Schema(description = "点击数量") + private Integer clicks; + + @Schema(description = "购买数量") + private Integer buys; + + @Schema(description = "下载数量") + private Integer downloads; + + @Schema(description = "状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "维护说明") + private String statusText; + + @Schema(description = "关闭说明") + private String statusClose; + + @Schema(description = "全局样式") + private String styles; + + @Schema(description = "运行状态") + @QueryField(type = QueryType.EQ) + private Integer running; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "按userId集搜索") + @QueryField(type = QueryType.EQ) + private Set userIds; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "按WebsiteIds集搜索") + private Set websiteIds; + + @Schema(description = "当前登录用户ID") + @QueryField(type = QueryType.EQ) + private Integer loginUserId; + + @Schema(description = "管理员手机号") + @QueryField(type = QueryType.EQ) + private String adminPhone; + +} diff --git a/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteSettingParam.java b/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteSettingParam.java new file mode 100644 index 0000000..6d195a6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/param/CmsWebsiteSettingParam.java @@ -0,0 +1,86 @@ +package com.gxwebsoft.cms.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站设置查询参数 + * + * @author 科技小王子 + * @since 2025-02-19 01:35:44 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CmsWebsiteSettingParam对象", description = "网站设置查询参数") +public class CmsWebsiteSettingParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "关联网站ID") + @QueryField(type = QueryType.EQ) + private Integer websiteId; + + @Schema(description = "是否官方插件") + @QueryField(type = QueryType.EQ) + private Boolean official; + + @Schema(description = "是否展示在插件市场") + @QueryField(type = QueryType.EQ) + private Boolean market; + + @Schema(description = "是否允许被搜索") + @QueryField(type = QueryType.EQ) + private Boolean search; + + @Schema(description = "是否共享") + @QueryField(type = QueryType.EQ) + private Boolean share; + + @Schema(description = "是否插件 0应用1 插件 ") + @QueryField(type = QueryType.EQ) + private Boolean plugin; + + @Schema(description = "编辑器类型 1 md-editor-v3, 2 tinymce-editor") + @QueryField(type = QueryType.EQ) + private Boolean editor; + + @Schema(description = "显示站内搜索") + @QueryField(type = QueryType.EQ) + private Boolean searchBtn; + + @Schema(description = "显示登录注册功能") + @QueryField(type = QueryType.EQ) + private Boolean loginBtn; + + @Schema(description = "显示悬浮客服工具") + @QueryField(type = QueryType.EQ) + private Boolean floatTool; + + @Schema(description = "显示版权链接") + @QueryField(type = QueryType.EQ) + private Boolean copyrightLink; + + @Schema(description = "导航栏最多显示数量") + @QueryField(type = QueryType.EQ) + private Boolean maxMenuNum; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsAdRecordService.java b/src/main/java/com/gxwebsoft/cms/service/CmsAdRecordService.java new file mode 100644 index 0000000..5610204 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsAdRecordService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsAdRecord; +import com.gxwebsoft.cms.param.CmsAdRecordParam; + +import java.util.List; + +/** + * 广告图片Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsAdRecordService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsAdRecordParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsAdRecordParam param); + + /** + * 根据id查询 + * + * @param adRecordId ID + * @return CmsAdRecord + */ + CmsAdRecord getByIdRel(Integer adRecordId); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsAdService.java b/src/main/java/com/gxwebsoft/cms/service/CmsAdService.java new file mode 100644 index 0000000..9cef726 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsAdService.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsAd; +import com.gxwebsoft.cms.param.CmsAdParam; + +import java.util.List; + +/** + * 广告位Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsAdService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsAdParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsAdParam param); + + /** + * 根据id查询 + * + * @param adId ID + * @return CmsAd + */ + CmsAd getByIdRel(Integer adId); + + /** + * 根据code查询 + * + * @return CmsAd + */ + CmsAd getByIdCode(String code); +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsArticleCategoryService.java b/src/main/java/com/gxwebsoft/cms/service/CmsArticleCategoryService.java new file mode 100644 index 0000000..38d55be --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsArticleCategoryService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsArticleCategory; +import com.gxwebsoft.cms.param.CmsArticleCategoryParam; + +import java.util.List; + +/** + * 文章分类表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleCategoryService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsArticleCategoryParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsArticleCategoryParam param); + + /** + * 根据id查询 + * + * @param categoryId 文章分类ID + * @return CmsArticleCategory + */ + CmsArticleCategory getByIdRel(Integer categoryId); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsArticleCommentService.java b/src/main/java/com/gxwebsoft/cms/service/CmsArticleCommentService.java new file mode 100644 index 0000000..68b4fd8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsArticleCommentService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsArticleComment; +import com.gxwebsoft.cms.param.CmsArticleCommentParam; + +import java.util.List; + +/** + * 文章评论表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleCommentService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsArticleCommentParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsArticleCommentParam param); + + /** + * 根据id查询 + * + * @param commentId 评价ID + * @return CmsArticleComment + */ + CmsArticleComment getByIdRel(Integer commentId); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsArticleContentService.java b/src/main/java/com/gxwebsoft/cms/service/CmsArticleContentService.java new file mode 100644 index 0000000..640ad80 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsArticleContentService.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.entity.TranslateDataVo; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsArticleContent; +import com.gxwebsoft.cms.param.CmsArticleContentParam; + +import java.util.List; + +/** + * 文章记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleContentService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsArticleContentParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsArticleContentParam param); + + + CmsArticleContent getByIdRel(Integer id); + + void translate(CmsArticle article); +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsArticleCountService.java b/src/main/java/com/gxwebsoft/cms/service/CmsArticleCountService.java new file mode 100644 index 0000000..c9cd3b1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsArticleCountService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsArticleCount; +import com.gxwebsoft.cms.param.CmsArticleCountParam; + +import java.util.List; + +/** + * 点赞文章Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleCountService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsArticleCountParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsArticleCountParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return CmsArticleCount + */ + CmsArticleCount getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsArticleLikeService.java b/src/main/java/com/gxwebsoft/cms/service/CmsArticleLikeService.java new file mode 100644 index 0000000..08cd5a3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsArticleLikeService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsArticleLike; +import com.gxwebsoft.cms.param.CmsArticleLikeParam; + +import java.util.List; + +/** + * 点赞文章Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleLikeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsArticleLikeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsArticleLikeParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return CmsArticleLike + */ + CmsArticleLike getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsArticleService.java b/src/main/java/com/gxwebsoft/cms/service/CmsArticleService.java new file mode 100644 index 0000000..5eccbea --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsArticleService.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.param.CmsArticleParam; + +import javax.validation.Valid; +import java.util.List; + +/** + * 文章Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsArticleService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsArticleParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsArticleParam param); + + /** + * 根据id查询 + * + * @param articleId 文章ID + * @return CmsArticle + */ + CmsArticle getByIdRel(Integer articleId); + + void saveInc(Integer formId); + + boolean saveRel(@Valid CmsArticle article); + + boolean updateByIdRel(CmsArticle article); +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsDesignRecordService.java b/src/main/java/com/gxwebsoft/cms/service/CmsDesignRecordService.java new file mode 100644 index 0000000..e9e3465 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsDesignRecordService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsDesignRecord; +import com.gxwebsoft.cms.param.CmsDesignRecordParam; + +import java.util.List; + +/** + * 页面组件表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsDesignRecordService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsDesignRecordParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsDesignRecordParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return CmsDesignRecord + */ + CmsDesignRecord getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsDesignService.java b/src/main/java/com/gxwebsoft/cms/service/CmsDesignService.java new file mode 100644 index 0000000..6a895d3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsDesignService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsDesign; +import com.gxwebsoft.cms.param.CmsDesignParam; + +import java.util.List; + +/** + * 页面管理记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsDesignService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsDesignParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsDesignParam param); + + /** + * 根据id查询 + * + * @param pageId ID + * @return CmsDesign + */ + CmsDesign getByIdRel(Integer pageId); + + void translate(CmsDesign cmsDesign); +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsDomainService.java b/src/main/java/com/gxwebsoft/cms/service/CmsDomainService.java new file mode 100644 index 0000000..bdb1944 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsDomainService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsDomain; +import com.gxwebsoft.cms.param.CmsDomainParam; + +import java.util.List; + +/** + * 网站域名记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +public interface CmsDomainService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsDomainParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsDomainParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return CmsDomain + */ + CmsDomain getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsFormRecordService.java b/src/main/java/com/gxwebsoft/cms/service/CmsFormRecordService.java new file mode 100644 index 0000000..86b1b2f --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsFormRecordService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsFormRecord; +import com.gxwebsoft.cms.param.CmsFormRecordParam; + +import java.util.List; + +/** + * 表单数据记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsFormRecordService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsFormRecordParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsFormRecordParam param); + + /** + * 根据id查询 + * + * @param formRecordId ID + * @return CmsFormRecord + */ + CmsFormRecord getByIdRel(Integer formRecordId); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsFormService.java b/src/main/java/com/gxwebsoft/cms/service/CmsFormService.java new file mode 100644 index 0000000..a6c98fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsFormService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsForm; +import com.gxwebsoft.cms.param.CmsFormParam; + +import java.util.List; + +/** + * 表单设计表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsFormService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsFormParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsFormParam param); + + /** + * 根据id查询 + * + * @param formId ID + * @return CmsForm + */ + CmsForm getByIdRel(Integer formId); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsLangLogService.java b/src/main/java/com/gxwebsoft/cms/service/CmsLangLogService.java new file mode 100644 index 0000000..d3e59ff --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsLangLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.param.CmsLangLogParam; + +import java.util.List; + +/** + * 国际化记录启用Service + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +public interface CmsLangLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsLangLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsLangLogParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return CmsLangLog + */ + CmsLangLog getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsLangService.java b/src/main/java/com/gxwebsoft/cms/service/CmsLangService.java new file mode 100644 index 0000000..d1c1fa3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsLangService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsLang; +import com.gxwebsoft.cms.param.CmsLangParam; + +import java.util.List; + +/** + * 国际化Service + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +public interface CmsLangService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsLangParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsLangParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return CmsLang + */ + CmsLang getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsLinkService.java b/src/main/java/com/gxwebsoft/cms/service/CmsLinkService.java new file mode 100644 index 0000000..e65eee7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsLinkService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsLink; +import com.gxwebsoft.cms.param.CmsLinkParam; + +import java.util.List; + +/** + * 常用链接Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsLinkService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsLinkParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsLinkParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CmsLink + */ + CmsLink getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsModelService.java b/src/main/java/com/gxwebsoft/cms/service/CmsModelService.java new file mode 100644 index 0000000..1fe9778 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsModelService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.param.CmsModelParam; + +import java.util.List; + +/** + * 模型Service + * + * @author 科技小王子 + * @since 2024-11-26 15:44:53 + */ +public interface CmsModelService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsModelParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsModelParam param); + + /** + * 根据id查询 + * + * @param modelId ID + * @return CmsModel + */ + CmsModel getByIdRel(Integer modelId); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsNavigationService.java b/src/main/java/com/gxwebsoft/cms/service/CmsNavigationService.java new file mode 100644 index 0000000..5188ea2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsNavigationService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.param.CmsNavigationParam; + +import java.util.List; + +/** + * 网站导航记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +public interface CmsNavigationService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsNavigationParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsNavigationParam param); + + /** + * 根据id查询 + * + * @param navigationId ID + * @return CmsNavigation + */ + CmsNavigation getByIdRel(Integer navigationId); + + void saveAsync(CmsNavigation cmsNavigation); +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsStatisticsService.java b/src/main/java/com/gxwebsoft/cms/service/CmsStatisticsService.java new file mode 100644 index 0000000..948d780 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsStatisticsService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsStatistics; +import com.gxwebsoft.cms.param.CmsStatisticsParam; + +import java.util.List; + +/** + * 站点统计信息表Service + * + * @author 科技小王子 + * @since 2025-07-25 12:32:06 + */ +public interface CmsStatisticsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsStatisticsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsStatisticsParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CmsStatistics + */ + CmsStatistics getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsTemplateService.java b/src/main/java/com/gxwebsoft/cms/service/CmsTemplateService.java new file mode 100644 index 0000000..979fa0c --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsTemplateService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsTemplate; +import com.gxwebsoft.cms.param.CmsTemplateParam; + +import java.util.List; + +/** + * 网站模版Service + * + * @author 科技小王子 + * @since 2025-01-21 14:21:16 + */ +public interface CmsTemplateService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsTemplateParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsTemplateParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return CmsTemplate + */ + CmsTemplate getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteFieldService.java b/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteFieldService.java new file mode 100644 index 0000000..9eab912 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteFieldService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsWebsiteField; +import com.gxwebsoft.cms.param.CmsWebsiteFieldParam; + +import java.util.List; + +/** + * 应用参数Service + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +public interface CmsWebsiteFieldService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsWebsiteFieldParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsWebsiteFieldParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CmsWebsiteField + */ + CmsWebsiteField getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteService.java b/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteService.java new file mode 100644 index 0000000..a78dfa2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteService.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.cms.param.CmsWebsiteParam; +import com.gxwebsoft.shop.vo.ShopVo; + +import java.util.List; + +/** + * 网站信息记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +public interface CmsWebsiteService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsWebsiteParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsWebsiteParam param); + + /** + * 根据id查询 + * + * @param websiteId 站点ID + * @return CmsWebsite + */ + CmsWebsite getByIdRel(Integer websiteId); + + PageResult pageRelAll(CmsWebsiteParam param); + + // 创建站点 + CmsWebsite create(CmsWebsite cmsWebsite); + + CmsWebsite getByIdRelAll(Integer id); + + boolean updateByIdAll(CmsWebsite cmsWebsite); + + boolean removeByIdAll(Integer id); + + CmsWebsite getByTenantId(Integer tenantId); + + /** + * 获取网站基本信息(VO格式) + * + * @param tenantId 租户ID + * @return 网站信息VO + */ + ShopVo getSiteInfo(Integer tenantId); + + /** + * 清除网站信息缓存 + * + * @param tenantId 租户ID + */ + void clearSiteInfoCache(Integer tenantId); +} diff --git a/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteSettingService.java b/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteSettingService.java new file mode 100644 index 0000000..610a7c1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/CmsWebsiteSettingService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.cms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.cms.entity.CmsWebsiteSetting; +import com.gxwebsoft.cms.param.CmsWebsiteSettingParam; + +import java.util.List; + +/** + * 网站设置Service + * + * @author 科技小王子 + * @since 2025-02-19 01:35:44 + */ +public interface CmsWebsiteSettingService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CmsWebsiteSettingParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CmsWebsiteSettingParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CmsWebsiteSetting + */ + CmsWebsiteSetting getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsAdRecordServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsAdRecordServiceImpl.java new file mode 100644 index 0000000..d82d016 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsAdRecordServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsAdRecordMapper; +import com.gxwebsoft.cms.service.CmsAdRecordService; +import com.gxwebsoft.cms.entity.CmsAdRecord; +import com.gxwebsoft.cms.param.CmsAdRecordParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 广告图片Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsAdRecordServiceImpl extends ServiceImpl implements CmsAdRecordService { + + @Override + public PageResult pageRel(CmsAdRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsAdRecordParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsAdRecord getByIdRel(Integer adRecordId) { + CmsAdRecordParam param = new CmsAdRecordParam(); + param.setAdRecordId(adRecordId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsAdServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsAdServiceImpl.java new file mode 100644 index 0000000..2988b3f --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsAdServiceImpl.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.cms.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsAdMapper; +import com.gxwebsoft.cms.service.CmsAdService; +import com.gxwebsoft.cms.entity.CmsAd; +import com.gxwebsoft.cms.param.CmsAdParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 广告位Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsAdServiceImpl extends ServiceImpl implements CmsAdService { + + @Override + public PageResult pageRel(CmsAdParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsAdParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time asc"); + return page.sortRecords(list); + } + + @Override + public CmsAd getByIdRel(Integer adId) { + CmsAdParam param = new CmsAdParam(); + param.setAdId(adId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public CmsAd getByIdCode(String code) { + CmsAdParam param = new CmsAdParam(); + param.setCode(code); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCategoryServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCategoryServiceImpl.java new file mode 100644 index 0000000..e52e243 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCategoryServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsArticleCategoryMapper; +import com.gxwebsoft.cms.service.CmsArticleCategoryService; +import com.gxwebsoft.cms.entity.CmsArticleCategory; +import com.gxwebsoft.cms.param.CmsArticleCategoryParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 文章分类表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsArticleCategoryServiceImpl extends ServiceImpl implements CmsArticleCategoryService { + + @Override + public PageResult pageRel(CmsArticleCategoryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsArticleCategoryParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsArticleCategory getByIdRel(Integer categoryId) { + CmsArticleCategoryParam param = new CmsArticleCategoryParam(); + param.setCategoryId(categoryId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCommentServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCommentServiceImpl.java new file mode 100644 index 0000000..cc41aed --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCommentServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsArticleCommentMapper; +import com.gxwebsoft.cms.service.CmsArticleCommentService; +import com.gxwebsoft.cms.entity.CmsArticleComment; +import com.gxwebsoft.cms.param.CmsArticleCommentParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 文章评论表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsArticleCommentServiceImpl extends ServiceImpl implements CmsArticleCommentService { + + @Override + public PageResult pageRel(CmsArticleCommentParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsArticleCommentParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsArticleComment getByIdRel(Integer commentId) { + CmsArticleCommentParam param = new CmsArticleCommentParam(); + param.setCommentId(commentId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleContentServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleContentServiceImpl.java new file mode 100644 index 0000000..a2a0faf --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleContentServiceImpl.java @@ -0,0 +1,189 @@ +package com.gxwebsoft.cms.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.*; +import com.gxwebsoft.cms.mapper.CmsArticleContentMapper; +import com.gxwebsoft.cms.service.CmsArticleContentService; +import com.gxwebsoft.cms.param.CmsArticleContentParam; +import com.gxwebsoft.cms.service.CmsArticleService; +import com.gxwebsoft.cms.service.CmsLangLogService; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.common.core.utils.AliYunSender; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 文章记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsArticleContentServiceImpl extends ServiceImpl implements CmsArticleContentService { + @Resource + @Lazy + private CmsNavigationService cmsNavigationService; + @Resource + @Lazy + private CmsArticleService cmsArticleService; + @Resource + private CmsLangLogService cmsLangLogService; + @Override + public PageResult pageRel(CmsArticleContentParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsArticleContentParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsArticleContent getByIdRel(Integer id) { + CmsArticleContentParam param = new CmsArticleContentParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void translate(CmsArticle article) { + // 未开启多语言 + if(cmsLangLogService.count() == 0){ + return; + } + // 仅限定新增简体中文才会同步翻译其他目标语言 + if(!article.getLang().equals("zh_CN")){ + return; + } + // 是否启用自动翻译 + if(!article.getTranslation()){ + return; + } + // 查询关联的默认语言栏目ID + CmsNavigation navigation = cmsNavigationService.getOne(new LambdaQueryWrapper().eq(CmsNavigation::getLangCategoryId, article.getCategoryId()).last("limit 1")); + if (ObjectUtil.isNotEmpty(navigation)) { + TranslateDataVo vo = new TranslateDataVo(); + vo.setFormatType("text"); + vo.setSourceLanguage("auto"); + vo.setTargetLanguage("en"); + + // 翻译标题 + vo.setScene("title"); + vo.setSourceText(article.getTitle()); + article.setTitle(getTranslateApi(vo)); + + // 翻译摘要 + vo.setSourceText(article.getComments()); + vo.setScene("description"); + article.setComments(getTranslateApi(vo)); + + // 翻译产品概述 + vo.setSourceText(article.getOverview()); + article.setOverview(getTranslateApi(vo)); + + // 翻译关键词 + vo.setScene("title"); + vo.setSourceText(article.getTags()); + article.setTags(getTranslateApi(vo)); + + // 翻译话题 + vo.setScene("title"); + vo.setSourceText(article.getTopic()); + article.setTopic(getTranslateApi(vo)); + + // 翻译来源 + vo.setScene("title"); + vo.setSourceText(article.getSource()); + article.setSource(getTranslateApi(vo)); + + // 翻译内容 + vo.setScene(null); + vo.setSourceText(article.getContent()); + article.setContent(getTranslateApi(vo)); + + // 其他参数 + article.setLang("en"); + article.setCategoryId(navigation.getNavigationId()); + + + CmsArticle target = cmsArticleService.getOne(new LambdaQueryWrapper().eq(CmsArticle::getLangArticleId,article.getArticleId()).last("limit 1")); + if(article.getIsUpdate() != null && target != null){ + // 更新操作 + if (ObjectUtil.isNotEmpty(target)) { + target.setTitle(article.getTitle()); + target.setImage(article.getImage()); + target.setFiles(article.getFiles()); + target.setComments(article.getComments()); + target.setTags(article.getTags()); + target.setRecommend(article.getRecommend()); + target.setOverview(article.getOverview()); + target.setContent(article.getContent()); + cmsArticleService.updateById(target); + this.update(new LambdaUpdateWrapper().eq(CmsArticleContent::getArticleId, target.getArticleId()).set(CmsArticleContent::getContent,target.getContent())); + } + }else { + // 新增操作 + article.setLangArticleId(article.getArticleId()); + cmsArticleService.save(article); + final CmsArticleContent content = new CmsArticleContent(); + content.setArticleId(article.getArticleId()); + content.setContent(article.getContent()); + content.setTenantId(article.getTenantId()); + this.save(content); + } + } + + } + + /** + * 机器翻译 + * 阿里云接口 + * ... + */ + public String getTranslateApi(TranslateDataVo item){ + String serviceURL = "http://mt.cn-hangzhou.aliyuncs.com/api/translate/web/ecommerce"; + String accessKeyId = "LTAI5tEsyhW4GCKbds1qsopg";// 使用您的阿里云访问密钥 AccessKeyId + String accessKeySecret = "zltFlQrYVAoq2KMFDWgLa3GhkMNeyO"; // 使用您的阿里云访问密钥 + + final Map map = new HashMap<>(); + map.put("FormatType","text"); + map.put("SourceLanguage","auto"); + map.put("TargetLanguage","en"); + map.put("SourceText",item.getSourceText()); + map.put("Scene","description"); + map.put("Context","产品介绍"); + // Sender代码请参考帮助文档“签名方法” + String result = AliYunSender.sendPost(serviceURL, JSONUtil.toJSONString(map), accessKeyId, accessKeySecret); + JSONObject jsonObject = JSON.parseObject(result); + final Object code = jsonObject.get("Code"); + if (code.equals("200")) { + final Object data = jsonObject.get("Data"); + JSONObject data1 = JSON.parseObject(data.toString()); + final Object translated = data1.get("Translated"); + final Object wordCount = data1.get("WordCount"); + return translated.toString(); + } + return ""; + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCountServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCountServiceImpl.java new file mode 100644 index 0000000..f5804d4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleCountServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsArticleCountMapper; +import com.gxwebsoft.cms.service.CmsArticleCountService; +import com.gxwebsoft.cms.entity.CmsArticleCount; +import com.gxwebsoft.cms.param.CmsArticleCountParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 点赞文章Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsArticleCountServiceImpl extends ServiceImpl implements CmsArticleCountService { + + @Override + public PageResult pageRel(CmsArticleCountParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsArticleCountParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsArticleCount getByIdRel(Integer id) { + CmsArticleCountParam param = new CmsArticleCountParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleLikeServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleLikeServiceImpl.java new file mode 100644 index 0000000..26d7521 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleLikeServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsArticleLikeMapper; +import com.gxwebsoft.cms.service.CmsArticleLikeService; +import com.gxwebsoft.cms.entity.CmsArticleLike; +import com.gxwebsoft.cms.param.CmsArticleLikeParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 点赞文章Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsArticleLikeServiceImpl extends ServiceImpl implements CmsArticleLikeService { + + @Override + public PageResult pageRel(CmsArticleLikeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsArticleLikeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsArticleLike getByIdRel(Integer id) { + CmsArticleLikeParam param = new CmsArticleLikeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleServiceImpl.java new file mode 100644 index 0000000..6ade636 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsArticleServiceImpl.java @@ -0,0 +1,246 @@ +package com.gxwebsoft.cms.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.CmsArticleContent; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.mapper.CmsArticleMapper; +import com.gxwebsoft.cms.service.CmsArticleContentService; +import com.gxwebsoft.cms.service.CmsArticleService; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.param.CmsArticleParam; +import com.gxwebsoft.cms.service.CmsModelService; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.service.UserService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.gxwebsoft.common.core.constants.ArticleConstants.CACHE_KEY_ARTICLE; + +/** + * 文章Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsArticleServiceImpl extends ServiceImpl implements CmsArticleService { + @Resource + @Lazy + private CmsNavigationService cmsNavigationService; + @Resource + private CmsArticleContentService cmsArticleContentService; + @Resource + private UserService userService; + @Resource + private CmsModelService cmsModelService; + @Resource + private RedisUtil redisUtil; + + private static final int PERMISSION_PASSWORD = 2; + private static final long CACHE_MINUTES = 30L; + + @Override + public PageResult pageRel(CmsArticleParam param) { + if (param.getParentId() != null && !param.getParentId().equals(0)) { + final List cmsNavigations = cmsNavigationService.list(new LambdaQueryWrapper().eq(CmsNavigation::getParentId, param.getParentId())); + if (!CollectionUtils.isEmpty(cmsNavigations)) { + param.setCategoryIds(cmsNavigations.stream().map(CmsNavigation::getNavigationId).collect(Collectors.toSet())); + } + } + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc,create_time desc"); + List list = baseMapper.selectPageRel(page, param); + + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsArticleParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc,create_time desc"); + + if (StrUtil.isNotBlank(param.getSceneType())) { + // 导出数据 + if (param.getSceneType().equals("Content")) { + final Set collectIds = list.stream().map(CmsArticle::getArticleId).collect(Collectors.toSet()); + final List contents = cmsArticleContentService.list(new LambdaQueryWrapper().in(CmsArticleContent::getArticleId, collectIds)); + final Map> collect = contents.stream().collect(Collectors.groupingBy(CmsArticleContent::getArticleId)); + list.forEach(d -> { + final List cmsArticleContents = collect.get(d.getArticleId()); + final CmsArticleContent content = cmsArticleContents.get(0); + if (ObjectUtil.isNotEmpty(content)) { + d.setContent(content.getContent()); + } + }); + } + } + return page.sortRecords(list); + } + + @Override + public CmsArticle getByIdRel(Integer articleId) { +// String key = CACHE_KEY_ARTICLE + articleId; +// final String cacheInfo = redisUtil.get(key); +// if (StrUtil.isNotBlank(cacheInfo)) { +// final CmsArticle article = JSONUtil.parseObject(cacheInfo, CmsArticle.class); +// // 更新阅读数量 +// assert article != null; +// article.setActualViews(article.getActualViews() + 1); +// updateById(article); +// return article; +// } + // 缓存不存在 + CmsArticleParam param = new CmsArticleParam(); + param.setArticleId(articleId); + final CmsArticle article = param.getOne(baseMapper.selectListRel(param)); + if (ObjectUtil.isNotEmpty(article)) { + // 更新阅读数量 + article.setActualViews(article.getActualViews() + 1); + updateById(article); + // 读取Banner +// final CmsModel model = cmsModelService.getOne(new LambdaQueryWrapper().eq(CmsModel::getModel, article.getModel()).last("limit 1")); +// if (ObjectUtil.isNotEmpty(model)) { +// article.setBanner(model.getBanner()); +// } + // 附加文字内容 + CmsArticleContent content = cmsArticleContentService.getOne(new LambdaQueryWrapper().eq(CmsArticleContent::getArticleId, article.getArticleId()).last("limit 1")); + if (content != null) { + article.setContent(content.getContent()); + } +// redisUtil.set(key, article, CACHE_MINUTES, TimeUnit.MINUTES); + return article; + } + return null; + } + + @Override + public void saveInc(Integer formId) { + final CmsArticle article = getById(formId); + if (ObjectUtil.isNull(article)) { + return; + } + article.setBmUsers(article.getBmUsers() + 1); + updateById(article); + } + + @Override + public boolean saveRel(CmsArticle article) { + try { + // 保存文章模型 + final CmsNavigation cmsNavigation = cmsNavigationService.getByIdRel(article.getCategoryId()); + final CmsModel modelInfo = cmsNavigation.getModelInfo(); + final String componentDetail = modelInfo.getComponentDetail(); + if (ObjectUtil.isNotEmpty(componentDetail)) { + final String[] split = componentDetail.split("/"); + article.setModel(modelInfo.getModel()); + if (split[2].equals(modelInfo.getModel())) { + article.setDetail(split[2].concat("/").concat(split[3])); + } else { + article.setDetail(split[2]); + } + } + // 是否密码可见 + if (article.getPermission() != null && article.getPermission() == PERMISSION_PASSWORD) { + article.setPassword(userService.encodePassword(article.getPassword())); + } + // 保存文章内容 + final boolean save = save(article); + if(StrUtil.isBlank(article.getContent())){ + return true; + } + if (save) { + final CmsArticleContent content = new CmsArticleContent(); + content.setArticleId(article.getArticleId()); + content.setContent(article.getContent()); + content.setTenantId(article.getTenantId()); + cmsArticleContentService.save(content); + // 同步翻译并保存 + cmsArticleContentService.translate(article); + return true; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return false; + } + + @Override + public boolean updateByIdRel(CmsArticle article) { + // 是否密码可见 + if (article.getPermission() == PERMISSION_PASSWORD) { + article.setPassword(userService.encodePassword(article.getPassword())); + } + try { + // 保存文章模型 + final CmsNavigation cmsNavigation = cmsNavigationService.getByIdRel(article.getCategoryId()); + // 模型信息 + if (ObjectUtil.isNotEmpty(cmsNavigation)) { + final CmsModel modelInfo = cmsNavigation.getModelInfo(); + final String componentDetail = modelInfo.getComponentDetail(); + if (ObjectUtil.isNotEmpty(componentDetail)) { + final String[] split = componentDetail.split("/"); + article.setModel(modelInfo.getModel()); + if (split[2].equals(modelInfo.getModel())) { + article.setDetail(split[2].concat("/").concat(split[3])); + } else { + article.setDetail(split[2]); + } + } + } + // 修正父级栏目ID + if (article.getParentId().equals(0)) { + final CmsNavigation current = cmsNavigationService.getById(article.getCategoryId()); + if (ObjectUtil.isNotEmpty(current)) { + article.setParentId(current.getParentId()); + } + } + if (updateById(article)) { + if (StrUtil.isBlank(article.getContent())) { + return true; + } + // 删除缓存 + String key = CACHE_KEY_ARTICLE + article.getArticleId(); + redisUtil.delete(key); + // 更新内容 + final boolean update = cmsArticleContentService.update(new LambdaUpdateWrapper().eq(CmsArticleContent::getArticleId, article.getArticleId()).set(CmsArticleContent::getContent, article.getContent())); + if (update) { + // 同步翻译并保存 + article.setIsUpdate(true); + cmsArticleContentService.translate(article); + return true; + } else { + // 添加内容 + final CmsArticleContent content = new CmsArticleContent(); + content.setArticleId(article.getArticleId()); + content.setContent(article.getContent()); + content.setTenantId(article.getTenantId()); + cmsArticleContentService.save(content); + } + return true; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return false; + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignRecordServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignRecordServiceImpl.java new file mode 100644 index 0000000..f1810c4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignRecordServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsDesignRecordMapper; +import com.gxwebsoft.cms.service.CmsDesignRecordService; +import com.gxwebsoft.cms.entity.CmsDesignRecord; +import com.gxwebsoft.cms.param.CmsDesignRecordParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 页面组件表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsDesignRecordServiceImpl extends ServiceImpl implements CmsDesignRecordService { + + @Override + public PageResult pageRel(CmsDesignRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsDesignRecordParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsDesignRecord getByIdRel(Integer id) { + CmsDesignRecordParam param = new CmsDesignRecordParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignServiceImpl.java new file mode 100644 index 0000000..ed53a12 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsDesignServiceImpl.java @@ -0,0 +1,157 @@ +package com.gxwebsoft.cms.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.entity.TranslateDataVo; +import com.gxwebsoft.cms.mapper.CmsDesignMapper; +import com.gxwebsoft.cms.service.CmsArticleContentService; +import com.gxwebsoft.cms.service.CmsDesignService; +import com.gxwebsoft.cms.entity.CmsDesign; +import com.gxwebsoft.cms.param.CmsDesignParam; +import com.gxwebsoft.cms.service.CmsLangLogService; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.common.core.utils.AliYunSender; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 页面管理记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsDesignServiceImpl extends ServiceImpl implements CmsDesignService { + @Resource + private CmsLangLogService cmsLangLogService; + @Resource + @Lazy + private CmsNavigationService cmsNavigationService; + @Resource + @Lazy + private CmsArticleContentService cmsArticleContentService; + + @Override + public PageResult pageRel(CmsDesignParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsDesignParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time asc"); + return page.sortRecords(list); + } + + @Override + public CmsDesign getByIdRel(Integer pageId) { + CmsDesignParam param = new CmsDesignParam(); + param.setPageId(pageId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void translate(CmsDesign cmsDesign) { + // 是否启用自动翻译 + if (!cmsDesign.getTranslation()) { + return; + } + // 未开启多语言 + if (cmsLangLogService.count() == 0) { + return; + } + // 查询关联的默认语言栏目ID + CmsNavigation navigation = cmsNavigationService.getOne(new LambdaQueryWrapper().eq(CmsNavigation::getLangCategoryId, cmsDesign.getCategoryId()).last("limit 1")); + if (ObjectUtil.isEmpty(navigation)) { + return; + } + // 仅限定新增简体中文才会同步翻译其他目标语言 + if (!navigation.getLang().equals("en")) { + return; + } + // 查找要翻译的单页面信息 + final CmsDesign design = getOne(new LambdaQueryWrapper().eq(CmsDesign::getCategoryId, navigation.getNavigationId()).last("limit 1")); + if (ObjectUtil.isNotEmpty(design)) { + + TranslateDataVo vo = new TranslateDataVo(); + vo.setFormatType("text"); + vo.setSourceLanguage("auto"); + vo.setTargetLanguage("en"); + + // 翻译标题 + vo.setScene("title"); + vo.setSourceText(cmsDesign.getName()); + design.setName(getTranslateApi(vo)); + + // 翻译关键词 + vo.setSourceText(cmsDesign.getKeywords()); + design.setKeywords(getTranslateApi(vo)); + + // 翻译描述 + vo.setSourceText(cmsDesign.getDescription()); + design.setDescription(getTranslateApi(vo)); + + // 翻译页面内容 + vo.setScene(null); + vo.setSourceText(cmsDesign.getContent()); + design.setContent(getTranslateApi(vo)); + design.setShowBanner(cmsDesign.getShowBanner()); + design.setStyle(cmsDesign.getStyle()); + design.setShowButton(cmsDesign.getShowButton()); + design.setBuyUrl(cmsDesign.getBuyUrl()); + updateById(design); + } + } + + /** + * 机器翻译 + * 阿里云接口 + * ... + */ + public String getTranslateApi(TranslateDataVo item) { + String serviceURL = "http://mt.cn-hangzhou.aliyuncs.com/api/translate/web/ecommerce"; + String accessKeyId = "LTAI5tEsyhW4GCKbds1qsopg";// 使用您的阿里云访问密钥 AccessKeyId + String accessKeySecret = "zltFlQrYVAoq2KMFDWgLa3GhkMNeyO"; // 使用您的阿里云访问密钥 + + final Map map = new HashMap<>(); + map.put("FormatType", "text"); + map.put("SourceLanguage", "auto"); + map.put("TargetLanguage", "en"); + map.put("SourceText", item.getSourceText()); + map.put("Scene", "description"); + map.put("Context", "产品介绍"); + // Sender代码请参考帮助文档“签名方法” + String result = AliYunSender.sendPost(serviceURL, JSONUtil.toJSONString(map), accessKeyId, accessKeySecret); + JSONObject jsonObject = JSON.parseObject(result); + final Object code = jsonObject.get("Code"); + if (code.equals("200")) { + final Object data = jsonObject.get("Data"); + JSONObject data1 = JSON.parseObject(data.toString()); + final Object translated = data1.get("Translated"); + final Object wordCount = data1.get("WordCount"); + return translated.toString(); + } + return ""; + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsDomainServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsDomainServiceImpl.java new file mode 100644 index 0000000..dea153f --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsDomainServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsDomainMapper; +import com.gxwebsoft.cms.service.CmsDomainService; +import com.gxwebsoft.cms.entity.CmsDomain; +import com.gxwebsoft.cms.param.CmsDomainParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 网站域名记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Service +public class CmsDomainServiceImpl extends ServiceImpl implements CmsDomainService { + + @Override + public PageResult pageRel(CmsDomainParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsDomainParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsDomain getByIdRel(Integer id) { + CmsDomainParam param = new CmsDomainParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsFormRecordServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsFormRecordServiceImpl.java new file mode 100644 index 0000000..4156faf --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsFormRecordServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsFormRecordMapper; +import com.gxwebsoft.cms.service.CmsFormRecordService; +import com.gxwebsoft.cms.entity.CmsFormRecord; +import com.gxwebsoft.cms.param.CmsFormRecordParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 表单数据记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsFormRecordServiceImpl extends ServiceImpl implements CmsFormRecordService { + + @Override + public PageResult pageRel(CmsFormRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsFormRecordParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsFormRecord getByIdRel(Integer formRecordId) { + CmsFormRecordParam param = new CmsFormRecordParam(); + param.setFormRecordId(formRecordId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsFormServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsFormServiceImpl.java new file mode 100644 index 0000000..b42ffb1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsFormServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsFormMapper; +import com.gxwebsoft.cms.service.CmsFormService; +import com.gxwebsoft.cms.entity.CmsForm; +import com.gxwebsoft.cms.param.CmsFormParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 表单设计表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsFormServiceImpl extends ServiceImpl implements CmsFormService { + + @Override + public PageResult pageRel(CmsFormParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsFormParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsForm getByIdRel(Integer formId) { + CmsFormParam param = new CmsFormParam(); + param.setFormId(formId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsLangLogServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsLangLogServiceImpl.java new file mode 100644 index 0000000..f8627cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsLangLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsLangLogMapper; +import com.gxwebsoft.cms.service.CmsLangLogService; +import com.gxwebsoft.cms.entity.CmsLangLog; +import com.gxwebsoft.cms.param.CmsLangLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 国际化记录启用Service实现 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Service +public class CmsLangLogServiceImpl extends ServiceImpl implements CmsLangLogService { + + @Override + public PageResult pageRel(CmsLangLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsLangLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsLangLog getByIdRel(Integer id) { + CmsLangLogParam param = new CmsLangLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsLangServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsLangServiceImpl.java new file mode 100644 index 0000000..4e27908 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsLangServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsLangMapper; +import com.gxwebsoft.cms.service.CmsLangService; +import com.gxwebsoft.cms.entity.CmsLang; +import com.gxwebsoft.cms.param.CmsLangParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 国际化Service实现 + * + * @author 科技小王子 + * @since 2025-01-06 19:29:26 + */ +@Service +public class CmsLangServiceImpl extends ServiceImpl implements CmsLangService { + + @Override + public PageResult pageRel(CmsLangParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsLangParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsLang getByIdRel(Integer id) { + CmsLangParam param = new CmsLangParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsLinkServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsLinkServiceImpl.java new file mode 100644 index 0000000..b85d845 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsLinkServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsLinkMapper; +import com.gxwebsoft.cms.service.CmsLinkService; +import com.gxwebsoft.cms.entity.CmsLink; +import com.gxwebsoft.cms.param.CmsLinkParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 常用链接Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsLinkServiceImpl extends ServiceImpl implements CmsLinkService { + + @Override + public PageResult pageRel(CmsLinkParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc,create_time asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsLinkParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time asc"); + return page.sortRecords(list); + } + + @Override + public CmsLink getByIdRel(Integer id) { + CmsLinkParam param = new CmsLinkParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsModelServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsModelServiceImpl.java new file mode 100644 index 0000000..071f7fa --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsModelServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsModelMapper; +import com.gxwebsoft.cms.service.CmsModelService; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.param.CmsModelParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 模型Service实现 + * + * @author 科技小王子 + * @since 2024-11-26 15:44:53 + */ +@Service +public class CmsModelServiceImpl extends ServiceImpl implements CmsModelService { + + @Override + public PageResult pageRel(CmsModelParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsModelParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time asc"); + return page.sortRecords(list); + } + + @Override + public CmsModel getByIdRel(Integer modelId) { + CmsModelParam param = new CmsModelParam(); + param.setModelId(modelId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsNavigationServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsNavigationServiceImpl.java new file mode 100644 index 0000000..b9254d1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsNavigationServiceImpl.java @@ -0,0 +1,161 @@ +package com.gxwebsoft.cms.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.CmsDesign; +import com.gxwebsoft.cms.entity.CmsModel; +import com.gxwebsoft.cms.mapper.CmsNavigationMapper; +import com.gxwebsoft.cms.service.CmsDesignService; +import com.gxwebsoft.cms.service.CmsModelService; +import com.gxwebsoft.cms.service.CmsNavigationService; +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.param.CmsNavigationParam; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.service.UserService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.text.MessageFormat; +import java.util.List; + +/** + * 网站导航记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:47:57 + */ +@Service +public class CmsNavigationServiceImpl extends ServiceImpl implements CmsNavigationService { + @Resource + @Lazy + private CmsDesignService cmsDesignService; + @Resource + private CmsModelService cmsModelService; + @Resource + private UserService userService; + + @Override + public PageResult pageRel(CmsNavigationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, position asc, navigation_id asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsNavigationParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, position asc,navigation_id asc"); + return page.sortRecords(list); + } + + @Override + public CmsNavigation getByIdRel(Integer navigationId) { + CmsNavigationParam param = new CmsNavigationParam(); + param.setNavigationId(navigationId); + CmsNavigation navigation; + navigation = param.getOne(baseMapper.selectListRel(param)); + if (ObjectUtil.isEmpty(navigation)) { + return null; + } + // 父级栏目并且是page模型则读取子项目第一条 + if (navigation.getParentId().equals(0) && navigation.getModel().equals("page")) { + final CmsNavigation parent = this.getOne(new LambdaQueryWrapper().eq(CmsNavigation::getParentId, navigation.getNavigationId()).last("limit 1")); + if (ObjectUtil.isNotEmpty(parent)) { + navigation = parent; + } + } + // 所属页面 + navigation.setDesign(cmsDesignService.getOne(new LambdaQueryWrapper().eq(CmsDesign::getCategoryId, navigation.getNavigationId()).last("limit 1"))); + // 所属模型 + if (StrUtil.isNotBlank(navigation.getModel())) { + navigation.setModelInfo(cmsModelService.getOne(new LambdaQueryWrapper().eq(CmsModel::getModel, navigation.getModel()).last("limit 1"))); + if (StrUtil.isBlank(navigation.getBanner())) { + navigation.setBanner(navigation.getModelInfo().getBanner()); + navigation.setMpBanner(navigation.getModelInfo().getThumb()); + } + } + return navigation; + } + + /** + * 配置路由生成规则 + * path:/模型/导航ID + * component: /pages/模型/index.vue + */ + @Override + public void saveAsync(CmsNavigation navigation) { + // TODO 1.设计path和component生产规则 + final String path = navigation.getPath(); + final CmsModel model = cmsModelService.getOne(new LambdaQueryWrapper().eq(CmsModel::getModel, navigation.getModel()).last("limit 1")); + // 1.自动配置 + navigation.setPath("/" + navigation.getModel() + "/" + navigation.getNavigationId()); + navigation.setTarget("_self"); + navigation.setComponent(MessageFormat.format("/pages/{0}/{1}", navigation.getModel(), "[id].vue")); + + // 1.2自定义文件后缀 + if(!navigation.getModel().equals("index") && model.getSuffix() != null){ + navigation.setPath(navigation.getPath() + model.getSuffix()); + } + + // 2.特例:默认首页 + if (navigation.getPath().equals("/") || navigation.getModel().equals("index")) { + final long count = count(new LambdaQueryWrapper().eq(CmsNavigation::getPath, "/").eq(CmsNavigation::getLang,navigation.getLang())); + if(count > 1){ + throw new BusinessException("路由地址已存在!"); + } + navigation.setPath("/"); + navigation.setComponent("/pages/index.vue"); + navigation.setModel("index"); + navigation.setHome(1); + } + // 3.外链模型 + if (navigation.getModel().equals("links")) { + navigation.setPath(path); + navigation.setTarget("_blank"); + navigation.setComponent(null); + } + + // 4.密码可见 + if(StrUtil.isNotBlank(navigation.getPassword())){ + navigation.setPassword(userService.encodePassword(navigation.getPassword())); + } + + // 更新操作 + updateById(navigation); + + // TODO 2.同步添加页面 + final CmsDesign one = cmsDesignService.getOne(new LambdaQueryWrapper().eq(CmsDesign::getCategoryId, navigation.getNavigationId()).eq(CmsDesign::getDeleted, 0).last("limit 1")); + if (ObjectUtil.isEmpty(one)) { + final CmsDesign design = new CmsDesign(); + design.setName(navigation.getTitle()); + design.setCategoryId(navigation.getNavigationId()); + design.setKeywords(navigation.getTitle()); + design.setDescription(navigation.getComments()); + design.setPath(navigation.getPath()); + design.setComponent(navigation.getComponent()); + design.setTenantId(navigation.getTenantId()); + if (StrUtil.isNotBlank(navigation.getContent())) { + design.setContent(navigation.getContent()); + } + cmsDesignService.save(design); + } + + // 面包屑 +// final CmsNavigation parent = getById(navigation.getParentId()); +// if (ObjectUtil.isNotEmpty(parent) && navigation.getParentId() > 0) { +// navigation.setParentName(parent.getTitle()); +// navigation.setParentPath(parent.getPath()); +// navigation.setParentId(parent.getNavigationId()); +// updateById(parent); +// } + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsStatisticsServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsStatisticsServiceImpl.java new file mode 100644 index 0000000..b965b41 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsStatisticsServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsStatisticsMapper; +import com.gxwebsoft.cms.service.CmsStatisticsService; +import com.gxwebsoft.cms.entity.CmsStatistics; +import com.gxwebsoft.cms.param.CmsStatisticsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 站点统计信息表Service实现 + * + * @author 科技小王子 + * @since 2025-07-25 12:32:06 + */ +@Service +public class CmsStatisticsServiceImpl extends ServiceImpl implements CmsStatisticsService { + + @Override + public PageResult pageRel(CmsStatisticsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsStatisticsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsStatistics getByIdRel(Integer id) { + CmsStatisticsParam param = new CmsStatisticsParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsTemplateServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsTemplateServiceImpl.java new file mode 100644 index 0000000..3be39e4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsTemplateServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsTemplateMapper; +import com.gxwebsoft.cms.service.CmsTemplateService; +import com.gxwebsoft.cms.entity.CmsTemplate; +import com.gxwebsoft.cms.param.CmsTemplateParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 网站模版Service实现 + * + * @author 科技小王子 + * @since 2025-01-21 14:21:16 + */ +@Service +public class CmsTemplateServiceImpl extends ServiceImpl implements CmsTemplateService { + + @Override + public PageResult pageRel(CmsTemplateParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsTemplateParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsTemplate getByIdRel(Integer id) { + CmsTemplateParam param = new CmsTemplateParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteFieldServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteFieldServiceImpl.java new file mode 100644 index 0000000..7214be1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteFieldServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsWebsiteFieldMapper; +import com.gxwebsoft.cms.service.CmsWebsiteFieldService; +import com.gxwebsoft.cms.entity.CmsWebsiteField; +import com.gxwebsoft.cms.param.CmsWebsiteFieldParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用参数Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Service +public class CmsWebsiteFieldServiceImpl extends ServiceImpl implements CmsWebsiteFieldService { + + @Override + public PageResult pageRel(CmsWebsiteFieldParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsWebsiteFieldParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time asc"); + return page.sortRecords(list); + } + + @Override + public CmsWebsiteField getByIdRel(Integer id) { + CmsWebsiteFieldParam param = new CmsWebsiteFieldParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImpl.java new file mode 100644 index 0000000..fbb60db --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImpl.java @@ -0,0 +1,416 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.*; +import com.gxwebsoft.cms.mapper.*; +import com.gxwebsoft.cms.param.*; +import com.gxwebsoft.cms.service.*; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.CompanyService; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.project.entity.Project; +import com.gxwebsoft.project.service.ProjectService; +import com.gxwebsoft.shop.vo.ShopVo; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * 网站信息记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Slf4j +@Service +public class CmsWebsiteServiceImpl extends ServiceImpl implements CmsWebsiteService { + + private static final String SITE_INFO_KEY_PREFIX = "SiteInfo:"; + @Resource + private CmsWebsiteFieldMapper cmsWebsiteFieldMapper; + @Resource + private CmsModelMapper cmsModelMapper; + @Resource + private CmsNavigationMapper cmsNavigationMapper; + @Resource + private CmsLangLogMapper cmsLangLogMapper; + @Resource + private CmsAdMapper cmsAdMapper; + @Resource + private CmsLinkMapper cmsLinkMapper; + @Resource + private CmsArticleMapper cmsArticleMapper; + @Resource + private CmsWebsiteFieldService cmsWebsiteFieldService; + @Resource + private CmsModelService cmsModelService; + @Resource + private CmsNavigationService cmsNavigationService; + @Resource + private CmsLangLogService cmsLangLogService; + @Resource + private CmsAdService cmsAdService; + @Resource + private CmsLinkService cmsLinkService; + @Resource + private CmsArticleService cmsArticleService; + @Resource + private CmsArticleContentService cmsArticleContentService; + @Resource + private CmsWebsiteMapper cmsWebsiteMapper; + @Resource + private ProjectService projectService; + @Resource + private RedisUtil redisUtil; + @Resource + private UserService userService; + @Resource + private CompanyService companyService; + + @Override + public PageResult pageRel(CmsWebsiteParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + list.forEach(d -> { + LocalDateTime now = LocalDateTime.now(); + // 即将过期(一周内过期的) + d.setSoon(d.getExpirationTime().minusDays(30).compareTo(now)); + // 是否过期 -1已过期 大于0 未过期 + d.setStatus(d.getExpirationTime().compareTo(now)); + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsWebsiteParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsWebsite getByIdRel(Integer websiteId) { + CmsWebsiteParam param = new CmsWebsiteParam(); + param.setWebsiteId(websiteId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public PageResult pageRelAll(CmsWebsiteParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRelAll(page, param); + list.forEach(d -> { + LocalDateTime now = LocalDateTime.now(); + // 即将过期(一周内过期的) + d.setSoon(d.getExpirationTime().minusDays(30).compareTo(now)); + // 是否过期 -1已过期 大于0 未过期 + d.setStatus(d.getExpirationTime().compareTo(now)); + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public CmsWebsite create(CmsWebsite website) { + final User loginUser = website.getLoginUser(); + // 创建站点 +// website.setWebsiteName("网站名称"); + website.setWebsiteCode("site-".concat(loginUser.getTenantId().toString())); +// if (StrUtil.isBlank(website.getWebsiteLogo())) { +// website.setWebsiteLogo("https://oss.wsdns.cn/20240822/0252ad4ed46449cdafe12f8d3d96c2ea.svg"); +// } + website.setWebsiteIcon("/favicon.ico"); + website.setWebsiteType("云·企业官网"); + website.setAdminUrl("site.websoft.top"); + website.setVersion(10); + website.setExpirationTime(LocalDateTime.now().plusMonths(1)); + website.setUserId(loginUser.getUserId()); + website.setDeveloper(loginUser.getNickname()); + website.setTenantId(loginUser.getTenantId()); + website.setTemplateId(loginUser.getTemplateId()); + website.setCompanyId(loginUser.getCompanyId()); + + // 初始化数据 + if(save(website)){ + // 插入网站设置记录 +// final CmsWebsiteSetting setting = new CmsWebsiteSetting(); +// setting.setWebsiteId(website.getWebsiteId()); +// setting.setCreateTime(DateUtil.date()); +// setting.setUpdateTime(DateUtil.date()); +// cmsWebsiteSettingService.save(setting); + + // 将网站创建者的userId做为查询条件 10257(4716),10324(6978),10398(26564) + Integer websiteUserId = website.getTemplateId() != null ? website.getTemplateId() : 0; + + + // TODO 国际化 + final CmsLangLogParam cmsLangLogParam = new CmsLangLogParam(); + cmsLangLogParam.setWebsiteUserId(websiteUserId); + final List logs = cmsLangLogMapper.selectListAllRel(cmsLangLogParam); + logs.forEach(d->{ + d.setTenantId(loginUser.getTenantId()); + }); + cmsLangLogService.saveBatch(logs); + + // TODO 复制参数 + final CmsWebsiteFieldParam param = new CmsWebsiteFieldParam(); + param.setUserId(websiteUserId); + final List fields = cmsWebsiteFieldMapper.selectListAllRel(param); + fields.forEach(d->{ + d.setTenantId(loginUser.getTenantId()); + }); + cmsWebsiteFieldService.saveBatch(fields); + + // TODO 复制模型 + final CmsModelParam modelParam = new CmsModelParam(); + modelParam.setWebsiteUserId(websiteUserId); + final List models = cmsModelMapper.selectListAllRel(modelParam); + models.forEach(d->{ + d.setUserId(loginUser.getUserId()); + d.setTenantId(loginUser.getTenantId()); + }); + cmsModelService.saveBatch(models); + + // TODO 复制广告 + final CmsAdParam cmsAdParam = new CmsAdParam(); + cmsAdParam.setWebsiteUserId(websiteUserId); + final List ads = cmsAdMapper.selectListAllRel(cmsAdParam); + ads.forEach(d -> { + d.setUserId(loginUser.getUserId()); + d.setTenantId(loginUser.getTenantId()); + }); + cmsAdService.saveBatch(ads); + + // TODO 复制链接 + CmsLinkParam cmsLinkParam = new CmsLinkParam(); + cmsLinkParam.setWebsiteUserId(websiteUserId); + final List links = cmsLinkMapper.selectListAllRel(cmsLinkParam); + links.forEach(d -> { + d.setUserId(loginUser.getUserId()); + d.setTenantId(loginUser.getTenantId()); + }); + cmsLinkService.saveBatch(links); + + // TODO 复制订单 +// CmsOrderParam cmsOrderParam = new CmsOrderParam(); +// cmsOrderParam.setWebsiteUserId(websiteUserId); +// final List orders = cmsOrderMapper.selectListAllRel(cmsOrderParam); +// orders.forEach(d -> { +// d.setUserId(loginUser.getUserId()); +// d.setTenantId(loginUser.getTenantId()); +// }); +// cmsOrderService.saveBatch(orders); + + + // TODO 复制栏目和文章、文章内容 + CmsNavigationParam cmsNavigationParam = new CmsNavigationParam(); + cmsNavigationParam.setWebsiteUserId(websiteUserId); + cmsNavigationParam.setParentId(0); + final List parents = cmsNavigationMapper.selectListAllRel(cmsNavigationParam); + parents.forEach(d -> { + Integer navigationId = d.getNavigationId(); + // 复制顶级栏目 + d.setTenantId(loginUser.getTenantId()); + d.setUserId(loginUser.getUserId()); + if (cmsNavigationService.save(d)) { + cmsNavigationService.saveAsync(d); + // 复制栏目文章 + CmsArticleParam cmsArticleParam = new CmsArticleParam(); + cmsArticleParam.setWebsiteUserId(websiteUserId); + cmsArticleParam.setCategoryId(navigationId); + final List articles = cmsArticleMapper.selectListAllRel(cmsArticleParam); + articles.forEach(a -> { + a.setCategoryId(d.getNavigationId()); + a.setUserId(loginUser.getUserId()); + a.setTenantId(loginUser.getTenantId()); + if (cmsArticleService.save(a)) { + final CmsArticleContent content = new CmsArticleContent(); + content.setArticleId(a.getArticleId()); + content.setContent(a.getContent()); + cmsArticleContentService.save(content); + } + }); + // 复制子栏目 + cmsNavigationParam.setParentId(navigationId); + final List navigations = cmsNavigationMapper.selectListAllRel(cmsNavigationParam); + navigations.forEach(c -> { + cmsArticleParam.setCategoryId(c.getNavigationId()); + c.setParentId(d.getNavigationId()); + c.setTenantId(loginUser.getTenantId()); + c.setUserId(loginUser.getUserId()); + cmsNavigationService.save(c); + cmsNavigationService.saveAsync(c); + // 复制子栏目文章 + final List articles2 = cmsArticleMapper.selectListAllRel(cmsArticleParam); + articles2.forEach(a2 -> { + a2.setCategoryId(c.getNavigationId()); + a2.setParentId(c.getParentId()); + a2.setUserId(loginUser.getUserId()); + a2.setTenantId(loginUser.getTenantId()); + if (cmsArticleService.save(a2)) { + final CmsArticleContent content = new CmsArticleContent(); + content.setArticleId(a2.getArticleId()); + content.setContent(a2.getContent()); + cmsArticleContentService.save(content); + } + }); + }); + } + }); + + // 新增项目 + final Project project = new Project(); + project.setUserId(website.getUserId()); + project.setAppName(website.getWebsiteName()); + project.setAppIcon(website.getWebsiteIcon()); + project.setAppCode(website.getWebsiteCode()); + project.setAdminUrl(website.getAdminUrl()); + project.setRenewMoney(website.getPrice()); + project.setWebsiteId(website.getWebsiteId()); + project.setAdminUrl(website.getAdminUrl()); + project.setAppType(website.getWebsiteType()); + project.setAppIcon(website.getWebsiteLogo()); + project.setAppUrl(website.getDomain()); + project.setCompanyId(website.getUserId()); + project.setTenantId(5); + projectService.save(project); + } + return website; + } + + @Override + public CmsWebsite getByIdRelAll(Integer id) { + return cmsWebsiteMapper.getByIdRelAll(id); + } + + @Override + public boolean updateByIdAll(CmsWebsite cmsWebsite) { + return baseMapper.updateByIdAll(cmsWebsite); + } + + @Override + public boolean removeByIdAll(Integer id) { + return baseMapper.removeByIdAll(id); + } + + @Override + public CmsWebsite getByTenantId(Integer tenantId) { + return baseMapper.getByTenantId(tenantId); + } + + @Override + public ShopVo getSiteInfo(Integer tenantId) { + // 参数验证 + if (ObjectUtil.isEmpty(tenantId)) { + throw new IllegalArgumentException("租户ID不能为空"); + } + + // 尝试从缓存获取 + String cacheKey = SITE_INFO_KEY_PREFIX + tenantId; + String siteInfo = redisUtil.get(cacheKey); + if (StrUtil.isNotBlank(siteInfo)) { + log.info("从缓存获取网站信息,租户ID: {}", tenantId); + try { + return JSONUtil.parseObject(siteInfo, ShopVo.class); + } catch (Exception e) { + log.warn("缓存解析失败,从数据库重新获取: {}", e.getMessage()); + } + } + + // 从数据库获取站点信息 + CmsWebsite website = getWebsiteFromDatabase(tenantId); + + + if (website == null) { + throw new RuntimeException("请先创建站点"); + } + + // 构建完整的网站信息 + buildCompleteWebsiteInfo(website); + + // 处理过期时间 + CmsWebsiteServiceImplHelper.processExpirationTime(website); + + // 转换为VO对象 + ShopVo websiteVO = CmsWebsiteServiceImplHelper.convertToVO(website); + + // 缓存结果 + try { + redisUtil.set(cacheKey, websiteVO, 1L, TimeUnit.DAYS); + } catch (Exception e) { + log.warn("缓存网站信息失败: {}", e.getMessage()); + } + + log.info("获取网站信息成功,网站ID: {}, 租户ID: {}", website.getWebsiteId(), tenantId); + return websiteVO; + } + + @Override + public void clearSiteInfoCache(Integer tenantId) { + if (tenantId != null) { + String cacheKey = SITE_INFO_KEY_PREFIX + tenantId; + redisUtil.delete(cacheKey); + log.info("清除网站信息缓存成功,租户ID: {}", tenantId); + } + } + + /** + * 从数据库获取网站信息 + */ + private CmsWebsite getWebsiteFromDatabase(Integer tenantId) { + return getByTenantId(tenantId); + } + + /** + * 构建完整的网站信息 + */ + private void buildCompleteWebsiteInfo(CmsWebsite website) { + // 设置网站状态 + CmsWebsiteServiceImplHelper.setWebsiteStatus(website); + + // 设置网站配置 + CmsWebsiteServiceImplHelper.setWebsiteConfig(website); + + // 设置网站导航 + setWebsiteNavigation(website); + + // 设置网站设置信息 + CmsWebsiteServiceImplHelper.setWebsiteSetting(website); + + // 设置服务器时间信息 + CmsWebsiteServiceImplHelper.setServerTimeInfo(website); + } + + /** + * 设置网站导航 + */ + private void setWebsiteNavigation(CmsWebsite website) { + // 获取顶部导航 + CmsNavigationParam navigationParam = new CmsNavigationParam(); + navigationParam.setHide(0); + navigationParam.setTop(0); + navigationParam.setBottom(null); + List topNavs = cmsNavigationService.listRel(navigationParam); + website.setTopNavs(topNavs); + + // 获取底部导航 + navigationParam.setTop(null); + navigationParam.setBottom(0); + List bottomNavs = cmsNavigationService.listRel(navigationParam); + website.setBottomNavs(bottomNavs); + } +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImplHelper.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImplHelper.java new file mode 100644 index 0000000..95488cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImplHelper.java @@ -0,0 +1,221 @@ +package com.gxwebsoft.cms.service.impl; + +import com.gxwebsoft.cms.entity.CmsNavigation; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.shop.vo.MenuVo; +import com.gxwebsoft.shop.vo.ShopVo; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +/** + * CmsWebsiteServiceImpl 辅助方法 + * 包含转换和处理逻辑 + */ +public class CmsWebsiteServiceImplHelper { + + /** + * 处理过期时间,只处理真正需要的字段 + */ + public static void processExpirationTime(CmsWebsite website) { + if (website.getExpirationTime() != null) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime expirationTime = website.getExpirationTime(); + + // 计算是否即将过期(30天内过期) + LocalDateTime thirtyDaysLater = now.plusDays(30); + website.setSoon(expirationTime.isBefore(thirtyDaysLater) ? 1 : 0); + + // 计算是否已过期 + website.setExpired(expirationTime.isBefore(now) ? -1 : 1); + + // 计算剩余天数 + long daysBetween = ChronoUnit.DAYS.between(now, expirationTime); + website.setExpiredDays(daysBetween); + } else { + // 没有过期时间的默认值 + website.setSoon(0); + website.setExpired(1); + website.setExpiredDays(0L); + } + } + + /** + * 将实体对象转换为VO对象 + */ + public static ShopVo convertToVO(CmsWebsite website) { + ShopVo vo = new ShopVo(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + // 基本信息 + vo.setAppId(website.getTenantId()); + vo.setAppName(website.getTenantName()); + vo.setTitle(website.getWebsiteName()); + vo.setKeywords(website.getKeywords()); + vo.setDescription(website.getComments()); + vo.setLogo(website.getWebsiteLogo()); + vo.setMpQrCode(website.getWebsiteDarkLogo()); + vo.setDomain(website.getDomain()); + vo.setRunning(website.getRunning()); + vo.setVersion(website.getVersion()); + vo.setCreateTime(String.valueOf(website.getCreateTime())); + + // 时间字段 - 格式化为字符串 + if (website.getExpirationTime() != null) { + vo.setExpirationTime(website.getExpirationTime().format(formatter)); + } + + // 过期相关信息 + vo.setExpired(website.getExpired()); + vo.setExpiredDays(website.getExpiredDays()); + vo.setSoon(website.getSoon()); + + // 状态信息 + vo.setStatusIcon(website.getStatusIcon()); + vo.setStatusText(website.getStatusText()); + + // 复杂对象 + vo.setConfig(website.getConfig()); + vo.setServerTime(website.getServerTime()); + vo.setSetting(website.getSetting()); // CmsWebsiteSetting对象可以直接设置给Object类型 + + // 导航信息 + vo.setTopNavs(convertNavigationToVO(website.getTopNavs())); + vo.setBottomNavs(convertNavigationToVO(website.getBottomNavs())); + + return vo; + } + + /** + * 安全转换 target 字段为整数 + * + * @param target 字符串类型的 target 值 + * @return 对应的整数值 + */ + private static Integer convertTargetToInteger(String target) { + if (target == null) { + return 0; // 默认值:当前窗口 + } + + switch (target.toLowerCase()) { + case "_self": + return 0; // 当前窗口 + case "_blank": + return 1; // 新窗口 + default: + // 如果是数字字符串,尝试直接转换 + try { + return Integer.valueOf(target); + } catch (NumberFormatException e) { + // 转换失败时返回默认值 + return 0; + } + } + } + + /** + * 转换导航列表为VO + * 整理导航栏目录结构(ShopInfo) + */ + public static List convertNavigationToVO(List navigations) { + if (navigations == null) { + return null; + } + + return navigations.stream().map(nav -> { + MenuVo navVO = new MenuVo(); + navVO.setNavigationId(nav.getNavigationId()); + navVO.setTitle(nav.getTitle()); + navVO.setPath(nav.getPath()); + navVO.setIcon(nav.getIcon()); + navVO.setColor(nav.getColor()); + navVO.setParentId(nav.getParentId()); + navVO.setSort(nav.getSortNumber()); + navVO.setHide(nav.getHide()); + navVO.setTop(nav.getTop()); + navVO.setPath(nav.getPath()); + navVO.setTarget(convertTargetToInteger(nav.getTarget())); + navVO.setModel(nav.getModel()); + + // 递归处理子导航 + if (nav.getChildren() != null) { + navVO.setChildren(convertNavigationToVO(nav.getChildren())); + } + + return navVO; + }).collect(Collectors.toList()); + } + + /** + * 设置网站状态 + */ + public static void setWebsiteStatus(CmsWebsite website) { + if (website.getRunning() != null) { + switch (website.getRunning()) { + case 0: + website.setStatusIcon("🔴"); + website.setStatusText("未开通"); + break; + case 1: + website.setStatusIcon("🟢"); + website.setStatusText("正常运行"); + break; + case 2: + website.setStatusIcon("🟡"); + website.setStatusText("维护中"); + break; + case 3: + website.setStatusIcon("🔴"); + website.setStatusText("违规关停"); + break; + default: + website.setStatusIcon("❓"); + website.setStatusText("未知状态"); + } + } + } + + /** + * 设置网站配置 + */ + public static void setWebsiteConfig(CmsWebsite website) { + HashMap config = new HashMap<>(); + config.put("websiteName", website.getWebsiteName()); + config.put("websiteComments", website.getComments()); + config.put("websiteTitle", website.getWebsiteName()); + config.put("websiteKeywords", website.getKeywords()); + config.put("websiteDescription", website.getContent()); // 使用 content 字段作为描述 + config.put("websiteLogo", website.getWebsiteLogo()); + config.put("websiteIcon", website.getWebsiteIcon()); + config.put("domain", website.getDomain()); + website.setConfig(config); + } + + /** + * 设置服务器时间信息 + */ + public static void setServerTimeInfo(CmsWebsite website) { + HashMap serverTime = new HashMap<>(); + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + serverTime.put("currentTime", now.format(formatter)); + serverTime.put("timestamp", System.currentTimeMillis()); + serverTime.put("timezone", "Asia/Shanghai"); + + website.setServerTime(serverTime); + } + + /** + * 设置网站设置信息 + */ + public static void setWebsiteSetting(CmsWebsite website) { + // 这里可以根据需要设置网站的其他设置信息 + // 暂时设置为null,因为setting字段类型是CmsWebsiteSetting而不是HashMap + website.setSetting(null); + } +} diff --git a/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteSettingServiceImpl.java b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteSettingServiceImpl.java new file mode 100644 index 0000000..2788563 --- /dev/null +++ b/src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteSettingServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.cms.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.mapper.CmsWebsiteSettingMapper; +import com.gxwebsoft.cms.service.CmsWebsiteSettingService; +import com.gxwebsoft.cms.entity.CmsWebsiteSetting; +import com.gxwebsoft.cms.param.CmsWebsiteSettingParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 网站设置Service实现 + * + * @author 科技小王子 + * @since 2025-02-19 01:35:44 + */ +@Service +public class CmsWebsiteSettingServiceImpl extends ServiceImpl implements CmsWebsiteSettingService { + + @Override + public PageResult pageRel(CmsWebsiteSettingParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CmsWebsiteSettingParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public CmsWebsiteSetting getByIdRel(Integer id) { + CmsWebsiteSettingParam param = new CmsWebsiteSettingParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/Constants.java b/src/main/java/com/gxwebsoft/common/core/Constants.java new file mode 100644 index 0000000..be48387 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/Constants.java @@ -0,0 +1,93 @@ +package com.gxwebsoft.common.core; + +/** + * 系统常量 + * Created by WebSoft on 2019-10-29 15:55 + */ +public class Constants { + /** + * 默认成功码 + */ + public static final int RESULT_OK_CODE = 0; + + /** + * 默认失败码 + */ + public static final int RESULT_ERROR_CODE = 1; + + /** + * 默认成功信息 + */ + public static final String RESULT_OK_MSG = "操作成功"; + + /** + * 默认失败信息 + */ + public static final String RESULT_ERROR_MSG = "操作失败"; + + /** + * 无权限错误码 + */ + public static final int UNAUTHORIZED_CODE = 403; + + /** + * 无权限提示信息 + */ + public static final String UNAUTHORIZED_MSG = "没有访问权限"; + + /** + * 未认证错误码 + */ + public static final int UNAUTHENTICATED_CODE = 401; + + /** + * 未认证提示信息 + */ + public static final String UNAUTHENTICATED_MSG = "请先登录"; + + /** + * 登录过期错误码 + */ + public static final int TOKEN_EXPIRED_CODE = 401; + + /** + * 登录过期提示信息 + */ + public static final String TOKEN_EXPIRED_MSG = "登录已过期"; + + /** + * 非法token错误码 + */ + public static final int BAD_CREDENTIALS_CODE = 401; + + /** + * 非法token提示信息 + */ + public static final String BAD_CREDENTIALS_MSG = "请退出重新登录"; + + /** + * 表示升序的值 + */ + public static final String ORDER_ASC_VALUE = "asc"; + + /** + * 表示降序的值 + */ + public static final String ORDER_DESC_VALUE = "desc"; + + /** + * token通过header传递的名称 + */ + public static final String TOKEN_HEADER_NAME = "Authorization"; + + /** + * token通过参数传递的名称 + */ + public static final String TOKEN_PARAM_NAME = "access_token"; + + /** + * token认证类型 + */ + public static final String TOKEN_TYPE = "Bearer"; + +} diff --git a/src/main/java/com/gxwebsoft/common/core/annotation/IgnoreTenant.java b/src/main/java/com/gxwebsoft/common/core/annotation/IgnoreTenant.java new file mode 100644 index 0000000..ace48fa --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/annotation/IgnoreTenant.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.common.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 忽略租户隔离注解 + * + * 用于标记需要跨租户操作的方法,如定时任务、系统管理等场景 + * + * @author WebSoft + * @since 2025-01-26 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IgnoreTenant { + + /** + * 说明信息,用于记录为什么需要忽略租户隔离 + */ + String value() default ""; + + /** + * 是否记录日志 + */ + boolean logAccess() default true; +} diff --git a/src/main/java/com/gxwebsoft/common/core/annotation/OperationLog.java b/src/main/java/com/gxwebsoft/common/core/annotation/OperationLog.java new file mode 100644 index 0000000..87bdf2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/annotation/OperationLog.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.common.core.annotation; + +import java.lang.annotation.*; + +/** + * 操作日志记录注解 + * + * @author WebSoft + * @since 2020-03-21 17:03:08 + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationLog { + + /** + * 操作功能 + */ + String value() default ""; + + /** + * 操作模块 + */ + String module() default ""; + + /** + * 备注 + */ + String comments() default ""; + + /** + * 是否记录请求参数 + */ + boolean param() default true; + + /** + * 是否记录返回结果 + */ + boolean result() default true; + +} diff --git a/src/main/java/com/gxwebsoft/common/core/annotation/OperationModule.java b/src/main/java/com/gxwebsoft/common/core/annotation/OperationModule.java new file mode 100644 index 0000000..60ab018 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/annotation/OperationModule.java @@ -0,0 +1,21 @@ +package com.gxwebsoft.common.core.annotation; + +import java.lang.annotation.*; + +/** + * 操作日志模块注解 + * + * @author WebSoft + * @since 2021-09-01 20:48:16 + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationModule { + + /** + * 模块名称 + */ + String value(); + +} diff --git a/src/main/java/com/gxwebsoft/common/core/annotation/QueryField.java b/src/main/java/com/gxwebsoft/common/core/annotation/QueryField.java new file mode 100644 index 0000000..9377b9b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/annotation/QueryField.java @@ -0,0 +1,22 @@ +package com.gxwebsoft.common.core.annotation; + +import java.lang.annotation.*; + +/** + * 查询条件注解 + * + * @author WebSoft + * @since 2021-09-01 20:48:16 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +public @interface QueryField { + + // 字段名称 + String value() default ""; + + // 查询方式 + QueryType type() default QueryType.LIKE; + +} diff --git a/src/main/java/com/gxwebsoft/common/core/annotation/QueryType.java b/src/main/java/com/gxwebsoft/common/core/annotation/QueryType.java new file mode 100644 index 0000000..3eb540e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/annotation/QueryType.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.core.annotation; + +/** + * 查询方式 + * + * @author WebSoft + * @since 2021-09-01 20:48:16 + */ +public enum QueryType { + // 等于 + EQ, + // 不等于 + NE, + // 大于 + GT, + // 大于等于 + GE, + // 小于 + LT, + // 小于等于 + LE, + // 包含 + LIKE, + // 不包含 + NOT_LIKE, + // 结尾等于 + LIKE_LEFT, + // 开头等于 + LIKE_RIGHT, + // 为NULL + IS_NULL, + // 不为空 + IS_NOT_NULL, + // IN + IN, + // NOT IN + NOT_IN, + // IN条件解析逗号分割 + IN_STR, + // NOT IN条件解析逗号分割 + NOT_IN_STR +} diff --git a/src/main/java/com/gxwebsoft/common/core/aspect/IgnoreTenantAspect.java b/src/main/java/com/gxwebsoft/common/core/aspect/IgnoreTenantAspect.java new file mode 100644 index 0000000..da58a3b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/aspect/IgnoreTenantAspect.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.common.core.aspect; + +import com.gxwebsoft.common.core.annotation.IgnoreTenant; +import com.gxwebsoft.common.core.context.TenantContext; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * 忽略租户隔离切面 + * + * 自动处理 @IgnoreTenant 注解标记的方法,临时禁用租户隔离 + * + * @author WebSoft + * @since 2025-01-26 + */ +@Slf4j +@Aspect +@Component +@Order(1) // 确保在其他切面之前执行 +public class IgnoreTenantAspect { + + @Around("@annotation(com.gxwebsoft.common.core.annotation.IgnoreTenant)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + IgnoreTenant ignoreTenant = method.getAnnotation(IgnoreTenant.class); + + // 记录原始状态 + boolean originalIgnore = TenantContext.isIgnoreTenant(); + + try { + // 设置忽略租户隔离 + TenantContext.setIgnoreTenant(true); + + // 记录日志 + if (ignoreTenant.logAccess()) { + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = method.getName(); + String reason = ignoreTenant.value(); + + if (reason.isEmpty()) { + log.debug("执行跨租户操作: {}.{}", className, methodName); + } else { + log.debug("执行跨租户操作: {}.{} - {}", className, methodName, reason); + } + } + + // 执行目标方法 + return joinPoint.proceed(); + + } finally { + // 恢复原始状态 + TenantContext.setIgnoreTenant(originalIgnore); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/aspect/OperationLogAspect.java b/src/main/java/com/gxwebsoft/common/core/aspect/OperationLogAspect.java new file mode 100644 index 0000000..4b15358 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/aspect/OperationLogAspect.java @@ -0,0 +1,227 @@ +package com.gxwebsoft.common.core.aspect; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.annotation.OperationModule; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.system.entity.OperationRecord; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.OperationRecordService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.*; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * 操作日志记录 + * + * @author WebSoft + * @since 2020-03-21 16:58:16:05 + */ +@Aspect +@Component +public class OperationLogAspect { + @Resource + private OperationRecordService operationRecordService; + + // 参数、返回结果、错误信息等最大保存长度 + private static final int MAX_LENGTH = 1000; + // 用于记录请求耗时 + private final ThreadLocal startTime = new ThreadLocal<>(); + + @Pointcut("@annotation(com.gxwebsoft.common.core.annotation.OperationLog)") + public void operationLog() { + } + + @Before("operationLog()") + public void doBefore(JoinPoint joinPoint) throws Throwable { + startTime.set(System.currentTimeMillis()); + } + + @AfterReturning(pointcut = "operationLog()", returning = "result") + public void doAfterReturning(JoinPoint joinPoint, Object result) { + saveLog(joinPoint, result, null); + } + + @AfterThrowing(value = "operationLog()", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Exception e) { + saveLog(joinPoint, null, e); + } + + /** + * 保存操作记录 + */ + private void saveLog(JoinPoint joinPoint, Object result, Exception e) { + OperationRecord record = new OperationRecord(); + // 记录操作耗时 + if (startTime.get() != null) { + record.setSpendTime(System.currentTimeMillis() - startTime.get()); + } + // 记录当前登录用户id、租户id + User user = getLoginUser(); + if (user != null) { + record.setUserId(user.getUserId()); + record.setTenantId(user.getTenantId()); + } + // 记录请求地址、请求方式、ip + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = (attributes == null ? null : attributes.getRequest()); + if (request != null) { + record.setUrl(request.getRequestURI()); + record.setRequestMethod(request.getMethod()); + UserAgent ua = UserAgentUtil.parse(ServletUtil.getHeaderIgnoreCase(request, "User-Agent")); + record.setOs(ua.getPlatform().toString()); + record.setDevice(ua.getOs().toString()); + record.setBrowser(ua.getBrowser().toString()); + record.setIp(ServletUtil.getClientIP(request)); + } + // 记录异常信息 + if (e != null) { + record.setStatus(1); + record.setError(StrUtil.sub(e.toString(), 0, MAX_LENGTH)); + } + // 记录模块名、操作功能、请求方法、请求参数、返回结果 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + record.setMethod(joinPoint.getTarget().getClass().getName() + "." + signature.getName()); + Method method = signature.getMethod(); + if (method != null) { + OperationLog ol = method.getAnnotation(OperationLog.class); + if (ol != null) { + // 记录操作功能 + record.setDescription(getDescription(method, ol)); + // 记录操作模块 + record.setModule(getModule(joinPoint, ol)); + // 记录备注 + if (StrUtil.isNotEmpty(ol.comments())) { + record.setComments(ol.comments()); + } + // 记录请求参数 + if (ol.param() && request != null) { + record.setParams(StrUtil.sub(getParams(joinPoint, request), 0, MAX_LENGTH)); + } + // 记录请求结果 + if (ol.result() && result != null) { + record.setResult(StrUtil.sub(JSONUtil.toJSONString(result), 0, MAX_LENGTH)); + } + } + } + + // 记录访客日志 +// System.out.println("record = " + record); +// if (record.getMethod().equals("com.gxwebsoft.love.controller.UserProfileController.detail")) { +// final Integer toUserId = Integer.valueOf(StrUtil.removeSuffix(record.getParams()," ")); +// if (userLookService.count(new LambdaQueryWrapper().eq(UserLook::getUserId,record.getUserId()).eq(UserLook::getToUserId,toUserId)) == 0) { +// final UserLook userLook = new UserLook(); +// userLook.setUserId(record.getUserId()); +// userLook.setToUserId(toUserId); +// userLookService.save(userLook); +// } +// } + + operationRecordService.saveAsync(record); + } + + /** + * 获取当前登录用户 + */ + private User getLoginUser() { + Authentication subject = SecurityContextHolder.getContext().getAuthentication(); + if (subject != null) { + Object object = subject.getPrincipal(); + if (object instanceof User) { + return (User) object; + } + } + return null; + } + + /** + * 获取请求参数 + * + * @param joinPoint JoinPoint + * @param request HttpServletRequest + * @return String + */ + private String getParams(JoinPoint joinPoint, HttpServletRequest request) { + String params; + Map paramsMap = ServletUtil.getParamMap(request); + if (paramsMap.keySet().size() > 0) { + params = JSONUtil.toJSONString(paramsMap); + } else { + StringBuilder sb = new StringBuilder(); + for (Object arg : joinPoint.getArgs()) { + if (ObjectUtil.isNull(arg) + || arg instanceof MultipartFile + || arg instanceof HttpServletRequest + || arg instanceof HttpServletResponse) { + continue; + } + sb.append(JSONUtil.toJSONString(arg)).append(" "); + } + params = sb.toString(); + } + return params; + } + + /** + * 获取操作模块 + * + * @param joinPoint JoinPoint + * @param ol OperationLog + * @return String + */ + private String getModule(JoinPoint joinPoint, OperationLog ol) { + if (StrUtil.isNotEmpty(ol.module())) { + return ol.module(); + } + OperationModule om = joinPoint.getTarget().getClass().getAnnotation(OperationModule.class); + if (om != null && StrUtil.isNotEmpty(om.value())) { + return om.value(); + } + // 尝试获取 SpringDoc 的 @Tag 注解 + io.swagger.v3.oas.annotations.tags.Tag tag = joinPoint.getTarget().getClass().getAnnotation(io.swagger.v3.oas.annotations.tags.Tag.class); + if (tag != null && StrUtil.isNotEmpty(tag.name())) { + return tag.name(); + } + return null; + } + + /** + * 获取操作功能 + * + * @param method Method + * @param ol OperationLog + * @return String + */ + private String getDescription(Method method, OperationLog ol) { + if (StrUtil.isNotEmpty(ol.value())) { + return ol.value(); + } + // 尝试获取 SpringDoc 的 @Operation 注解 + io.swagger.v3.oas.annotations.Operation operation = method.getAnnotation(io.swagger.v3.oas.annotations.Operation.class); + if (operation != null && StrUtil.isNotEmpty(operation.summary())) { + return operation.summary(); + } + return null; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java b/src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java new file mode 100644 index 0000000..ed76d34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.common.core.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * BigDecimal 自定义反序列化器 + * 处理null值和空字符串,避免反序列化异常 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +public class BigDecimalDeserializer extends JsonDeserializer { + + @Override + public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + + if (value == null || value.trim().isEmpty() || "null".equals(value)) { + return null; + } + + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + log.warn("无法解析BigDecimal值: {}, 返回null", value); + return null; + } + } + + @Override + public BigDecimal getNullValue(DeserializationContext ctxt) { + return null; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/CertificateProperties.java b/src/main/java/com/gxwebsoft/common/core/config/CertificateProperties.java new file mode 100644 index 0000000..d8cbb5e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/CertificateProperties.java @@ -0,0 +1,213 @@ +package com.gxwebsoft.common.core.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 证书配置属性类 + * 支持开发环境从classpath加载证书,生产环境从Docker挂载卷加载证书 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Data +@Component +@ConfigurationProperties(prefix = "certificate") +public class CertificateProperties { + + /** + * 证书加载模式 + * CLASSPATH: 从classpath加载(开发环境) + * FILESYSTEM: 从文件系统加载(生产环境) + * VOLUME: 从Docker挂载卷加载(容器环境) + */ + private LoadMode loadMode = LoadMode.CLASSPATH; + + /** + * Docker挂载卷证书根路径 + */ + private String certRootPath = "/www/wwwroot/file.ws"; + + /** + * 开发环境证书路径前缀 + */ + private String devCertPath = "dev"; + + /** + * 微信支付证书配置 + */ + private WechatPayConfig wechatPay = new WechatPayConfig(); + + /** + * 支付宝证书配置 + */ + private AlipayConfig alipay = new AlipayConfig(); + + /** + * 证书加载模式枚举 + */ + public enum LoadMode { + CLASSPATH, // 从classpath加载 + FILESYSTEM, // 从文件系统加载 + VOLUME // 从Docker挂载卷加载 + } + + /** + * 微信支付证书配置 + */ + @Data + public static class WechatPayConfig { + /** + * 开发环境配置 + */ + private DevConfig dev = new DevConfig(); + + /** + * 生产环境基础路径 + */ + private String prodBasePath = "/file"; + + /** + * 微信支付证书目录名 + */ + private String certDir = "wechat"; + + @Data + public static class DevConfig { + /** + * APIv3密钥 + */ + private String apiV3Key; + + /** + * 商户私钥证书文件名 + */ + private String privateKeyFile = "apiclient_key.pem"; + + /** + * 商户证书文件名 + */ + private String apiclientCertFile = "apiclient_cert.pem"; + + /** + * 微信支付平台证书文件名 + */ + private String wechatpayCertFile = "wechatpay_cert.pem"; + } + } + + /** + * 支付宝证书配置 + */ + @Data + public static class AlipayConfig { + /** + * 支付宝证书目录名 + */ + private String certDir = "alipay"; + + /** + * 应用私钥文件名 + */ + private String appPrivateKeyFile = "app_private_key.pem"; + + /** + * 应用公钥证书文件名 + */ + private String appCertPublicKeyFile = "appCertPublicKey.crt"; + + /** + * 支付宝公钥证书文件名 + */ + private String alipayCertPublicKeyFile = "alipayCertPublicKey.crt"; + + /** + * 支付宝根证书文件名 + */ + private String alipayRootCertFile = "alipayRootCert.crt"; + } + + /** + * 获取证书文件的完整路径 + * + * @param certType 证书类型(wechat/alipay) + * @param fileName 文件名 + * @return 完整路径 + */ + public String getCertificatePath(String certType, String fileName) { + switch (loadMode) { + case CLASSPATH: + return devCertPath + "/" + certType + "/" + fileName; + case FILESYSTEM: + return System.getProperty("user.dir") + "/certs/" + certType + "/" + fileName; + case VOLUME: + return certRootPath + "/" + certType + "/" + fileName; + default: + throw new IllegalArgumentException("不支持的证书加载模式: " + loadMode); + } + } + + /** + * 获取微信支付证书路径 + * + * @param fileName 文件名 + * @return 完整路径 + */ + public String getWechatPayCertPath(String fileName) { + // 生产环境特殊处理:数据库中存储的路径需要拼接到 /file/ 目录下 + if (loadMode == LoadMode.VOLUME) { + // 修复路径拼接逻辑:数据库中存储的路径如果已经包含 /file,则直接拼接 + if (fileName.startsWith("/file/")) { + // 路径已经包含 /file/ 前缀,直接拼接到根路径 + return certRootPath + fileName; + } else if (fileName.startsWith("file/")) { + // 路径包含 file/ 前缀,添加根路径和斜杠 + return certRootPath + "/" + fileName; + } else { + // 路径不包含 file 前缀,添加完整的 /file/ 前缀 + return certRootPath + "/file/" + fileName; + } + } else { + // 开发环境和文件系统模式使用原有逻辑 + return getCertificatePath(wechatPay.getCertDir(), fileName); + } + } + + /** + * 获取支付宝证书路径 + * + * @param fileName 文件名 + * @return 完整路径 + */ + public String getAlipayCertPath(String fileName) { + return getCertificatePath(alipay.getCertDir(), fileName); + } + + /** + * 检查证书加载模式是否为classpath模式 + * + * @return true if classpath mode + */ + public boolean isClasspathMode() { + return LoadMode.CLASSPATH.equals(loadMode); + } + + /** + * 检查证书加载模式是否为文件系统模式 + * + * @return true if filesystem mode + */ + public boolean isFilesystemMode() { + return LoadMode.FILESYSTEM.equals(loadMode); + } + + /** + * 检查证书加载模式是否为挂载卷模式 + * + * @return true if volume mode + */ + public boolean isVolumeMode() { + return LoadMode.VOLUME.equals(loadMode); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/ConfigProperties.java b/src/main/java/com/gxwebsoft/common/core/config/ConfigProperties.java new file mode 100644 index 0000000..d500177 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/ConfigProperties.java @@ -0,0 +1,105 @@ +package com.gxwebsoft.common.core.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 系统配置属性 + * + * @author WebSoft + * @since 2021-08-30 17:58:16 + */ +@Data +@ConfigurationProperties(prefix = "config") +public class ConfigProperties { + /** + * 文件上传磁盘位置 + */ + private Integer uploadLocation = 0; + + /** + * 文件上传是否使用uuid命名 + */ + private Boolean uploadUuidName = true; + + /** + * 文件上传生成缩略图的大小(kb) + */ + private Integer thumbnailSize = 60; + + /** + * OpenOffice的安装目录 + */ + private String openOfficeHome; + + /** + * swagger扫描包 + */ + private String swaggerBasePackage; + + /** + * swagger文档标题 + */ + private String swaggerTitle; + + /** + * swagger文档描述 + */ + private String swaggerDescription; + + /** + * swagger文档版本号 + */ + private String swaggerVersion; + + /** + * swagger地址 + */ + private String swaggerHost; + + /** + * token过期时间, 单位秒 + */ + private Long tokenExpireTime = 60 * 60 * 365 * 24L; + + /** + * token快要过期自动刷新时间, 单位分钟 + */ + private int tokenRefreshTime = 30; + + /** + * 生成token的密钥Key的base64字符 + */ + private String tokenKey; + + /** + * 文件上传目录 + */ + private String uploadPath; + + /** + * 本地文件上传目录(开发环境) + */ + private String localUploadPath; + + /** + * 文件服务器 + */ + private String fileServer; + + /** + * 网关地址 + */ + private String serverUrl; + + /** + * 阿里云存储 OSS + * Endpoint + */ + private String endpoint; + private String accessKeyId; + private String accessKeySecret; + private String bucketName; + private String bucketDomain; + +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/HttpMessageConverter.java b/src/main/java/com/gxwebsoft/common/core/config/HttpMessageConverter.java new file mode 100644 index 0000000..6bae59d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/HttpMessageConverter.java @@ -0,0 +1,15 @@ +package com.gxwebsoft.common.core.config; + +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +import java.util.ArrayList; +import java.util.List; + +public class HttpMessageConverter extends MappingJackson2HttpMessageConverter { + public HttpMessageConverter(){ + List mediaTypes = new ArrayList<>(); + mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); + setSupportedMediaTypes(mediaTypes); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java b/src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java new file mode 100644 index 0000000..eebe6c5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java @@ -0,0 +1,26 @@ +package com.gxwebsoft.common.core.config; + +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Jackson配置类 + * 确保JSR310模块被正确注册 + * + * @author WebSoft + * @since 2025-01-12 + */ +@Configuration +public class JacksonConfig { + + /** + * 确保JavaTimeModule被注册 + */ + @Bean + @ConditionalOnMissingBean + public JavaTimeModule javaTimeModule() { + return new JavaTimeModule(); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeDeserializer.java b/src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeDeserializer.java new file mode 100644 index 0000000..52ddd0a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeDeserializer.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.common.core.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * LocalDateTime自定义反序列化器 + * + * @author WebSoft + * @since 2025-01-12 + */ +public class LocalDateTimeDeserializer extends JsonDeserializer { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if (value != null && !value.isEmpty()) { + return LocalDateTime.parse(value, FORMATTER); + } + return null; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeSerializer.java b/src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeSerializer.java new file mode 100644 index 0000000..d3849e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/LocalDateTimeSerializer.java @@ -0,0 +1,27 @@ +package com.gxwebsoft.common.core.config; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * LocalDateTime自定义序列化器 + * + * @author WebSoft + * @since 2025-01-12 + */ +public class LocalDateTimeSerializer extends JsonSerializer { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value != null) { + gen.writeString(value.format(FORMATTER)); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/MqttProperties.java b/src/main/java/com/gxwebsoft/common/core/config/MqttProperties.java new file mode 100644 index 0000000..cfc882d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/MqttProperties.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.common.core.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * MQTT配置属性 + * + * @author 科技小王子 + * @since 2025-07-02 + */ +@Data +@Component +@ConfigurationProperties(prefix = "mqtt") +public class MqttProperties { + + /** + * 是否启用MQTT服务 + */ + private boolean enabled = false; + + /** + * MQTT服务器地址 + */ + private String host = "tcp://127.0.0.1:1883"; + + /** + * 用户名 + */ + private String username = ""; + + /** + * 密码 + */ + private String password = ""; + + /** + * 客户端ID前缀 + */ + private String clientIdPrefix = "mqtt_client_"; + + /** + * 订阅主题 + */ + private String topic = "/SW_GPS/#"; + + /** + * QoS等级 + */ + private int qos = 2; + + /** + * 连接超时时间(秒) + */ + private int connectionTimeout = 10; + + /** + * 心跳间隔(秒) + */ + private int keepAliveInterval = 20; + + /** + * 是否自动重连 + */ + private boolean autoReconnect = true; + + /** + * 是否清除会话 + */ + private boolean cleanSession = false; +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/MybatisPlusConfig.java b/src/main/java/com/gxwebsoft/common/core/config/MybatisPlusConfig.java new file mode 100644 index 0000000..a579cc5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/MybatisPlusConfig.java @@ -0,0 +1,143 @@ +package com.gxwebsoft.common.core.config; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.context.TenantContext; +import com.gxwebsoft.common.system.entity.User; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.NullValue; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * MybatisPlus配置 + * + * @author WebSoft + * @since 2018-02-22 11:29:28 + */ +@Configuration +public class MybatisPlusConfig { + @Resource + private RedisUtil redisUtil; + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + + // 多租户插件配置 + TenantLineHandler tenantLineHandler = new TenantLineHandler() { + @Override + public Expression getTenantId() { + String tenantId = null; + try { + // 从Spring上下文获取当前请求 + HttpServletRequest request = getCurrentRequest(); + if (request != null) { + // 从请求头拿ID + tenantId = request.getHeader("tenantId"); + if(tenantId != null){ + return new LongValue(tenantId); + } + // 从域名拿ID + String Domain = request.getHeader("Domain"); + if (StrUtil.isNotBlank(Domain)) { + String key = "Domain:" + Domain; + tenantId = redisUtil.get(key); + if(tenantId != null){ + System.out.println("从域名拿TID = " + tenantId); + return new LongValue(tenantId); + } + } + } + } catch (Exception e) { + // 忽略异常,使用默认逻辑 + } + return getLoginUserTenantId(); + } + + @Override + public boolean ignoreTable(String tableName) { + // 如果当前上下文设置了忽略租户隔离,则忽略所有表的租户隔离 + if (TenantContext.isIgnoreTenant()) { + return true; + } + + // 系统级别的表始终忽略租户隔离 + return Arrays.asList( + "sys_tenant", + "sys_dictionary", + "sys_dictionary_data", + "apps_test_data", + "cms_lang" +// "hjm_car", +// "hjm_fence" +// "cms_website" +// "sys_user" +// "cms_domain" +// "shop_order_goods", +// "shop_goods" +// "shop_users", +// "shop_order" // 移除shop_order,改为通过注解控制 +// "shop_order_info", +// "booking_user_invoice" + ).contains(tableName); + } + }; + TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor(tenantLineHandler); + interceptor.addInnerInterceptor(tenantLineInnerInterceptor); + + // 分页插件配置 + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); + paginationInnerInterceptor.setMaxLimit(2000L); + interceptor.addInnerInterceptor(paginationInnerInterceptor); + + return interceptor; + } + + /** + * 获取当前登录用户的租户id + * + * @return Integer + */ + public Expression getLoginUserTenantId() { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Object object = authentication.getPrincipal(); + if (object instanceof User) { + return new LongValue(((User) object).getTenantId()); + } + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } + return new NullValue(); + } + + /** + * 获取当前HTTP请求 + */ + private HttpServletRequest getCurrentRequest() { + try { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return attributes != null ? attributes.getRequest() : null; + } catch (Exception e) { + return null; + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/RestTemplateConfig.java b/src/main/java/com/gxwebsoft/common/core/config/RestTemplateConfig.java new file mode 100644 index 0000000..786798f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/RestTemplateConfig.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.common.core.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate(ClientHttpRequestFactory factory) { + RestTemplate restTemplate = new RestTemplate(factory); + restTemplate.getMessageConverters().add(new HttpMessageConverter()); + return restTemplate; + } + @Bean + public ClientHttpRequestFactory simpleClientHttpRequestFactory() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + // ms + factory.setReadTimeout(60000); + // ms + factory.setConnectTimeout(60000); + + return factory; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/SpringContextUtil.java b/src/main/java/com/gxwebsoft/common/core/config/SpringContextUtil.java new file mode 100644 index 0000000..4e6d883 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/SpringContextUtil.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.common.core.config; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @Author ds + * @Date 2022-05-05 + */ +@Component +public class SpringContextUtil implements ApplicationContextAware { + /** + * spring的应用上下文 + */ + private static ApplicationContext applicationContext; + + /** + * 初始化时将应用上下文设置进applicationContext + * @param applicationContext + * @throws BeansException + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContextUtil.applicationContext=applicationContext; + } + + public static ApplicationContext getApplicationContext(){ + return applicationContext; + } + + /** + * 根据bean名称获取某个bean对象 + * + * @param name bean名称 + * @return Object + * @throws BeansException + */ + public static Object getBean(String name) throws BeansException { + return applicationContext.getBean(name); + } + + /** + * 根据bean的class获取某个bean对象 + * @param beanClass + * @param + * @return + * @throws BeansException + */ + public static T getBean(Class beanClass) throws BeansException { + return applicationContext.getBean(beanClass); + } + + /** + * 获取spring.profiles.active + * @return + */ + public static String getProfile(){ + return getApplicationContext().getEnvironment().getActiveProfiles()[0]; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java b/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java new file mode 100644 index 0000000..c63c2c7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java @@ -0,0 +1,111 @@ +package com.gxwebsoft.common.core.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.Components; +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; + +/** + * SpringDoc OpenAPI 配置 + * + * @author WebSoft + * @since 2018-02-22 11:29:05 + */ +@Configuration +public class SwaggerConfig { + @Resource + private ConfigProperties config; + + /** + * 全局 OpenAPI 配置 + */ + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title(config.getSwaggerTitle()) + .description(config.getSwaggerDescription()) + .version(config.getSwaggerVersion()) + .contact(new Contact() + .name("科技小王子") + .url("https://www.gxwebsoft.com") + .email("170083662@qq.com")) + .termsOfService(config.getServerUrl() + "/system")) + .components(new Components() + .addSecuritySchemes("Authorization", + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .name("Authorization") + .description("JWT Authorization header using the Bearer scheme"))) + .addSecurityItem(new SecurityRequirement().addList("Authorization")); + } + + /** + * Common 模块分组 + */ + @Bean + public GroupedOpenApi commonApi() { + return GroupedOpenApi.builder() + .group("common") + .pathsToMatch("/api/common/**", "/api/system/**", "/api/user/**", "/api/role/**", "/api/menu/**") + .packagesToScan("com.gxwebsoft.common") + .build(); + } + + /** + * CMS 模块分组 + */ + @Bean + public GroupedOpenApi cmsApi() { + return GroupedOpenApi.builder() + .group("cms") + .pathsToMatch("/api/cms/**") + .packagesToScan("com.gxwebsoft.cms") + .build(); + } + + /** + * Shop 模块分组 + */ + @Bean + public GroupedOpenApi shopApi() { + return GroupedOpenApi.builder() + .group("shop") + .pathsToMatch("/api/shop/**") + .packagesToScan("com.gxwebsoft.shop") + .build(); + } + + /** + * OA 模块分组 + */ + @Bean + public GroupedOpenApi oaApi() { + return GroupedOpenApi.builder() + .group("oa") + .pathsToMatch("/api/oa/**") + .packagesToScan("com.gxwebsoft.oa") + .build(); + } + + /** + * 其他模块分组 + */ + @Bean + public GroupedOpenApi otherApi() { + return GroupedOpenApi.builder() + .group("other") + .pathsToMatch("/api/docs/**", "/api/project/**", "/api/pwl/**", "/api/bszx/**", "/api/hjm/**") + .packagesToScan("com.gxwebsoft.docs", "com.gxwebsoft.project", "com.gxwebsoft.pwl", "com.gxwebsoft.bszx", "com.gxwebsoft.hjm") + .build(); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/WebMvcConfig.java b/src/main/java/com/gxwebsoft/common/core/config/WebMvcConfig.java new file mode 100644 index 0000000..2ed9d8b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/WebMvcConfig.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.core.config; + +import com.gxwebsoft.common.core.Constants; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * WebMvc配置, 拦截器、资源映射等都在此配置 + * + * @author WebSoft + * @since 2019-06-12 10:11:16 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + /** + * 支持跨域访问 + */ + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedHeaders("*") + .exposedHeaders(Constants.TOKEN_HEADER_NAME) + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") + .allowCredentials(true) + .maxAge(3600); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/AppUserConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/AppUserConstants.java new file mode 100644 index 0000000..538e295 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/AppUserConstants.java @@ -0,0 +1,8 @@ +package com.gxwebsoft.common.core.constants; + +public class AppUserConstants { + // 成员角色 + public static final Integer TRIAL = 10; // 体验成员 + public static final Integer DEVELOPER = 20; // 开发者 + public static final Integer ADMINISTRATOR = 30; // 管理员 +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/ArticleConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/ArticleConstants.java new file mode 100644 index 0000000..62a38cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/ArticleConstants.java @@ -0,0 +1,6 @@ +package com.gxwebsoft.common.core.constants; + +public class ArticleConstants extends BaseConstants { + public static final String[] ARTICLE_STATUS = {"已发布","待审核","已驳回","违规内容"}; + public static final String CACHE_KEY_ARTICLE = "Article:"; +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/BalanceConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/BalanceConstants.java new file mode 100644 index 0000000..6857250 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/BalanceConstants.java @@ -0,0 +1,10 @@ +package com.gxwebsoft.common.core.constants; + +public class BalanceConstants { + // 余额变动场景 + public static final Integer BALANCE_RECHARGE = 10; // 用户充值 + public static final Integer BALANCE_USE = 20; // 用户消费 + public static final Integer BALANCE_RE_LET = 21; // 续租 + public static final Integer BALANCE_ADMIN = 30; // 管理员操作 + public static final Integer BALANCE_REFUND = 40; // 订单退款 +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/BaseConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/BaseConstants.java new file mode 100644 index 0000000..66cf4c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/BaseConstants.java @@ -0,0 +1,5 @@ +package com.gxwebsoft.common.core.constants; + +public class BaseConstants { + public static final String[] STATUS = {"未定义","显示","隐藏"}; +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/OrderConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/OrderConstants.java new file mode 100644 index 0000000..e866654 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/OrderConstants.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.core.constants; + +public class OrderConstants { + // 支付方式 + public static final String PAY_METHOD_BALANCE = "10"; // 余额支付 + public static final String PAY_METHOD_WX = "20"; // 微信支付 + public static final String PAY_METHOD_ALIPAY = "30"; // 支付宝支付 + public static final String PAY_METHOD_OTHER = "40"; // 其他支付 + + // 付款状态 + public static final Integer PAY_STATUS_NO_PAY = 10; // 未付款 + public static final Integer PAY_STATUS_SUCCESS = 20; // 已付款 + + // 发货状态 + public static final Integer DELIVERY_STATUS_NO = 10; // 未发货 + public static final Integer DELIVERY_STATUS_YES = 20; // 已发货 + public static final Integer DELIVERY_STATUS_30 = 30; // 部分发货 + + // 收货状态 + public static final Integer RECEIPT_STATUS_NO = 10; // 未收货 + public static final Integer RECEIPT_STATUS_YES = 20; // 已收货 + public static final Integer RECEIPT_STATUS_RETURN = 30; // 已退货 + + // 订单状态 + public static final Integer ORDER_STATUS_DOING = 10; // 进行中 + public static final Integer ORDER_STATUS_CANCEL = 20; // 已取消 + public static final Integer ORDER_STATUS_TO_CANCEL = 21; // 待取消 + public static final Integer ORDER_STATUS_COMPLETED = 30; // 已完成 + + // 订单结算状态 + public static final Integer ORDER_SETTLED_YES = 1; // 已结算 + public static final Integer ORDER_SETTLED_NO = 0; // 未结算 + + + + +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/PlatformConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/PlatformConstants.java new file mode 100644 index 0000000..896f8e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/PlatformConstants.java @@ -0,0 +1,12 @@ +package com.gxwebsoft.common.core.constants; + +public class PlatformConstants { + public static final String MP_OFFICIAL = "MP-OFFICIAL"; // 微信公众号 + public static final String MP_WEIXIN = "MP-WEIXIN"; // 微信小程序 + public static final String MP_ALIPAY = "MP-ALIPAY"; // 支付宝小程序 + public static final String WEB = "WEB"; // web(同H5) + public static final String H5 = "H5"; // H5(推荐使用 WEB) + public static final String APP = "APP"; // App + public static final String MP_BAIDU = "MP-BAIDU"; // 百度小程序 + public static final String MP_TOUTIAO = "MP-TOUTIAO"; // 百度小程序 +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/ProfitConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/ProfitConstants.java new file mode 100644 index 0000000..2cb60fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/ProfitConstants.java @@ -0,0 +1,9 @@ +package com.gxwebsoft.common.core.constants; + +public class ProfitConstants { + // 收益类型 + public static final Integer PROFIT_TYPE10 = 10; // 推广收益 + public static final Integer PROFIT_TYPE20 = 20; // 团队收益 + public static final Integer PROFIT_TYPE30 = 30; // 门店收益 + public static final Integer PROFIT_TYPE40 = 30; // 区域收益 +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/QRCodeConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/QRCodeConstants.java new file mode 100644 index 0000000..1b30868 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/QRCodeConstants.java @@ -0,0 +1,10 @@ +package com.gxwebsoft.common.core.constants; + +public class QRCodeConstants { + // 二维码类型 + public static final String USER_QRCODE = "user"; // 用户二维码 + public static final String TASK_QRCODE = "task"; // 工单二维码 + public static final String ARTICLE_QRCODE = "article"; // 文章二维码 + public static final String GOODS_QRCODE = "goods"; // 商品二维码 + public static final String DIY_QRCODE = "diy"; // 工单二维码 +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java new file mode 100644 index 0000000..68d8fef --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.core.constants; + +public class RedisConstants { + // 短信验证码Key + public static final String SMS_CODE_KEY = "sms"; + // 验证码过期时间 + public static final Long SMS_CODE_TTL = 5L; + // 微信凭证access-token + public static final String ACCESS_TOKEN_KEY = "access-token"; + // 空值防止击穿数据库 + public static final Long CACHE_NULL_TTL = 2L; + // 商户信息 + public static final String MERCHANT_KEY = "merchant"; + // 添加商户定位点 + public static final String MERCHANT_GEO_KEY = "merchant-geo"; + + // token + public static final String TOKEN_USER_ID = "cache:token:"; + // 排行榜 + public static final String USER_RANKING_BY_APPS = "userRankingByApps"; + // 搜索历史 + public static final String SEARCH_HISTORY = "searchHistory"; + // 租户系统设置信息 + public static final String TEN_ANT_SETTING_KEY = "setting"; + // 排行榜Key + public static final String USER_RANKING_BY_APPS_5 = "cache5:userRankingByApps"; + + + + // 扫码登录相关key + public static final String QR_LOGIN_TOKEN_KEY = "qr-login:token:"; // 扫码登录token前缀 + public static final Long QR_LOGIN_TOKEN_TTL = 300L; // 扫码登录token过期时间(5分钟) + public static final String QR_LOGIN_STATUS_PENDING = "pending"; // 等待扫码 + public static final String QR_LOGIN_STATUS_SCANNED = "scanned"; // 已扫码 + public static final String QR_LOGIN_STATUS_CONFIRMED = "confirmed"; // 已确认 + public static final String QR_LOGIN_STATUS_EXPIRED = "expired"; // 已过期 + + // 哗啦啦key + public static final String getAllShop = "allShop"; + public static final String getBaseInfo = "baseInfo"; + public static final String getFoodClassCategory = "foodCategory"; + public static final String getOpenFood = "openFood"; + public static final String haulalaGeoKey = "cache10:hualala-geo"; + public static final String HLL_CART_KEY = "hll-cart"; // hll-cart[shopId]:[userId] + public static final String HLL_CART_FOOD_KEY = "hll-cart-list"; // hll-cart-list[shopId]:[userId] + +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/TaskConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/TaskConstants.java new file mode 100644 index 0000000..42cec5e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/TaskConstants.java @@ -0,0 +1,22 @@ +package com.gxwebsoft.common.core.constants; + +public class TaskConstants { + // 工单进度 + public static final Integer TOBEARRANGED = 0; // 待安排 + public static final Integer PENDING = 1; // 待处理 + public static final Integer PROCESSING = 2; // 处理中 + public static final Integer TOBECONFIRMED = 3; // 待评价 + public static final Integer COMPLETED = 4; // 已完成 + public static final Integer CLOSED = 5; // 已关闭 + + // 工单状态 + public static final Integer TASK_STATUS_0 = 0; // 待处理 + public static final Integer TASK_STATUS_1 = 1; // 已完成 + + // 操作类型 + public static final String ACTION_1 = "派单"; + public static final String ACTION_2 = "已解决"; + public static final String ACTION_3 = "关单"; + public static final String ACTION_4 = "分享"; + public static final String ACTION_5 = "编辑"; +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/WebsiteConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/WebsiteConstants.java new file mode 100644 index 0000000..a49f4ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/WebsiteConstants.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.core.constants; + +public class WebsiteConstants extends BaseConstants { + // 运行状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停 + public static final String[] WEBSITE_STATUS_NAME = {"未开通","运行中","维护中","已关闭","已欠费停机","违规关停"}; + // 状态图标 + public static final String[] WEBSITE_STATUS_ICON = {"error","success","warning","error","error","error"}; + // 关闭原因 + public static final String[] WEBSITE_STATUS_TEXT = {"产品未开通","","系统升级维护","","已欠费停机","违规关停"}; + // 跳转地址 + public static final String[] WEBSITE_STATUS_URL = {"https://websoft.top","","","","https://websoft.top/user","https://websoft.top/user"}; + // 跳转按钮文字 + public static final String[] WEBSITE_STATUS_BTN_TEXT = {"立即开通","","","","立即续费","申请解封"}; +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/WxOfficialConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/WxOfficialConstants.java new file mode 100644 index 0000000..a025610 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/constants/WxOfficialConstants.java @@ -0,0 +1,6 @@ +package com.gxwebsoft.common.core.constants; + +public class WxOfficialConstants { + // 获取 Access token + public static final String GET_ACCESS_TOKEN_API = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; +} diff --git a/src/main/java/com/gxwebsoft/common/core/context/TenantContext.java b/src/main/java/com/gxwebsoft/common/core/context/TenantContext.java new file mode 100644 index 0000000..42adb46 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/context/TenantContext.java @@ -0,0 +1,67 @@ +package com.gxwebsoft.common.core.context; + +/** + * 租户上下文管理器 + * + * 用于在特定场景下临时禁用租户隔离 + * + * @author WebSoft + * @since 2025-01-26 + */ +public class TenantContext { + + private static final ThreadLocal IGNORE_TENANT = new ThreadLocal<>(); + + /** + * 设置忽略租户隔离 + */ + public static void setIgnoreTenant(boolean ignore) { + IGNORE_TENANT.set(ignore); + } + + /** + * 是否忽略租户隔离 + */ + public static boolean isIgnoreTenant() { + Boolean ignore = IGNORE_TENANT.get(); + return ignore != null && ignore; + } + + /** + * 清除租户上下文 + */ + public static void clear() { + IGNORE_TENANT.remove(); + } + + /** + * 在忽略租户隔离的上下文中执行操作 + * + * @param runnable 要执行的操作 + */ + public static void runIgnoreTenant(Runnable runnable) { + boolean originalIgnore = isIgnoreTenant(); + try { + setIgnoreTenant(true); + runnable.run(); + } finally { + setIgnoreTenant(originalIgnore); + } + } + + /** + * 在忽略租户隔离的上下文中执行操作并返回结果 + * + * @param supplier 要执行的操作 + * @return 操作结果 + */ + public static T callIgnoreTenant(java.util.function.Supplier supplier) { + boolean originalIgnore = isIgnoreTenant(); + try { + setIgnoreTenant(true); + return supplier.get(); + } finally { + setIgnoreTenant(originalIgnore); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/CertificateController.java b/src/main/java/com/gxwebsoft/common/core/controller/CertificateController.java new file mode 100644 index 0000000..d3dee92 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/CertificateController.java @@ -0,0 +1,187 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.service.CertificateHealthService; +import com.gxwebsoft.common.core.service.CertificateService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Map; + +/** + * 证书管理控制器 + * 提供证书状态查询、健康检查等功能 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Slf4j +@Tag(name = "证书管理") +@RestController +@RequestMapping("/api/system/certificate") +public class CertificateController extends BaseController { + + @Resource + private CertificateService certificateService; + + @Resource + private CertificateHealthService certificateHealthService; + + @Operation(summary = "获取所有证书状态") + @GetMapping("/status") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult> getCertificateStatus() { + try { + Map status = certificateService.getAllCertificateStatus(); + return success("获取证书状态成功", status); + } catch (Exception e) { + log.error("获取证书状态失败", e); + return new ApiResult<>(1, "获取证书状态失败: " + e.getMessage()); + } + } + + @Operation(summary = "证书健康检查") + @GetMapping("/health") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult> healthCheck() { + try { + CertificateHealthService.HealthResult health = certificateHealthService.health(); + Map result = Map.of( + "status", health.getStatus(), + "details", health.getDetails() + ); + return success("证书健康检查完成", result); + } catch (Exception e) { + log.error("证书健康检查失败", e); + return new ApiResult<>(1, "证书健康检查失败: " + e.getMessage()); + } + } + + @Operation(summary = "获取证书诊断信息") + @GetMapping("/diagnostic") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult> getDiagnosticInfo() { + try { + Map diagnostic = certificateHealthService.getDiagnosticInfo(); + return success("获取证书诊断信息成功", diagnostic); + } catch (Exception e) { + log.error("获取证书诊断信息失败", e); + return new ApiResult<>(1, "获取证书诊断信息失败: " + e.getMessage()); + } + } + + @Operation(summary = "检查特定证书") + @GetMapping("/check/{certType}/{fileName}") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult> checkSpecificCertificate( + @Parameter(description = "证书类型", example = "wechat") @PathVariable String certType, + @Parameter(description = "文件名", example = "apiclient_key.pem") @PathVariable String fileName) { + try { + Map result = certificateHealthService.checkSpecificCertificate(certType, fileName); + return success("检查证书完成", result); + } catch (Exception e) { + log.error("检查证书失败: {}/{}", certType, fileName, e); + return new ApiResult<>(1, "检查证书失败: " + e.getMessage()); + } + } + + @Operation(summary = "验证证书文件") + @GetMapping("/validate/{certType}/{fileName}") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult validateCertificate( + @Parameter(description = "证书类型", example = "wechat") @PathVariable String certType, + @Parameter(description = "文件名", example = "apiclient_cert.pem") @PathVariable String fileName) { + try { + CertificateService.CertificateInfo certInfo = + certificateService.validateX509Certificate(certType, fileName); + + if (certInfo != null) { + return success("证书验证成功", certInfo); + } else { + return new ApiResult<>(1, "证书验证失败,可能不是有效的X509证书"); + } + } catch (Exception e) { + log.error("验证证书失败: {}/{}", certType, fileName, e); + return new ApiResult<>(1, "验证证书失败: " + e.getMessage()); + } + } + + @Operation(summary = "检查证书文件是否存在") + @GetMapping("/exists/{certType}/{fileName}") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult checkCertificateExists( + @Parameter(description = "证书类型", example = "alipay") @PathVariable String certType, + @Parameter(description = "文件名", example = "appCertPublicKey.crt") @PathVariable String fileName) { + try { + boolean exists = certificateService.certificateExists(certType, fileName); + String message = exists ? "证书文件存在" : "证书文件不存在"; + return success(message, exists); + } catch (Exception e) { + log.error("检查证书文件存在性失败: {}/{}", certType, fileName, e); + return new ApiResult<>(1, "检查证书文件存在性失败: " + e.getMessage()); + } + } + + @Operation(summary = "获取证书文件路径") + @GetMapping("/path/{certType}/{fileName}") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult getCertificatePath( + @Parameter(description = "证书类型", example = "wechat") @PathVariable String certType, + @Parameter(description = "文件名", example = "wechatpay_cert.pem") @PathVariable String fileName) { + try { + String path = certificateService.getCertificateFilePath(certType, fileName); + return success("获取证书路径成功", path); + } catch (Exception e) { + log.error("获取证书路径失败: {}/{}", certType, fileName, e); + return new ApiResult<>(1, "获取证书路径失败: " + e.getMessage()); + } + } + + @Operation(summary = "获取微信支付证书路径") + @GetMapping("/wechat-path/{fileName}") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult getWechatPayCertPath( + @Parameter(description = "文件名", example = "apiclient_key.pem") @PathVariable String fileName) { + try { + String path = certificateService.getWechatPayCertPath(fileName); + return success("获取微信支付证书路径成功", path); + } catch (Exception e) { + log.error("获取微信支付证书路径失败: {}", fileName, e); + return new ApiResult<>(1, "获取微信支付证书路径失败: " + e.getMessage()); + } + } + + @Operation(summary = "获取支付宝证书路径") + @GetMapping("/alipay-path/{fileName}") + @PreAuthorize("hasAuthority('system:certificate:view')") + public ApiResult getAlipayCertPath( + @Parameter(description = "文件名", example = "appCertPublicKey.crt") @PathVariable String fileName) { + try { + String path = certificateService.getAlipayCertPath(fileName); + return success("获取支付宝证书路径成功", path); + } catch (Exception e) { + log.error("获取支付宝证书路径失败: {}", fileName, e); + return new ApiResult<>(1, "获取支付宝证书路径失败: " + e.getMessage()); + } + } + + @Operation(summary = "刷新证书缓存") + @PostMapping("/refresh") + @PreAuthorize("hasAuthority('system:certificate:manage')") + public ApiResult refreshCertificateCache() { + try { + // 这里可以添加刷新证书缓存的逻辑 + log.info("证书缓存刷新请求,操作用户: {}", getLoginUser().getUsername()); + return new ApiResult<>(0, "证书缓存刷新成功", "success"); + } catch (Exception e) { + log.error("刷新证书缓存失败", e); + return new ApiResult<>(1, "刷新证书缓存失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java b/src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java new file mode 100644 index 0000000..166e9cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java @@ -0,0 +1,204 @@ +package com.gxwebsoft.common.core.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.service.ShopCouponService; +import com.gxwebsoft.shop.service.ShopUserCouponService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 数据库修复工具控制器 + * 仅在开发环境启用,用于修复数据库问题 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Tag(name = "数据库修复工具") +@RestController +@RequestMapping("/api/database-fix") +// @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev") +public class DatabaseFixController extends BaseController { + + @Autowired + private ShopUserCouponService shopUserCouponService; + + @Autowired + private ShopCouponService shopCouponService; + + @Operation(summary = "检查BigDecimal null值问题") + @GetMapping("/check-bigdecimal-nulls") + public ApiResult> checkBigDecimalNulls() { + try { + Map result = new HashMap<>(); + + // 检查用户优惠券表 + List userCoupons = shopUserCouponService.list(); + long userCouponNullReducePrice = userCoupons.stream() + .mapToLong(c -> c.getReducePrice() == null ? 1 : 0) + .sum(); + long userCouponNullMinPrice = userCoupons.stream() + .mapToLong(c -> c.getMinPrice() == null ? 1 : 0) + .sum(); + + // 检查优惠券模板表 + List coupons = shopCouponService.list(); + long couponNullReducePrice = coupons.stream() + .mapToLong(c -> c.getReducePrice() == null ? 1 : 0) + .sum(); + long couponNullMinPrice = coupons.stream() + .mapToLong(c -> c.getMinPrice() == null ? 1 : 0) + .sum(); + + Map userCouponStats = new HashMap<>(); + userCouponStats.put("totalRecords", userCoupons.size()); + userCouponStats.put("nullReducePrice", userCouponNullReducePrice); + userCouponStats.put("nullMinPrice", userCouponNullMinPrice); + + Map couponStats = new HashMap<>(); + couponStats.put("totalRecords", coupons.size()); + couponStats.put("nullReducePrice", couponNullReducePrice); + couponStats.put("nullMinPrice", couponNullMinPrice); + + result.put("shopUserCoupon", userCouponStats); + result.put("shopCoupon", couponStats); + result.put("needsFix", userCouponNullReducePrice > 0 || userCouponNullMinPrice > 0 || + couponNullReducePrice > 0 || couponNullMinPrice > 0); + + return success("检查完成", result); + + } catch (Exception e) { + log.error("检查BigDecimal null值失败", e); + return fail("检查失败: " + e.getMessage(),null); + } + } + + @Operation(summary = "修复BigDecimal null值问题") + @PostMapping("/fix-bigdecimal-nulls") + public ApiResult> fixBigDecimalNulls() { + try { + Map result = new HashMap<>(); + int userCouponFixed = 0; + int couponFixed = 0; + + // 修复用户优惠券表 + List userCoupons = shopUserCouponService.list(); + for (ShopUserCoupon userCoupon : userCoupons) { + boolean needUpdate = false; + + if (userCoupon.getReducePrice() == null) { + userCoupon.setReducePrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (userCoupon.getMinPrice() == null) { + userCoupon.setMinPrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (needUpdate) { + shopUserCouponService.updateById(userCoupon); + userCouponFixed++; + } + } + + // 修复优惠券模板表 + List coupons = shopCouponService.list(); + for (ShopCoupon coupon : coupons) { + boolean needUpdate = false; + + if (coupon.getReducePrice() == null) { + coupon.setReducePrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (coupon.getMinPrice() == null) { + coupon.setMinPrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (needUpdate) { + shopCouponService.updateById(coupon); + couponFixed++; + } + } + + result.put("userCouponFixed", userCouponFixed); + result.put("couponFixed", couponFixed); + result.put("totalFixed", userCouponFixed + couponFixed); + + log.info("BigDecimal null值修复完成: 用户优惠券{}条, 优惠券模板{}条", userCouponFixed, couponFixed); + + return success("修复完成", result); + + } catch (Exception e) { + log.error("修复BigDecimal null值失败", e); + return fail("修复失败: " + e.getMessage(), null); + } + } + + @Operation(summary = "测试优惠券接口") + @GetMapping("/test-coupon-api") + public ApiResult> testCouponApi() { + try { + Map result = new HashMap<>(); + + // 测试查询用户优惠券 + List userCoupons = shopUserCouponService.list( + new QueryWrapper().last("LIMIT 5") + ); + + // 测试查询优惠券模板 + List coupons = shopCouponService.list( + new QueryWrapper().last("LIMIT 5") + ); + + result.put("userCouponsCount", userCoupons.size()); + result.put("couponsCount", coupons.size()); + result.put("userCouponsSample", userCoupons); + result.put("couponsSample", coupons); + result.put("testStatus", "SUCCESS"); + + return success("测试成功", result); + + } catch (Exception e) { + log.error("测试优惠券接口失败", e); + Map errorResult = new HashMap<>(); + errorResult.put("testStatus", "FAILED"); + errorResult.put("errorMessage", e.getMessage()); + errorResult.put("errorType", e.getClass().getSimpleName()); + + return fail("测试失败: " + e.getMessage(), errorResult); + } + } + + @Operation(summary = "获取修复指南") + @GetMapping("/guide") + public ApiResult> getFixGuide() { + Map guide = new HashMap<>(); + + guide.put("step1", "GET /api/database-fix/check-bigdecimal-nulls - 检查null值问题"); + guide.put("step2", "POST /api/database-fix/fix-bigdecimal-nulls - 修复null值问题"); + guide.put("step3", "GET /api/database-fix/test-coupon-api - 测试修复效果"); + guide.put("step4", "重启应用,验证优惠券功能正常"); + + guide.put("note1", "此工具仅在开发环境可用"); + guide.put("note2", "修复前建议备份数据库"); + guide.put("note3", "修复完成后可以删除此控制器"); + + return success("获取成功", guide); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java b/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java new file mode 100644 index 0000000..8e30a34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java @@ -0,0 +1,236 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.service.EnvironmentAwarePaymentService; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.service.PaymentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 开发环境管理控制器 + * 仅在开发环境启用,用于管理开发调试配置 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Tag(name = "开发环境管理") +@RestController +@RequestMapping("/api/dev") +// @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev") +public class DevEnvironmentController extends BaseController { + + @Autowired + private EnvironmentAwarePaymentService environmentAwarePaymentService; + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private PaymentService paymentService; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + @Operation(summary = "获取当前环境信息") + @GetMapping("/environment/info") + public ApiResult> getEnvironmentInfo() { + Map info = new HashMap<>(); + info.put("activeProfile", activeProfile); + info.put("isDevelopment", environmentAwarePaymentService.isDevelopmentEnvironment()); + info.put("isProduction", environmentAwarePaymentService.isProductionEnvironment()); + info.put("currentEnvironment", environmentAwarePaymentService.getCurrentEnvironment()); + + return success("获取成功", info); + } + + @Operation(summary = "获取环境感知的支付配置") + @GetMapping("/payment/config/{payType}") + public ApiResult> getPaymentConfig(@PathVariable Integer payType) { + try { + Integer tenantId = getTenantId(); + + // 获取原始配置 + Payment originalConfig = paymentCacheService.getPaymentConfig(payType, tenantId); + + // 获取环境感知配置 + Payment envConfig = environmentAwarePaymentService.getEnvironmentAwarePaymentConfig(payType, tenantId); + + Map result = new HashMap<>(); + result.put("tenantId", tenantId); + result.put("payType", payType); + result.put("environment", activeProfile); + result.put("originalConfig", originalConfig); + result.put("environmentAwareConfig", envConfig); + + if (originalConfig != null && envConfig != null) { + result.put("notifyUrlChanged", !originalConfig.getNotifyUrl().equals(envConfig.getNotifyUrl())); + result.put("originalNotifyUrl", originalConfig.getNotifyUrl()); + result.put("environmentNotifyUrl", envConfig.getNotifyUrl()); + } + + return success("获取成功", result); + + } catch (Exception e) { + log.error("获取支付配置失败", e); + return fail("获取失败: " + e.getMessage(),null); + } + } + + @Operation(summary = "切换开发环境回调地址") + @PostMapping("/payment/switch-notify-url") + public ApiResult switchNotifyUrl(@RequestBody Map request) { + try { + String newNotifyUrl = request.get("notifyUrl"); + Integer payType = Integer.valueOf(request.getOrDefault("payType", "0")); + + if (newNotifyUrl == null || newNotifyUrl.trim().isEmpty()) { + return fail("回调地址不能为空"); + } + + Integer tenantId = getTenantId(); + + // 获取当前配置 + Payment payment = paymentCacheService.getPaymentConfig(payType, tenantId); + if (payment == null) { + return fail("未找到支付配置"); + } + + // 更新回调地址 + payment.setNotifyUrl(newNotifyUrl); + + // 更新数据库 + boolean updated = paymentService.updateById(payment); + + if (updated) { + // 清除缓存,强制重新加载 + paymentCacheService.removePaymentConfig(payType.toString(), tenantId); + + log.info("开发环境回调地址已更新: {} -> {}", payment.getNotifyUrl(), newNotifyUrl); + + Map result = new HashMap<>(); + result.put("oldNotifyUrl", payment.getNotifyUrl()); + result.put("newNotifyUrl", newNotifyUrl); + result.put("payType", payType); + result.put("tenantId", tenantId); + + return success("回调地址更新成功", result); + } else { + return fail("更新失败"); + } + + } catch (Exception e) { + log.error("切换回调地址失败", e); + return fail("切换失败: " + e.getMessage()); + } + } + + @Operation(summary = "重置为生产环境回调地址") + @PostMapping("/payment/reset-to-prod") + public ApiResult resetToProdNotifyUrl(@RequestParam(defaultValue = "0") Integer payType) { + try { + Integer tenantId = getTenantId(); + + // 获取当前配置 + Payment payment = paymentCacheService.getPaymentConfig(payType, tenantId); + if (payment == null) { + return fail("未找到支付配置"); + } + + // 设置为生产环境回调地址 + String prodNotifyUrl = "https://cms-api.websoft.top/api/shop/shop-order/notify"; + String oldNotifyUrl = payment.getNotifyUrl(); + + payment.setNotifyUrl(prodNotifyUrl); + + // 更新数据库 + boolean updated = paymentService.updateById(payment); + + if (updated) { + // 清除缓存 + paymentCacheService.removePaymentConfig(payType.toString(), tenantId); + + log.info("回调地址已重置为生产环境: {} -> {}", oldNotifyUrl, prodNotifyUrl); + + Map result = new HashMap<>(); + result.put("oldNotifyUrl", oldNotifyUrl); + result.put("newNotifyUrl", prodNotifyUrl); + result.put("payType", payType); + result.put("tenantId", tenantId); + + return success("已重置为生产环境回调地址", result); + } else { + return fail("重置失败"); + } + + } catch (Exception e) { + log.error("重置回调地址失败", e); + return fail("重置失败: " + e.getMessage()); + } + } + + @Operation(summary = "清除支付配置缓存") + @PostMapping("/payment/clear-cache") + public ApiResult clearPaymentCache(@RequestParam(defaultValue = "0") Integer payType) { + try { + Integer tenantId = getTenantId(); + + paymentCacheService.removePaymentConfig(payType.toString(), tenantId); + + log.info("支付配置缓存已清除: payType={}, tenantId={}", payType, tenantId); + + return success("缓存清除成功"); + + } catch (Exception e) { + log.error("清除缓存失败", e); + return fail("清除失败: " + e.getMessage()); + } + } + + @Operation(summary = "获取开发环境使用指南") + @GetMapping("/guide") + public ApiResult> getDevGuide() { + Map guide = new HashMap<>(); + + guide.put("title", "开发环境支付调试指南"); + guide.put("environment", activeProfile); + + Map steps = new HashMap<>(); + steps.put("step1", "使用 /api/dev/payment/switch-notify-url 切换到本地回调地址"); + steps.put("step2", "进行支付功能调试和测试"); + steps.put("step3", "调试完成后使用 /api/dev/payment/reset-to-prod 恢复生产环境配置"); + steps.put("step4", "或者直接在后台管理界面修改回调地址"); + + guide.put("steps", steps); + + Map apis = new HashMap<>(); + apis.put("环境信息", "GET /api/dev/environment/info"); + apis.put("查看配置", "GET /api/dev/payment/config/{payType}"); + apis.put("切换回调", "POST /api/dev/payment/switch-notify-url"); + apis.put("重置生产", "POST /api/dev/payment/reset-to-prod"); + apis.put("清除缓存", "POST /api/dev/payment/clear-cache"); + + guide.put("apis", apis); + + Map tips = new HashMap<>(); + tips.put("tip1", "此控制器仅在开发环境启用"); + tips.put("tip2", "生产环境不会加载这些接口"); + tips.put("tip3", "建议使用环境感知服务自动切换"); + tips.put("tip4", "记得在调试完成后恢复生产配置"); + + guide.put("tips", tips); + + return success("获取成功", guide); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/PaymentConfigController.java b/src/main/java/com/gxwebsoft/common/core/controller/PaymentConfigController.java new file mode 100644 index 0000000..37706fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/PaymentConfigController.java @@ -0,0 +1,149 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.service.PaymentService; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 支付配置管理控制器 + * 用于检查和管理支付配置 + * + * @author 科技小王子 + * @since 2025-07-27 + */ +@Slf4j +@Tag(name = "支付配置管理") +@RestController +@RequestMapping("/api/system/payment-config") +public class PaymentConfigController extends BaseController { + + @Autowired + private PaymentService paymentService; + + @Autowired + private PaymentCacheService paymentCacheService; + + @Operation(summary = "检查支付配置") + @GetMapping("/check/{payType}") + @PreAuthorize("hasAuthority('sys:payment:list')") + public ApiResult> checkPaymentConfig(@PathVariable Integer payType) { + try { + Map result = new HashMap<>(); + + // 获取支付配置 + Payment payment = paymentCacheService.getPaymentConfig(payType, getTenantId()); + + if (payment == null) { + result.put("status", "error"); + result.put("message", "未找到支付配置"); + return success("检查完成", result); + } + + // 检查配置完整性 + Map configCheck = new HashMap<>(); + configCheck.put("id", payment.getId()); + configCheck.put("name", payment.getName()); + configCheck.put("type", payment.getType()); + configCheck.put("code", payment.getCode()); + configCheck.put("appId", payment.getAppId()); + configCheck.put("mchId", payment.getMchId()); + configCheck.put("apiKeyConfigured", payment.getApiKey() != null && !payment.getApiKey().trim().isEmpty()); + configCheck.put("apiKeyLength", payment.getApiKey() != null ? payment.getApiKey().length() : 0); + configCheck.put("merchantSerialNumber", payment.getMerchantSerialNumber()); + configCheck.put("status", payment.getStatus()); + configCheck.put("tenantId", payment.getTenantId()); + + // 检查必要字段 + boolean isValid = true; + StringBuilder errors = new StringBuilder(); + + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + isValid = false; + errors.append("商户号(mchId)未配置; "); + } + + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + isValid = false; + errors.append("API密钥(apiKey)未配置; "); + } + + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + isValid = false; + errors.append("商户证书序列号(merchantSerialNumber)未配置; "); + } + + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + isValid = false; + errors.append("应用ID(appId)未配置; "); + } + + result.put("status", isValid ? "success" : "error"); + result.put("valid", isValid); + result.put("errors", errors.toString()); + result.put("config", configCheck); + + return success("检查完成", result); + + } catch (Exception e) { + log.error("检查支付配置失败", e); + Map result = new HashMap<>(); + result.put("status", "error"); + result.put("message", "检查失败: " + e.getMessage()); + return success("检查完成", result); + } + } + + @Operation(summary = "初始化微信支付配置") + @PostMapping("/init-wechat") + @PreAuthorize("hasAuthority('sys:payment:save')") + public ApiResult initWechatPayConfig(@RequestBody Map config) { + try { + Payment payment = new Payment(); + payment.setName("微信支付"); + payment.setType(0); // 微信支付类型为0 + payment.setCode("0"); + payment.setAppId(config.get("appId")); + payment.setMchId(config.get("mchId")); + payment.setApiKey(config.get("apiKey")); + payment.setMerchantSerialNumber(config.get("merchantSerialNumber")); + payment.setStatus(true); + payment.setTenantId(getTenantId()); + + if (paymentService.save(payment)) { + // 缓存配置 + paymentCacheService.cachePaymentConfig(payment, getTenantId()); + return success("微信支付配置初始化成功"); + } else { + return fail("微信支付配置初始化失败"); + } + + } catch (Exception e) { + log.error("初始化微信支付配置失败", e); + return fail("初始化失败: " + e.getMessage()); + } + } + + @Operation(summary = "清除支付配置缓存") + @DeleteMapping("/cache/{payType}") + @PreAuthorize("hasAuthority('sys:payment:update')") + public ApiResult clearPaymentCache(@PathVariable Integer payType) { + try { + paymentCacheService.removePaymentConfig(payType.toString(), getTenantId()); + return success("缓存清除成功"); + } catch (Exception e) { + log.error("清除支付配置缓存失败", e); + return fail("清除缓存失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/QrCodeController.java b/src/main/java/com/gxwebsoft/common/core/controller/QrCodeController.java new file mode 100644 index 0000000..b589901 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/QrCodeController.java @@ -0,0 +1,258 @@ +package com.gxwebsoft.common.core.controller; + +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.extra.qrcode.QrConfig; +import com.gxwebsoft.common.core.dto.qr.*; +import com.gxwebsoft.common.core.utils.EncryptedQrCodeUtil; +import com.gxwebsoft.common.core.utils.QrCodeDecryptResult; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.awt.*; +import java.io.IOException; +import java.util.Base64; +import java.util.Map; + +/** + * 二维码生成控制器 + * + * @author WebSoft + * @since 2025-08-18 + */ +@RestController +@RequestMapping("/api/qr-code") +@Tag(name = "二维码生成API") +@Validated +public class QrCodeController extends BaseController { + + @Autowired + private EncryptedQrCodeUtil encryptedQrCodeUtil; + + @Operation(summary = "生成普通二维码") + @GetMapping("/create-qr-code") + public void createQrCode( + @Parameter(description = "要编码的数据") @RequestParam("data") String data, + @Parameter(description = "二维码尺寸,格式:宽x高 或 单个数字") @RequestParam(value = "size", defaultValue = "200x200") String size, + HttpServletResponse response) throws IOException { + + try { + // 解析尺寸 + String[] dimensions = size.split("x"); + int width = Integer.parseInt(dimensions[0]); + int height = dimensions.length > 1 ? Integer.parseInt(dimensions[1]) : width; + + // 验证尺寸范围,防止过大的图片 + if (width < 50 || width > 1000 || height < 50 || height > 1000) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("尺寸必须在50-1000像素之间"); + return; + } + + // 配置二维码 + QrConfig config = new QrConfig(width, height); + config.setMargin(1); + config.setForeColor(Color.BLACK); + config.setBackColor(Color.WHITE); + + // 设置响应头 + response.setContentType("image/png"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Disposition", "inline; filename=qrcode.png"); + + // 生成二维码并直接输出到响应流 + QrCodeUtil.generate(data, config, "png", response.getOutputStream()); + + } catch (NumberFormatException e) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("尺寸格式错误,请使用如:200x200 的格式"); + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("生成二维码失败:" + e.getMessage()); + } + } + + @Operation(summary = "生成加密二维码") + @PostMapping("/create-encrypted-qr-code") + public ApiResult createEncryptedQrCode(@Valid @RequestBody CreateEncryptedQrCodeRequest request) { + + try { + // 生成加密二维码 + Map result = encryptedQrCodeUtil.generateEncryptedQrCode( + request.getData(), + request.getWidth(), + request.getHeight(), + request.getExpireMinutes(), + request.getBusinessType()); + + return success("生成加密二维码成功", result); + + } catch (Exception e) { + return fail("生成加密二维码失败:" + e.getMessage()); + } + } + + @Operation(summary = "生成加密二维码图片流") + @GetMapping("/create-encrypted-qr-image") + public void createEncryptedQrImage( + @Parameter(description = "要加密的数据") @RequestParam("data") String data, + @Parameter(description = "二维码尺寸") @RequestParam(value = "size", defaultValue = "200x200") String size, + @Parameter(description = "过期时间(分钟)") @RequestParam(value = "expireMinutes", defaultValue = "30") Long expireMinutes, + @Parameter(description = "业务类型(可选)") @RequestParam(value = "businessType", required = false) String businessType, + HttpServletResponse response) throws IOException { + + try { + // 解析尺寸 + String[] dimensions = size.split("x"); + int width = Integer.parseInt(dimensions[0]); + int height = dimensions.length > 1 ? Integer.parseInt(dimensions[1]) : width; + + // 验证尺寸范围 + if (width < 50 || width > 1000 || height < 50 || height > 1000) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("尺寸必须在50-1000像素之间"); + return; + } + + // 验证过期时间 + if (expireMinutes <= 0 || expireMinutes > 1440) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("过期时间必须在1-1440分钟之间"); + return; + } + + // 生成加密二维码 + Map result = encryptedQrCodeUtil.generateEncryptedQrCode(data, width, height, expireMinutes, businessType); + String base64Image = (String) result.get("qrCodeBase64"); + + // 解码Base64图片 + byte[] imageBytes = Base64.getDecoder().decode(base64Image); + + // 设置响应头 + response.setContentType("image/png"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Disposition", "inline; filename=encrypted_qrcode.png"); + response.setHeader("X-QR-Token", (String) result.get("token")); + response.setHeader("X-QR-Expire-Minutes", result.get("expireMinutes").toString()); + + // 输出图片 + response.getOutputStream().write(imageBytes); + + } catch (NumberFormatException e) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("尺寸格式错误,请使用如:200x200 的格式"); + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("生成加密二维码失败:" + e.getMessage()); + } + } + + @Operation(summary = "解密二维码数据") + @PostMapping("/decrypt-qr-data") + public ApiResult decryptQrData(@Valid @RequestBody DecryptQrDataRequest request) { + + try { + String decryptedData = encryptedQrCodeUtil.decryptData(request.getToken(), request.getEncryptedData()); + return success("解密成功", decryptedData); + + } catch (Exception e) { + return fail("解密失败:" + e.getMessage()); + } + } + + @Operation(summary = "验证并解密二维码内容(自包含模式)") + @PostMapping("/verify-and-decrypt-qr") + public ApiResult verifyAndDecryptQr(@Valid @RequestBody VerifyQrContentRequest request) { + + try { + String originalData = encryptedQrCodeUtil.verifyAndDecryptQrCode(request.getQrContent()); + return success("验证和解密成功", originalData); + + } catch (Exception e) { + return fail("验证和解密失败:" + e.getMessage()); + } + } + + @Operation(summary = "验证并解密二维码内容(返回完整结果,包含业务类型)") + @PostMapping("/verify-and-decrypt-qr-with-type") + public ApiResult verifyAndDecryptQrWithType(@Valid @RequestBody VerifyQrContentRequest request) { + + try { + QrCodeDecryptResult result = encryptedQrCodeUtil.verifyAndDecryptQrCodeWithResult(request.getQrContent()); + return success("验证和解密成功", result); + + } catch (Exception e) { + return fail("验证和解密失败:" + e.getMessage(),null); + } + } + + @Operation(summary = "生成业务加密二维码(门店核销模式)") + @PostMapping("/create-business-encrypted-qr-code") + public ApiResult createBusinessEncryptedQrCode(@Valid @RequestBody CreateBusinessEncryptedQrCodeRequest request) { + + try { + // 生成业务加密二维码 + Map result = encryptedQrCodeUtil.generateBusinessEncryptedQrCode( + request.getData(), + request.getWidth(), + request.getHeight(), + request.getBusinessKey(), + request.getExpireMinutes(), + request.getBusinessType()); + + return success("生成业务加密二维码成功", result); + + } catch (Exception e) { + return fail("生成业务加密二维码失败:" + e.getMessage()); + } + } + + @Operation(summary = "门店核销二维码(业务模式)") + @PostMapping("/verify-business-qr") + public ApiResult verifyBusinessQr(@Valid @RequestBody VerifyBusinessQrRequest request) { + + try { + String originalData = encryptedQrCodeUtil.verifyAndDecryptQrCodeWithBusinessKey( + request.getQrContent(), request.getBusinessKey()); + return success("核销成功", originalData); + + } catch (Exception e) { + return fail("核销失败:" + e.getMessage()); + } + } + + @Operation(summary = "检查token是否有效") + @GetMapping("/check-token") + public ApiResult checkToken( + @Parameter(description = "要检查的token") @RequestParam("token") String token) { + + try { + boolean isValid = encryptedQrCodeUtil.isTokenValid(token); + return success("检查完成", isValid); + + } catch (Exception e) { + return fail("检查token失败:" + e.getMessage()); + } + } + + @Operation(summary = "使token失效") + @PostMapping("/invalidate-token") + public ApiResult invalidateToken(@Valid @RequestBody InvalidateTokenRequest request) { + + try { + encryptedQrCodeUtil.invalidateToken(request.getToken()); + return success("token已失效"); + + } catch (Exception e) { + return fail("使token失效失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/TestController.java b/src/main/java/com/gxwebsoft/common/core/controller/TestController.java new file mode 100644 index 0000000..05c4246 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/TestController.java @@ -0,0 +1,302 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.service.PaymentService; +import com.gxwebsoft.common.system.param.PaymentParam; +import com.gxwebsoft.common.core.web.ApiResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 测试控制器 + * 用于测试LocalDateTime序列化 + * + * @author WebSoft + * @since 2025-01-12 + */ +@Tag(name = "测试接口") +@RestController +@RequestMapping("/api/test") +public class TestController extends BaseController { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private PaymentService paymentService; + + @Operation(summary = "测试LocalDateTime序列化") + @GetMapping("/datetime") + public ApiResult> testDateTime() { + Map result = new HashMap<>(); + // 使用字符串格式避免序列化问题 + result.put("currentTime", LocalDateTime.now().toString()); + result.put("currentTimeFormatted", LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + result.put("message", "LocalDateTime序列化测试"); + result.put("timestamp", System.currentTimeMillis()); + return success(result); + } + + @Operation(summary = "基础诊断 - 不依赖支付服务") + @GetMapping("/basic-debug/{tenantId}") + public ApiResult basicDebug(@PathVariable Integer tenantId) { + try { + System.out.println("=== 基础诊断开始 ==="); + System.out.println("接收到的租户ID: " + tenantId); + + if (tenantId == null) { + return fail("租户ID为null",null); + } + + return success("基础诊断通过,租户ID: " + tenantId,null); + + } catch (Exception e) { + System.err.println("基础诊断异常: " + e.getMessage()); + e.printStackTrace(); + return fail("基础诊断异常: " + e.getMessage(),null); + } + } + + @Operation(summary = "快速诊断支付配置") + @GetMapping("/payment-debug/{tenantId}") + public ApiResult debugPaymentConfig(@PathVariable Integer tenantId) { + try { + System.out.println("=== 开始诊断租户 " + tenantId + " 的支付配置 ==="); + + // 检查基础参数 + if (tenantId == null) { + return fail("租户ID为null",null); + } + + // 检查服务是否可用 + if (paymentCacheService == null) { + return fail("PaymentCacheService未注入",null); + } + + System.out.println("准备调用 paymentCacheService.getWechatPayConfig(" + tenantId + ")"); + + // 获取支付配置 + Payment payment = null; + try { + payment = paymentCacheService.getWechatPayConfig(tenantId); + System.out.println("成功调用 getWechatPayConfig,结果: " + (payment != null ? "非null" : "null")); + } catch (Exception e) { + System.err.println("调用 getWechatPayConfig 异常: " + e.getMessage()); + e.printStackTrace(); + return fail("获取支付配置异常: " + e.getMessage() + " (类型: " + e.getClass().getName() + ")",null); + } + + if (payment == null) { + System.out.println("❌ 支付配置不存在"); + return fail("支付配置不存在,租户ID: " + tenantId,null); + } + + // 构建诊断信息字符串,避免序列化问题 + StringBuilder diagnosis = new StringBuilder(); + diagnosis.append("=== 支付配置诊断结果 ===\n"); + diagnosis.append("租户ID: ").append(tenantId).append("\n"); + diagnosis.append("商户号: ").append(payment.getMchId()).append("\n"); + diagnosis.append("应用ID: ").append(payment.getAppId()).append("\n"); + diagnosis.append("证书序列号: ").append(payment.getMerchantSerialNumber()).append("\n"); + diagnosis.append("API密钥: ").append(payment.getApiKey() != null ? "已配置(长度:" + payment.getApiKey().length() + ")" : "未配置").append("\n"); + diagnosis.append("状态: ").append(payment.getStatus()).append("\n"); + diagnosis.append("私钥文件: ").append(payment.getApiclientKey()).append("\n"); + diagnosis.append("证书文件: ").append(payment.getApiclientCert()).append("\n"); + diagnosis.append("公钥文件: ").append(payment.getPubKey()).append("\n"); + diagnosis.append("公钥ID: ").append(payment.getPubKeyId()).append("\n"); + + // 检查问题 + StringBuilder issues = new StringBuilder(); + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + issues.append("❌ 商户号为空\n"); + } + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + issues.append("❌ 应用ID为空\n"); + } + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + issues.append("❌ 证书序列号为空\n"); + } + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + issues.append("❌ API密钥为空\n"); + } else if (payment.getApiKey().length() != 32) { + issues.append("❌ API密钥长度错误(").append(payment.getApiKey().length()).append("位)\n"); + } + if (payment.getStatus() == null || !payment.getStatus()) { + issues.append("❌ 支付配置未启用\n"); + } + + if (issues.length() > 0) { + diagnosis.append("\n=== 发现的问题 ===\n"); + diagnosis.append(issues.toString()); + } else { + diagnosis.append("\n✅ 配置检查通过,无问题发现"); + } + + // 打印到控制台 + System.out.println(diagnosis.toString()); + + if (issues.length() > 0) { + return fail(diagnosis.toString(),null); + } else { + return success(diagnosis.toString(),null); + } + + } catch (Exception e) { + String errorMsg = "诊断失败: " + e.getMessage() + " (类型: " + e.getClass().getName() + ")"; + System.err.println(errorMsg); + e.printStackTrace(); + return fail(errorMsg,null); + } + } + + @Operation(summary = "直接数据库查询支付配置") + @GetMapping("/db-payment-check/{tenantId}") + public ApiResult checkPaymentFromDB(@PathVariable Integer tenantId) { + try { + System.out.println("=== 直接数据库查询支付配置 ==="); + System.out.println("租户ID: " + tenantId); + + if (tenantId == null) { + return fail("租户ID为null",null); + } + + if (paymentService == null) { + return fail("PaymentService未注入",null); + } + + // 直接查询数据库,不使用缓存 + PaymentParam param = new PaymentParam(); + param.setType(0); // 微信支付 + param.setTenantId(tenantId); + + System.out.println("准备查询数据库,参数: type=0, tenantId=" + tenantId); + + java.util.List payments = paymentService.listRel(param); + + System.out.println("查询结果数量: " + (payments != null ? payments.size() : "null")); + + if (payments == null || payments.isEmpty()) { + return fail("数据库中没有找到租户 " + tenantId + " 的微信支付配置",null); + } + + Payment payment = payments.get(0); + + StringBuilder result = new StringBuilder(); + result.append("=== 数据库查询结果 ===\n"); + result.append("租户ID: ").append(payment.getTenantId()).append("\n"); + result.append("支付方式: ").append(payment.getName()).append("\n"); + result.append("类型: ").append(payment.getType()).append("\n"); + result.append("商户号: ").append(payment.getMchId()).append("\n"); + result.append("应用ID: ").append(payment.getAppId()).append("\n"); + result.append("证书序列号: ").append(payment.getMerchantSerialNumber()).append("\n"); + result.append("API密钥状态: ").append(payment.getApiKey() != null ? "已配置(长度:" + payment.getApiKey().length() + ")" : "未配置").append("\n"); + result.append("状态: ").append(payment.getStatus()).append("\n"); + result.append("是否删除: ").append(payment.getDeleted()).append("\n"); + + System.out.println(result.toString()); + + return success(result.toString(),null); + + } catch (Exception e) { + String errorMsg = "数据库查询异常: " + e.getMessage() + " (类型: " + e.getClass().getName() + ")"; + System.err.println(errorMsg); + e.printStackTrace(); + return fail(errorMsg,null); + } + } + + @Operation(summary = "清理支付配置缓存") + @GetMapping("/clear-payment-cache/{tenantId}") + public String clearPaymentCache(@PathVariable Integer tenantId) { + try { + System.out.println("=== 清理支付配置缓存 ==="); + System.out.println("租户ID: " + tenantId); + + if (tenantId == null) { + return "错误: 租户ID为null"; + } + + // 清理可能的缓存键 + paymentCacheService.removePaymentConfig("0", tenantId); // 微信支付 + paymentCacheService.removePaymentConfig("wechat", tenantId); // 可能的其他格式 + + String result = "✅ 缓存已清理,租户ID: " + tenantId; + System.out.println(result); + return result; + + } catch (Exception e) { + String errorMsg = "❌ 清理缓存异常: " + e.getMessage(); + System.err.println(errorMsg); + e.printStackTrace(); + return errorMsg; + } + } + + @Operation(summary = "最简单的测试接口") + @GetMapping("/simple-test") + public String simpleTest() { + return "✅ 测试接口正常工作,时间: " + System.currentTimeMillis(); + } + + @Operation(summary = "测试支付配置是否存在") + @GetMapping("/check-payment-exists/{tenantId}") + public String checkPaymentExists(@PathVariable Integer tenantId) { + try { + System.out.println("=== 检查支付配置是否存在 ==="); + System.out.println("租户ID: " + tenantId); + + if (tenantId == null) { + return "❌ 租户ID为null"; + } + + if (paymentService == null) { + return "❌ PaymentService未注入"; + } + + // 使用最简单的查询 + PaymentParam param = new PaymentParam(); + param.setType(0); + param.setTenantId(tenantId); + + java.util.List payments = paymentService.listRel(param); + + if (payments == null) { + return "❌ 查询结果为null"; + } + + if (payments.isEmpty()) { + return "❌ 没有找到支付配置,租户ID: " + tenantId; + } + + Payment payment = payments.get(0); + StringBuilder result = new StringBuilder(); + result.append("✅ 找到支付配置\n"); + result.append("租户ID: ").append(payment.getTenantId()).append("\n"); + result.append("商户号: ").append(payment.getMchId() != null ? payment.getMchId() : "NULL").append("\n"); + result.append("应用ID: ").append(payment.getAppId() != null ? payment.getAppId() : "NULL").append("\n"); + result.append("状态: ").append(payment.getStatus()).append("\n"); + + return result.toString(); + + } catch (Exception e) { + String error = "❌ 检查异常: " + e.getMessage(); + System.err.println(error); + e.printStackTrace(); + return error; + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java b/src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java new file mode 100644 index 0000000..148ece5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java @@ -0,0 +1,211 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.utils.WechatCertAutoConfig; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.wechat.pay.java.core.Config; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 微信支付证书自动配置测试控制器 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Slf4j +@RestController +@RequestMapping("/api/wechat-cert-test") +@Tag(name = "微信支付证书自动配置测试") +public class WechatCertTestController extends BaseController { + + @Autowired + private WechatCertAutoConfig wechatCertAutoConfig; + + @Operation(summary = "测试默认开发环境证书配置") + @PostMapping("/test-default") + public ApiResult> testDefaultConfig() { + Map result = new HashMap<>(); + + try { + log.info("开始测试默认开发环境证书配置..."); + + // 创建自动证书配置 + Config config = wechatCertAutoConfig.createDefaultDevConfig(); + + // 测试配置 + boolean testResult = wechatCertAutoConfig.testConfig(config); + + result.put("success", true); + result.put("configCreated", config != null); + result.put("testPassed", testResult); + result.put("message", "默认证书配置测试完成"); + result.put("instructions", wechatCertAutoConfig.getUsageInstructions()); + + log.info("✅ 默认证书配置测试成功"); + return success("测试成功", result); + + } catch (Exception e) { + log.error("❌ 默认证书配置测试失败: {}", e.getMessage(), e); + + result.put("success", false); + result.put("error", e.getMessage()); + result.put("message", "证书配置测试失败"); + result.put("troubleshooting", getTroubleshootingInfo()); + + return fail("测试失败: " + e.getMessage(), result); + } + } + + @Operation(summary = "测试自定义证书配置") + @PostMapping("/test-custom") + public ApiResult> testCustomConfig( + @Parameter(description = "商户号") @RequestParam String merchantId, + @Parameter(description = "私钥文件路径") @RequestParam String privateKeyPath, + @Parameter(description = "证书序列号") @RequestParam String merchantSerialNumber, + @Parameter(description = "APIv3密钥") @RequestParam String apiV3Key) { + + Map result = new HashMap<>(); + + try { + log.info("开始测试自定义证书配置..."); + log.info("商户号: {}", merchantId); + log.info("私钥路径: {}", privateKeyPath); + + // 创建自动证书配置 + Config config = wechatCertAutoConfig.createAutoConfig( + merchantId, privateKeyPath, merchantSerialNumber, apiV3Key); + + // 测试配置 + boolean testResult = wechatCertAutoConfig.testConfig(config); + + result.put("success", true); + result.put("configCreated", config != null); + result.put("testPassed", testResult); + result.put("message", "自定义证书配置测试完成"); + result.put("merchantId", merchantId); + result.put("privateKeyPath", privateKeyPath); + + log.info("✅ 自定义证书配置测试成功"); + return success("测试成功", result); + + } catch (Exception e) { + log.error("❌ 自定义证书配置测试失败: {}", e.getMessage(), e); + + result.put("success", false); + result.put("error", e.getMessage()); + result.put("message", "证书配置测试失败"); + result.put("troubleshooting", getTroubleshootingInfo()); + + return fail("测试失败: " + e.getMessage(), result); + } + } + + @Operation(summary = "获取使用说明") + @GetMapping("/instructions") + public ApiResult getInstructions() { + String instructions = wechatCertAutoConfig.getUsageInstructions(); + return success("获取使用说明成功", instructions); + } + + @Operation(summary = "获取故障排除信息") + @GetMapping("/troubleshooting") + public ApiResult> getTroubleshooting() { + Map troubleshooting = getTroubleshootingInfo(); + return success("获取故障排除信息成功", troubleshooting); + } + + /** + * 获取故障排除信息 + */ + private Map getTroubleshootingInfo() { + Map info = new HashMap<>(); + + info.put("commonIssues", Map.of( + "404错误", "商户平台未开启API安全功能或未申请使用微信支付公钥", + "证书序列号错误", "请检查商户平台中的证书序列号是否正确", + "APIv3密钥错误", "请确认APIv3密钥是否正确设置", + "私钥文件不存在", "请检查私钥文件路径是否正确", + "网络连接问题", "请检查网络连接是否正常" + )); + + info.put("solutions", Map.of( + "开启API安全", "登录微信商户平台 -> 账户中心 -> API安全 -> 申请使用微信支付公钥", + "获取证书序列号", "在API安全页面查看或重新下载证书", + "设置APIv3密钥", "在API安全页面设置APIv3密钥", + "检查私钥文件", "确保apiclient_key.pem文件存在且路径正确" + )); + + info.put("advantages", Map.of( + "自动下载", "RSAAutoCertificateConfig会自动下载平台证书", + "自动更新", "证书过期时会自动更新", + "简化管理", "无需手动管理wechatpay_cert.pem文件", + "官方推荐", "微信支付官方推荐的证书管理方式" + )); + + info.put("documentation", "https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); + + return info; + } + + @Operation(summary = "检查商户平台配置状态") + @PostMapping("/check-merchant-config") + public ApiResult> checkMerchantConfig( + @RequestParam String merchantId, + @RequestParam String privateKeyPath, + @RequestParam String merchantSerialNumber, + @RequestParam String apiV3Key) { + + Map result = new HashMap<>(); + + try { + log.info("开始检查商户平台配置状态..."); + log.info("商户号: {}", merchantId); + + // 尝试创建自动证书配置 + Config config = wechatCertAutoConfig.createAutoConfig( + merchantId, privateKeyPath, merchantSerialNumber, apiV3Key); + + result.put("success", true); + result.put("configCreated", true); + result.put("message", "商户平台配置正常,自动证书配置创建成功"); + result.put("merchantId", merchantId); + result.put("recommendation", "配置正常,可以正常使用微信支付功能"); + + log.info("✅ 商户平台配置检查成功"); + return success("配置检查成功", result); + + } catch (Exception e) { + log.error("❌ 商户平台配置检查失败: {}", e.getMessage(), e); + + result.put("success", false); + result.put("configCreated", false); + result.put("error", e.getMessage()); + result.put("merchantId", merchantId); + + // 分析错误类型并提供解决方案 + if (e.getMessage().contains("404") || e.getMessage().contains("RESOURCE_NOT_EXISTS")) { + result.put("errorType", "商户平台配置问题"); + result.put("solution", "请在微信支付商户平台完成以下配置:\n" + + "1. 登录商户平台:https://pay.weixin.qq.com/\n" + + "2. 进入:产品中心 → 开发配置 → API安全\n" + + "3. 申请API证书\n" + + "4. 申请使用微信支付公钥\n" + + "5. 确保API证书和微信支付公钥状态为\"已生效\""); + result.put("documentUrl", "https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); + } else { + result.put("errorType", "其他配置问题"); + result.put("solution", "请检查商户号、证书序列号、API密钥等配置是否正确"); + } + + return success("配置检查完成(发现问题)", result); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java b/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java new file mode 100644 index 0000000..18c75a1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java @@ -0,0 +1,318 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.utils.WechatPayCertificateDiagnostic; +import com.gxwebsoft.common.core.utils.WechatPayConfigChecker; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.system.entity.Payment; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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 java.util.HashMap; +import java.util.Map; + +/** + * 微信支付诊断控制器 + * 用于诊断和解决微信支付证书相关问题 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@RestController +@RequestMapping("/system/wechat-pay-diagnostic") +@Tag(name = "微信支付诊断", description = "微信支付证书诊断和问题排查") +public class WechatPayDiagnosticController extends com.gxwebsoft.common.core.web.BaseController { + + @Autowired + private WechatPayCertificateDiagnostic certificateDiagnostic; + + @Autowired + private com.gxwebsoft.common.core.service.PaymentCacheService paymentCacheService; + + @Autowired + private WechatPayConfigChecker configChecker; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + @Operation(summary = "诊断租户微信支付证书配置") + @GetMapping("/diagnose/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> diagnoseTenantCertificate( + @Parameter(description = "租户ID", example = "10550") @PathVariable Integer tenantId) { + + try { + log.info("开始诊断租户 {} 的微信支付证书配置", tenantId); + + // 获取支付配置 (微信支付类型为0) + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("error", "支付配置不存在"); + return fail("租户 " + tenantId + " 的微信支付配置不存在", errorResponse); + } + + // 执行诊断 + WechatPayCertificateDiagnostic.DiagnosticResult result = + certificateDiagnostic.diagnoseCertificateConfig(payment, tenantId, activeProfile); + + Map response = new HashMap<>(); + response.put("tenantId", tenantId); + response.put("environment", activeProfile); + response.put("hasErrors", result.hasErrors()); + response.put("errors", result.getErrors()); + response.put("warnings", result.getWarnings()); + response.put("info", result.getInfo()); + response.put("recommendations", result.getRecommendations()); + response.put("fullReport", result.getFullReport()); + + if (result.hasErrors()) { + return fail("诊断发现问题", response); + } else { + return success("诊断完成", response); + } + + } catch (Exception e) { + log.error("诊断租户 {} 证书配置时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("诊断过程中发生异常: " + e.getMessage(), errorResponse); + } + } + + @Operation(summary = "获取证书问题解决方案") + @GetMapping("/solutions") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> getCertificateSolutions() { + Map solutions = new HashMap<>(); + + solutions.put("commonIssues", Map.of( + "X509Certificate.getSerialNumber() null", "证书对象为空,通常是自动证书配置失败导致", + "404错误", "商户平台未开启API安全功能或未申请使用微信支付公钥", + "证书序列号错误", "请检查商户平台中的证书序列号是否正确", + "APIv3密钥错误", "请确认APIv3密钥是否正确设置", + "私钥文件不存在", "请检查私钥文件路径是否正确", + "网络连接问题", "请检查网络连接是否正常" + )); + + solutions.put("stepByStepSolutions", Map.of( + "开启API安全", "登录微信商户平台 -> 账户中心 -> API安全 -> 申请使用微信支付公钥", + "获取证书序列号", "在API安全页面查看或重新下载证书", + "设置APIv3密钥", "在API安全页面设置APIv3密钥(32位字符串)", + "检查私钥文件", "确保apiclient_key.pem文件存在且路径正确", + "使用自动证书配置", "推荐使用RSAAutoCertificateConfig,可自动管理平台证书" + )); + + solutions.put("configurationAdvice", Map.of( + "开发环境", "证书文件放在 src/main/resources/dev/wechat/{tenantId}/ 目录下", + "生产环境", "证书文件放在 Docker 挂载卷或指定的文件系统路径", + "证书文件要求", "需要 apiclient_key.pem(私钥)和 apiclient_cert.pem(商户证书)", + "自动配置优势", "无需手动管理微信支付平台证书,自动下载和更新" + )); + + solutions.put("troubleshootingSteps", new String[]{ + "1. 检查商户平台是否已开启API安全功能", + "2. 确认已申请使用微信支付公钥", + "3. 验证商户证书序列号是否正确", + "4. 检查APIv3密钥是否为32位字符串", + "5. 确认私钥文件路径正确且文件存在", + "6. 测试网络连接是否正常", + "7. 查看详细错误日志进行进一步诊断" + }); + + return success("获取解决方案成功", solutions); + } + + @Operation(summary = "测试证书配置") + @PostMapping("/test/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:edit')") + public ApiResult> testCertificateConfig( + @Parameter(description = "租户ID", example = "10550") @PathVariable Integer tenantId) { + + try { + log.info("开始测试租户 {} 的证书配置", tenantId); + + // 获取支付配置 (微信支付类型为0) + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("error", "支付配置不存在"); + return fail("租户 " + tenantId + " 的微信支付配置不存在", errorResponse); + } + + // 执行诊断 + WechatPayCertificateDiagnostic.DiagnosticResult result = + certificateDiagnostic.diagnoseCertificateConfig(payment, tenantId, activeProfile); + + Map testResult = new HashMap<>(); + testResult.put("tenantId", tenantId); + testResult.put("configurationValid", !result.hasErrors()); + testResult.put("testTime", System.currentTimeMillis()); + testResult.put("environment", activeProfile); + + if (result.hasErrors()) { + testResult.put("status", "FAILED"); + testResult.put("errors", result.getErrors()); + testResult.put("recommendations", result.getRecommendations()); + return fail("证书配置测试失败", testResult); + } else { + testResult.put("status", "SUCCESS"); + testResult.put("message", "证书配置正常"); + return success("证书配置测试通过", testResult); + } + + } catch (Exception e) { + log.error("测试租户 {} 证书配置时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("测试过程中发生异常: " + e.getMessage(), errorResponse); + } + } + + @Operation(summary = "获取环境信息") + @GetMapping("/environment") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> getEnvironmentInfo() { + Map envInfo = new HashMap<>(); + envInfo.put("activeProfile", activeProfile); + envInfo.put("javaVersion", System.getProperty("java.version")); + envInfo.put("osName", System.getProperty("os.name")); + envInfo.put("userDir", System.getProperty("user.dir")); + envInfo.put("timestamp", System.currentTimeMillis()); + + return success("获取环境信息成功", envInfo); + } + + @Operation(summary = "获取证书配置指南") + @GetMapping("/guide") + public ApiResult> getCertificateGuide() { + Map guide = new HashMap<>(); + + guide.put("overview", "微信支付证书配置完整指南"); + + guide.put("prerequisites", new String[]{ + "1. 拥有微信支付商户账号", + "2. 已完成商户入驻和资质审核", + "3. 具备开发者权限" + }); + + guide.put("merchantPlatformSteps", new String[]{ + "1. 登录微信商户平台 (pay.weixin.qq.com)", + "2. 进入【账户中心】->【API安全】", + "3. 点击【申请使用微信支付公钥】", + "4. 下载商户证书(apiclient_cert.pem 和 apiclient_key.pem)", + "5. 设置APIv3密钥(32位字符串)", + "6. 记录商户证书序列号" + }); + + guide.put("developmentSetup", new String[]{ + "1. 在项目中创建证书目录:src/main/resources/dev/wechat/{tenantId}/", + "2. 将 apiclient_key.pem 放入该目录", + "3. 将 apiclient_cert.pem 放入该目录(可选,自动配置不需要)", + "4. 在数据库中配置支付信息:商户号、应用ID、证书序列号、APIv3密钥" + }); + + guide.put("productionSetup", new String[]{ + "1. 将证书文件上传到服务器指定目录", + "2. 确保应用有读取证书文件的权限", + "3. 在数据库中配置正确的证书文件路径", + "4. 测试证书配置是否正常" + }); + + guide.put("bestPractices", new String[]{ + "1. 使用RSAAutoCertificateConfig自动证书配置", + "2. 定期检查证书有效期", + "3. 妥善保管私钥文件", + "4. 使用HTTPS传输敏感信息", + "5. 定期更新微信支付SDK版本" + }); + + return success("获取配置指南成功", guide); + } + + @Operation(summary = "快速检查租户配置状态") + @GetMapping("/check/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> quickCheckConfig( + @Parameter(description = "租户ID", example = "10547") @PathVariable Integer tenantId) { + + try { + log.info("快速检查租户 {} 的配置状态", tenantId); + + WechatPayConfigChecker.ConfigStatus status = configChecker.checkTenantConfig(tenantId); + + Map response = new HashMap<>(); + response.put("tenantId", status.tenantId); + response.put("environment", status.environment); + response.put("configMode", status.configMode); + response.put("configComplete", status.configComplete); + response.put("hasError", status.hasError); + response.put("errorMessage", status.errorMessage); + response.put("recommendation", status.recommendation); + response.put("issues", status.issues); + + // 详细配置信息 + Map configDetails = new HashMap<>(); + configDetails.put("merchantId", status.merchantId); + configDetails.put("appId", status.appId); + configDetails.put("serialNumber", status.serialNumber); + configDetails.put("hasApiKey", status.hasApiKey); + configDetails.put("apiKeyLength", status.apiKeyLength); + configDetails.put("hasPublicKey", status.hasPublicKey); + configDetails.put("publicKeyFile", status.publicKeyFile); + configDetails.put("publicKeyId", status.publicKeyId); + configDetails.put("publicKeyExists", status.publicKeyExists); + configDetails.put("privateKeyExists", status.privateKeyExists); + configDetails.put("merchantCertExists", status.merchantCertExists); + response.put("configDetails", configDetails); + + if (status.hasError || !status.configComplete) { + return fail("配置检查发现问题", response); + } else { + return success("配置检查通过", response); + } + + } catch (Exception e) { + log.error("检查租户 {} 配置状态时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("检查过程中发生异常: " + e.getMessage(), errorResponse); + } + } + + @Operation(summary = "获取配置建议") + @GetMapping("/advice/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> getConfigAdvice( + @Parameter(description = "租户ID", example = "10547") @PathVariable Integer tenantId) { + + try { + String advice = configChecker.generateConfigAdvice(tenantId); + + Map response = new HashMap<>(); + response.put("tenantId", tenantId); + response.put("advice", advice); + response.put("timestamp", System.currentTimeMillis()); + + return success("获取配置建议成功", response); + + } catch (Exception e) { + log.error("获取租户 {} 配置建议时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("获取建议过程中发生异常: " + e.getMessage(), errorResponse); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/dto/qr/CreateBusinessEncryptedQrCodeRequest.java b/src/main/java/com/gxwebsoft/common/core/dto/qr/CreateBusinessEncryptedQrCodeRequest.java new file mode 100644 index 0000000..bae53d8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/dto/qr/CreateBusinessEncryptedQrCodeRequest.java @@ -0,0 +1,114 @@ +package com.gxwebsoft.common.core.dto.qr; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.*; + +/** + * 创建业务加密二维码请求DTO + * + * @author WebSoft + * @since 2025-08-18 + */ +@Schema(description = "创建业务加密二维码请求") +public class CreateBusinessEncryptedQrCodeRequest { + + @Schema(description = "要加密的数据", required = true, example = "订单ID:ORDER123") + @NotBlank(message = "数据不能为空") + private String data; + + @Schema(description = "业务密钥(如门店密钥)", required = true, example = "store_key_123") + @NotBlank(message = "业务密钥不能为空") + private String businessKey; + + @Schema(description = "二维码宽度", example = "200") + @Min(value = 50, message = "宽度不能小于50像素") + @Max(value = 1000, message = "宽度不能大于1000像素") + private Integer width = 200; + + @Schema(description = "二维码高度", example = "200") + @Min(value = 50, message = "高度不能小于50像素") + @Max(value = 1000, message = "高度不能大于1000像素") + private Integer height = 200; + + @Schema(description = "过期时间(分钟)", example = "30") + @Min(value = 1, message = "过期时间不能小于1分钟") + @Max(value = 1440, message = "过期时间不能大于1440分钟") + private Long expireMinutes = 30L; + + @Schema(description = "业务类型(可选)", example = "ORDER") + private String businessType; + + // 构造函数 + public CreateBusinessEncryptedQrCodeRequest() {} + + public CreateBusinessEncryptedQrCodeRequest(String data, String businessKey, Integer width, Integer height, Long expireMinutes, String businessType) { + this.data = data; + this.businessKey = businessKey; + this.width = width; + this.height = height; + this.expireMinutes = expireMinutes; + this.businessType = businessType; + } + + // Getter和Setter方法 + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getBusinessKey() { + return businessKey; + } + + public void setBusinessKey(String businessKey) { + this.businessKey = businessKey; + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Long getExpireMinutes() { + return expireMinutes; + } + + public void setExpireMinutes(Long expireMinutes) { + this.expireMinutes = expireMinutes; + } + + public String getBusinessType() { + return businessType; + } + + public void setBusinessType(String businessType) { + this.businessType = businessType; + } + + @Override + public String toString() { + return "CreateBusinessEncryptedQrCodeRequest{" + + "data='" + data + '\'' + + ", businessKey='" + businessKey + '\'' + + ", width=" + width + + ", height=" + height + + ", expireMinutes=" + expireMinutes + + ", businessType='" + businessType + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/dto/qr/CreateEncryptedQrCodeRequest.java b/src/main/java/com/gxwebsoft/common/core/dto/qr/CreateEncryptedQrCodeRequest.java new file mode 100644 index 0000000..f81c9d1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/dto/qr/CreateEncryptedQrCodeRequest.java @@ -0,0 +1,100 @@ +package com.gxwebsoft.common.core.dto.qr; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.*; + +/** + * 创建加密二维码请求DTO + * + * @author WebSoft + * @since 2025-08-18 + */ +@Schema(description = "创建加密二维码请求") +public class CreateEncryptedQrCodeRequest { + + @Schema(description = "要加密的数据", required = true, example = "用户ID:12345") + @NotBlank(message = "数据不能为空") + private String data; + + @Schema(description = "二维码宽度", example = "200") + @Min(value = 50, message = "宽度不能小于50像素") + @Max(value = 1000, message = "宽度不能大于1000像素") + private Integer width = 200; + + @Schema(description = "二维码高度", example = "200") + @Min(value = 50, message = "高度不能小于50像素") + @Max(value = 1000, message = "高度不能大于1000像素") + private Integer height = 200; + + @Schema(description = "过期时间(分钟)", example = "30") + @Min(value = 1, message = "过期时间不能小于1分钟") + @Max(value = 1440, message = "过期时间不能大于1440分钟") + private Long expireMinutes = 30L; + + @Schema(description = "业务类型(可选)", example = "LOGIN") + private String businessType; + + // 构造函数 + public CreateEncryptedQrCodeRequest() {} + + public CreateEncryptedQrCodeRequest(String data, Integer width, Integer height, Long expireMinutes, String businessType) { + this.data = data; + this.width = width; + this.height = height; + this.expireMinutes = expireMinutes; + this.businessType = businessType; + } + + // Getter和Setter方法 + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Long getExpireMinutes() { + return expireMinutes; + } + + public void setExpireMinutes(Long expireMinutes) { + this.expireMinutes = expireMinutes; + } + + public String getBusinessType() { + return businessType; + } + + public void setBusinessType(String businessType) { + this.businessType = businessType; + } + + @Override + public String toString() { + return "CreateEncryptedQrCodeRequest{" + + "data='" + data + '\'' + + ", width=" + width + + ", height=" + height + + ", expireMinutes=" + expireMinutes + + ", businessType='" + businessType + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/dto/qr/DecryptQrDataRequest.java b/src/main/java/com/gxwebsoft/common/core/dto/qr/DecryptQrDataRequest.java new file mode 100644 index 0000000..3d35550 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/dto/qr/DecryptQrDataRequest.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.common.core.dto.qr; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + * 解密二维码数据请求DTO + * + * @author WebSoft + * @since 2025-08-18 + */ +@Schema(description = "解密二维码数据请求") +public class DecryptQrDataRequest { + + @Schema(description = "token密钥", required = true, example = "abc123def456") + @NotBlank(message = "token不能为空") + private String token; + + @Schema(description = "加密的数据", required = true, example = "encrypted_data_string") + @NotBlank(message = "加密数据不能为空") + private String encryptedData; + + // 构造函数 + public DecryptQrDataRequest() {} + + public DecryptQrDataRequest(String token, String encryptedData) { + this.token = token; + this.encryptedData = encryptedData; + } + + // Getter和Setter方法 + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getEncryptedData() { + return encryptedData; + } + + public void setEncryptedData(String encryptedData) { + this.encryptedData = encryptedData; + } + + @Override + public String toString() { + return "DecryptQrDataRequest{" + + "token='" + token + '\'' + + ", encryptedData='" + encryptedData + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/dto/qr/InvalidateTokenRequest.java b/src/main/java/com/gxwebsoft/common/core/dto/qr/InvalidateTokenRequest.java new file mode 100644 index 0000000..1e8aa67 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/dto/qr/InvalidateTokenRequest.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.core.dto.qr; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + * 使token失效请求DTO + * + * @author WebSoft + * @since 2025-08-18 + */ +@Schema(description = "使token失效请求") +public class InvalidateTokenRequest { + + @Schema(description = "要使失效的token", required = true, example = "abc123def456") + @NotBlank(message = "token不能为空") + private String token; + + // 构造函数 + public InvalidateTokenRequest() {} + + public InvalidateTokenRequest(String token) { + this.token = token; + } + + // Getter和Setter方法 + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + @Override + public String toString() { + return "InvalidateTokenRequest{" + + "token='" + token + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyBusinessQrRequest.java b/src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyBusinessQrRequest.java new file mode 100644 index 0000000..fbdfe3a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyBusinessQrRequest.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.common.core.dto.qr; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + * 门店核销二维码请求DTO + * + * @author WebSoft + * @since 2025-08-18 + */ +@Schema(description = "门店核销二维码请求") +public class VerifyBusinessQrRequest { + + @Schema(description = "二维码扫描得到的完整内容", required = true, example = "qr_content_string") + @NotBlank(message = "二维码内容不能为空") + private String qrContent; + + @Schema(description = "门店业务密钥", required = true, example = "store_key_123") + @NotBlank(message = "业务密钥不能为空") + private String businessKey; + + // 构造函数 + public VerifyBusinessQrRequest() {} + + public VerifyBusinessQrRequest(String qrContent, String businessKey) { + this.qrContent = qrContent; + this.businessKey = businessKey; + } + + // Getter和Setter方法 + public String getQrContent() { + return qrContent; + } + + public void setQrContent(String qrContent) { + this.qrContent = qrContent; + } + + public String getBusinessKey() { + return businessKey; + } + + public void setBusinessKey(String businessKey) { + this.businessKey = businessKey; + } + + @Override + public String toString() { + return "VerifyBusinessQrRequest{" + + "qrContent='" + qrContent + '\'' + + ", businessKey='" + businessKey + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyQrContentRequest.java b/src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyQrContentRequest.java new file mode 100644 index 0000000..eb60b09 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/dto/qr/VerifyQrContentRequest.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.core.dto.qr; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotBlank; + +/** + * 验证二维码内容请求DTO + * + * @author WebSoft + * @since 2025-08-18 + */ +@Schema(description = "验证二维码内容请求") +public class VerifyQrContentRequest { + + @Schema(description = "二维码扫描得到的完整内容", required = true, example = "qr_content_string") + @NotBlank(message = "二维码内容不能为空") + private String qrContent; + + // 构造函数 + public VerifyQrContentRequest() {} + + public VerifyQrContentRequest(String qrContent) { + this.qrContent = qrContent; + } + + // Getter和Setter方法 + public String getQrContent() { + return qrContent; + } + + public void setQrContent(String qrContent) { + this.qrContent = qrContent; + } + + @Override + public String toString() { + return "VerifyQrContentRequest{" + + "qrContent='" + qrContent + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/exception/BusinessException.java b/src/main/java/com/gxwebsoft/common/core/exception/BusinessException.java new file mode 100644 index 0000000..8e10e82 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/exception/BusinessException.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.common.core.exception; + +import com.gxwebsoft.common.core.Constants; + +/** + * 自定义业务异常 + * + * @author WebSoft + * @since 2018-02-22 11:29:28 + */ +public class BusinessException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private Integer code; + + public BusinessException() { + this(Constants.RESULT_ERROR_MSG); + } + + public BusinessException(String message) { + this(Constants.RESULT_ERROR_CODE, message); + } + + public BusinessException(Integer code, String message) { + super(message); + this.code = code; + } + + public BusinessException(Integer code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + public BusinessException(Integer code, String message, Throwable cause, + boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + this.code = code; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/exception/GlobalExceptionHandler.java b/src/main/java/com/gxwebsoft/common/core/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..7fb4c8c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/exception/GlobalExceptionHandler.java @@ -0,0 +1,89 @@ +package com.gxwebsoft.common.core.exception; + +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.Set; + +/** + * 全局异常处理器 + * + * @author WebSoft + * @since 2018-02-22 11:29:30 + */ +@ControllerAdvice +public class GlobalExceptionHandler { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @ResponseBody + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ApiResult methodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e, + HttpServletResponse response) { + CommonUtil.addCrossHeaders(response); + return new ApiResult<>(Constants.RESULT_ERROR_CODE, "请求方式不正确").setError(e.toString()); + } + + @ResponseBody + @ExceptionHandler(AccessDeniedException.class) + public ApiResult accessDeniedExceptionHandler(AccessDeniedException e, HttpServletResponse response) { + CommonUtil.addCrossHeaders(response); + return new ApiResult<>(Constants.UNAUTHORIZED_CODE, Constants.UNAUTHORIZED_MSG).setError(e.toString()); + } + + @ResponseBody + @ExceptionHandler(BusinessException.class) + public ApiResult businessExceptionHandler(BusinessException e, HttpServletResponse response) { + CommonUtil.addCrossHeaders(response); + return new ApiResult<>(e.getCode(), e.getMessage()); + } + + @ResponseBody + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e, HttpServletResponse response) { + CommonUtil.addCrossHeaders(response); + FieldError fieldError = e.getBindingResult().getFieldError(); + String message = fieldError != null ? fieldError.getDefaultMessage() : "参数验证失败"; + return new ApiResult<>(Constants.RESULT_ERROR_CODE, message); + } + + @ResponseBody + @ExceptionHandler(BindException.class) + public ApiResult bindExceptionHandler(BindException e, HttpServletResponse response) { + CommonUtil.addCrossHeaders(response); + FieldError fieldError = e.getBindingResult().getFieldError(); + String message = fieldError != null ? fieldError.getDefaultMessage() : "参数绑定失败"; + return new ApiResult<>(Constants.RESULT_ERROR_CODE, message); + } + + @ResponseBody + @ExceptionHandler(ConstraintViolationException.class) + public ApiResult constraintViolationExceptionHandler(ConstraintViolationException e, HttpServletResponse response) { + CommonUtil.addCrossHeaders(response); + Set> violations = e.getConstraintViolations(); + String message = violations.isEmpty() ? "参数验证失败" : violations.iterator().next().getMessage(); + return new ApiResult<>(Constants.RESULT_ERROR_CODE, message); + } + + @ResponseBody + @ExceptionHandler(Throwable.class) + public ApiResult exceptionHandler(Throwable e, HttpServletResponse response) { + logger.error(e.getMessage(), e); + CommonUtil.addCrossHeaders(response); + return new ApiResult<>(Constants.RESULT_ERROR_CODE, Constants.RESULT_ERROR_MSG).setError(e.toString()); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/security/JwtAccessDeniedHandler.java b/src/main/java/com/gxwebsoft/common/core/security/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..66acb5c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/security/JwtAccessDeniedHandler.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.common.core.security; + +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.utils.CommonUtil; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 没有访问权限异常处理 + * + * @author WebSoft + * @since 2020-03-25 00:35:03 + */ +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) + throws IOException, ServletException { + CommonUtil.responseError(response, Constants.UNAUTHORIZED_CODE, Constants.UNAUTHORIZED_MSG, e.getMessage()); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..3be2908 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationEntryPoint.java @@ -0,0 +1,30 @@ +package com.gxwebsoft.common.core.security; + +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.utils.CommonUtil; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 没有登录异常处理 + * + * @author WebSoft + * @since 2020-03-25 00:35:03 + */ +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException, ServletException { +// CommonUtil.responseError(response, Constants.UNAUTHENTICATED_CODE, Constants.UNAUTHENTICATED_MSG, +// e.getMessage()); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationFilter.java b/src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..e910c6d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/security/JwtAuthenticationFilter.java @@ -0,0 +1,118 @@ +package com.gxwebsoft.common.core.security; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.utils.SignCheckUtil; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.User; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.Resource; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 处理携带token的请求过滤器 + * + * @author WebSoft + * @since 2020-03-30 20:48:05 + */ +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + @Resource + private ConfigProperties configProperties; + @Value("${spring.profiles.active}") + String active; + @Resource + private RedisUtil redisUtil; + // 是否读取用户信息 + public static Boolean isReadUserInfo = true; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + String access_token = JwtUtil.getAccessToken(request); + if (StrUtil.isNotBlank(access_token)) { + try { + // 解析token + Claims claims = JwtUtil.parseToken(access_token, configProperties.getTokenKey()); + JwtSubject jwtSubject = JwtUtil.getJwtSubject(claims); + + // 请求主服务器获取用户信息 + if (isReadUserInfo) { + HashMap map = new HashMap<>(); + map.put("username", jwtSubject.getUsername()); + map.put("tenantId", jwtSubject.getTenantId()); + // 链式构建请求 + String result = HttpRequest.post(configProperties.getServerUrl() + "/auth/user") + .header("Authorization", access_token) + .header("Tenantid", jwtSubject.getTenantId().toString()) + .body(JSONUtil.toJSONString(map))//表单内容 + .timeout(20000)//超时,毫秒 + .execute().body(); + + // 校验服务器域名白名单 + final SignCheckUtil checkUtil = new SignCheckUtil(); + String key = "WhiteDomain:" + jwtSubject.getTenantId(); + List whiteDomains = redisUtil.get(key, List.class); + // 生产环境 + if (active.equals("prod") && !checkUtil.checkWhiteDomains(whiteDomains, request.getServerName())) { + throw new UsernameNotFoundException("The requested domain name is not on the whitelist"); + } + + JSONObject jsonObject = JSONObject.parseObject(result); + if(jsonObject.getString("code").equals("401")){ + throw new UsernameNotFoundException("Username not found"); + } + final String data = jsonObject.getString("data"); + final User user = JSONObject.parseObject(data, User.class); + List authorities = user.getAuthorities().stream() + .filter(m -> StrUtil.isNotBlank(m.getAuthority())).collect(Collectors.toList()); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + user, null, authorities); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // token将要过期签发新token, 防止突然退出登录 +// long expiration = (claims.getExpiration().getTime() - new Date().getTime()) / 1000 / 60; +// if (expiration < configProperties.getTokenRefreshTime()) { +// String token = JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), +// configProperties.getTokenKey()); +// response.addHeader(Constants.TOKEN_HEADER_NAME, token); +// loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REFRESH, null, +// user.getTenantId(), request); +// } + + } + } catch (ExpiredJwtException e) { + CommonUtil.responseError(response, Constants.TOKEN_EXPIRED_CODE, Constants.TOKEN_EXPIRED_MSG, + e.getMessage()); + return; + } catch (Exception e) { + CommonUtil.responseError(response, Constants.BAD_CREDENTIALS_CODE, Constants.BAD_CREDENTIALS_MSG, + e.toString()); + return; + } + } + chain.doFilter(request, response); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/security/JwtSubject.java b/src/main/java/com/gxwebsoft/common/core/security/JwtSubject.java new file mode 100644 index 0000000..1a0ff7d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/security/JwtSubject.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.core.security; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Jwt载体 + * + * @author WebSoft + * @since 2021-09-03 00:11:12 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JwtSubject implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 账号 + */ + private String username; + + /** + * 租户id + */ + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/core/security/JwtUtil.java b/src/main/java/com/gxwebsoft/common/core/security/JwtUtil.java new file mode 100644 index 0000000..2f05d23 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/security/JwtUtil.java @@ -0,0 +1,141 @@ +package com.gxwebsoft.common.core.security; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.utils.JSONUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.security.Keys; + +import javax.servlet.http.HttpServletRequest; +import java.security.Key; +import java.util.Date; + +/** + * JWT工具类 + * + * @author WebSoft + * @since 2018-01-21 16:30:59 + */ +public class JwtUtil { + + /** + * 获取请求中的access_token + * + * @param request HttpServletRequest + * @return String + */ + public static String getAccessToken(HttpServletRequest request) { + String access_token = ServletUtil.getHeaderIgnoreCase(request, Constants.TOKEN_HEADER_NAME); + if (StrUtil.isNotBlank(access_token)) { + if (access_token.startsWith(Constants.TOKEN_TYPE)) { + access_token = StrUtil.removePrefix(access_token, Constants.TOKEN_TYPE).trim(); + } + } else { + access_token = request.getParameter(Constants.TOKEN_PARAM_NAME); + } + return access_token; + } + + /** + * 生成token + * + * @param subject 载体 + * @param expire 过期时间 + * @param base64EncodedKey base64编码的Key + * @return token + */ + public static String buildToken(JwtSubject subject, Long expire, String base64EncodedKey) { + return buildToken(JSONUtil.toJSONString(subject), expire, decodeKey(base64EncodedKey)); + } + + /** + * 生成token + * + * @param subject 载体 + * @param expire 过期时间 + * @param key 密钥 + * @return token + */ + public static String buildToken(String subject, Long expire, Key key) { + Date expireDate = new Date(new Date().getTime() + 1000 * expire); + return Jwts.builder() + .setSubject(subject) + .setExpiration(expireDate) + .setIssuedAt(new Date()) + .signWith(key) + .compact(); + } + + /** + * 解析token + * + * @param token token + * @param base64EncodedKey base64编码的Key + * @return Claims + */ + public static Claims parseToken(String token, String base64EncodedKey) { + return parseToken(token, decodeKey(base64EncodedKey)); + } + + /** + * 解析token + * + * @param token token + * @param key 密钥 + * @return Claims + */ + public static Claims parseToken(String token, Key key) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + + /** + * 获取JwtSubject + * + * @param claims Claims + * @return JwtSubject + */ + public static JwtSubject getJwtSubject(Claims claims) { + return JSONUtil.parseObject(claims.getSubject(), JwtSubject.class); + } + + /** + * 生成Key + * + * @return Key + */ + public static Key randomKey() { + return Keys.secretKeyFor(SignatureAlgorithm.HS256); + } + + /** + * base64编码key + * + * @return String + */ + public static String encodeKey(Key key) { + return Encoders.BASE64.encode(key.getEncoded()); + } + + /** + * base64编码Key + * + * @param base64EncodedKey base64编码的key + * @return Key + */ + public static Key decodeKey(String base64EncodedKey) { + if (StrUtil.isBlank(base64EncodedKey)) { + return null; + } + return Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64EncodedKey)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java new file mode 100644 index 0000000..7972875 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java @@ -0,0 +1,113 @@ +package com.gxwebsoft.common.core.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import javax.annotation.Resource; + +/** + * Spring Security配置 + * + * @author WebSoft + * @since 2020-03-23 18:04:52 + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig { + @Resource + private JwtAccessDeniedHandler jwtAccessDeniedHandler; + @Resource + private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + @Resource + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeRequests() + .antMatchers(HttpMethod.OPTIONS, "/**") + .permitAll() + .antMatchers(HttpMethod.GET, "/api/file/**","/**", "/api/captcha", "/") + .permitAll() + .antMatchers( + "/api/login", + "/api/qr-login/**", + "/api/register", + "/api/cms/website/createWebsite", + "/druid/**", + "/swagger-ui.html", + "/swagger-resources/**", + "/webjars/**", + "/v2/api-docs", + "/v3/api-docs", + "/swagger-ui/**", + "/doc.html", + "/api/open/**", + "/hxz/v1/**", + "/api/sendSmsCaptcha", + "/api/login-alipay/*", + "/api/wx-login/loginByMpWxPhone", + "/api/shop/payment/mp-alipay/notify", + "/api/shop/payment/mp-alipay/test/**", + "/api/shop/payment/mp-alipay/getPhoneNumber", + "/api/cms/cms-order/**", + "/api/shop/shop-order/notify/**", + "/api/mp/mp/component_verify_ticket", + "/api/mp/mp/callback", + "/api/shop/test/**", + "/api/test/payment-debug/**", + "/api/shop/wx-login/**", + "/api/shop/wx-native-pay/**", + "/api/shop/wx-pay/**", + "/api/bszx/bszx-pay/notify/**", + "/api/wxWorkQrConnect", + "/WW_verify_QMv7HoblYU6z63bb.txt", + "/5zbYEPkyV4.txt", + "/api/love/user-plan-log/wx-pay/**", + "/api/cms/form-record", + "/api/shop/merchant-account/getMerchantAccountByPhone", + "/api/hjm/hjm-car/**", + "/api/chat/**", + "/api/shop/getShopInfo", + "/api/shop/shop-order/test", + "/api/qr-code/**" + ) + .permitAll() + .anyRequest() + .authenticated() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .csrf() + .disable() + .cors() + .and() + .logout() + .disable() + .headers() + .frameOptions() + .disable() + .and() + .exceptionHandling() + .accessDeniedHandler(jwtAccessDeniedHandler) + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .and() + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/service/CertificateHealthService.java b/src/main/java/com/gxwebsoft/common/core/service/CertificateHealthService.java new file mode 100644 index 0000000..e37bb05 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/service/CertificateHealthService.java @@ -0,0 +1,253 @@ +package com.gxwebsoft.common.core.service; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * 证书健康检查服务 + * 提供证书状态检查和健康监控功能 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Slf4j +@Service +public class CertificateHealthService { + + private final CertificateService certificateService; + private final CertificateProperties certificateProperties; + + public CertificateHealthService(CertificateService certificateService, + CertificateProperties certificateProperties) { + this.certificateService = certificateService; + this.certificateProperties = certificateProperties; + } + + /** + * 自定义健康检查结果类 + */ + public static class HealthResult { + private final String status; + private final Map details; + + public HealthResult(String status, Map details) { + this.status = status; + this.details = details; + } + + public String getStatus() { + return status; + } + + public Map getDetails() { + return details; + } + + public static HealthResult up(Map details) { + return new HealthResult("UP", details); + } + + public static HealthResult down(Map details) { + return new HealthResult("DOWN", details); + } + } + + public HealthResult health() { + try { + Map details = new HashMap<>(); + boolean allHealthy = true; + + // 检查微信支付证书 + Map wechatHealth = checkWechatPayCertificates(); + details.put("wechatPay", wechatHealth); + if (!(Boolean) wechatHealth.get("healthy")) { + allHealthy = false; + } + + // 检查支付宝证书 + Map alipayHealth = checkAlipayCertificates(); + details.put("alipay", alipayHealth); + if (!(Boolean) alipayHealth.get("healthy")) { + allHealthy = false; + } + + // 添加系统信息 + details.put("loadMode", certificateProperties.getLoadMode()); + details.put("certRootPath", certificateProperties.getCertRootPath()); + + if (allHealthy) { + return HealthResult.up(details); + } else { + return HealthResult.down(details); + } + + } catch (Exception e) { + log.error("证书健康检查失败", e); + Map errorDetails = new HashMap<>(); + errorDetails.put("error", e.getMessage()); + return HealthResult.down(errorDetails); + } + } + + /** + * 检查微信支付证书健康状态 + */ + private Map checkWechatPayCertificates() { + Map health = new HashMap<>(); + boolean healthy = true; + Map certificates = new HashMap<>(); + + CertificateProperties.WechatPayConfig wechatConfig = certificateProperties.getWechatPay(); + + // 检查私钥证书 + String privateKeyFile = wechatConfig.getDev().getPrivateKeyFile(); + boolean privateKeyExists = certificateService.certificateExists("wechat", privateKeyFile); + certificates.put("privateKey", Map.of( + "file", privateKeyFile, + "exists", privateKeyExists, + "path", certificateService.getWechatPayCertPath(privateKeyFile) + )); + if (!privateKeyExists) healthy = false; + + // 检查商户证书 + String apiclientCertFile = wechatConfig.getDev().getApiclientCertFile(); + boolean apiclientCertExists = certificateService.certificateExists("wechat", apiclientCertFile); + certificates.put("apiclientCert", Map.of( + "file", apiclientCertFile, + "exists", apiclientCertExists, + "path", certificateService.getWechatPayCertPath(apiclientCertFile) + )); + if (!apiclientCertExists) healthy = false; + + // 检查微信支付平台证书 + String wechatpayCertFile = wechatConfig.getDev().getWechatpayCertFile(); + boolean wechatpayCertExists = certificateService.certificateExists("wechat", wechatpayCertFile); + certificates.put("wechatpayCert", Map.of( + "file", wechatpayCertFile, + "exists", wechatpayCertExists, + "path", certificateService.getWechatPayCertPath(wechatpayCertFile) + )); + if (!wechatpayCertExists) healthy = false; + + health.put("healthy", healthy); + health.put("certificates", certificates); + return health; + } + + /** + * 检查支付宝证书健康状态 + */ + private Map checkAlipayCertificates() { + Map health = new HashMap<>(); + boolean healthy = true; + Map certificates = new HashMap<>(); + + CertificateProperties.AlipayConfig alipayConfig = certificateProperties.getAlipay(); + + // 检查应用私钥 + String appPrivateKeyFile = alipayConfig.getAppPrivateKeyFile(); + boolean appPrivateKeyExists = certificateService.certificateExists("alipay", appPrivateKeyFile); + certificates.put("appPrivateKey", Map.of( + "file", appPrivateKeyFile, + "exists", appPrivateKeyExists, + "path", certificateService.getAlipayCertPath(appPrivateKeyFile) + )); + if (!appPrivateKeyExists) healthy = false; + + // 检查应用公钥证书 + String appCertPublicKeyFile = alipayConfig.getAppCertPublicKeyFile(); + boolean appCertExists = certificateService.certificateExists("alipay", appCertPublicKeyFile); + certificates.put("appCertPublicKey", Map.of( + "file", appCertPublicKeyFile, + "exists", appCertExists, + "path", certificateService.getAlipayCertPath(appCertPublicKeyFile) + )); + if (!appCertExists) healthy = false; + + // 检查支付宝公钥证书 + String alipayCertPublicKeyFile = alipayConfig.getAlipayCertPublicKeyFile(); + boolean alipayCertExists = certificateService.certificateExists("alipay", alipayCertPublicKeyFile); + certificates.put("alipayCertPublicKey", Map.of( + "file", alipayCertPublicKeyFile, + "exists", alipayCertExists, + "path", certificateService.getAlipayCertPath(alipayCertPublicKeyFile) + )); + if (!alipayCertExists) healthy = false; + + // 检查支付宝根证书 + String alipayRootCertFile = alipayConfig.getAlipayRootCertFile(); + boolean rootCertExists = certificateService.certificateExists("alipay", alipayRootCertFile); + certificates.put("alipayRootCert", Map.of( + "file", alipayRootCertFile, + "exists", rootCertExists, + "path", certificateService.getAlipayCertPath(alipayRootCertFile) + )); + if (!rootCertExists) healthy = false; + + health.put("healthy", healthy); + health.put("certificates", certificates); + return health; + } + + /** + * 获取详细的证书诊断信息 + */ + public Map getDiagnosticInfo() { + Map diagnostic = new HashMap<>(); + + try { + // 基本系统信息 + diagnostic.put("loadMode", certificateProperties.getLoadMode()); + diagnostic.put("certRootPath", certificateProperties.getCertRootPath()); + diagnostic.put("devCertPath", certificateProperties.getDevCertPath()); + + // 获取所有证书状态 + diagnostic.put("certificateStatus", certificateService.getAllCertificateStatus()); + + // 健康检查结果 + HealthResult health = health(); + diagnostic.put("healthStatus", health.getStatus()); + diagnostic.put("healthDetails", health.getDetails()); + + } catch (Exception e) { + log.error("获取证书诊断信息失败", e); + diagnostic.put("error", e.getMessage()); + } + + return diagnostic; + } + + /** + * 检查特定证书的详细信息 + */ + public Map checkSpecificCertificate(String certType, String fileName) { + Map result = new HashMap<>(); + + try { + boolean exists = certificateService.certificateExists(certType, fileName); + String path = certificateService.getCertificateFilePath(certType, fileName); + + result.put("certType", certType); + result.put("fileName", fileName); + result.put("exists", exists); + result.put("path", path); + + if (exists && (fileName.endsWith(".crt") || fileName.endsWith(".pem"))) { + // 尝试验证证书 + CertificateService.CertificateInfo certInfo = + certificateService.validateX509Certificate(certType, fileName); + result.put("certificateInfo", certInfo); + } + + } catch (Exception e) { + log.error("检查证书失败: {}/{}", certType, fileName, e); + result.put("error", e.getMessage()); + } + + return result; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/service/CertificateService.java b/src/main/java/com/gxwebsoft/common/core/service/CertificateService.java new file mode 100644 index 0000000..e12854a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/service/CertificateService.java @@ -0,0 +1,281 @@ +package com.gxwebsoft.common.core.service; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 证书管理服务 + * 负责处理不同环境下的证书加载、验证和管理 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Slf4j +@Service +public class CertificateService { + + private final CertificateProperties certificateProperties; + + public CertificateService(CertificateProperties certificateProperties) { + this.certificateProperties = certificateProperties; + } + + @PostConstruct + public void init() { + log.info("证书服务初始化,当前加载模式: {}", certificateProperties.getLoadMode()); + log.info("证书根路径: {}", certificateProperties.getCertRootPath()); + + // 检查证书目录和文件 + checkCertificateDirectories(); + } + + /** + * 获取证书文件的输入流 + * + * @param certType 证书类型(wechat/alipay) + * @param fileName 文件名 + * @return 输入流 + * @throws IOException 文件读取异常 + */ + public InputStream getCertificateInputStream(String certType, String fileName) throws IOException { + String certPath = certificateProperties.getCertificatePath(certType, fileName); + + if (certificateProperties.isClasspathMode()) { + // 从classpath加载 + Resource resource = new ClassPathResource(certPath); + if (!resource.exists()) { + throw new IOException("证书文件不存在: " + certPath); + } + log.debug("从classpath加载证书: {}", certPath); + return resource.getInputStream(); + } else { + // 从文件系统加载 + File file = new File(certPath); + if (!file.exists()) { + throw new IOException("证书文件不存在: " + certPath); + } + log.debug("从文件系统加载证书: {}", certPath); + return Files.newInputStream(file.toPath()); + } + } + + /** + * 获取证书文件路径 + * + * @param certType 证书类型 + * @param fileName 文件名 + * @return 文件路径 + */ + public String getCertificateFilePath(String certType, String fileName) { + return certificateProperties.getCertificatePath(certType, fileName); + } + + /** + * 检查证书文件是否存在 + * + * @param certType 证书类型 + * @param fileName 文件名 + * @return 是否存在 + */ + public boolean certificateExists(String certType, String fileName) { + try { + String certPath = certificateProperties.getCertificatePath(certType, fileName); + + if (certificateProperties.isClasspathMode()) { + Resource resource = new ClassPathResource(certPath); + return resource.exists(); + } else { + File file = new File(certPath); + return file.exists() && file.isFile(); + } + } catch (Exception e) { + log.error("检查证书文件存在性时出错: {}", e.getMessage()); + return false; + } + } + + /** + * 获取微信支付证书路径 + * + * @param fileName 文件名 + * @return 证书路径 + */ + public String getWechatPayCertPath(String fileName) { + return certificateProperties.getWechatPayCertPath(fileName); + } + + /** + * 获取支付宝证书路径 + * + * @param fileName 文件名 + * @return 证书路径 + */ + public String getAlipayCertPath(String fileName) { + return certificateProperties.getAlipayCertPath(fileName); + } + + /** + * 验证X509证书 + * + * @param certType 证书类型 + * @param fileName 文件名 + * @return 证书信息 + */ + public CertificateInfo validateX509Certificate(String certType, String fileName) { + try (InputStream inputStream = getCertificateInputStream(certType, fileName)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + + CertificateInfo info = new CertificateInfo(); + info.setSubject(cert.getSubjectX500Principal().toString()); + info.setIssuer(cert.getIssuerX500Principal().toString()); + info.setNotBefore(convertToLocalDateTime(cert.getNotBefore())); + info.setNotAfter(convertToLocalDateTime(cert.getNotAfter())); + info.setSerialNumber(cert.getSerialNumber().toString()); + info.setValid(isValidDate(cert.getNotBefore(), cert.getNotAfter())); + + return info; + } catch (Exception e) { + log.error("验证证书失败: {}/{}, 错误: {}", certType, fileName, e.getMessage()); + return null; + } + } + + /** + * 检查证书目录结构 + */ + private void checkCertificateDirectories() { + String[] certTypes = {"wechat", "alipay"}; + + for (String certType : certTypes) { + if (!certificateProperties.isClasspathMode()) { + // 检查文件系统目录 + String dirPath = certificateProperties.getCertificatePath(certType, ""); + File dir = new File(dirPath); + if (!dir.exists()) { + log.warn("证书目录不存在: {}", dirPath); + } else { + log.info("证书目录存在: {}", dirPath); + } + } + } + } + + /** + * 获取所有证书状态 + * + * @return 证书状态映射 + */ + public Map getAllCertificateStatus() { + Map status = new HashMap<>(); + + // 微信支付证书状态 + Map wechatStatus = new HashMap<>(); + CertificateProperties.WechatPayConfig wechatConfig = certificateProperties.getWechatPay(); + wechatStatus.put("privateKey", getCertStatus("wechat", wechatConfig.getDev().getPrivateKeyFile())); + wechatStatus.put("apiclientCert", getCertStatus("wechat", wechatConfig.getDev().getApiclientCertFile())); + wechatStatus.put("wechatpayCert", getCertStatus("wechat", wechatConfig.getDev().getWechatpayCertFile())); + status.put("wechat", wechatStatus); + + // 支付宝证书状态 + Map alipayStatus = new HashMap<>(); + CertificateProperties.AlipayConfig alipayConfig = certificateProperties.getAlipay(); + alipayStatus.put("appPrivateKey", getCertStatus("alipay", alipayConfig.getAppPrivateKeyFile())); + alipayStatus.put("appCertPublicKey", getCertStatus("alipay", alipayConfig.getAppCertPublicKeyFile())); + alipayStatus.put("alipayCertPublicKey", getCertStatus("alipay", alipayConfig.getAlipayCertPublicKeyFile())); + alipayStatus.put("alipayRootCert", getCertStatus("alipay", alipayConfig.getAlipayRootCertFile())); + status.put("alipay", alipayStatus); + + // 系统信息 + Map systemInfo = new HashMap<>(); + systemInfo.put("loadMode", certificateProperties.getLoadMode()); + systemInfo.put("certRootPath", certificateProperties.getCertRootPath()); + systemInfo.put("devCertPath", certificateProperties.getDevCertPath()); + status.put("system", systemInfo); + + return status; + } + + /** + * 获取单个证书状态 + */ + private Map getCertStatus(String certType, String fileName) { + Map status = new HashMap<>(); + status.put("fileName", fileName); + status.put("exists", certificateExists(certType, fileName)); + status.put("path", getCertificateFilePath(certType, fileName)); + + // 如果是.crt或.pem文件,尝试验证证书 + if (fileName.endsWith(".crt") || fileName.endsWith(".pem")) { + CertificateInfo certInfo = validateX509Certificate(certType, fileName); + status.put("certificateInfo", certInfo); + } + + return status; + } + + /** + * 检查日期是否有效 + */ + private boolean isValidDate(Date notBefore, Date notAfter) { + Date now = new Date(); + return now.after(notBefore) && now.before(notAfter); + } + + /** + * 将Date转换为LocalDateTime + */ + private LocalDateTime convertToLocalDateTime(Date date) { + if (date == null) { + return null; + } + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + /** + * 证书信息类 + */ + public static class CertificateInfo { + private String subject; + private String issuer; + private LocalDateTime notBefore; + private LocalDateTime notAfter; + private String serialNumber; + private boolean valid; + + // Getters and Setters + public String getSubject() { return subject; } + public void setSubject(String subject) { this.subject = subject; } + + public String getIssuer() { return issuer; } + public void setIssuer(String issuer) { this.issuer = issuer; } + + public LocalDateTime getNotBefore() { return notBefore; } + public void setNotBefore(LocalDateTime notBefore) { this.notBefore = notBefore; } + + public LocalDateTime getNotAfter() { return notAfter; } + public void setNotAfter(LocalDateTime notAfter) { this.notAfter = notAfter; } + + public String getSerialNumber() { return serialNumber; } + public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; } + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/service/EnvironmentAwarePaymentService.java b/src/main/java/com/gxwebsoft/common/core/service/EnvironmentAwarePaymentService.java new file mode 100644 index 0000000..2155cc9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/service/EnvironmentAwarePaymentService.java @@ -0,0 +1,143 @@ +package com.gxwebsoft.common.core.service; + +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * 环境感知的支付配置服务 + * 根据不同环境自动切换支付回调地址 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Service +public class EnvironmentAwarePaymentService { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + @Value("${config.server-url:}") + private String serverUrl; + + // 开发环境回调地址配置 + @Value("${payment.dev.notify-url:http://frps-10550.s209.websoft.top/api/shop/shop-order/notify}") + private String devNotifyUrl; + + // 生产环境回调地址配置 + @Value("${payment.prod.notify-url:https://cms-api.websoft.top/api/shop/shop-order/notify}") + private String prodNotifyUrl; + + /** + * 获取环境感知的支付配置 + * 根据当前环境自动调整回调地址 + * + * @param payType 支付类型 + * @param tenantId 租户ID + * @return 支付配置 + */ + public Payment getEnvironmentAwarePaymentConfig(Integer payType, Integer tenantId) { + // 获取原始支付配置 + Payment payment = paymentCacheService.getPaymentConfig(payType, tenantId); + + if (payment == null) { + return null; + } + + // 根据环境调整回调地址 + Payment envPayment = clonePayment(payment); + String notifyUrl = getEnvironmentNotifyUrl(); + + log.info("环境感知支付配置 - 环境: {}, 原始回调: {}, 调整后回调: {}", + activeProfile, payment.getNotifyUrl(), notifyUrl); + + envPayment.setNotifyUrl(notifyUrl); + + return envPayment; + } + + /** + * 根据当前环境获取回调地址 + */ + private String getEnvironmentNotifyUrl() { + if ("dev".equals(activeProfile) || "test".equals(activeProfile)) { + // 开发/测试环境使用本地回调地址 + return devNotifyUrl; + } else if ("prod".equals(activeProfile)) { + // 生产环境使用生产回调地址 + return prodNotifyUrl; + } else { + // 默认使用配置的服务器地址 + return serverUrl + "/shop/shop-order/notify"; + } + } + + /** + * 克隆支付配置对象 + */ + private Payment clonePayment(Payment original) { + Payment cloned = new Payment(); + cloned.setId(original.getId()); + cloned.setName(original.getName()); + cloned.setType(original.getType()); + cloned.setCode(original.getCode()); + cloned.setImage(original.getImage()); + cloned.setWechatType(original.getWechatType()); + cloned.setAppId(original.getAppId()); + cloned.setMchId(original.getMchId()); + cloned.setApiKey(original.getApiKey()); + cloned.setApiclientCert(original.getApiclientCert()); + cloned.setApiclientKey(original.getApiclientKey()); + cloned.setPubKey(original.getPubKey()); + cloned.setPubKeyId(original.getPubKeyId()); + cloned.setMerchantSerialNumber(original.getMerchantSerialNumber()); + cloned.setNotifyUrl(original.getNotifyUrl()); // 这个会被后续覆盖 + cloned.setComments(original.getComments()); + cloned.setSortNumber(original.getSortNumber()); + cloned.setStatus(original.getStatus()); + cloned.setDeleted(original.getDeleted()); + cloned.setTenantId(original.getTenantId()); + return cloned; + } + + /** + * 获取微信支付配置(环境感知) + */ + public Payment getWechatPayConfig(Integer tenantId) { + return getEnvironmentAwarePaymentConfig(0, tenantId); + } + + /** + * 获取支付宝配置(环境感知) + */ + public Payment getAlipayConfig(Integer tenantId) { + return getEnvironmentAwarePaymentConfig(1, tenantId); + } + + /** + * 检查当前环境 + */ + public String getCurrentEnvironment() { + return activeProfile; + } + + /** + * 是否为开发环境 + */ + public boolean isDevelopmentEnvironment() { + return "dev".equals(activeProfile) || "test".equals(activeProfile); + } + + /** + * 是否为生产环境 + */ + public boolean isProductionEnvironment() { + return "prod".equals(activeProfile); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java b/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java new file mode 100644 index 0000000..17a8e32 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java @@ -0,0 +1,174 @@ +package com.gxwebsoft.common.core.service; + +import cn.hutool.core.util.ObjectUtil; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.param.PaymentParam; +import com.gxwebsoft.common.system.service.PaymentService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 支付配置缓存服务 + * 统一管理支付配置的缓存读取,支持 Payment:1* 格式 + * + * @author 科技小王子 + * @since 2025-07-27 + */ +@Slf4j +@Service +public class PaymentCacheService { + + @Autowired + private RedisUtil redisUtil; + + @Autowired + private PaymentService paymentService; + + /** + * 根据支付类型获取支付配置 + * 优先从 Payment:1{payType} 格式的缓存读取 + * + * @param payType 支付类型 (0=微信支付, 1=支付宝, 2=其他) + * @param tenantId 租户ID (用于兜底查询) + * @return Payment 支付配置 + */ + public Payment getPaymentConfig(Integer payType, Integer tenantId) { + // 1. 优先使用 Payment:1{payType} 格式的缓存键 + String primaryKey = "Payment:1:" + tenantId; + Payment payment = redisUtil.get(primaryKey, Payment.class); + + if (ObjectUtil.isNotEmpty(payment)) { + log.debug("从缓存获取支付配置成功: {}", primaryKey); + return payment; + } + + // 2. 如果 Payment:1* 格式不存在,尝试原有格式 + String fallbackKey = "Payment:" + payType + ":" + tenantId; + payment = redisUtil.get(fallbackKey, Payment.class); + + if (ObjectUtil.isNotEmpty(payment)) { + log.debug("从兜底缓存获取支付配置成功: {}", fallbackKey); + // 将查询结果缓存到 Payment:1* 格式 + redisUtil.set(primaryKey, payment); + return payment; + } + + // 3. 最后从数据库查询 + log.debug("从数据库查询支付配置, payType: {}, tenantId: {}", payType, tenantId); + PaymentParam paymentParam = new PaymentParam(); + paymentParam.setType(payType); + paymentParam.setTenantId(tenantId); // 设置租户ID进行过滤 + List payments = paymentService.listRel(paymentParam); + + if (payments.isEmpty()) { + throw new BusinessException("请完成支付配置,支付类型: " + payType); + } + + Payment dbPayment = payments.get(0); + + // 清理时间字段,避免序列化问题 + Payment cachePayment = cleanPaymentForCache(dbPayment); + + // 将查询结果缓存到 Payment:1* 格式 + redisUtil.set(primaryKey, cachePayment); + log.debug("支付配置已缓存到: {}", primaryKey); + + return dbPayment; // 返回原始对象,不影响业务逻辑 + } + + /** + * 缓存支付配置 + * 同时缓存到 Payment:1{payType} 和原有格式 + * + * @param payment 支付配置 + * @param tenantId 租户ID + */ + public void cachePaymentConfig(Payment payment, Integer tenantId) { + // 缓存到 Payment:1* 格式 + String primaryKey = "Payment:1" + payment.getCode(); + redisUtil.set(primaryKey, payment); + log.debug("支付配置已缓存到: {}", primaryKey); + + // 兼容原有格式 + String legacyKey = "Payment:" + payment.getCode() + ":" + tenantId; + redisUtil.set(legacyKey, payment); + log.debug("支付配置已缓存到兼容格式: {}", legacyKey); + } + + /** + * 删除支付配置缓存 + * 同时删除 Payment:1{payType} 和原有格式 + * + * @param paymentCode 支付代码 (可以是String或Integer) + * @param tenantId 租户ID + */ + public void removePaymentConfig(String paymentCode, Integer tenantId) { + // 删除 Payment:1* 格式缓存 + String primaryKey = "Payment:1" + paymentCode; + redisUtil.delete(primaryKey); + log.debug("已删除支付配置缓存: {}", primaryKey); + + // 删除原有格式缓存 + String legacyKey = "Payment:" + paymentCode + ":" + tenantId; + redisUtil.delete(legacyKey); + log.debug("已删除兼容格式缓存: {}", legacyKey); + } + + /** + * 获取微信支付配置 (payType = 0) + */ + public Payment getWechatPayConfig(Integer tenantId) { + return getPaymentConfig(0, tenantId); + } + + /** + * 获取支付宝配置 (payType = 1) + */ + public Payment getAlipayConfig(Integer tenantId) { + return getPaymentConfig(1, tenantId); + } + + /** + * 清理Payment对象用于缓存 + * 移除可能导致序列化问题的时间字段 + */ + private Payment cleanPaymentForCache(Payment original) { + if (original == null) { + return null; + } + + Payment cleaned = new Payment(); + // 复制所有业务相关字段 + cleaned.setId(original.getId()); + cleaned.setName(original.getName()); + cleaned.setType(original.getType()); + cleaned.setCode(original.getCode()); + cleaned.setImage(original.getImage()); + cleaned.setWechatType(original.getWechatType()); + cleaned.setAppId(original.getAppId()); + cleaned.setMchId(original.getMchId()); + cleaned.setApiKey(original.getApiKey()); + cleaned.setApiclientCert(original.getApiclientCert()); + cleaned.setApiclientKey(original.getApiclientKey()); + cleaned.setPubKey(original.getPubKey()); + cleaned.setPubKeyId(original.getPubKeyId()); + cleaned.setMerchantSerialNumber(original.getMerchantSerialNumber()); + cleaned.setNotifyUrl(original.getNotifyUrl()); + cleaned.setComments(original.getComments()); + cleaned.setSortNumber(original.getSortNumber()); + cleaned.setStatus(original.getStatus()); + cleaned.setDeleted(original.getDeleted()); + cleaned.setTenantId(original.getTenantId()); + + // 不设置时间字段,避免序列化问题 + // cleaned.setCreateTime(null); + // cleaned.setUpdateTime(null); + + return cleaned; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/AliYunSender.java b/src/main/java/com/gxwebsoft/common/core/utils/AliYunSender.java new file mode 100644 index 0000000..80fe4b1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/AliYunSender.java @@ -0,0 +1,145 @@ +package com.gxwebsoft.common.core.utils; +import cn.hutool.core.codec.Base64; +import org.springframework.stereotype.Component; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.MessageDigest; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.UUID; + +@Component +public class AliYunSender { + /* + * 计算MD5+BASE64 + */ + public static String MD5Base64(String s) { + if (s == null) + return null; + String encodeStr = ""; + byte[] utfBytes = s.getBytes(); + MessageDigest mdTemp; + try { + mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(utfBytes); + byte[] md5Bytes = mdTemp.digest(); + encodeStr = Base64.encode(md5Bytes); + } catch (Exception e) { + throw new Error("Failed to generate MD5 : " + e.getMessage()); + } + return encodeStr; + } + /* + * 计算 HMAC-SHA1 + */ + public static String HMACSha1(String data, String key) { + String result; + try { + SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(signingKey); + byte[] rawHmac = mac.doFinal(data.getBytes()); + result = Base64.encode(rawHmac); + } catch (Exception e) { + throw new Error("Failed to generate HMAC : " + e.getMessage()); + } + return result; + } + /* + * 获取时间 + */ + public static String toGMTString(Date date) { + SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.UK); + df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT")); + return df.format(date); + } + /* + * 发送POST请求 + */ + public static String sendPost(String url, String body, String ak_id, String ak_secret) { + PrintWriter out = null; + BufferedReader in = null; + String result = ""; + try { + URL realUrl = new URL(url); + /* + * http header 参数 + */ + String method = "POST"; + String accept = "application/json"; + String content_type = "application/json;chrset=utf-8"; + String path = realUrl.getFile(); + String date = toGMTString(new Date()); + String host = realUrl.getHost(); + // 1.对body做MD5+BASE64加密 + String bodyMd5 = MD5Base64(body); + String uuid = UUID.randomUUID().toString(); + String stringToSign = method + "\n" + accept + "\n" + bodyMd5 + "\n" + content_type + "\n" + date + "\n" + + "x-acs-signature-method:HMAC-SHA1\n" + + "x-acs-signature-nonce:" + uuid + "\n" + + "x-acs-version:2019-01-02\n" + + path; + // 2.计算 HMAC-SHA1 + String signature = HMACSha1(stringToSign, ak_secret); + // 3.得到 authorization header + String authHeader = "acs " + ak_id + ":" + signature; + // 打开和URL之间的连接 + URLConnection conn = realUrl.openConnection(); + // 设置通用的请求属性 + conn.setRequestProperty("Accept", accept); + conn.setRequestProperty("Content-Type", content_type); + conn.setRequestProperty("Content-MD5", bodyMd5); + conn.setRequestProperty("Date", date); + conn.setRequestProperty("Host", host); + conn.setRequestProperty("Authorization", authHeader); + conn.setRequestProperty("x-acs-signature-nonce", uuid); + conn.setRequestProperty("x-acs-signature-method", "HMAC-SHA1"); + conn.setRequestProperty("x-acs-version", "2019-01-02"); // 版本可选 + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + // 获取URLConnection对象对应的输出流 + out = new PrintWriter(conn.getOutputStream()); + // 发送请求参数 + out.print(body); + // flush输出流的缓冲 + out.flush(); + // 定义BufferedReader输入流来读取URL的响应 + InputStream is; + HttpURLConnection httpconn = (HttpURLConnection) conn; + if (httpconn.getResponseCode() == 200) { + is = httpconn.getInputStream(); + } else { + is = httpconn.getErrorStream(); + } + in = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + } catch (Exception e) { + System.out.println("发送 POST 请求出现异常!" + e); + e.printStackTrace(); + } + // 使用finally块来关闭输出流、输入流 + finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + return result; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/AlipayConfigUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/AlipayConfigUtil.java new file mode 100644 index 0000000..42975ef --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/AlipayConfigUtil.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.common.core.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayConstants; +import com.alipay.api.CertAlipayRequest; +import com.alipay.api.DefaultAlipayClient; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.exception.BusinessException; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 支付宝工具类 + * @author leng + * + */ +@Component +public class AlipayConfigUtil { + private final StringRedisTemplate stringRedisTemplate; + public Integer tenantId; + public String gateway; + public JSONObject config; + public String appId; + public String privateKey; + public String appCertPublicKey; + public String alipayCertPublicKey; + public String alipayRootCert; + + @Resource + private ConfigProperties pathConfig; + + public AlipayConfigUtil(StringRedisTemplate stringRedisTemplate){ + this.stringRedisTemplate = stringRedisTemplate; + } + + // 实例化客户端 + public DefaultAlipayClient alipayClient(Integer tenantId) throws AlipayApiException { + this.gateway = "https://openapi.alipay.com/gateway.do"; + this.tenantId = tenantId; + this.payment(tenantId); + CertAlipayRequest certAlipayRequest = new CertAlipayRequest(); + certAlipayRequest.setServerUrl(this.gateway); + certAlipayRequest.setAppId(this.appId); + certAlipayRequest.setPrivateKey(this.privateKey); + certAlipayRequest.setFormat(AlipayConstants.FORMAT_JSON); + certAlipayRequest.setCharset(AlipayConstants.CHARSET_UTF8); + certAlipayRequest.setSignType(AlipayConstants.SIGN_TYPE_RSA2); + certAlipayRequest.setCertPath(this.appCertPublicKey); + certAlipayRequest.setAlipayPublicCertPath(this.alipayCertPublicKey); + certAlipayRequest.setRootCertPath(this.alipayRootCert); +// System.out.println("this.appId = " + this.appId); +// System.out.println("this.appId = " + this.gateway); +// System.out.println("this.appId = " + this.privateKey); +// System.out.println("this.appId = " + this.appCertPublicKey); +// System.out.println("this.appId = " + this.alipayCertPublicKey); +// System.out.println("this.appId = " + this.alipayRootCert); +// System.out.println("this.config = " + this.config); + return new DefaultAlipayClient(certAlipayRequest); + } + + /** + * 获取支付宝秘钥 + */ + public JSONObject payment(Integer tenantId) { + System.out.println("tenantId = " + tenantId); + String key = "cache".concat(tenantId.toString()).concat(":setting:payment"); + System.out.println("key = " + key); + String cache = stringRedisTemplate.opsForValue().get(key); + if (cache == null) { + throw new BusinessException("支付方式未配置"); + } + // 解析json数据 + JSONObject payment = JSON.parseObject(cache.getBytes()); + this.config = payment; + this.appId = payment.getString("alipayAppId"); + this.privateKey = payment.getString("privateKey"); + this.appCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("appCertPublicKey"); + this.alipayCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("alipayCertPublicKey"); + this.alipayRootCert = pathConfig.getUploadPath() + "file" + payment.getString("alipayRootCert"); + return payment; + } + + public String appId(){ + return this.appId; + } + + public String privateKey(){ + return this.privateKey; + } + + public String appCertPublicKey(){ + return this.appCertPublicKey; + } + + public String alipayCertPublicKey(){ + return this.alipayCertPublicKey; + } + + public String alipayRootCert(){ + return this.alipayRootCert; + } + + + + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/CacheClient.java b/src/main/java/com/gxwebsoft/common/core/utils/CacheClient.java new file mode 100644 index 0000000..62f063e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/CacheClient.java @@ -0,0 +1,265 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.result.RedisResult; +import org.springframework.data.geo.Point; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static com.gxwebsoft.common.core.constants.RedisConstants.CACHE_NULL_TTL; + +@Component +public class CacheClient { + private final StringRedisTemplate stringRedisTemplate; + public static Integer tenantId; + + public CacheClient(StringRedisTemplate stringRedisTemplate){ + this.stringRedisTemplate = stringRedisTemplate; + } + + /** + * 写入redis缓存 + * @param key [表名]:id + * @param entity 实体类对象 + * 示例 cacheClient.set("merchant:"+id,merchant) + */ + public void set(String key, T entity){ + stringRedisTemplate.opsForValue().set(prefix(key), JSONUtil.toJSONString(entity)); + } + + /** + * 写入redis缓存 + * @param key [表名]:id + * @param entity 实体类对象 + * 示例 cacheClient.set("merchant:"+id,merchant,1L,TimeUnit.DAYS) + */ + public void set(String key, T entity, Long time, TimeUnit unit){ + stringRedisTemplate.opsForValue().set(prefix(key), JSONUtil.toJSONString(entity),time,unit); + } + + /** + * 读取redis缓存 + * @param key [表名]:id + * 示例 cacheClient.get(key) + * @return merchant + */ + public String get(String key) { + return stringRedisTemplate.opsForValue().get(prefix(key)); + } + + /** + * 读取redis缓存 + * @param key [表名]:id + * @param clazz Merchant.class + * @param + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + * @return merchant + */ + public T get(String key, Class clazz) { + String json = stringRedisTemplate.opsForValue().get(prefix(key)); + if(StrUtil.isNotBlank(json)){ + return JSONUtil.parseObject(json, clazz); + } + return null; + } + + /** + * 写redis缓存(哈希类型) + * @param key [表名]:id + * @param field 字段 + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + */ + public void hPut(String key, String field, T entity) { + stringRedisTemplate.opsForHash().put(prefix(key),field,JSONUtil.toJSONString(entity)); + } + + /** + * 写redis缓存(哈希类型) + * @param key [表名]:id + * @param map 字段 + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + */ + public void hPutAll(String key, Map map) { + stringRedisTemplate.opsForHash().putAll(prefix(key),map); + } + + /** + * 读取redis缓存(哈希类型) + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + * @param key [表名]:id + * @param field 字段 + * @return merchant + */ + public T hGet(String key, String field, Class clazz) { + Object obj = stringRedisTemplate.opsForHash().get(prefix(key), field); + return JSONUtil.parseObject(JSONUtil.toJSONString(obj),clazz); + } + + public List hValues(String key){ + return stringRedisTemplate.opsForHash().values(prefix(key)); + } + + public Long hSize(String key){ + return stringRedisTemplate.opsForHash().size(prefix(key)); + } + + // 逻辑过期方式写入redis + public void setWithLogicalExpire(String key, T value, Long time, TimeUnit unit){ + // 设置逻辑过期时间 + final RedisResult redisResult = new RedisResult<>(); + redisResult.setData(value); + redisResult.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); + stringRedisTemplate.opsForValue().set(prefix(key),JSONUtil.toJSONString(redisResult)); + } + + // 读取redis + public R query(String keyPrefix, ID id, Class clazz, Function dbFallback, Long time, TimeUnit unit){ + String key = keyPrefix + id; + // 1.从redis查询缓存 + final String json = stringRedisTemplate.opsForValue().get(prefix(key)); + // 2.判断是否存在 + if (StrUtil.isNotBlank(json)) { + // 3.存在,直接返回 + return JSONUtil.parseObject(json,clazz); + } + // 判断命中的是否为空值 + if (json != null) { + return null; + } + // 4. 不存在,跟进ID查询数据库 + R r = dbFallback.apply(id); + // 5. 数据库不存在,返回错误 + if(r == null){ + // 空值写入数据库 + this.set(prefix(key),"",CACHE_NULL_TTL,TimeUnit.MINUTES); + return null; + } + // 写入redis + this.set(prefix(key),r,time,unit); + return r; + } + + /** + * 添加商户定位点 + * @param key geo + * @param id + * 示例 cacheClient.geoAdd("merchant-geo",merchant) + */ + public void geoAdd(String key, Double x, Double y, String id){ + stringRedisTemplate.opsForGeo().add(prefix(key),new Point(x,y),id); + } + + /** + * 删除定位 + * @param key geo + * @param id + * 示例 cacheClient.geoRemove("merchant-geo",id) + */ + public void geoRemove(String key, Integer id){ + stringRedisTemplate.opsForGeo().remove(prefix(key),id.toString()); + } + + + + public void sAdd(String key, T entity){ + stringRedisTemplate.opsForSet().add(prefix(key),JSONUtil.toJSONString(entity)); + } + + public Set sMembers(String key){ + return stringRedisTemplate.opsForSet().members(prefix(key)); + } + + // 更新排行榜 + public void zAdd(String key, Integer userId, Double value) { + stringRedisTemplate.opsForZSet().add(prefix(key),userId.toString(),value); + } + // 增加元素的score值,并返回增加后的值 + public Double zIncrementScore(String key,Integer userId, Double delta){ + return stringRedisTemplate.opsForZSet().incrementScore(key, userId.toString(), delta); + } + // 获取排名榜 + public Set range(String key, Integer start, Integer end) { + return stringRedisTemplate.opsForZSet().range(prefix(key), start, end); + } + // 获取排名榜 + public Set reverseRange(String key, Integer start, Integer end){ + return stringRedisTemplate.opsForZSet().reverseRange(prefix(key), start, end); + } + // 获取分数 + public Double score(String key, Object value){ + return stringRedisTemplate.opsForZSet().score(prefix(key), value); + } + + public void delete(String key){ + stringRedisTemplate.delete(prefix(key)); + } + + // 存储在list头部 + public void leftPush(String key, String keyword){ + stringRedisTemplate.opsForList().leftPush(prefix(key),keyword); + } + + // 获取列表指定范围内的元素 + public List listRange(String key,Long start, Long end){ + return stringRedisTemplate.opsForList().range(prefix(key), start, end); + } + + // 获取列表长度 + public Long listSize(String key){ + return stringRedisTemplate.opsForList().size(prefix(key)); + } + + // 裁剪list + public void listTrim(String key){ + stringRedisTemplate.opsForList().trim(prefix(key), 0L, 100L); + } + + /** + * 读取后台系统设置信息 + * @param keyName 键名wx-word + * @param tenantId 租户ID + * @return + * key示例 cache10048:setting:wx-work + */ + public JSONObject getSettingInfo(String keyName,Integer tenantId){ + String key = "cache" + tenantId + ":setting:" + keyName; + final String cache = stringRedisTemplate.opsForValue().get(key); + assert cache != null; + return JSON.parseObject(cache); + } + + /** + * KEY前缀 + * cache[tenantId]:[key+id] + */ + public static String prefix(String key){ + String prefix = "cache"; + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Object object = authentication.getPrincipal(); + if (object instanceof User) { + final Integer tenantId = ((User) object).getTenantId(); + prefix = prefix.concat(tenantId.toString()).concat(":"); + } + } + return prefix.concat(key); + } + + // 组装key + public String key(String name,Integer id){ + return name.concat(":").concat(id.toString()); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/CertificateLoader.java b/src/main/java/com/gxwebsoft/common/core/utils/CertificateLoader.java new file mode 100644 index 0000000..a256e49 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/CertificateLoader.java @@ -0,0 +1,230 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.config.CertificateProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * 证书加载工具类 + * 支持多种证书加载方式,适配Docker容器化部署 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Component +public class CertificateLoader { + + private final CertificateProperties certConfig; + + public CertificateLoader(CertificateProperties certConfig) { + this.certConfig = certConfig; + } + + @PostConstruct + public void init() { + log.info("证书加载器初始化,加载模式:{}", certConfig.getLoadMode()); + if (certConfig.getLoadMode() == CertificateProperties.LoadMode.VOLUME) { + log.info("Docker挂载卷证书路径:{}", certConfig.getCertRootPath()); + validateCertDirectory(); + } + } + + /** + * 验证证书目录是否存在 + */ + private void validateCertDirectory() { + File certDir = new File(certConfig.getCertRootPath()); + if (!certDir.exists()) { + log.warn("证书目录不存在:{},将尝试创建", certConfig.getCertRootPath()); + if (!certDir.mkdirs()) { + log.error("无法创建证书目录:{}", certConfig.getCertRootPath()); + } + } else { + log.info("证书目录验证成功:{}", certConfig.getCertRootPath()); + } + } + + /** + * 加载证书文件路径 + * + * @param certPath 证书路径(可能是相对路径、绝对路径或classpath路径) + * @return 实际的证书文件路径 + */ + public String loadCertificatePath(String certPath) { + if (!StringUtils.hasText(certPath)) { + throw new IllegalArgumentException("证书路径不能为空"); + } + + try { + switch (certConfig.getLoadMode()) { + case CLASSPATH: + return loadFromClasspath(certPath); + case VOLUME: + return loadFromVolume(certPath); + case FILESYSTEM: + default: + return loadFromFileSystem(certPath); + } + } catch (Exception e) { + log.error("加载证书失败,路径:{}", certPath, e); + throw new RuntimeException("证书加载失败:" + certPath, e); + } + } + + /** + * 从classpath加载证书 + */ + private String loadFromClasspath(String certPath) throws IOException { + String resourcePath = certPath.startsWith("classpath:") ? + certPath.substring("classpath:".length()) : certPath; + + ClassPathResource resource = new ClassPathResource(resourcePath); + if (!resource.exists()) { + throw new IOException("Classpath中找不到证书文件:" + resourcePath); + } + + // 将classpath中的文件复制到临时目录 + Path tempFile = Files.createTempFile("cert_", ".pem"); + try (InputStream inputStream = resource.getInputStream()) { + Files.copy(inputStream, tempFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + + String tempPath = tempFile.toAbsolutePath().toString(); + log.debug("从classpath加载证书:{} -> {}", resourcePath, tempPath); + return tempPath; + } + + /** + * 从Docker挂载卷加载证书 + */ + private String loadFromVolume(String certPath) { + log.debug("尝试从Docker挂载卷加载证书:{}", certPath); + + // 如果是完整路径,直接使用 + if (certPath.startsWith("/") || certPath.contains(":")) { + File file = new File(certPath); + log.debug("检查完整路径文件是否存在:{}", certPath); + if (file.exists()) { + log.debug("使用完整路径加载证书:{}", certPath); + return certPath; + } else { + log.error("完整路径文件不存在:{}", certPath); + } + } + + // 否则拼接挂载卷路径 + String fullPath = Paths.get(certConfig.getCertRootPath(), certPath).toString(); + File file = new File(fullPath); + if (!file.exists()) { + throw new RuntimeException("Docker挂载卷中找不到证书文件:" + fullPath); + } + + log.debug("从Docker挂载卷加载证书:{}", fullPath); + return fullPath; + } + + /** + * 从文件系统加载证书 + */ + private String loadFromFileSystem(String certPath) { + File file = new File(certPath); + if (!file.exists()) { + throw new RuntimeException("文件系统中找不到证书文件:" + certPath); + } + + log.debug("从文件系统加载证书:{}", certPath); + return certPath; + } + + /** + * 检查证书文件是否存在 + * + * @param certPath 证书路径 + * @return 是否存在 + */ + public boolean certificateExists(String certPath) { + try { + switch (certConfig.getLoadMode()) { + case CLASSPATH: + String resourcePath = certPath.startsWith("classpath:") ? + certPath.substring("classpath:".length()) : certPath; + ClassPathResource resource = new ClassPathResource(resourcePath); + return resource.exists(); + case VOLUME: + String fullPath = certPath.startsWith("/") ? certPath : + Paths.get(certConfig.getCertRootPath(), certPath).toString(); + return new File(fullPath).exists(); + case FILESYSTEM: + default: + return new File(certPath).exists(); + } + } catch (Exception e) { + log.warn("检查证书文件存在性时出错:{}", certPath, e); + return false; + } + } + + /** + * 获取证书文件的输入流 + * + * @param certPath 证书路径 + * @return 输入流 + */ + public InputStream getCertificateInputStream(String certPath) throws IOException { + switch (certConfig.getLoadMode()) { + case CLASSPATH: + String resourcePath = certPath.startsWith("classpath:") ? + certPath.substring("classpath:".length()) : certPath; + ClassPathResource resource = new ClassPathResource(resourcePath); + return resource.getInputStream(); + case VOLUME: + case FILESYSTEM: + default: + String actualPath = loadCertificatePath(certPath); + return Files.newInputStream(Paths.get(actualPath)); + } + } + + /** + * 列出证书目录中的所有文件 + * + * @return 证书文件列表 + */ + public String[] listCertificateFiles() { + try { + switch (certConfig.getLoadMode()) { + case VOLUME: + File certDir = new File(certConfig.getCertRootPath()); + if (certDir.exists() && certDir.isDirectory()) { + return certDir.list(); + } + break; + case CLASSPATH: + // classpath模式下不支持列出文件 + log.warn("Classpath模式下不支持列出证书文件"); + break; + case FILESYSTEM: + default: + // 文件系统模式下证书可能分散在不同目录,不支持统一列出 + log.warn("文件系统模式下不支持列出证书文件"); + break; + } + } catch (Exception e) { + log.error("列出证书文件时出错", e); + } + return new String[0]; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/CommonUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/CommonUtil.java new file mode 100644 index 0000000..6435db6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/CommonUtil.java @@ -0,0 +1,321 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.system.entity.Role; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 常用工具方法 + * + * @author WebSoft + * @since 2017-06-10 10:10:22 + */ +public class CommonUtil { + + // 生成uuid的字符 + private static final String[] chars = new String[]{ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" + }; + + /** + * 生成8位uuid + * + * @return String + */ + public static String randomUUID8() { + StringBuilder sb = new StringBuilder(); + String uuid = UUID.randomUUID().toString().replace("-", ""); + for (int i = 0; i < 8; i++) { + String str = uuid.substring(i * 4, i * 4 + 4); + int x = Integer.parseInt(str, 16); + sb.append(chars[x % 0x3E]); + } + return sb.toString(); + } + + /** + * 生成16位uuid + * + * @return String + */ + public static String randomUUID16() { + StringBuilder sb = new StringBuilder(); + String uuid = UUID.randomUUID().toString().replace("-", ""); + for (int i = 0; i < 16; i++) { + String str = uuid.substring(i * 2, i * 2 + 2); + int x = Integer.parseInt(str, 16); + sb.append(chars[x % 0x3E]); + } + return sb.toString(); + } + + /** + * 获取当前时间 + * + * @return String + */ + public static String currentTime() { + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); + return sdf.format(date); + } + + /** + * 生成10位随机用户名 + * + * @return String + */ + public static String randomUsername(String prefix) { + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); + String currentTime = sdf.format(date); + return prefix + currentTime; + } + + /** + * 生成订单号 + * 20233191166110426 + * 20230419135802391412 + * @return + */ + public static String createOrderNo() { + String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_PATTERN); + return prefix + RandomUtil.randomNumbers(2); + } + + /** + * 生成订单号 + * @param tenantId + * 20233191166110426 + * 20230419135802391412 + * @return + */ + public static String createOrderNo(String tenantId) { + String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_PATTERN); + return prefix + tenantId + RandomUtil.randomNumbers(2); + } + + /** + * 生成订单水流号 + * @param tenantId + * @return + */ + public static String serialNo(int tenantId) { + String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_PATTERN); + return prefix + tenantId + RandomUtil.randomNumbers(2); + } + + /** + * 生成会员卡号 + * @return + */ + public static String createCardNo() { + String prefix = DateTime.now().toString(DatePattern.PURE_TIME_PATTERN); + return "00" + prefix + RandomUtil.randomNumbers(2); + } + + /** + * 检查List是否有重复元素 + * + * @param list List + * @param mapper 获取需要检查的字段的Function + * @param 数据的类型 + * @param 需要检查的字段的类型 + * @return boolean + */ + public static boolean checkRepeat(List list, Function mapper) { + for (int i = 0; i < list.size(); i++) { + for (int j = 0; j < list.size(); j++) { + if (i != j && mapper.apply(list.get(i)).equals(mapper.apply(list.get(j)))) { + return true; + } + } + } + return false; + } + + /** + * List转为树形结构 + * + * @param data List + * @param parentId 顶级的parentId + * @param parentIdMapper 获取parentId的Function + * @param idMapper 获取id的Function + * @param consumer 赋值children的Consumer + * @param 数据的类型 + * @param parentId的类型 + * @return List + */ + public static List toTreeData(List data, R parentId, + Function parentIdMapper, + Function idMapper, + BiConsumer> consumer) { + List result = new ArrayList<>(); + for (T d : data) { + R dParentId = parentIdMapper.apply(d); + if (ObjectUtil.equals(parentId, dParentId)) { + R dId = idMapper.apply(d); + List children = toTreeData(data, dId, parentIdMapper, idMapper, consumer); + consumer.accept(d, children); + result.add(d); + } + } + return result; + } + + /** + * 遍历树形结构数据 + * + * @param data List + * @param consumer 回调 + * @param mapper 获取children的Function + * @param 数据的类型 + */ + public static void eachTreeData(List data, Consumer consumer, Function> mapper) { + for (T d : data) { + consumer.accept(d); + List children = mapper.apply(d); + if (children != null && children.size() > 0) { + eachTreeData(children, consumer, mapper); + } + } + } + + /** + * 获取集合中的第一条数据 + * + * @param records 集合 + * @return 第一条数据 + */ + public static T listGetOne(List records) { + return records == null || records.size() == 0 ? null : records.get(0); + } + + /** + * 支持跨域 + * + * @param response HttpServletResponse + */ + public static void addCrossHeaders(HttpServletResponse response) { + response.setHeader("Access-Control-Max-Age", "3600"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setHeader("Access-Control-Allow-Headers", "*"); + response.setHeader("Access-Control-Expose-Headers", Constants.TOKEN_HEADER_NAME); + } + + /** + * 输出错误信息 + * + * @param response HttpServletResponse + * @param code 错误码 + * @param message 提示信息 + * @param error 错误信息 + */ + public static void responseError(HttpServletResponse response, Integer code, String message, String error) { + response.setContentType("application/json;charset=UTF-8"); + try { + PrintWriter out = response.getWriter(); + out.write(JSONUtil.toJSONString(new ApiResult<>(code, message, null, error))); + out.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static boolean hasRole(List array,String value){ + System.out.println("value = " + value); + if (value == null) { + return true; + } + if (array == null) { + return false; + } + if (!array.isEmpty()) { + final List collect = array.stream().map(Role::getRoleCode) + .collect(Collectors.toList()); + final boolean contains = collect.contains(value); + if (contains) { + return true; + } + } + return false; + } + + public static boolean hasRole(List array,List value){ + System.out.println("value = " + value); + if (value == null) { + return true; + } + if (array == null) { + return false; + } + if (!array.isEmpty()) { + final List collect = array.stream().map(Role::getRoleCode) + .collect(Collectors.toList()); + final boolean disjoint = Collections.disjoint(collect, value); + if (!disjoint) { + return true; + } + } + return false; + } + + public static AES aes(){ + // 随机生成密钥 + byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded(); + return SecureUtil.aes(key); + } + + // 机密文本 + public static String encrypt(String text){ + final AES aes = aes(); + return aes.encryptHex(text); + } + + // 解密 + public static String decrypt(String encrypt){ + final AES aes = aes(); + return aes.decryptStr(encrypt, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 验证给定的字符串是否为有效的中国大陆手机号码。 + * + * @param phoneNumber 要验证的电话号码字符串 + * @return 如果字符串是有效的手机号码,则返回true;否则返回false + */ + public static boolean isValidPhoneNumber(String phoneNumber) { + // 定义手机号码的正则表达式 + String regex = "^1[3-9]\\d{9}$"; + + // 创建Pattern对象 + Pattern pattern = Pattern.compile(regex); + + // 使用matcher方法创建Matcher对象并进行匹配 + return pattern.matcher(phoneNumber).matches(); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/DateTimeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/DateTimeUtil.java new file mode 100644 index 0000000..bde0280 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/DateTimeUtil.java @@ -0,0 +1,93 @@ +package com.gxwebsoft.common.core.utils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 时间格式化工具类 + * 用于统一处理LocalDateTime的格式化 + * + * @author WebSoft + * @since 2025-08-23 + */ +public class DateTimeUtil { + + /** + * 默认的日期时间格式 + */ + public static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + /** + * 默认的日期格式 + */ + public static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd"; + + /** + * 默认的时间格式 + */ + public static final String DEFAULT_TIME_PATTERN = "HH:mm:ss"; + + /** + * 默认的日期时间格式化器 + */ + private static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER = + DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN); + + /** + * 格式化LocalDateTime为字符串 + * 使用默认格式:yyyy-MM-dd HH:mm:ss + * + * @param dateTime 要格式化的时间 + * @return 格式化后的字符串,如果输入为null则返回null + */ + public static String formatDateTime(LocalDateTime dateTime) { + if (dateTime == null) { + return null; + } + return dateTime.format(DEFAULT_DATETIME_FORMATTER); + } + + /** + * 格式化LocalDateTime为字符串 + * 使用指定格式 + * + * @param dateTime 要格式化的时间 + * @param pattern 格式模式 + * @return 格式化后的字符串,如果输入为null则返回null + */ + public static String formatDateTime(LocalDateTime dateTime, String pattern) { + if (dateTime == null) { + return null; + } + return dateTime.format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 解析字符串为LocalDateTime + * 使用默认格式:yyyy-MM-dd HH:mm:ss + * + * @param dateTimeStr 时间字符串 + * @return LocalDateTime对象,如果输入为null或空字符串则返回null + */ + public static LocalDateTime parseDateTime(String dateTimeStr) { + if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) { + return null; + } + return LocalDateTime.parse(dateTimeStr, DEFAULT_DATETIME_FORMATTER); + } + + /** + * 解析字符串为LocalDateTime + * 使用指定格式 + * + * @param dateTimeStr 时间字符串 + * @param pattern 格式模式 + * @return LocalDateTime对象,如果输入为null或空字符串则返回null + */ + public static LocalDateTime parseDateTime(String dateTimeStr, String pattern) { + if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) { + return null; + } + return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern)); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/DomainUtils.java b/src/main/java/com/gxwebsoft/common/core/utils/DomainUtils.java new file mode 100644 index 0000000..64f4ad2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/DomainUtils.java @@ -0,0 +1,34 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.cms.entity.CmsDomain; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class DomainUtils { + public static boolean isDomainResolvable(String domain) { + try { + InetAddress.getByName(domain); + return true; + } catch (UnknownHostException e) { + return false; + } + } + + public static boolean DNSLookup(CmsDomain domain){ + try { + // 获取域名对应的InetAddress对象 + InetAddress inetAddress = InetAddress.getByName(domain.getDomain()); + final String hostAddress = inetAddress.getHostAddress(); + InetAddress inetAddress2 = InetAddress.getByName(domain.getHostValue()); + final String hostAddress2 = inetAddress2.getHostAddress(); + if(hostAddress.equals(hostAddress2)){ + return true; + } + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtil.java new file mode 100644 index 0000000..ff66ddc --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtil.java @@ -0,0 +1,433 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.extra.qrcode.QrConfig; +import com.alibaba.fastjson.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.crypto.spec.SecretKeySpec; +import java.awt.*; +import java.io.ByteArrayOutputStream; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 加密二维码工具类 + * 使用token作为密钥对二维码数据进行AES加密 + * + * @author WebSoft + * @since 2025-08-18 + */ +@Component +public class EncryptedQrCodeUtil { + + @Autowired + private RedisUtil redisUtil; + + private static final String QR_TOKEN_PREFIX = "qr_token:"; + private static final long DEFAULT_EXPIRE_MINUTES = 30; // 默认30分钟过期 + + /** + * 生成加密的二维码数据 + * + * @param originalData 原始数据 + * @param expireMinutes 过期时间(分钟) + * @return 包含token和加密数据的Map + */ + public Map generateEncryptedData(String originalData, Long expireMinutes) { + if (StrUtil.isBlank(originalData)) { + throw new IllegalArgumentException("原始数据不能为空"); + } + + if (expireMinutes == null || expireMinutes <= 0) { + expireMinutes = DEFAULT_EXPIRE_MINUTES; + } + + // 生成随机token作为密钥 + String token = RandomUtil.randomString(32); + + try { + // 使用token生成AES密钥 + AES aes = createAESFromToken(token); + + // 加密原始数据 + String encryptedData = aes.encryptHex(originalData); + + // 将token和原始数据存储到Redis中,设置过期时间 + String redisKey = QR_TOKEN_PREFIX + token; + redisUtil.set(redisKey, originalData, expireMinutes, TimeUnit.MINUTES); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("encryptedData", encryptedData); + result.put("expireMinutes", expireMinutes.toString()); + + return result; + + } catch (Exception e) { + throw new RuntimeException("生成加密数据失败: " + e.getMessage(), e); + } + } + + /** + * 解密二维码数据 + * + * @param token 密钥token + * @param encryptedData 加密的数据 + * @return 解密后的原始数据 + */ + public String decryptData(String token, String encryptedData) { + if (StrUtil.isBlank(token) || StrUtil.isBlank(encryptedData)) { + throw new IllegalArgumentException("token和加密数据不能为空"); + } + + try { + // 从Redis验证token是否有效 + String redisKey = QR_TOKEN_PREFIX + token; + String originalData = redisUtil.get(redisKey); + + if (StrUtil.isBlank(originalData)) { + throw new RuntimeException("token已过期或无效"); + } + + // 使用token生成AES密钥 + AES aes = createAESFromToken(token); + + // 解密数据 + String decryptedData = aes.decryptStr(encryptedData, CharsetUtil.CHARSET_UTF_8); + + // 验证解密结果与Redis中存储的数据是否一致 + if (!originalData.equals(decryptedData)) { + throw new RuntimeException("数据验证失败"); + } + + return decryptedData; + + } catch (Exception e) { + throw new RuntimeException("解密数据失败: " + e.getMessage(), e); + } + } + + /** + * 生成加密的二维码图片(自包含模式) + * + * @param originalData 原始数据 + * @param width 二维码宽度 + * @param height 二维码高度 + * @param expireMinutes 过期时间(分钟) + * @param businessType 业务类型(可选,如:order、user、coupon等) + * @return 包含二维码图片Base64和token的Map + */ + public Map generateEncryptedQrCode(String originalData, int width, int height, Long expireMinutes, String businessType) { + try { + // 生成加密数据 + Map encryptedInfo = generateEncryptedData(originalData, expireMinutes); + + // 创建二维码内容(包含token、加密数据和业务类型) + Map qrContent = new HashMap<>(); + qrContent.put("token", encryptedInfo.get("token")); + qrContent.put("data", encryptedInfo.get("encryptedData")); + qrContent.put("type", "encrypted"); + + // 添加业务类型(如果提供) + if (StrUtil.isNotBlank(businessType)) { + qrContent.put("businessType", businessType); + } + + String qrDataJson = JSONObject.toJSONString(qrContent); + + // 配置二维码 + QrConfig config = new QrConfig(width, height); + config.setMargin(1); + config.setForeColor(Color.BLACK); + config.setBackColor(Color.WHITE); + + // 生成二维码图片 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + QrCodeUtil.generate(qrDataJson, config, "png", outputStream); + + // 转换为Base64 + String base64Image = Base64.getEncoder().encodeToString(outputStream.toByteArray()); + + Map result = new HashMap<>(); + result.put("qrCodeBase64", base64Image); + result.put("token", encryptedInfo.get("token")); + result.put("originalData", originalData); + result.put("expireMinutes", encryptedInfo.get("expireMinutes")); + result.put("businessType", businessType); + + return result; + + } catch (Exception e) { + throw new RuntimeException("生成加密二维码失败: " + e.getMessage(), e); + } + } + + /** + * 生成加密的二维码图片(自包含模式,无业务类型) + * 向后兼容的重载方法 + * + * @param originalData 原始数据 + * @param width 二维码宽度 + * @param height 二维码高度 + * @param expireMinutes 过期时间(分钟) + * @return 包含二维码图片Base64和token的Map + */ + public Map generateEncryptedQrCode(String originalData, int width, int height, Long expireMinutes) { + return generateEncryptedQrCode(originalData, width, height, expireMinutes, null); + } + + /** + * 生成业务加密二维码(门店核销模式) + * 使用统一的业务密钥,门店可以直接解密 + * + * @param originalData 原始数据 + * @param width 二维码宽度 + * @param height 二维码高度 + * @param businessKey 业务密钥(如门店密钥) + * @param expireMinutes 过期时间(分钟) + * @param businessType 业务类型(如:order、coupon、ticket等) + * @return 包含二维码图片Base64的Map + */ + public Map generateBusinessEncryptedQrCode(String originalData, int width, int height, + String businessKey, Long expireMinutes, String businessType) { + try { + if (StrUtil.isBlank(businessKey)) { + throw new IllegalArgumentException("业务密钥不能为空"); + } + + if (expireMinutes == null || expireMinutes <= 0) { + expireMinutes = DEFAULT_EXPIRE_MINUTES; + } + + // 生成唯一的二维码ID + String qrId = RandomUtil.randomString(16); + + // 使用业务密钥加密数据 + AES aes = createAESFromToken(businessKey); + String encryptedData = aes.encryptHex(originalData); + + // 将二维码信息存储到Redis(用于验证和防重复使用) + String qrInfoKey = "qr_info:" + qrId; + Map qrInfo = new HashMap<>(); + qrInfo.put("originalData", originalData); + qrInfo.put("createTime", String.valueOf(System.currentTimeMillis())); + qrInfo.put("businessKey", businessKey); + redisUtil.set(qrInfoKey, JSONObject.toJSONString(qrInfo), expireMinutes, TimeUnit.MINUTES); + + // 创建二维码内容 + Map qrContent = new HashMap<>(); + qrContent.put("qrId", qrId); + qrContent.put("data", encryptedData); + qrContent.put("type", "business_encrypted"); + qrContent.put("expire", String.valueOf(System.currentTimeMillis() + expireMinutes * 60 * 1000)); + + // 添加业务类型(如果提供) + if (StrUtil.isNotBlank(businessType)) { + qrContent.put("businessType", businessType); + } + + String qrDataJson = JSONObject.toJSONString(qrContent); + + // 配置二维码 + QrConfig config = new QrConfig(width, height); + config.setMargin(1); + config.setForeColor(Color.BLACK); + config.setBackColor(Color.WHITE); + + // 生成二维码图片 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + QrCodeUtil.generate(qrDataJson, config, "png", outputStream); + + // 转换为Base64 + String base64Image = Base64.getEncoder().encodeToString(outputStream.toByteArray()); + + Map result = new HashMap<>(); + result.put("qrCodeBase64", base64Image); + result.put("qrId", qrId); + result.put("originalData", originalData); + result.put("expireMinutes", expireMinutes.toString()); + result.put("businessType", businessType); + // 注意:出于安全考虑,不返回businessKey + + return result; + + } catch (Exception e) { + throw new RuntimeException("生成业务加密二维码失败: " + e.getMessage(), e); + } + } + + /** + * 生成业务加密二维码(门店核销模式,无业务类型) + * 向后兼容的重载方法 + * + * @param originalData 原始数据 + * @param width 二维码宽度 + * @param height 二维码高度 + * @param businessKey 业务密钥(如门店密钥) + * @param expireMinutes 过期时间(分钟) + * @return 包含二维码图片Base64的Map + */ + public Map generateBusinessEncryptedQrCode(String originalData, int width, int height, + String businessKey, Long expireMinutes) { + return generateBusinessEncryptedQrCode(originalData, width, height, businessKey, expireMinutes, null); + } + + /** + * 验证并解密二维码内容(自包含模式) + * 二维码包含token和加密数据,扫码方无需额外信息 + * + * @param qrContent 二维码扫描得到的内容 + * @return 解密后的原始数据 + */ + public String verifyAndDecryptQrCode(String qrContent) { + QrCodeDecryptResult result = verifyAndDecryptQrCodeWithResult(qrContent); + return result.getOriginalData(); + } + + /** + * 验证并解密二维码内容(自包含模式,返回完整结果) + * 包含业务类型等详细信息 + * + * @param qrContent 二维码扫描得到的内容 + * @return 包含解密数据和业务类型的完整结果 + */ + public QrCodeDecryptResult verifyAndDecryptQrCodeWithResult(String qrContent) { + try { + // 解析二维码内容 + @SuppressWarnings("unchecked") + Map contentMap = JSONObject.parseObject(qrContent, Map.class); + + String type = contentMap.get("type"); + + // 严格验证二维码类型,防止前端伪造 + if (!isValidQrCodeType(type, "encrypted")) { + throw new RuntimeException("无效的二维码类型或二维码已被篡改"); + } + + String token = contentMap.get("token"); + String encryptedData = contentMap.get("data"); + + // 验证必要字段 + if (StrUtil.isBlank(token) || StrUtil.isBlank(encryptedData)) { + throw new RuntimeException("二维码数据不完整"); + } + + String businessType = contentMap.get("businessType"); // 获取业务类型 + + // 解密数据(自包含模式:token就在二维码中) + String originalData = decryptData(token, encryptedData); + + // 返回包含业务类型的完整结果 + return QrCodeDecryptResult.createEncryptedResult(originalData, businessType); + + } catch (Exception e) { + throw new RuntimeException("验证和解密二维码失败: " + e.getMessage(), e); + } + } + + /** + * 验证并解密二维码内容(业务模式) + * 适用于门店核销场景:门店有统一的解密密钥 + * + * @param qrContent 二维码扫描得到的内容 + * @param businessKey 业务密钥(如门店密钥) + * @return 解密后的原始数据 + */ + public String verifyAndDecryptQrCodeWithBusinessKey(String qrContent, String businessKey) { + try { + // 解析二维码内容 + @SuppressWarnings("unchecked") + Map contentMap = JSONObject.parseObject(qrContent, Map.class); + + String type = contentMap.get("type"); + if (!"business_encrypted".equals(type)) { + throw new RuntimeException("不是业务加密类型的二维码"); + } + + String encryptedData = contentMap.get("data"); + String qrId = contentMap.get("qrId"); // 二维码唯一ID + + // 验证二维码是否已被使用(防止重复核销) + String usedKey = "qr_used:" + qrId; + if (StrUtil.isNotBlank(redisUtil.get(usedKey))) { + throw new RuntimeException("二维码已被使用"); + } + + // 使用业务密钥解密 + AES aes = createAESFromToken(businessKey); + String decryptedData = aes.decryptStr(encryptedData, CharsetUtil.CHARSET_UTF_8); + + // 标记二维码为已使用(24小时过期,防止重复使用) + redisUtil.set(usedKey, "used", 24L, TimeUnit.HOURS); + + return decryptedData; + + } catch (Exception e) { + throw new RuntimeException("业务验证和解密二维码失败: " + e.getMessage(), e); + } + } + + /** + * 删除token(使二维码失效) + * + * @param token 要删除的token + */ + public void invalidateToken(String token) { + if (StrUtil.isNotBlank(token)) { + String redisKey = QR_TOKEN_PREFIX + token; + redisUtil.delete(redisKey); + } + } + + /** + * 检查token是否有效 + * + * @param token 要检查的token + * @return true表示有效,false表示无效或过期 + */ + public boolean isTokenValid(String token) { + if (StrUtil.isBlank(token)) { + return false; + } + + String redisKey = QR_TOKEN_PREFIX + token; + String data = redisUtil.get(redisKey); + return StrUtil.isNotBlank(data); + } + + /** + * 验证二维码类型是否有效 + * + * @param actualType 实际的类型 + * @param expectedType 期望的类型 + * @return true表示有效,false表示无效 + */ + private boolean isValidQrCodeType(String actualType, String expectedType) { + return expectedType.equals(actualType); + } + + /** + * 根据token创建AES加密器 + * + * @param token 密钥token + * @return AES加密器 + */ + private AES createAESFromToken(String token) { + // 使用token生成固定长度的密钥 + String keyString = SecureUtil.md5(token); + // 取前16字节作为AES密钥 + byte[] keyBytes = keyString.substring(0, 16).getBytes(CharsetUtil.CHARSET_UTF_8); + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, SymmetricAlgorithm.AES.getValue()); + return SecureUtil.aes(secretKey.getEncoded()); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/FileServerUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/FileServerUtil.java new file mode 100644 index 0000000..45201d2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/FileServerUtil.java @@ -0,0 +1,401 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.tika.Tika; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 文件上传下载工具类 + * + * @author WebSoft + * @since 2018-12-14 08:38:53 + */ +public class FileServerUtil { + // 除 text/* 外也需要设置输出编码的 content-type + private final static List SET_CHARSET_CONTENT_TYPES = Arrays.asList( + "application/json", + "application/javascript" + ); + + /** + * 上传文件 + * + * @param file MultipartFile + * @param directory 文件保存的目录 + * @param uuidName 是否用uuid命名 + * @return File + */ + public static File upload(MultipartFile file, String directory, boolean uuidName) + throws IOException, IllegalStateException { + File outFile = getUploadFile(file.getOriginalFilename(), directory, uuidName); + if (!outFile.getParentFile().exists()) { + if (!outFile.getParentFile().mkdirs()) { + throw new RuntimeException("make directory fail"); + } + } + file.transferTo(outFile); + return outFile; + } + + /** + * 上传base64格式文件 + * + * @param base64 base64编码字符 + * @param fileName 文件名称, 为空使用uuid命名 + * @param directory 文件保存的目录 + * @return File + */ + public static File upload(String base64, String fileName, String directory) + throws FileNotFoundException, IORuntimeException { + if (StrUtil.isBlank(base64) || !base64.startsWith("data:image/") || !base64.contains(";base64,")) { + throw new RuntimeException("base64 data error"); + } + String suffix = "." + base64.substring(11, base64.indexOf(";")); // 获取文件后缀 + boolean uuidName = StrUtil.isBlank(fileName); + File outFile = getUploadFile(uuidName ? suffix : fileName, directory, uuidName); + byte[] bytes = Base64.getDecoder().decode(base64.substring(base64.indexOf(";") + 8).getBytes()); + IoUtil.write(new FileOutputStream(outFile), true, bytes); + return outFile; + } + + /** + * 获取上传文件位置 + * + * @param name 文件名称 + * @param directory 上传目录 + * @param uuidName 是否使用uuid命名 + * @return File + */ + public static File getUploadFile(String name, String directory, boolean uuidName) { + // 当前日期作为上传子目录 + String dir = new SimpleDateFormat("yyyyMMdd/").format(new Date()); + // 获取文件后缀 + String suffix = (name == null || !name.contains(".")) ? "" : name.substring(name.lastIndexOf(".")); + // 使用uuid命名 + if (uuidName || name == null) { + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + return new File(directory, dir + uuid + suffix); + } + // 使用原名称, 存在相同则加(1) + File file = new File(directory, dir + name); + String prefix = StrUtil.removeSuffix(name, suffix); + int sameSize = 2; + while (file.exists()) { + file = new File(directory, dir + prefix + "(" + sameSize + ")" + suffix); + sameSize++; + } + return file; + } + + /** + * 查看文件, 支持断点续传 + * + * @param file 文件 + * @param pdfDir office转pdf输出目录 + * @param officeHome openOffice安装目录 + * @param response HttpServletResponse + * @param request HttpServletRequest + */ + public static void preview(File file, String pdfDir, String officeHome, + HttpServletResponse response, HttpServletRequest request) { + preview(file, false, null, pdfDir, officeHome, response, request); + } + + /** + * 查看文件, 支持断点续传 + * + * @param file 文件 + * @param forceDownload 是否强制下载 + * @param fileName 强制下载的文件名称 + * @param pdfDir office转pdf输出目录 + * @param officeHome openOffice安装目录 + * @param response HttpServletResponse + * @param request HttpServletRequest + */ + public static void preview(File file, boolean forceDownload, String fileName, String pdfDir, String officeHome, + HttpServletResponse response, HttpServletRequest request) { + CommonUtil.addCrossHeaders(response); + if (file == null || !file.exists()) { + outNotFund(response); + return; + } + if (forceDownload) { + setDownloadHeader(response, StrUtil.isBlank(fileName) ? file.getName() : fileName); + } else { + // office转pdf预览 + if (OpenOfficeUtil.canConverter(file.getName())) { + File pdfFile = OpenOfficeUtil.converterToPDF(file.getAbsolutePath(), pdfDir, officeHome); + if (pdfFile != null) { + file = pdfFile; + } + } + // 获取文件类型 + String contentType = getContentType(file); + if (contentType != null) { + response.setContentType(contentType); + // 设置编码 + if (contentType.startsWith("text/") || SET_CHARSET_CONTENT_TYPES.contains(contentType)) { + try { + String charset = JChardetFacadeUtil.detectCodepage(file.toURI().toURL()); + if (charset != null) { + response.setCharacterEncoding(charset); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + } else { + setDownloadHeader(response, file.getName()); + } + } + response.setHeader("Cache-Control", "public"); + output(file, response, request); + } + + /** + * 查看缩略图 + * + * @param file 原文件 + * @param thumbnail 缩略图文件 + * @param size 缩略图文件的最大值(kb) + * @param response HttpServletResponse + * @param request HttpServletRequest + */ + public static void previewThumbnail(File file, File thumbnail, Integer size, + HttpServletResponse response, HttpServletRequest request) { + // 如果是图片并且缩略图不存在则生成 + if (!thumbnail.exists() && isImage(file)) { + long fileSize = file.length(); + if ((fileSize / 1024) > size) { + try { + if (thumbnail.getParentFile().mkdirs()) { + System.out.println("生成缩略图1>>>>>>>>>>>>>>>> = " + thumbnail); + ImgUtil.scale(file, thumbnail, size / (fileSize / 1024f)); + if (thumbnail.exists() && thumbnail.length() > file.length()) { + FileUtil.copy(file, thumbnail, true); + } + }else{ + System.out.println("生成缩略图2>>>>>>>>>>>>>>>> = " + thumbnail); + ImgUtil.scale(file, thumbnail, size / (fileSize / 1024f)); + if (thumbnail.exists() && thumbnail.length() > file.length()) { + FileUtil.copy(file, thumbnail, true); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } else { + preview(file, null, null, response, request); + return; + } + } + preview(thumbnail.exists() ? thumbnail : file, null, null, response, request); + } + + /** + * 输出文件流, 支持断点续传 + * + * @param file 文件 + * @param response HttpServletResponse + * @param request HttpServletRequest + */ + public static void output(File file, HttpServletResponse response, HttpServletRequest request) { + long length = file.length(); // 文件总大小 + long start = 0, to = length - 1; // 开始读取位置, 结束读取位置 + long lastModified = file.lastModified(); // 文件修改时间 + response.setHeader("Accept-Ranges", "bytes"); + response.setHeader("ETag", "\"" + length + "-" + lastModified + "\""); + response.setHeader("Last-Modified", new Date(lastModified).toString()); + String range = request.getHeader("Range"); + if (range != null) { + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + String[] ranges = range.replace("bytes=", "").split("-"); + start = Long.parseLong(ranges[0].trim()); + if (ranges.length > 1) { + to = Long.parseLong(ranges[1].trim()); + } + response.setHeader("Content-Range", "bytes " + start + "-" + to + "/" + length); + } + response.setHeader("Content-Length", String.valueOf(to - start + 1)); + try { + output(file, response.getOutputStream(), 2048, start, to); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 输出文件流 + * + * @param file 文件 + * @param os 输出流 + */ + public static void output(File file, OutputStream os) { + output(file, os, null); + } + + /** + * 输出文件流 + * + * @param file 文件 + * @param os 输出流 + * @param size 读取缓冲区大小 + */ + public static void output(File file, OutputStream os, Integer size) { + output(file, os, size, null, null); + } + + /** + * 输出文件流, 支持分片 + * + * @param file 文件 + * @param os 输出流 + * @param size 读取缓冲区大小 + * @param start 开始位置 + * @param to 结束位置 + */ + public static void output(File file, OutputStream os, Integer size, Long start, Long to) { + BufferedInputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + if (start != null) { + long skip = is.skip(start); + if (skip < start) { + System.out.println("ERROR: skip fail[ skipped=" + skip + ", start= " + start + " ]"); + } + to = to - start + 1; + } + byte[] bytes = new byte[size == null ? 2048 : size]; + int len; + if (to == null) { + while ((len = is.read(bytes)) != -1) { + os.write(bytes, 0, len); + } + } else { + while (to > 0 && (len = is.read(bytes)) != -1) { + os.write(bytes, 0, to < len ? (int) ((long) to) : len); + to -= len; + } + } + os.flush(); + } catch (IOException ignored) { + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException ignored) { + } + } + if (is != null) { + try { + is.close(); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + } + } + + /** + * 获取文件类型 + * + * @param file 文件 + * @return String + */ + public static String getContentType(File file) { + String contentType = null; + if (file.exists()) { + try { + contentType = new Tika().detect(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + return contentType; + } + + /** + * 判断文件是否是图片类型 + * + * @param file 文件 + * @return boolean + */ + public static boolean isImage(File file) { + return isImage(getContentType(file)); + } + + /** + * 判断文件是否是图片类型 + * + * @param contentType 文件类型 + * @return boolean + */ + public static boolean isImage(String contentType) { + return contentType != null && contentType.startsWith("image/"); + } + + /** + * 设置下载文件的header + * + * @param response HttpServletResponse + * @param fileName 文件名称 + */ + public static void setDownloadHeader(HttpServletResponse response, String fileName) { + response.setContentType("application/force-download"); + try { + fileName = URLEncoder.encode(fileName, "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + response.setHeader("Content-Disposition", "attachment;fileName=" + fileName); + } + + /** + * 输出404错误页面 + * + * @param response HttpServletResponse + */ + public static void outNotFund(HttpServletResponse response) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + outMessage("404 Not Found", null, response); + } + + /** + * 输出错误页面 + * + * @param title 标题 + * @param message 内容 + * @param response HttpServletResponse + */ + public static void outMessage(String title, String message, HttpServletResponse response) { + response.setContentType("text/html;charset=UTF-8"); + try { + PrintWriter writer = response.getWriter(); + writer.write(""); + writer.write("" + title + ""); + writer.write("

" + title + "

"); + if (message != null) { + writer.write(message); + } + writer.write("

WebSoft File Server

"); + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/HttpUtils.java b/src/main/java/com/gxwebsoft/common/core/utils/HttpUtils.java new file mode 100644 index 0000000..888acc2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/HttpUtils.java @@ -0,0 +1,311 @@ +package com.gxwebsoft.common.core.utils; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class HttpUtils { + + /** + * get + * + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @return + * @throws Exception + */ + public static HttpResponse doGet(String host, String path, String method, + Map headers, + Map querys) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpGet request = new HttpGet(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + return httpClient.execute(request); + } + + /** + * post form + * + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @param bodys + * @return + * @throws Exception + */ + public static HttpResponse doPost(String host, String path, String method, + Map headers, + Map querys, + Map bodys) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpPost request = new HttpPost(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + if (bodys != null) { + List nameValuePairList = new ArrayList(); + + for (String key : bodys.keySet()) { + nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key))); + } + UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8"); + formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8"); + request.setEntity(formEntity); + } + + return httpClient.execute(request); + } + + /** + * Post String + * + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @param body + * @return + * @throws Exception + */ + public static HttpResponse doPost(String host, String path, String method, + Map headers, + Map querys, + String body) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpPost request = new HttpPost(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + if (StringUtils.isNotBlank(body)) { + request.setEntity(new StringEntity(body, "utf-8")); + } + + return httpClient.execute(request); + } + + /** + * Post stream + * + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @param body + * @return + * @throws Exception + */ + public static HttpResponse doPost(String host, String path, String method, + Map headers, + Map querys, + byte[] body) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpPost request = new HttpPost(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + if (body != null) { + request.setEntity(new ByteArrayEntity(body)); + } + + return httpClient.execute(request); + } + + /** + * Put String + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @param body + * @return + * @throws Exception + */ + public static HttpResponse doPut(String host, String path, String method, + Map headers, + Map querys, + String body) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpPut request = new HttpPut(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + if (StringUtils.isNotBlank(body)) { + request.setEntity(new StringEntity(body, "utf-8")); + } + + return httpClient.execute(request); + } + + /** + * Put stream + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @param body + * @return + * @throws Exception + */ + public static HttpResponse doPut(String host, String path, String method, + Map headers, + Map querys, + byte[] body) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpPut request = new HttpPut(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + if (body != null) { + request.setEntity(new ByteArrayEntity(body)); + } + + return httpClient.execute(request); + } + + /** + * Delete + * + * @param host + * @param path + * @param method + * @param headers + * @param querys + * @return + * @throws Exception + */ + public static HttpResponse doDelete(String host, String path, String method, + Map headers, + Map querys) + throws Exception { + HttpClient httpClient = wrapClient(host); + + HttpDelete request = new HttpDelete(buildUrl(host, path, querys)); + for (Map.Entry e : headers.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + + return httpClient.execute(request); + } + + private static String buildUrl(String host, String path, Map querys) throws UnsupportedEncodingException { + StringBuilder sbUrl = new StringBuilder(); + sbUrl.append(host); + if (!StringUtils.isBlank(path)) { + sbUrl.append(path); + } + if (null != querys) { + StringBuilder sbQuery = new StringBuilder(); + for (Map.Entry query : querys.entrySet()) { + if (0 < sbQuery.length()) { + sbQuery.append("&"); + } + if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) { + sbQuery.append(query.getValue()); + } + if (!StringUtils.isBlank(query.getKey())) { + sbQuery.append(query.getKey()); + if (!StringUtils.isBlank(query.getValue())) { + sbQuery.append("="); + sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8")); + } + } + } + if (0 < sbQuery.length()) { + sbUrl.append("?").append(sbQuery); + } + } + + return sbUrl.toString(); + } + + private static HttpClient wrapClient(String host) { + HttpClient httpClient = new DefaultHttpClient(); + if (host.startsWith("https://")) { + sslClient(httpClient); + } + + return httpClient; + } + + private static void sslClient(HttpClient httpClient) { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + X509TrustManager tm = new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted(X509Certificate[] xcs, String str) { + + } + public void checkServerTrusted(X509Certificate[] xcs, String str) { + + } + }; + ctx.init(null, new TrustManager[] { tm }, null); + SSLSocketFactory ssf = new SSLSocketFactory(ctx); + ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + ClientConnectionManager ccm = httpClient.getConnectionManager(); + SchemeRegistry registry = ccm.getSchemeRegistry(); + registry.register(new Scheme("https", 443, ssf)); + } catch (KeyManagementException ex) { + throw new RuntimeException(ex); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/common/core/utils/ImageUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/ImageUtil.java new file mode 100644 index 0000000..b345923 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/ImageUtil.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.codec.Base64Encoder; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Iterator; +import java.io.File; + +public class ImageUtil { + public static String ImageBase64(String imgUrl) { + URL url = null; + InputStream is = null; + ByteArrayOutputStream outStream = null; + HttpURLConnection httpUrl = null; + try{ + url = new URL(imgUrl); + httpUrl = (HttpURLConnection) url.openConnection(); + httpUrl.connect(); + httpUrl.getInputStream(); + is = httpUrl.getInputStream(); + + outStream = new ByteArrayOutputStream(); + //创建一个Buffer字符串 + byte[] buffer = new byte[1024]; + //每次读取的字符串长度,如果为-1,代表全部读取完毕 + int len = 0; + //使用一个输入流从buffer里把数据读取出来 + while( (len=is.read(buffer)) != -1 ){ + //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 + outStream.write(buffer, 0, len); + } + // 对字节数组Base64编码 + return new Base64Encoder().encode(outStream.toByteArray()); + }catch (Exception e) { + e.printStackTrace(); + } + finally{ + if(is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if(outStream != null) { + try { + outStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if(httpUrl != null) { + httpUrl.disconnect(); + } + } + return null; + } + + + public static void adjustQuality(File inputFile, File outputFile, float quality) throws IOException { + // 读取图片文件 + BufferedImage image = ImageIO.read(inputFile); + + // 获取JPEG ImageWriters的迭代器 + Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); + ImageWriter writer = iter.next(); + + // 创建输出文件 + ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile); + writer.setOutput(ios); + + // 创建ImageWriteParam并设置压缩质量 + ImageWriteParam iwp = writer.getDefaultWriteParam(); + if (iwp.canWriteCompressed()) { + iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + iwp.setCompressionQuality(quality); // 设置质量,1.0为最好,0.0最差 + } + + // 写入图片 + writer.write(null, new IIOImage(image, null, null), iwp); + writer.dispose(); + ios.close(); + } + +} + diff --git a/src/main/java/com/gxwebsoft/common/core/utils/JChardetFacadeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/JChardetFacadeUtil.java new file mode 100644 index 0000000..1b49fb7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/JChardetFacadeUtil.java @@ -0,0 +1,2025 @@ +package com.gxwebsoft.common.core.utils; + +import java.io.*; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; + +/** + * 文件编码检测工具, 核心代码来自 cpDetector 和 jChardet, 可以检测大多数文件的编码 + * + * @author WebSoft + * @since 2020-09-15 09:24:20 + */ +public class JChardetFacadeUtil { + + public static String detectCodepage(URL url) { + try { + Charset ret = JChardetFacade.getInstance().detectCodepage(url); + return ret == null ? null : ret.name(); + } catch (Exception ignored) { + } + return null; + } + + /** + * 下面代码来自: https://github.com/r91987/cpdetector + */ + public static class JChardetFacade extends AbstractCodepageDetector implements nsICharsetDetectionObserver { + private static JChardetFacade instance = null; + private static nsDetector det; + private byte[] buf = new byte[4096]; + private Charset codpage = null; + private boolean m_guessing = true; + private int amountOfVerifiers = 0; + + private JChardetFacade() { + det = new nsDetector(0); + det.Init(this); + this.amountOfVerifiers = det.getProbableCharsets().length; + } + + public static JChardetFacade getInstance() { + if (instance == null) { + instance = new JChardetFacade(); + } + + return instance; + } + + public synchronized Charset detectCodepage(InputStream in, int length) throws IOException { + this.Reset(); + int read = 0; + boolean done = false; + boolean isAscii = true; + Charset ret = null; + + int len; + do { + len = in.read(this.buf, 0, Math.min(this.buf.length, length - read)); + if (len > 0) { + read += len; + } + + if (!done) { + done = det.DoIt(this.buf, len, false); + } + } while (len > 0 && !done); + + det.DataEnd(); + if (this.codpage == null) { + if (this.m_guessing) { + ret = this.guess(); + } + } else { + ret = this.codpage; + } + return ret; + } + + private Charset guess() { + Charset ret = null; + String[] possibilities = det.getProbableCharsets(); + if (possibilities.length == this.amountOfVerifiers) { + ret = Charset.forName("US-ASCII"); + } else { + String check = possibilities[0]; + if (!check.equalsIgnoreCase("nomatch")) { + for (int i = 0; ret == null && i < possibilities.length; ++i) { + try { + ret = Charset.forName(possibilities[i]); + } catch (UnsupportedCharsetException ignored) { + } + } + } + } + return ret; + } + + public void Notify(String charset) { + this.codpage = Charset.forName(charset); + } + + public void Reset() { + det.Reset(); + this.codpage = null; + } + + public boolean isGuessing() { + return this.m_guessing; + } + + public synchronized void setGuessing(boolean guessing) { + this.m_guessing = guessing; + } + } + + /** + * + */ + public static abstract class AbstractCodepageDetector implements ICodepageDetector { + public AbstractCodepageDetector() { + } + + public Charset detectCodepage(URL url) throws IOException { + BufferedInputStream in = new BufferedInputStream(url.openStream()); + Charset result = this.detectCodepage(in, 2147483647); + in.close(); + return result; + } + + public final Reader open(URL url) throws IOException { + Reader ret = null; + Charset cs = this.detectCodepage(url); + if (cs != null) { + ret = new InputStreamReader(new BufferedInputStream(url.openStream()), cs); + } + + return ret; + } + + public int compareTo(Object o) { + String other = o.getClass().getName(); + String mine = this.getClass().getName(); + return mine.compareTo(other); + } + } + + /** + * + */ + interface ICodepageDetector extends Serializable, Comparable { + Reader open(URL var1) throws IOException; + + Charset detectCodepage(URL var1) throws IOException; + + Charset detectCodepage(InputStream var1, int var2) throws IOException; + } + + /** + * 以下代码开始是由Mozilla组织提供的JChardet, 它可以检测大多数文件的编码 + * http://jchardet.sourceforge.net/ + */ + public static class nsDetector extends nsPSMDetector implements nsICharsetDetector { + nsICharsetDetectionObserver mObserver = null; + + public nsDetector() { + } + + public nsDetector(int var1) { + super(var1); + } + + public void Init(nsICharsetDetectionObserver var1) { + this.mObserver = var1; + } + + public boolean DoIt(byte[] var1, int var2, boolean var3) { + if (var1 != null && !var3) { + this.HandleData(var1, var2); + return this.mDone; + } else { + return false; + } + } + + public void Done() { + this.DataEnd(); + } + + public void Report(String var1) { + if (this.mObserver != null) { + this.mObserver.Notify(var1); + } + + } + + public boolean isAscii(byte[] var1, int var2) { + for (int var3 = 0; var3 < var2; ++var3) { + if ((128 & var1[var3]) != 0) { + return false; + } + } + + return true; + } + } + + /** + * + */ + public static abstract class nsPSMDetector { + public static final int ALL = 0; + public static final int JAPANESE = 1; + public static final int CHINESE = 2; + public static final int SIMPLIFIED_CHINESE = 3; + public static final int TRADITIONAL_CHINESE = 4; + public static final int KOREAN = 5; + public static final int NO_OF_LANGUAGES = 6; + public static final int MAX_VERIFIERS = 16; + nsVerifier[] mVerifier; + nsEUCStatistics[] mStatisticsData; + nsEUCSampler mSampler = new nsEUCSampler(); + byte[] mState = new byte[16]; + int[] mItemIdx = new int[16]; + int mItems; + int mClassItems; + boolean mDone; + boolean mRunSampler; + boolean mClassRunSampler; + + public nsPSMDetector() { + this.initVerifiers(0); + this.Reset(); + } + + public nsPSMDetector(int var1) { + this.initVerifiers(var1); + this.Reset(); + } + + public nsPSMDetector(int var1, nsVerifier[] var2, nsEUCStatistics[] var3) { + this.mClassRunSampler = var3 != null; + this.mStatisticsData = var3; + this.mVerifier = var2; + this.mClassItems = var1; + this.Reset(); + } + + public void Reset() { + this.mRunSampler = this.mClassRunSampler; + this.mDone = false; + this.mItems = this.mClassItems; + + for (int var1 = 0; var1 < this.mItems; this.mItemIdx[var1] = var1++) { + this.mState[var1] = 0; + } + + this.mSampler.Reset(); + } + + protected void initVerifiers(int var1) { + boolean var2 = false; + int var3; + if (var1 >= 0 && var1 < 6) { + var3 = var1; + } else { + var3 = 0; + } + + this.mVerifier = null; + this.mStatisticsData = null; + if (var3 == 4) { + this.mVerifier = new nsVerifier[]{new nsUTF8Verifier(), new nsBIG5Verifier(), new nsISO2022CNVerifier(), new nsEUCTWVerifier(), new nsCP1252Verifier(), new nsUCS2BEVerifier(), new nsUCS2LEVerifier()}; + this.mStatisticsData = new nsEUCStatistics[]{null, new Big5Statistics(), null, new EUCTWStatistics(), null, null, null}; + } else if (var3 == 5) { + this.mVerifier = new nsVerifier[]{new nsUTF8Verifier(), new nsEUCKRVerifier(), new nsISO2022KRVerifier(), new nsCP1252Verifier(), new nsUCS2BEVerifier(), new nsUCS2LEVerifier()}; + } else if (var3 == 3) { + this.mVerifier = new nsVerifier[]{new nsUTF8Verifier(), new nsGB2312Verifier(), new nsGB18030Verifier(), new nsISO2022CNVerifier(), new nsHZVerifier(), new nsCP1252Verifier(), new nsUCS2BEVerifier(), new nsUCS2LEVerifier()}; + } else if (var3 == 1) { + this.mVerifier = new nsVerifier[]{new nsUTF8Verifier(), new nsSJISVerifier(), new nsEUCJPVerifier(), new nsISO2022JPVerifier(), new nsCP1252Verifier(), new nsUCS2BEVerifier(), new nsUCS2LEVerifier()}; + } else if (var3 == 2) { + this.mVerifier = new nsVerifier[]{new nsUTF8Verifier(), new nsGB2312Verifier(), new nsGB18030Verifier(), new nsBIG5Verifier(), new nsISO2022CNVerifier(), new nsHZVerifier(), new nsEUCTWVerifier(), new nsCP1252Verifier(), new nsUCS2BEVerifier(), new nsUCS2LEVerifier()}; + this.mStatisticsData = new nsEUCStatistics[]{null, new GB2312Statistics(), null, new Big5Statistics(), null, null, new EUCTWStatistics(), null, null, null}; + } else if (var3 == 0) { + this.mVerifier = new nsVerifier[]{new nsUTF8Verifier(), new nsSJISVerifier(), new nsEUCJPVerifier(), new nsISO2022JPVerifier(), new nsEUCKRVerifier(), new nsISO2022KRVerifier(), new nsBIG5Verifier(), new nsEUCTWVerifier(), new nsGB2312Verifier(), new nsGB18030Verifier(), new nsISO2022CNVerifier(), new nsHZVerifier(), new nsCP1252Verifier(), new nsUCS2BEVerifier(), new nsUCS2LEVerifier()}; + this.mStatisticsData = new nsEUCStatistics[]{null, null, new EUCJPStatistics(), null, new EUCKRStatistics(), null, new Big5Statistics(), new EUCTWStatistics(), new GB2312Statistics(), null, null, null, null, null, null}; + } + + this.mClassRunSampler = this.mStatisticsData != null; + this.mClassItems = this.mVerifier.length; + } + + public abstract void Report(String var1); + + public boolean HandleData(byte[] var1, int var2) { + for (int var3 = 0; var3 < var2; ++var3) { + byte var5 = var1[var3]; + int var4 = 0; + + while (var4 < this.mItems) { + byte var6 = nsVerifier.getNextState(this.mVerifier[this.mItemIdx[var4]], var5, this.mState[var4]); + if (var6 == 2) { + this.Report(this.mVerifier[this.mItemIdx[var4]].charset()); + this.mDone = true; + return this.mDone; + } + + if (var6 == 1) { + --this.mItems; + if (var4 < this.mItems) { + this.mItemIdx[var4] = this.mItemIdx[this.mItems]; + this.mState[var4] = this.mState[this.mItems]; + } + } else { + this.mState[var4++] = var6; + } + } + + if (this.mItems <= 1) { + if (1 == this.mItems) { + this.Report(this.mVerifier[this.mItemIdx[0]].charset()); + } + + this.mDone = true; + return this.mDone; + } + + int var7 = 0; + int var8 = 0; + + for (var4 = 0; var4 < this.mItems; ++var4) { + if (!this.mVerifier[this.mItemIdx[var4]].isUCS2() && !this.mVerifier[this.mItemIdx[var4]].isUCS2()) { + ++var7; + var8 = var4; + } + } + + if (1 == var7) { + this.Report(this.mVerifier[this.mItemIdx[var8]].charset()); + this.mDone = true; + return this.mDone; + } + } + + if (this.mRunSampler) { + this.Sample(var1, var2); + } + + return this.mDone; + } + + public void DataEnd() { + if (!this.mDone) { + if (this.mItems == 2) { + if (this.mVerifier[this.mItemIdx[0]].charset().equals("GB18030")) { + this.Report(this.mVerifier[this.mItemIdx[1]].charset()); + this.mDone = true; + } else if (this.mVerifier[this.mItemIdx[1]].charset().equals("GB18030")) { + this.Report(this.mVerifier[this.mItemIdx[0]].charset()); + this.mDone = true; + } + } + + if (this.mRunSampler) { + this.Sample((byte[]) null, 0, true); + } + + } + } + + public void Sample(byte[] var1, int var2) { + this.Sample(var1, var2, false); + } + + public void Sample(byte[] var1, int var2, boolean var3) { + int var4 = 0; + int var6 = 0; + + int var5; + for (var5 = 0; var5 < this.mItems; ++var5) { + if (null != this.mStatisticsData[this.mItemIdx[var5]]) { + ++var6; + } + + if (!this.mVerifier[this.mItemIdx[var5]].isUCS2() && !this.mVerifier[this.mItemIdx[var5]].charset().equals("GB18030")) { + ++var4; + } + } + + this.mRunSampler = var6 > 1; + if (this.mRunSampler) { + this.mRunSampler = this.mSampler.Sample(var1, var2); + if ((var3 && this.mSampler.GetSomeData() || this.mSampler.EnoughData()) && var6 == var4) { + this.mSampler.CalFreq(); + int var7 = -1; + int var8 = 0; + float var9 = 0.0F; + + for (var5 = 0; var5 < this.mItems; ++var5) { + if (null != this.mStatisticsData[this.mItemIdx[var5]] && !this.mVerifier[this.mItemIdx[var5]].charset().equals("Big5")) { + float var10 = this.mSampler.GetScore(this.mStatisticsData[this.mItemIdx[var5]].mFirstByteFreq(), this.mStatisticsData[this.mItemIdx[var5]].mFirstByteWeight(), this.mStatisticsData[this.mItemIdx[var5]].mSecondByteFreq(), this.mStatisticsData[this.mItemIdx[var5]].mSecondByteWeight()); + if (0 == var8++ || var9 > var10) { + var9 = var10; + var7 = var5; + } + } + } + + if (var7 >= 0) { + this.Report(this.mVerifier[this.mItemIdx[var7]].charset()); + this.mDone = true; + } + } + } + + } + + public String[] getProbableCharsets() { + String[] var1; + if (this.mItems <= 0) { + var1 = new String[]{"nomatch"}; + return var1; + } else { + var1 = new String[this.mItems]; + + for (int var2 = 0; var2 < this.mItems; ++var2) { + var1[var2] = this.mVerifier[this.mItemIdx[var2]].charset(); + } + + return var1; + } + } + } + + /** + * + */ + public static interface nsICharsetDetectionObserver { + void Notify(String var1); + } + + /** + * + */ + public static interface nsICharsetDetector { + void Init(nsICharsetDetectionObserver var1); + + boolean DoIt(byte[] var1, int var2, boolean var3); + + void Done(); + } + + /** + * + */ + public static abstract class nsVerifier { + static final byte eStart = 0; + static final byte eError = 1; + static final byte eItsMe = 2; + static final int eidxSft4bits = 3; + static final int eSftMsk4bits = 7; + static final int eBitSft4bits = 2; + static final int eUnitMsk4bits = 15; + + nsVerifier() { + } + + public abstract String charset(); + + public abstract int stFactor(); + + public abstract int[] cclass(); + + public abstract int[] states(); + + public abstract boolean isUCS2(); + + public static byte getNextState(nsVerifier var0, byte var1, byte var2) { + return (byte) (255 & var0.states()[(var2 * var0.stFactor() + (var0.cclass()[(var1 & 255) >> 3] >> ((var1 & 7) << 2) & 15) & 255) >> 3] >> ((var2 * var0.stFactor() + (var0.cclass()[(var1 & 255) >> 3] >> ((var1 & 7) << 2) & 15) & 255 & 7) << 2) & 15); + } + } + + /** + * + */ + public static class nsEUCSampler { + int mTotal = 0; + int mThreshold = 200; + int mState = 0; + public int[] mFirstByteCnt = new int[94]; + public int[] mSecondByteCnt = new int[94]; + public float[] mFirstByteFreq = new float[94]; + public float[] mSecondByteFreq = new float[94]; + + public nsEUCSampler() { + this.Reset(); + } + + public void Reset() { + this.mTotal = 0; + this.mState = 0; + + for (int var1 = 0; var1 < 94; ++var1) { + this.mFirstByteCnt[var1] = this.mSecondByteCnt[var1] = 0; + } + + } + + boolean EnoughData() { + return this.mTotal > this.mThreshold; + } + + boolean GetSomeData() { + return this.mTotal > 1; + } + + boolean Sample(byte[] var1, int var2) { + if (this.mState == 1) { + return false; + } else { + int var3 = 0; + + for (int var4 = 0; var4 < var2 && 1 != this.mState; ++var3) { + int var10002; + switch (this.mState) { + case 0: + if ((var1[var3] & 128) != 0) { + if (255 != (255 & var1[var3]) && 161 <= (255 & var1[var3])) { + ++this.mTotal; + var10002 = this.mFirstByteCnt[(255 & var1[var3]) - 161]++; + this.mState = 2; + } else { + this.mState = 1; + } + } + case 1: + break; + case 2: + if ((var1[var3] & 128) != 0) { + if (255 != (255 & var1[var3]) && 161 <= (255 & var1[var3])) { + ++this.mTotal; + var10002 = this.mSecondByteCnt[(255 & var1[var3]) - 161]++; + this.mState = 0; + } else { + this.mState = 1; + } + } else { + this.mState = 1; + } + break; + default: + this.mState = 1; + } + + ++var4; + } + + return 1 != this.mState; + } + } + + void CalFreq() { + for (int var1 = 0; var1 < 94; ++var1) { + this.mFirstByteFreq[var1] = (float) this.mFirstByteCnt[var1] / (float) this.mTotal; + this.mSecondByteFreq[var1] = (float) this.mSecondByteCnt[var1] / (float) this.mTotal; + } + + } + + float GetScore(float[] var1, float var2, float[] var3, float var4) { + return var2 * this.GetScore(var1, this.mFirstByteFreq) + var4 * this.GetScore(var3, this.mSecondByteFreq); + } + + float GetScore(float[] var1, float[] var2) { + float var4 = 0.0F; + + for (int var5 = 0; var5 < 94; ++var5) { + float var3 = var1[var5] - var2[var5]; + var4 += var3 * var3; + } + + return (float) Math.sqrt((double) var4) / 94.0F; + } + } + + /** + * + */ + public static abstract class nsEUCStatistics { + public abstract float[] mFirstByteFreq(); + + public abstract float mFirstByteStdDev(); + + public abstract float mFirstByteMean(); + + public abstract float mFirstByteWeight(); + + public abstract float[] mSecondByteFreq(); + + public abstract float mSecondByteStdDev(); + + public abstract float mSecondByteMean(); + + public abstract float mSecondByteWeight(); + + public nsEUCStatistics() { + } + } + + /** + * + */ + public static class EUCJPStatistics extends nsEUCStatistics { + static float[] mFirstByteFreq; + static float mFirstByteStdDev; + static float mFirstByteMean; + static float mFirstByteWeight; + static float[] mSecondByteFreq; + static float mSecondByteStdDev; + static float mSecondByteMean; + static float mSecondByteWeight; + + public float[] mFirstByteFreq() { + return mFirstByteFreq; + } + + public float mFirstByteStdDev() { + return mFirstByteStdDev; + } + + public float mFirstByteMean() { + return mFirstByteMean; + } + + public float mFirstByteWeight() { + return mFirstByteWeight; + } + + public float[] mSecondByteFreq() { + return mSecondByteFreq; + } + + public float mSecondByteStdDev() { + return mSecondByteStdDev; + } + + public float mSecondByteMean() { + return mSecondByteMean; + } + + public float mSecondByteWeight() { + return mSecondByteWeight; + } + + public EUCJPStatistics() { + mFirstByteFreq = new float[]{0.364808F, 0.0F, 0.0F, 0.145325F, 0.304891F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.001835F, 0.010771F, 0.006462F, 0.001157F, 0.002114F, 0.003231F, 0.001356F, 0.00742F, 0.004189F, 0.003231F, 0.003032F, 0.03319F, 0.006303F, 0.006064F, 0.009973F, 0.002354F, 0.00367F, 0.009135F, 0.001675F, 0.002792F, 0.002194F, 0.01472F, 0.011928F, 8.78E-4F, 0.013124F, 0.001077F, 0.009295F, 0.003471F, 0.002872F, 0.002433F, 9.57E-4F, 0.001636F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 8.0E-5F, 2.79E-4F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 8.0E-5F, 0.0F}; + mFirstByteStdDev = 0.050407F; + mFirstByteMean = 0.010638F; + mFirstByteWeight = 0.640871F; + mSecondByteFreq = new float[]{0.002473F, 0.039134F, 0.152745F, 0.009694F, 3.59E-4F, 0.02218F, 7.58E-4F, 0.004308F, 1.6E-4F, 0.002513F, 0.003072F, 0.001316F, 0.00383F, 0.001037F, 0.00359F, 9.57E-4F, 1.6E-4F, 2.39E-4F, 0.006462F, 0.001596F, 0.031554F, 0.001316F, 0.002194F, 0.016555F, 0.003271F, 6.78E-4F, 5.98E-4F, 0.206438F, 7.18E-4F, 0.001077F, 0.00371F, 0.001356F, 0.001356F, 4.39E-4F, 0.004388F, 0.005704F, 8.78E-4F, 0.010172F, 0.007061F, 0.01468F, 6.38E-4F, 0.02573F, 0.002792F, 7.18E-4F, 0.001795F, 0.091551F, 7.58E-4F, 0.003909F, 5.58E-4F, 0.031195F, 0.007061F, 0.001316F, 0.022579F, 0.006981F, 0.00726F, 0.001117F, 2.39E-4F, 0.012127F, 8.78E-4F, 0.00379F, 0.001077F, 7.58E-4F, 0.002114F, 0.002234F, 6.78E-4F, 0.002992F, 0.003311F, 0.023416F, 0.001237F, 0.002753F, 0.005146F, 0.002194F, 0.007021F, 0.008497F, 0.013763F, 0.011768F, 0.006303F, 0.001915F, 6.38E-4F, 0.008776F, 9.18E-4F, 0.003431F, 0.057603F, 4.39E-4F, 4.39E-4F, 7.58E-4F, 0.002872F, 0.001675F, 0.01105F, 0.0F, 2.79E-4F, 0.012127F, 7.18E-4F, 0.00738F}; + mSecondByteStdDev = 0.028247F; + mSecondByteMean = 0.010638F; + mSecondByteWeight = 0.359129F; + } + } + + /** + * + */ + public static class nsEUCJPVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsEUCJPVerifier() { + cclass = new int[32]; + cclass[0] = 1145324612; + cclass[1] = 1430537284; + cclass[2] = 1145324612; + cclass[3] = 1145328708; + cclass[4] = 1145324612; + cclass[5] = 1145324612; + cclass[6] = 1145324612; + cclass[7] = 1145324612; + cclass[8] = 1145324612; + cclass[9] = 1145324612; + cclass[10] = 1145324612; + cclass[11] = 1145324612; + cclass[12] = 1145324612; + cclass[13] = 1145324612; + cclass[14] = 1145324612; + cclass[15] = 1145324612; + cclass[16] = 1431655765; + cclass[17] = 827675989; + cclass[18] = 1431655765; + cclass[19] = 1431655765; + cclass[20] = 572662309; + cclass[21] = 572662306; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662306; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 0; + cclass[29] = 0; + cclass[30] = 0; + cclass[31] = 1342177280; + states = new int[5]; + states[0] = 286282563; + states[1] = 572657937; + states[2] = 286265378; + states[3] = 319885329; + states[4] = 4371; + charset = "EUC-JP"; + stFactor = 6; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class EUCKRStatistics extends nsEUCStatistics { + static float[] mFirstByteFreq; + static float mFirstByteStdDev; + static float mFirstByteMean; + static float mFirstByteWeight; + static float[] mSecondByteFreq; + static float mSecondByteStdDev; + static float mSecondByteMean; + static float mSecondByteWeight; + + public float[] mFirstByteFreq() { + return mFirstByteFreq; + } + + public float mFirstByteStdDev() { + return mFirstByteStdDev; + } + + public float mFirstByteMean() { + return mFirstByteMean; + } + + public float mFirstByteWeight() { + return mFirstByteWeight; + } + + public float[] mSecondByteFreq() { + return mSecondByteFreq; + } + + public float mSecondByteStdDev() { + return mSecondByteStdDev; + } + + public float mSecondByteMean() { + return mSecondByteMean; + } + + public float mSecondByteWeight() { + return mSecondByteWeight; + } + + public EUCKRStatistics() { + mFirstByteFreq = new float[]{0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 4.12E-4F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.057502F, 0.033182F, 0.002267F, 0.016076F, 0.014633F, 0.032976F, 0.004122F, 0.011336F, 0.058533F, 0.024526F, 0.025969F, 0.054411F, 0.01958F, 0.063273F, 0.113974F, 0.029885F, 0.150041F, 0.059151F, 0.002679F, 0.009893F, 0.014839F, 0.026381F, 0.015045F, 0.069456F, 0.08986F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}; + mFirstByteStdDev = 0.025593F; + mFirstByteMean = 0.010638F; + mFirstByteWeight = 0.647437F; + mSecondByteFreq = new float[]{0.016694F, 0.0F, 0.012778F, 0.030091F, 0.002679F, 0.006595F, 0.001855F, 8.24E-4F, 0.005977F, 0.00474F, 0.003092F, 8.24E-4F, 0.01958F, 0.037304F, 0.008244F, 0.014633F, 0.001031F, 0.0F, 0.003298F, 0.002061F, 0.006183F, 0.005977F, 8.24E-4F, 0.021847F, 0.014839F, 0.052968F, 0.017312F, 0.007626F, 4.12E-4F, 8.24E-4F, 0.011129F, 0.0F, 4.12E-4F, 0.001649F, 0.005977F, 0.065746F, 0.020198F, 0.021434F, 0.014633F, 0.004122F, 0.001649F, 8.24E-4F, 8.24E-4F, 0.051937F, 0.01958F, 0.023289F, 0.026381F, 0.040396F, 0.009068F, 0.001443F, 0.00371F, 0.00742F, 0.001443F, 0.01319F, 0.002885F, 4.12E-4F, 0.003298F, 0.025969F, 4.12E-4F, 4.12E-4F, 0.006183F, 0.003298F, 0.066983F, 0.002679F, 0.002267F, 0.011129F, 4.12E-4F, 0.010099F, 0.015251F, 0.007626F, 0.043899F, 0.00371F, 0.002679F, 0.001443F, 0.010923F, 0.002885F, 0.009068F, 0.019992F, 4.12E-4F, 0.00845F, 0.005153F, 0.0F, 0.010099F, 0.0F, 0.001649F, 0.01216F, 0.011542F, 0.006595F, 0.001855F, 0.010923F, 4.12E-4F, 0.023702F, 0.00371F, 0.001855F}; + mSecondByteStdDev = 0.013937F; + mSecondByteMean = 0.010638F; + mSecondByteWeight = 0.352563F; + } + } + + /** + * + */ + public static class nsEUCKRVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsEUCKRVerifier() { + cclass = new int[32]; + cclass[0] = 286331153; + cclass[1] = 1118481; + cclass[2] = 286331153; + cclass[3] = 286327057; + cclass[4] = 286331153; + cclass[5] = 286331153; + cclass[6] = 286331153; + cclass[7] = 286331153; + cclass[8] = 286331153; + cclass[9] = 286331153; + cclass[10] = 286331153; + cclass[11] = 286331153; + cclass[12] = 286331153; + cclass[13] = 286331153; + cclass[14] = 286331153; + cclass[15] = 286331153; + cclass[16] = 0; + cclass[17] = 0; + cclass[18] = 0; + cclass[19] = 0; + cclass[20] = 572662304; + cclass[21] = 858923554; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662322; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 572662306; + cclass[29] = 572662306; + cclass[30] = 572662306; + cclass[31] = 35791394; + states = new int[2]; + states[0] = 286331649; + states[1] = 1122850; + charset = "EUC-KR"; + stFactor = 4; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class EUCTWStatistics extends nsEUCStatistics { + static float[] mFirstByteFreq; + static float mFirstByteStdDev; + static float mFirstByteMean; + static float mFirstByteWeight; + static float[] mSecondByteFreq; + static float mSecondByteStdDev; + static float mSecondByteMean; + static float mSecondByteWeight; + + public float[] mFirstByteFreq() { + return mFirstByteFreq; + } + + public float mFirstByteStdDev() { + return mFirstByteStdDev; + } + + public float mFirstByteMean() { + return mFirstByteMean; + } + + public float mFirstByteWeight() { + return mFirstByteWeight; + } + + public float[] mSecondByteFreq() { + return mSecondByteFreq; + } + + public float mSecondByteStdDev() { + return mSecondByteStdDev; + } + + public float mSecondByteMean() { + return mSecondByteMean; + } + + public float mSecondByteWeight() { + return mSecondByteWeight; + } + + public EUCTWStatistics() { + mFirstByteFreq = new float[]{0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.119286F, 0.052233F, 0.044126F, 0.052494F, 0.045906F, 0.019038F, 0.032465F, 0.026252F, 0.025502F, 0.015963F, 0.052493F, 0.019256F, 0.015137F, 0.031782F, 0.01737F, 0.018494F, 0.015575F, 0.016621F, 0.007444F, 0.011642F, 0.013916F, 0.019159F, 0.016445F, 0.007851F, 0.011079F, 0.022842F, 0.015513F, 0.010033F, 0.00995F, 0.010347F, 0.013103F, 0.015371F, 0.012502F, 0.007436F, 0.018253F, 0.014134F, 0.008907F, 0.005411F, 0.00957F, 0.013598F, 0.006092F, 0.007409F, 0.008432F, 0.005816F, 0.009349F, 0.005472F, 0.00717F, 0.00742F, 0.003681F, 0.007523F, 0.00461F, 0.006154F, 0.003348F, 0.005074F, 0.005922F, 0.005254F, 0.004682F, 0.002093F, 0.0F}; + mFirstByteStdDev = 0.016681F; + mFirstByteMean = 0.010638F; + mFirstByteWeight = 0.715599F; + mSecondByteFreq = new float[]{0.028933F, 0.011371F, 0.011053F, 0.007232F, 0.010192F, 0.004093F, 0.015043F, 0.011752F, 0.022387F, 0.00841F, 0.012448F, 0.007473F, 0.003594F, 0.007139F, 0.018912F, 0.006083F, 0.003302F, 0.010215F, 0.008791F, 0.024236F, 0.014107F, 0.014108F, 0.010303F, 0.009728F, 0.007877F, 0.009719F, 0.007952F, 0.021028F, 0.005764F, 0.009341F, 0.006591F, 0.012517F, 0.005921F, 0.008982F, 0.008771F, 0.012802F, 0.005926F, 0.008342F, 0.003086F, 0.006843F, 0.007576F, 0.004734F, 0.016404F, 0.008803F, 0.008071F, 0.005349F, 0.008566F, 0.01084F, 0.015401F, 0.031904F, 0.00867F, 0.011479F, 0.010936F, 0.007617F, 0.008995F, 0.008114F, 0.008658F, 0.005934F, 0.010452F, 0.009142F, 0.004519F, 0.008339F, 0.007476F, 0.007027F, 0.006025F, 0.021804F, 0.024248F, 0.015895F, 0.003768F, 0.010171F, 0.010007F, 0.010178F, 0.008316F, 0.006832F, 0.006364F, 0.009141F, 0.009148F, 0.012081F, 0.011914F, 0.004464F, 0.014257F, 0.006907F, 0.011292F, 0.018622F, 0.008149F, 0.004636F, 0.006612F, 0.013478F, 0.012614F, 0.005186F, 0.048285F, 0.006816F, 0.006743F, 0.008671F}; + mSecondByteStdDev = 0.00663F; + mSecondByteMean = 0.010638F; + mSecondByteWeight = 0.284401F; + } + } + + /** + * + */ + public static class nsEUCTWVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsEUCTWVerifier() { + cclass = new int[32]; + cclass[0] = 572662306; + cclass[1] = 2236962; + cclass[2] = 572662306; + cclass[3] = 572654114; + cclass[4] = 572662306; + cclass[5] = 572662306; + cclass[6] = 572662306; + cclass[7] = 572662306; + cclass[8] = 572662306; + cclass[9] = 572662306; + cclass[10] = 572662306; + cclass[11] = 572662306; + cclass[12] = 572662306; + cclass[13] = 572662306; + cclass[14] = 572662306; + cclass[15] = 572662306; + cclass[16] = 0; + cclass[17] = 100663296; + cclass[18] = 0; + cclass[19] = 0; + cclass[20] = 1145324592; + cclass[21] = 286331221; + cclass[22] = 286331153; + cclass[23] = 286331153; + cclass[24] = 858985233; + cclass[25] = 858993459; + cclass[26] = 858993459; + cclass[27] = 858993459; + cclass[28] = 858993459; + cclass[29] = 858993459; + cclass[30] = 858993459; + cclass[31] = 53687091; + states = new int[6]; + states[0] = 338898961; + states[1] = 571543825; + states[2] = 269623842; + states[3] = 286330880; + states[4] = 1052949; + states[5] = 16; + charset = "x-euc-tw"; + stFactor = 7; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class Big5Statistics extends nsEUCStatistics { + static float[] mFirstByteFreq; + static float mFirstByteStdDev; + static float mFirstByteMean; + static float mFirstByteWeight; + static float[] mSecondByteFreq; + static float mSecondByteStdDev; + static float mSecondByteMean; + static float mSecondByteWeight; + + public float[] mFirstByteFreq() { + return mFirstByteFreq; + } + + public float mFirstByteStdDev() { + return mFirstByteStdDev; + } + + public float mFirstByteMean() { + return mFirstByteMean; + } + + public float mFirstByteWeight() { + return mFirstByteWeight; + } + + public float[] mSecondByteFreq() { + return mSecondByteFreq; + } + + public float mSecondByteStdDev() { + return mSecondByteStdDev; + } + + public float mSecondByteMean() { + return mSecondByteMean; + } + + public float mSecondByteWeight() { + return mSecondByteWeight; + } + + public Big5Statistics() { + mFirstByteFreq = new float[]{0.0F, 0.0F, 0.0F, 0.114427F, 0.061058F, 0.075598F, 0.048386F, 0.063966F, 0.027094F, 0.095787F, 0.029525F, 0.031331F, 0.036915F, 0.021805F, 0.019349F, 0.037496F, 0.018068F, 0.01276F, 0.030053F, 0.017339F, 0.016731F, 0.019501F, 0.01124F, 0.032973F, 0.016658F, 0.015872F, 0.021458F, 0.012378F, 0.017003F, 0.020802F, 0.012454F, 0.009239F, 0.012829F, 0.007922F, 0.010079F, 0.009815F, 0.010104F, 0.0F, 0.0F, 0.0F, 5.3E-5F, 3.5E-5F, 1.05E-4F, 3.1E-5F, 8.8E-5F, 2.7E-5F, 2.7E-5F, 2.6E-5F, 3.5E-5F, 2.4E-5F, 3.4E-5F, 3.75E-4F, 2.5E-5F, 2.8E-5F, 2.0E-5F, 2.4E-5F, 2.8E-5F, 3.1E-5F, 5.9E-5F, 4.0E-5F, 3.0E-5F, 7.9E-5F, 3.7E-5F, 4.0E-5F, 2.3E-5F, 3.0E-5F, 2.7E-5F, 6.4E-5F, 2.0E-5F, 2.7E-5F, 2.5E-5F, 7.4E-5F, 1.9E-5F, 2.3E-5F, 2.1E-5F, 1.8E-5F, 1.7E-5F, 3.5E-5F, 2.1E-5F, 1.9E-5F, 2.5E-5F, 1.7E-5F, 3.7E-5F, 1.8E-5F, 1.8E-5F, 1.9E-5F, 2.2E-5F, 3.3E-5F, 3.2E-5F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}; + mFirstByteStdDev = 0.020606F; + mFirstByteMean = 0.010638F; + mFirstByteWeight = 0.675261F; + mSecondByteFreq = new float[]{0.020256F, 0.003293F, 0.045811F, 0.01665F, 0.007066F, 0.004146F, 0.009229F, 0.007333F, 0.003296F, 0.005239F, 0.008282F, 0.003791F, 0.006116F, 0.003536F, 0.004024F, 0.016654F, 0.009334F, 0.005429F, 0.033392F, 0.006121F, 0.008983F, 0.002801F, 0.004221F, 0.010357F, 0.014695F, 0.077937F, 0.006314F, 0.00402F, 0.007331F, 0.00715F, 0.005341F, 0.009195F, 0.00535F, 0.005698F, 0.004472F, 0.007242F, 0.004039F, 0.011154F, 0.016184F, 0.004741F, 0.012814F, 0.007679F, 0.008045F, 0.016631F, 0.009451F, 0.016487F, 0.007287F, 0.012688F, 0.017421F, 0.013205F, 0.03148F, 0.003404F, 0.009149F, 0.008921F, 0.007514F, 0.008683F, 0.008203F, 0.031403F, 0.011733F, 0.015617F, 0.015306F, 0.004004F, 0.010899F, 0.009961F, 0.008388F, 0.01092F, 0.003925F, 0.008585F, 0.009108F, 0.015546F, 0.004659F, 0.006934F, 0.007023F, 0.020252F, 0.005387F, 0.024704F, 0.006963F, 0.002625F, 0.009512F, 0.002971F, 0.008233F, 0.01F, 0.011973F, 0.010553F, 0.005945F, 0.006349F, 0.009401F, 0.008577F, 0.008186F, 0.008159F, 0.005033F, 0.008714F, 0.010614F, 0.006554F}; + mSecondByteStdDev = 0.009909F; + mSecondByteMean = 0.010638F; + mSecondByteWeight = 0.324739F; + } + } + + /** + * + */ + public static class nsBIG5Verifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsBIG5Verifier() { + cclass = new int[32]; + cclass[0] = 286331153; + cclass[1] = 1118481; + cclass[2] = 286331153; + cclass[3] = 286327057; + cclass[4] = 286331153; + cclass[5] = 286331153; + cclass[6] = 286331153; + cclass[7] = 286331153; + cclass[8] = 572662306; + cclass[9] = 572662306; + cclass[10] = 572662306; + cclass[11] = 572662306; + cclass[12] = 572662306; + cclass[13] = 572662306; + cclass[14] = 572662306; + cclass[15] = 304226850; + cclass[16] = 1145324612; + cclass[17] = 1145324612; + cclass[18] = 1145324612; + cclass[19] = 1145324612; + cclass[20] = 858993460; + cclass[21] = 858993459; + cclass[22] = 858993459; + cclass[23] = 858993459; + cclass[24] = 858993459; + cclass[25] = 858993459; + cclass[26] = 858993459; + cclass[27] = 858993459; + cclass[28] = 858993459; + cclass[29] = 858993459; + cclass[30] = 858993459; + cclass[31] = 53687091; + states = new int[3]; + states[0] = 286339073; + states[1] = 304226833; + states[2] = 1; + charset = "Big5"; + stFactor = 5; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class GB2312Statistics extends nsEUCStatistics { + static float[] mFirstByteFreq; + static float mFirstByteStdDev; + static float mFirstByteMean; + static float mFirstByteWeight; + static float[] mSecondByteFreq; + static float mSecondByteStdDev; + static float mSecondByteMean; + static float mSecondByteWeight; + + public float[] mFirstByteFreq() { + return mFirstByteFreq; + } + + public float mFirstByteStdDev() { + return mFirstByteStdDev; + } + + public float mFirstByteMean() { + return mFirstByteMean; + } + + public float mFirstByteWeight() { + return mFirstByteWeight; + } + + public float[] mSecondByteFreq() { + return mSecondByteFreq; + } + + public float mSecondByteStdDev() { + return mSecondByteStdDev; + } + + public float mSecondByteMean() { + return mSecondByteMean; + } + + public float mSecondByteWeight() { + return mSecondByteWeight; + } + + public GB2312Statistics() { + mFirstByteFreq = new float[]{0.011628F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.011628F, 0.012403F, 0.009302F, 0.003876F, 0.017829F, 0.037209F, 0.008527F, 0.010078F, 0.01938F, 0.054264F, 0.010078F, 0.041085F, 0.02093F, 0.018605F, 0.010078F, 0.013178F, 0.016279F, 0.006202F, 0.009302F, 0.017054F, 0.011628F, 0.008527F, 0.004651F, 0.006202F, 0.017829F, 0.024806F, 0.020155F, 0.013953F, 0.032558F, 0.035659F, 0.068217F, 0.010853F, 0.036434F, 0.117054F, 0.027907F, 0.100775F, 0.010078F, 0.017829F, 0.062016F, 0.012403F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.00155F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}; + mFirstByteStdDev = 0.020081F; + mFirstByteMean = 0.010638F; + mFirstByteWeight = 0.586533F; + mSecondByteFreq = new float[]{0.006202F, 0.031008F, 0.005426F, 0.003101F, 0.00155F, 0.003101F, 0.082171F, 0.014729F, 0.006977F, 0.00155F, 0.013953F, 0.0F, 0.013953F, 0.010078F, 0.008527F, 0.006977F, 0.004651F, 0.003101F, 0.003101F, 0.003101F, 0.008527F, 0.003101F, 0.005426F, 0.005426F, 0.005426F, 0.003101F, 0.00155F, 0.006202F, 0.014729F, 0.010853F, 0.0F, 0.011628F, 0.0F, 0.031783F, 0.013953F, 0.030233F, 0.039535F, 0.008527F, 0.015504F, 0.0F, 0.003101F, 0.008527F, 0.016279F, 0.005426F, 0.00155F, 0.013953F, 0.013953F, 0.044961F, 0.003101F, 0.004651F, 0.006977F, 0.00155F, 0.005426F, 0.012403F, 0.00155F, 0.015504F, 0.0F, 0.006202F, 0.00155F, 0.0F, 0.007752F, 0.006977F, 0.00155F, 0.009302F, 0.011628F, 0.004651F, 0.010853F, 0.012403F, 0.017829F, 0.005426F, 0.024806F, 0.0F, 0.006202F, 0.0F, 0.082171F, 0.015504F, 0.004651F, 0.0F, 0.006977F, 0.004651F, 0.0F, 0.008527F, 0.012403F, 0.004651F, 0.003876F, 0.003101F, 0.022481F, 0.024031F, 0.00155F, 0.047287F, 0.009302F, 0.00155F, 0.005426F, 0.017054F}; + mSecondByteStdDev = 0.014156F; + mSecondByteMean = 0.010638F; + mSecondByteWeight = 0.413467F; + } + } + + /** + * + */ + public static class nsGB2312Verifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsGB2312Verifier() { + cclass = new int[32]; + cclass[0] = 286331153; + cclass[1] = 1118481; + cclass[2] = 286331153; + cclass[3] = 286327057; + cclass[4] = 286331153; + cclass[5] = 286331153; + cclass[6] = 286331153; + cclass[7] = 286331153; + cclass[8] = 286331153; + cclass[9] = 286331153; + cclass[10] = 286331153; + cclass[11] = 286331153; + cclass[12] = 286331153; + cclass[13] = 286331153; + cclass[14] = 286331153; + cclass[15] = 286331153; + cclass[16] = 0; + cclass[17] = 0; + cclass[18] = 0; + cclass[19] = 0; + cclass[20] = 572662304; + cclass[21] = 858993442; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662306; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 572662306; + cclass[29] = 572662306; + cclass[30] = 572662306; + cclass[31] = 35791394; + states = new int[2]; + states[0] = 286331649; + states[1] = 1122850; + charset = "GB2312"; + stFactor = 4; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsGB18030Verifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsGB18030Verifier() { + cclass = new int[32]; + cclass[0] = 286331153; + cclass[1] = 1118481; + cclass[2] = 286331153; + cclass[3] = 286327057; + cclass[4] = 286331153; + cclass[5] = 286331153; + cclass[6] = 858993459; + cclass[7] = 286331187; + cclass[8] = 572662306; + cclass[9] = 572662306; + cclass[10] = 572662306; + cclass[11] = 572662306; + cclass[12] = 572662306; + cclass[13] = 572662306; + cclass[14] = 572662306; + cclass[15] = 1109533218; + cclass[16] = 1717986917; + cclass[17] = 1717986918; + cclass[18] = 1717986918; + cclass[19] = 1717986918; + cclass[20] = 1717986918; + cclass[21] = 1717986918; + cclass[22] = 1717986918; + cclass[23] = 1717986918; + cclass[24] = 1717986918; + cclass[25] = 1717986918; + cclass[26] = 1717986918; + cclass[27] = 1717986918; + cclass[28] = 1717986918; + cclass[29] = 1717986918; + cclass[30] = 1717986918; + cclass[31] = 107374182; + states = new int[6]; + states[0] = 318767105; + states[1] = 571543825; + states[2] = 17965602; + states[3] = 286326804; + states[4] = 303109393; + states[5] = 17; + charset = "GB18030"; + stFactor = 7; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsISO2022CNVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsISO2022CNVerifier() { + cclass = new int[32]; + cclass[0] = 2; + cclass[1] = 0; + cclass[2] = 0; + cclass[3] = 4096; + cclass[4] = 0; + cclass[5] = 48; + cclass[6] = 0; + cclass[7] = 0; + cclass[8] = 16384; + cclass[9] = 0; + cclass[10] = 0; + cclass[11] = 0; + cclass[12] = 0; + cclass[13] = 0; + cclass[14] = 0; + cclass[15] = 0; + cclass[16] = 572662306; + cclass[17] = 572662306; + cclass[18] = 572662306; + cclass[19] = 572662306; + cclass[20] = 572662306; + cclass[21] = 572662306; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662306; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 572662306; + cclass[29] = 572662306; + cclass[30] = 572662306; + cclass[31] = 572662306; + states = new int[8]; + states[0] = 304; + states[1] = 286331152; + states[2] = 572662289; + states[3] = 336663074; + states[4] = 286335249; + states[5] = 286331237; + states[6] = 286335249; + states[7] = 18944273; + charset = "ISO-2022-CN"; + stFactor = 9; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsISO2022JPVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsISO2022JPVerifier() { + cclass = new int[32]; + cclass[0] = 2; + cclass[1] = 570425344; + cclass[2] = 0; + cclass[3] = 4096; + cclass[4] = 458752; + cclass[5] = 3; + cclass[6] = 0; + cclass[7] = 0; + cclass[8] = 1030; + cclass[9] = 1280; + cclass[10] = 0; + cclass[11] = 0; + cclass[12] = 0; + cclass[13] = 0; + cclass[14] = 0; + cclass[15] = 0; + cclass[16] = 572662306; + cclass[17] = 572662306; + cclass[18] = 572662306; + cclass[19] = 572662306; + cclass[20] = 572662306; + cclass[21] = 572662306; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662306; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 572662306; + cclass[29] = 572662306; + cclass[30] = 572662306; + cclass[31] = 572662306; + states = new int[6]; + states[0] = 304; + states[1] = 286331153; + states[2] = 572662306; + states[3] = 1091653905; + states[4] = 303173905; + states[5] = 287445265; + charset = "ISO-2022-JP"; + stFactor = 8; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsISO2022KRVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsISO2022KRVerifier() { + cclass = new int[32]; + cclass[0] = 2; + cclass[1] = 0; + cclass[2] = 0; + cclass[3] = 4096; + cclass[4] = 196608; + cclass[5] = 64; + cclass[6] = 0; + cclass[7] = 0; + cclass[8] = 20480; + cclass[9] = 0; + cclass[10] = 0; + cclass[11] = 0; + cclass[12] = 0; + cclass[13] = 0; + cclass[14] = 0; + cclass[15] = 0; + cclass[16] = 572662306; + cclass[17] = 572662306; + cclass[18] = 572662306; + cclass[19] = 572662306; + cclass[20] = 572662306; + cclass[21] = 572662306; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662306; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 572662306; + cclass[29] = 572662306; + cclass[30] = 572662306; + cclass[31] = 572662306; + states = new int[5]; + states[0] = 285212976; + states[1] = 572657937; + states[2] = 289476898; + states[3] = 286593297; + states[4] = 8465; + charset = "ISO-2022-KR"; + stFactor = 6; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsUCS2BEVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsUCS2BEVerifier() { + cclass = new int[32]; + cclass[0] = 0; + cclass[1] = 2097408; + cclass[2] = 0; + cclass[3] = 12288; + cclass[4] = 0; + cclass[5] = 3355440; + cclass[6] = 0; + cclass[7] = 0; + cclass[8] = 0; + cclass[9] = 0; + cclass[10] = 0; + cclass[11] = 0; + cclass[12] = 0; + cclass[13] = 0; + cclass[14] = 0; + cclass[15] = 0; + cclass[16] = 0; + cclass[17] = 0; + cclass[18] = 0; + cclass[19] = 0; + cclass[20] = 0; + cclass[21] = 0; + cclass[22] = 0; + cclass[23] = 0; + cclass[24] = 0; + cclass[25] = 0; + cclass[26] = 0; + cclass[27] = 0; + cclass[28] = 0; + cclass[29] = 0; + cclass[30] = 0; + cclass[31] = 1409286144; + states = new int[7]; + states[0] = 288626549; + states[1] = 572657937; + states[2] = 291923490; + states[3] = 1713792614; + states[4] = 393569894; + states[5] = 1717659269; + states[6] = 1140326; + charset = "UTF-16BE"; + stFactor = 6; + } + + public boolean isUCS2() { + return true; + } + } + + /** + * + */ + public static class nsUCS2LEVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsUCS2LEVerifier() { + cclass = new int[32]; + cclass[0] = 0; + cclass[1] = 2097408; + cclass[2] = 0; + cclass[3] = 12288; + cclass[4] = 0; + cclass[5] = 3355440; + cclass[6] = 0; + cclass[7] = 0; + cclass[8] = 0; + cclass[9] = 0; + cclass[10] = 0; + cclass[11] = 0; + cclass[12] = 0; + cclass[13] = 0; + cclass[14] = 0; + cclass[15] = 0; + cclass[16] = 0; + cclass[17] = 0; + cclass[18] = 0; + cclass[19] = 0; + cclass[20] = 0; + cclass[21] = 0; + cclass[22] = 0; + cclass[23] = 0; + cclass[24] = 0; + cclass[25] = 0; + cclass[26] = 0; + cclass[27] = 0; + cclass[28] = 0; + cclass[29] = 0; + cclass[30] = 0; + cclass[31] = 1409286144; + states = new int[7]; + states[0] = 288647014; + states[1] = 572657937; + states[2] = 303387938; + states[3] = 1712657749; + states[4] = 357927015; + states[5] = 1427182933; + states[6] = 1381717; + charset = "UTF-16LE"; + stFactor = 6; + } + + public boolean isUCS2() { + return true; + } + } + + /** + * + */ + public static class nsCP1252Verifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsCP1252Verifier() { + cclass = new int[32]; + cclass[0] = 572662305; + cclass[1] = 2236962; + cclass[2] = 572662306; + cclass[3] = 572654114; + cclass[4] = 572662306; + cclass[5] = 572662306; + cclass[6] = 572662306; + cclass[7] = 572662306; + cclass[8] = 572662306; + cclass[9] = 572662306; + cclass[10] = 572662306; + cclass[11] = 572662306; + cclass[12] = 572662306; + cclass[13] = 572662306; + cclass[14] = 572662306; + cclass[15] = 572662306; + cclass[16] = 572662274; + cclass[17] = 16851234; + cclass[18] = 572662304; + cclass[19] = 285286690; + cclass[20] = 572662306; + cclass[21] = 572662306; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 286331153; + cclass[25] = 286331153; + cclass[26] = 554766609; + cclass[27] = 286331153; + cclass[28] = 286331153; + cclass[29] = 286331153; + cclass[30] = 554766609; + cclass[31] = 286331153; + states = new int[3]; + states[0] = 571543601; + states[1] = 340853778; + states[2] = 65; + charset = "windows-1252"; + stFactor = 3; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsHZVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsHZVerifier() { + cclass = new int[32]; + cclass[0] = 1; + cclass[1] = 0; + cclass[2] = 0; + cclass[3] = 4096; + cclass[4] = 0; + cclass[5] = 0; + cclass[6] = 0; + cclass[7] = 0; + cclass[8] = 0; + cclass[9] = 0; + cclass[10] = 0; + cclass[11] = 0; + cclass[12] = 0; + cclass[13] = 0; + cclass[14] = 0; + cclass[15] = 38813696; + cclass[16] = 286331153; + cclass[17] = 286331153; + cclass[18] = 286331153; + cclass[19] = 286331153; + cclass[20] = 286331153; + cclass[21] = 286331153; + cclass[22] = 286331153; + cclass[23] = 286331153; + cclass[24] = 286331153; + cclass[25] = 286331153; + cclass[26] = 286331153; + cclass[27] = 286331153; + cclass[28] = 286331153; + cclass[29] = 286331153; + cclass[30] = 286331153; + cclass[31] = 286331153; + states = new int[6]; + states[0] = 285213456; + states[1] = 572657937; + states[2] = 335548706; + states[3] = 341120533; + states[4] = 336872468; + states[5] = 36; + charset = "HZ-GB-2312"; + stFactor = 6; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsSJISVerifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsSJISVerifier() { + cclass = new int[32]; + cclass[0] = 286331152; + cclass[1] = 1118481; + cclass[2] = 286331153; + cclass[3] = 286327057; + cclass[4] = 286331153; + cclass[5] = 286331153; + cclass[6] = 286331153; + cclass[7] = 286331153; + cclass[8] = 572662306; + cclass[9] = 572662306; + cclass[10] = 572662306; + cclass[11] = 572662306; + cclass[12] = 572662306; + cclass[13] = 572662306; + cclass[14] = 572662306; + cclass[15] = 304226850; + cclass[16] = 858993459; + cclass[17] = 858993459; + cclass[18] = 858993459; + cclass[19] = 858993459; + cclass[20] = 572662308; + cclass[21] = 572662306; + cclass[22] = 572662306; + cclass[23] = 572662306; + cclass[24] = 572662306; + cclass[25] = 572662306; + cclass[26] = 572662306; + cclass[27] = 572662306; + cclass[28] = 858993459; + cclass[29] = 1145393971; + cclass[30] = 1145324612; + cclass[31] = 279620; + states = new int[3]; + states[0] = 286339073; + states[1] = 572657937; + states[2] = 4386; + charset = "Shift_JIS"; + stFactor = 6; + } + + public boolean isUCS2() { + return false; + } + } + + /** + * + */ + public static class nsUTF8Verifier extends nsVerifier { + static int[] cclass; + static int[] states; + static int stFactor; + static String charset; + + public int[] cclass() { + return cclass; + } + + public int[] states() { + return states; + } + + public int stFactor() { + return stFactor; + } + + public String charset() { + return charset; + } + + public nsUTF8Verifier() { + cclass = new int[32]; + cclass[0] = 286331153; + cclass[1] = 1118481; + cclass[2] = 286331153; + cclass[3] = 286327057; + cclass[4] = 286331153; + cclass[5] = 286331153; + cclass[6] = 286331153; + cclass[7] = 286331153; + cclass[8] = 286331153; + cclass[9] = 286331153; + cclass[10] = 286331153; + cclass[11] = 286331153; + cclass[12] = 286331153; + cclass[13] = 286331153; + cclass[14] = 286331153; + cclass[15] = 286331153; + cclass[16] = 858989090; + cclass[17] = 1145324612; + cclass[18] = 1145324612; + cclass[19] = 1145324612; + cclass[20] = 1431655765; + cclass[21] = 1431655765; + cclass[22] = 1431655765; + cclass[23] = 1431655765; + cclass[24] = 1717986816; + cclass[25] = 1717986918; + cclass[26] = 1717986918; + cclass[27] = 1717986918; + cclass[28] = -2004318073; + cclass[29] = -2003269496; + cclass[30] = -1145324614; + cclass[31] = 16702940; + states = new int[26]; + states[0] = -1408167679; + states[1] = 878082233; + states[2] = 286331153; + states[3] = 286331153; + states[4] = 572662306; + states[5] = 572662306; + states[6] = 290805009; + states[7] = 286331153; + states[8] = 290803985; + states[9] = 286331153; + states[10] = 293041937; + states[11] = 286331153; + states[12] = 293015825; + states[13] = 286331153; + states[14] = 295278865; + states[15] = 286331153; + states[16] = 294719761; + states[17] = 286331153; + states[18] = 298634257; + states[19] = 286331153; + states[20] = 297865489; + states[21] = 286331153; + states[22] = 287099921; + states[23] = 286331153; + states[24] = 285212689; + states[25] = 286331153; + charset = "UTF-8"; + stFactor = 16; + } + + public boolean isUCS2() { + return false; + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/JSONUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/JSONUtil.java new file mode 100644 index 0000000..8d63168 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/JSONUtil.java @@ -0,0 +1,69 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +/** + * JSON解析工具类 + * + * @author WebSoft + * @since 2017-06-10 10:10:39 + */ +public class JSONUtil { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); + + /** + * 对象转json字符串 + * + * @param value 对象 + * @return String + */ + public static String toJSONString(Object value) { + return toJSONString(value, false); + } + + /** + * 对象转json字符串 + * + * @param value 对象 + * @param pretty 是否格式化输出 + * @return String + */ + public static String toJSONString(Object value, boolean pretty) { + if (value != null) { + if (value instanceof String) { + return (String) value; + } + try { + if (pretty) { + return objectWriter.writeValueAsString(value); + } + return objectMapper.writeValueAsString(value); + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * json字符串转对象 + * + * @param json String + * @param clazz Class + * @return T + */ + public static T parseObject(String json, Class clazz) { + if (StrUtil.isNotBlank(json) && clazz != null) { + try { + return objectMapper.readValue(json, clazz); + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/MyQrCodeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/MyQrCodeUtil.java new file mode 100644 index 0000000..933deac --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/MyQrCodeUtil.java @@ -0,0 +1,85 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.extra.qrcode.QrConfig; +import org.springframework.beans.factory.annotation.Value; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; + +import static com.gxwebsoft.common.core.constants.QRCodeConstants.*; + +/** + * 常用工具方法 + * + * @author WebSoft + * @since 2017-06-10 10:10:22 + */ +public class MyQrCodeUtil { + + @Value("${config.upload-path}") + private static String uploadPath; + + private static final String logoUrl = "https://file.wsdns.cn/20230430/6fa31aca3b0d47af98a149cf2dd26a4f.jpeg"; + + /** + * 生成用户二维码 + */ + public static String getUserCode(Integer userId, String content) throws IOException { + return createQrCode(USER_QRCODE,userId,content); + } + + /** + * 生成工单二维码 + */ + public static String getTaskCode(Integer taskId, String content) throws IOException { + return createQrCode(TASK_QRCODE,taskId,content); + } + + /** + * 生成商品二维码 + */ + public static String getGoodsCode(Integer goodsId, String content) throws IOException { + return createQrCode(GOODS_QRCODE,goodsId,content); + } + + /** + * 生成自定义二维码 + */ + public static String getCodeMap(HashMap map) throws IOException { + return ""; + } + + /** + * 生成带水印的二维码 + * @param type 类型 + * @param id 实体ID + * @param content 二维码内容 + * @return 二维码图片地址 + */ + public static String createQrCode(String type,Integer id, String content) throws IOException { + String filePath = uploadPath + "/file/qrcode/".concat(type).concat("/"); + String qrcodeUrl = "https://file.websoft.top/qrcode/".concat(type).concat("/"); + // 将URL转为BufferedImage + BufferedImage bufferedImage = ImageIO.read(new URL(logoUrl)); + // 生成二维码 + QrConfig config = new QrConfig(300, 300); + // 设置边距,既二维码和背景之间的边距 + config.setMargin(1); + // 附带小logo + config.setImg(bufferedImage); + // 保存路径 + filePath = filePath.concat(id + ".jpg"); + qrcodeUrl = qrcodeUrl.concat(id + ".jpg") + "?v=" + DateUtil.current(); + + // 生成二维码 + QrCodeUtil.generate(content, config, FileUtil.file(filePath)); + return qrcodeUrl; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/OpenOfficeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/OpenOfficeUtil.java new file mode 100644 index 0000000..cca3990 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/OpenOfficeUtil.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.StrUtil; +import org.artofsolving.jodconverter.OfficeDocumentConverter; +import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration; +import org.artofsolving.jodconverter.office.OfficeManager; + +import java.io.File; +import java.util.Arrays; +import java.util.Base64; + +/** + * OpenOfficeUtil + * + * @author WebSoft + * @since 2018-12-14 08:38:19 + */ +public class OpenOfficeUtil { + // 支持转换pdf的文件后缀列表 + private static final String[] CAN_CONVERTER_FILES = new String[]{ + "doc", "docx", "xls", "xlsx", "ppt", "pptx" + }; + + /** + * 文件转pdf + * + * @param filePath 源文件路径 + * @param outDir 输出目录 + * @param officeHome OpenOffice安装路径 + * @return File + */ + public static File converterToPDF(String filePath, String outDir, String officeHome) { + return converterToPDF(filePath, outDir, officeHome, true); + } + + /** + * 文件转pdf + * + * @param filePath 源文件路径 + * @param outDir 输出目录 + * @param officeHome OpenOffice安装路径 + * @param cache 是否使用上次转换过的文件 + * @return File + */ + public static File converterToPDF(String filePath, String outDir, String officeHome, boolean cache) { + if (StrUtil.isBlank(filePath)) { + return null; + } + File srcFile = new File(filePath); + if (!srcFile.exists()) { + return null; + } + // 是否转换过 + String outPath = Base64.getEncoder().encodeToString(filePath.getBytes()) + .replace("/", "-").replace("+", "-"); + File outFile = new File(outDir, outPath + ".pdf"); + if (cache && outFile.exists()) { + return outFile; + } + // 转换 + OfficeManager officeManager = null; + try { + officeManager = getOfficeManager(officeHome); + OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager); + return converterFile(srcFile, outFile, converter); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (officeManager != null) { + officeManager.stop(); + } + } + return null; + } + + /** + * 转换文件 + * + * @param inFile 源文件 + * @param outFile 输出文件 + * @param converter OfficeDocumentConverter + * @return File + */ + public static File converterFile(File inFile, File outFile, OfficeDocumentConverter converter) { + if (!outFile.getParentFile().exists()) { + if (!outFile.getParentFile().mkdirs()) { + return outFile; + } + } + converter.convert(inFile, outFile); + return outFile; + } + + /** + * 判断文件后缀是否可以转换pdf + * + * @param path 文件路径 + * @return boolean + */ + public static boolean canConverter(String path) { + try { + String suffix = path.substring(path.lastIndexOf(".") + 1); + return Arrays.asList(CAN_CONVERTER_FILES).contains(suffix); + } catch (Exception e) { + return false; + } + } + + /** + * 连接并启动OpenOffice + * + * @param officeHome OpenOffice安装路径 + * @return OfficeManager + */ + public static OfficeManager getOfficeManager(String officeHome) { + if (officeHome == null || officeHome.trim().isEmpty()) return null; + DefaultOfficeManagerConfiguration config = new DefaultOfficeManagerConfiguration(); + config.setOfficeHome(officeHome); // 设置OpenOffice安装目录 + OfficeManager officeManager = config.buildOfficeManager(); + officeManager.start(); // 启动OpenOffice服务 + return officeManager; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/QrCodeDecryptResult.java b/src/main/java/com/gxwebsoft/common/core/utils/QrCodeDecryptResult.java new file mode 100644 index 0000000..bbfe3b6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/QrCodeDecryptResult.java @@ -0,0 +1,93 @@ +package com.gxwebsoft.common.core.utils; + +import lombok.Data; + +/** + * 二维码解密结果类 + * 包含解密后的数据和业务类型信息 + * + * @author WebSoft + * @since 2025-08-18 + */ +@Data +public class QrCodeDecryptResult { + + /** + * 解密后的原始数据 + */ + private String originalData; + + /** + * 业务类型(如:order、user、coupon、ticket等) + */ + private String businessType; + + /** + * 二维码类型(encrypted 或 business_encrypted) + */ + private String qrType; + + /** + * 二维码ID(仅业务模式有) + */ + private String qrId; + + /** + * 过期时间戳(仅业务模式有) + */ + private Long expireTime; + + /** + * 是否已过期 + */ + private Boolean expired; + + public QrCodeDecryptResult() {} + + public QrCodeDecryptResult(String originalData, String businessType, String qrType) { + this.originalData = originalData; + this.businessType = businessType; + this.qrType = qrType; + this.expired = false; + } + + /** + * 创建自包含模式的解密结果 + */ + public static QrCodeDecryptResult createEncryptedResult(String originalData, String businessType) { + return new QrCodeDecryptResult(originalData, businessType, "encrypted"); + } + + /** + * 创建业务模式的解密结果 + */ + public static QrCodeDecryptResult createBusinessResult(String originalData, String businessType, + String qrId, Long expireTime) { + QrCodeDecryptResult result = new QrCodeDecryptResult(originalData, businessType, "business_encrypted"); + result.setQrId(qrId); + result.setExpireTime(expireTime); + result.setExpired(expireTime != null && System.currentTimeMillis() > expireTime); + return result; + } + + /** + * 检查是否有业务类型 + */ + public boolean hasBusinessType() { + return businessType != null && !businessType.trim().isEmpty(); + } + + /** + * 检查是否为业务模式 + */ + public boolean isBusinessMode() { + return "business_encrypted".equals(qrType); + } + + /** + * 检查是否为自包含模式 + */ + public boolean isEncryptedMode() { + return "encrypted".equals(qrType); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/RedisUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/RedisUtil.java new file mode 100644 index 0000000..f02ce42 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/RedisUtil.java @@ -0,0 +1,279 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.result.RedisResult; +import org.springframework.data.geo.Point; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static com.gxwebsoft.common.core.constants.RedisConstants.CACHE_NULL_TTL; + +@Component +public class RedisUtil { + private final StringRedisTemplate stringRedisTemplate; + public static Integer tenantId; + + public RedisUtil(StringRedisTemplate stringRedisTemplate){ + this.stringRedisTemplate = stringRedisTemplate; + } + + /** + * 写入redis缓存 + * @param key [表名]:id + * @param entity 实体类对象 + * 示例 cacheClient.set("merchant:"+id,merchant) + */ + public void set(String key, T entity){ + stringRedisTemplate.opsForValue().set(key, JSONUtil.toJSONString(entity)); + } + + /** + * 写入redis缓存 + * @param key [表名]:id + * @param entity 实体类对象 + * 示例 cacheClient.set("merchant:"+id,merchant,1L,TimeUnit.DAYS) + */ + public void set(String key, T entity, Long time, TimeUnit unit){ + stringRedisTemplate.opsForValue().set(key, JSONUtil.toJSONString(entity),time,unit); + } + + /** + * 读取redis缓存 + * @param key [表名]:id + * 示例 cacheClient.get(key) + * @return merchant + */ + public String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 读取redis缓存 + * @param key [表名]:id + * @param clazz Merchant.class + * @param + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + * @return merchant + */ + public T get(String key, Class clazz) { + String json = stringRedisTemplate.opsForValue().get(key); + if(StrUtil.isNotBlank(json)){ + return JSONUtil.parseObject(json, clazz); + } + return null; + } + + /** + * 写redis缓存(哈希类型) + * @param key [表名]:id + * @param field 字段 + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + */ + public void hPut(String key, String field, T entity) { + stringRedisTemplate.opsForHash().put(key,field,JSONUtil.toJSONString(entity)); + } + + /** + * 写redis缓存(哈希类型) + * @param key [表名]:id + * @param map 字段 + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + */ + public void hPutAll(String key, Map map) { + stringRedisTemplate.opsForHash().putAll(key,map); + } + + /** + * 读取redis缓存(哈希类型) + * 示例 cacheClient.get("merchant:"+id,Merchant.class) + * @param key [表名]:id + * @param field 字段 + * @return merchant + */ + public T hGet(String key, String field, Class clazz) { + Object obj = stringRedisTemplate.opsForHash().get(key, field); + return JSONUtil.parseObject(JSONUtil.toJSONString(obj),clazz); + } + + public List hValues(String key){ + return stringRedisTemplate.opsForHash().values(key); + } + + public Long hSize(String key){ + return stringRedisTemplate.opsForHash().size(key); + } + + // 逻辑过期方式写入redis + public void setWithLogicalExpire(String key, T value, Long time, TimeUnit unit){ + // 设置逻辑过期时间 + final RedisResult redisResult = new RedisResult<>(); + redisResult.setData(value); + redisResult.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); + stringRedisTemplate.opsForValue().set(key,JSONUtil.toJSONString(redisResult)); + } + + // 读取redis + public R query(String keyPrefix, ID id, Class clazz, Function dbFallback, Long time, TimeUnit unit){ + String key = keyPrefix + id; + // 1.从redis查询缓存 + final String json = stringRedisTemplate.opsForValue().get(key); + // 2.判断是否存在 + if (StrUtil.isNotBlank(json)) { + // 3.存在,直接返回 + return JSONUtil.parseObject(json,clazz); + } + // 判断命中的是否为空值 + if (json != null) { + return null; + } + // 4. 不存在,跟进ID查询数据库 + R r = dbFallback.apply(id); + // 5. 数据库不存在,返回错误 + if(r == null){ + // 空值写入数据库 + this.set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES); + return null; + } + // 写入redis + this.set(key,r,time,unit); + return r; + } + + /** + * 添加商户定位点 + * @param key geo + * @param id + * 示例 cacheClient.geoAdd("merchant-geo",merchant) + */ + public void geoAdd(String key, Double x, Double y, String id){ + stringRedisTemplate.opsForGeo().add(key,new Point(x,y),id); + } + + /** + * 删除定位 + * @param key geo + * @param id + * 示例 cacheClient.geoRemove("merchant-geo",id) + */ + public void geoRemove(String key, Integer id){ + stringRedisTemplate.opsForGeo().remove(key,id.toString()); + } + + + + public void sAdd(String key, T entity){ + stringRedisTemplate.opsForSet().add(key,JSONUtil.toJSONString(entity)); + } + + public Set sMembers(String key){ + return stringRedisTemplate.opsForSet().members(key); + } + + // 更新排行榜 + public void zAdd(String key, Integer userId, Double value) { + stringRedisTemplate.opsForZSet().add(key,userId.toString(),value); + } + // 增加元素的score值,并返回增加后的值 + public Double zIncrementScore(String key,Integer userId, Double delta){ + return stringRedisTemplate.opsForZSet().incrementScore(key, userId.toString(), delta); + } + // 获取排名榜 + public Set range(String key, Integer start, Integer end) { + return stringRedisTemplate.opsForZSet().range(key, start, end); + } + // 获取排名榜 + public Set reverseRange(String key, Integer start, Integer end){ + return stringRedisTemplate.opsForZSet().reverseRange(key, start, end); + } + // 获取分数 + public Double score(String key, Object value){ + return stringRedisTemplate.opsForZSet().score(key, value); + } + + public void delete(String key){ + stringRedisTemplate.delete(key); + } + + // 存储在list头部 + public void leftPush(String key, String keyword){ + stringRedisTemplate.opsForList().leftPush(key,keyword); + } + + // 获取列表指定范围内的元素 + public List listRange(String key,Long start, Long end){ + return stringRedisTemplate.opsForList().range(key, start, end); + } + + // 获取列表长度 + public Long listSize(String key){ + return stringRedisTemplate.opsForList().size(key); + } + + // 裁剪list + public void listTrim(String key){ + stringRedisTemplate.opsForList().trim(key, 0L, 100L); + } + + /** + * 读取后台系统设置信息 + * @param keyName 键名wx-word + * @param tenantId 租户ID + * @return + * key示例 cache10048:setting:wx-work + */ + public JSONObject getSettingInfo(String keyName,Integer tenantId){ + String key = "cache" + tenantId + ":setting:" + keyName; + final String cache = stringRedisTemplate.opsForValue().get(key); + assert cache != null; + return JSON.parseObject(cache); + } + + /** + * KEY前缀 + * cache[tenantId]:[key+id] + */ + public static String prefix(String key){ + String prefix = "cache"; + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Object object = authentication.getPrincipal(); + if (object instanceof User) { + final Integer tenantId = ((User) object).getTenantId(); + prefix = prefix.concat(tenantId.toString()).concat(":"); + } + } + return prefix.concat(key); + } + + // 组装key + public String key(String name,Integer id){ + return name.concat(":").concat(id.toString()); + } + + // 获取上传配置 + public HashMap getUploadConfig(Integer tenantId){ + String key = "setting:upload:" + tenantId; + final String s = get(key); + final JSONObject jsonObject = JSONObject.parseObject(s); + final String uploadMethod = jsonObject.getString("uploadMethod"); + final String bucketDomain = jsonObject.getString("bucketDomain"); + + final HashMap map = new HashMap<>(); + map.put("uploadMethod",uploadMethod); + map.put("bucketDomain",bucketDomain); + return map; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/RequestUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/RequestUtil.java new file mode 100644 index 0000000..cdda4da --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/RequestUtil.java @@ -0,0 +1,343 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.entity.UserRole; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.entity.ShopMerchantAccount; +import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; + +@Component +public class RequestUtil { + + @Autowired + private ConfigProperties configProperties; + + private static String ACCESS_TOKEN; + private static String TENANT_ID; + + public void setTenantId(String tenantId) { + TENANT_ID = tenantId; + } + + public void setAccessToken(String token) { + ACCESS_TOKEN = token; + } + + private String getServerUrl() { + return configProperties.getServerUrl(); + } + + // 预付请求付款(余额支付) + public Object balancePay(ShopOrder order) { + // 设置租户ID + setTenantId(order.getTenantId().toString()); + // 设置token + setAccessToken(order.getAddress()); + // 余额支付接口 + String path = "/system/payment/balancePay"; + try { + // 链式构建请求 + final String body = HttpRequest.post(getServerUrl().concat(path)) + .header("Tenantid", TENANT_ID) + .header("Authorization", ACCESS_TOKEN) + .body(JSONUtil.toJSONString(order))//表单内容 + .timeout(20000)//超时,毫秒 + .execute().body(); + return JSONUtil.parseObject(body, ApiResult.class).getData(); + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + // 微信支付通知 + public String pushWxPayNotify(Transaction transaction, Payment payment) { + // 设置租户ID + setTenantId(payment.getTenantId().toString()); + // 推送支付通知地址 + String path = payment.getNotifyUrl(); + try { + // 链式构建请求 + return HttpRequest.post(path) + .header("Tenantid", TENANT_ID) + .body(JSONUtil.toJSONString(transaction))//表单内容 + .timeout(20000)//超时,毫秒 + .execute().body(); + + } catch (Exception e) { + e.printStackTrace(); + } + return "支付失败"; + } + + + public User getUserByPhone(String phone) { + String path = "/system/user/getByPhone/" + phone; + try { + // 链式构建请求 + String result = HttpRequest.get(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("Tenantid", TENANT_ID) + .timeout(20000)//超时,毫秒 + .execute().body(); + + JSONObject jsonObject = JSONObject.parseObject(result); + final String data = jsonObject.getString("data"); + return JSONObject.parseObject(data, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public User getByUserId(Integer userId) { + String path = "/system/user/" + userId; + try { + // 链式构建请求 + String result = HttpRequest.get(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("Tenantid", TENANT_ID) + .timeout(20000)//超时,毫秒 + .execute().body(); + + JSONObject jsonObject = JSONObject.parseObject(result); + System.out.println("jsonObject1111111111 = " + jsonObject); + final String data = jsonObject.getString("data"); + return JSONObject.parseObject(data, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public User getByUserIdWithoutLogin(Integer userId) { + String path = "/system/user/getByUserId/" + userId; + try { + // 链式构建请求 + String result = HttpRequest.get(getServerUrl().concat(path)) + .header("Tenantid", TENANT_ID) + .timeout(20000)//超时,毫秒 + .execute().body(); + JSONObject jsonObject = JSONObject.parseObject(result); + System.out.println("jsonObject1111 = " + jsonObject); + final String data = jsonObject.getString("data"); + return JSONObject.parseObject(data, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + // 新增用户 + public boolean saveUserByPhone(ShopMerchantAccount merchantAccount) { + String path = "/system/user/"; + try { + HashMap map = new HashMap<>(); + map.put("nickname", merchantAccount.getRealName()); + map.put("username", merchantAccount.getPhone()); + map.put("realName", merchantAccount.getRealName()); + map.put("phone", merchantAccount.getPhone()); + map.put("merchantId", merchantAccount.getMerchantId()); + final ArrayList roles = new ArrayList<>(); + final UserRole userRole = new UserRole(); + userRole.setUserId(merchantAccount.getUserId()); + userRole.setRoleId(merchantAccount.getRoleId()); + userRole.setTenantId(merchantAccount.getTenantId()); + roles.add(userRole); + map.put("roles", roles); + map.put("tenantId", TENANT_ID); + // 链式构建请求 + String result = HttpRequest.post(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("Tenantid", TENANT_ID) + .body(JSONUtil.toJSONString(map))//表单内容 + .timeout(20000)//超时,毫秒 + .execute().body(); + + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + + public ApiResult updateUserBalance(String path, User user) { + try { + // 链式构建请求 + final String body = HttpRequest.put(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("Tenantid", TENANT_ID) + .body(JSONUtil.toJSONString(user)) + .timeout(20000) + .execute().body(); + return JSONUtil.parseObject(body, ApiResult.class); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public User getParent(Integer userId) { + try { + // 链式构建请求 + final String result = HttpRequest.get(getServerUrl().concat("/system/user-referee/getReferee/" + userId)) + .header("Tenantid", TENANT_ID) + .timeout(20000) + .execute().body(); + JSONObject jsonObject = JSONObject.parseObject(result); + final String data = jsonObject.getString("data"); + return JSONObject.parseObject(data, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + // 更新用户信息 + public void updateUser(User user) { + String path = "/system/user/"; + try { + // 链式构建请求 + final String body = HttpRequest.put(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("Tenantid", TENANT_ID) + .body(JSONUtil.toJSONString(user)) + .timeout(20000) + .execute().body(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 更新用户信息 + public void updateWithoutLogin(User user) { + String path = "/system/user/updateWithoutLogin"; + try { + // 链式构建请求 + final String body = HttpRequest.put(getServerUrl().concat(path)) + .header("Tenantid", TENANT_ID) + .body(JSONUtil.toJSONString(user)) + .timeout(20000) + .execute().body(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getMpOrderQrCode(String orderNo) { + String path = "/wx-login/getOrderQRCode/"; + try { + // 链式构建请求 + final String body = HttpRequest.get(getServerUrl().concat(path).concat(orderNo)) + .header("Authorization", ACCESS_TOKEN) + .header("tenantId", TENANT_ID) + .timeout(20000) + .execute().body(); + final JSONObject jsonObject = JSONObject.parseObject(body); + final String qrCode = jsonObject.getString("message"); + return qrCode; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public String getOrderQRCodeUnlimited(String orderNo) { + String path = "/wx-login/getOrderQRCodeUnlimited/"; + try { + // 链式构建请求 + final String body = HttpRequest.get(getServerUrl().concat(path).concat(orderNo)) + .header("Authorization", ACCESS_TOKEN) + .header("tenantId", TENANT_ID) + .timeout(20000) + .execute().body(); + System.out.println("body = " + body); + final JSONObject jsonObject = JSONObject.parseObject(body); + final String qrCode = jsonObject.getString("message"); + System.out.println("qrCode = " + qrCode); + return qrCode; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public void updateUserMerchantId(User user) { + String path = "/system/user/updateUserMerchantId"; + try { + // 链式构建请求 + final String body = HttpRequest.put(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("tenantId", TENANT_ID) + .body(JSONUtil.toJSONString(user)) + .timeout(20000) + .execute().body(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ApiResult getWxConfig() { + String path = "/system/setting?settingKey=mp-weixin"; + try { + // 链式构建请求 + final String body = HttpRequest.get(getServerUrl().concat(path)) + .header("Authorization", ACCESS_TOKEN) + .header("tenantId", TENANT_ID) + .timeout(20000) + .execute().body(); + return JSONUtil.parseObject(body, ApiResult.class); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public ApiResult pageDictData(Integer dictId) { + String path = "/system/dict-data/page"; + try { + // 链式构建请求 + final String body = HttpRequest.get(getServerUrl().concat(path).concat("?dictId=" + dictId)) + .header("tenantId", TENANT_ID) + .timeout(20000) + .execute().body(); + return JSONUtil.parseObject(body, ApiResult.class); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // 余额支付通知 + public void pushBalancePayNotify(Transaction transaction, Payment payment) { + System.out.println("payment = " + payment); + System.out.println("transaction = " + transaction); + // 设置租户ID + setTenantId(payment.getTenantId().toString()); + // 推送支付通知地址 + String path = payment.getNotifyUrl(); + try { + // 链式构建请求 + HttpRequest.post(path) + .header("Tenantid", TENANT_ID) + .body(JSONUtil.toJSONString(transaction))//表单内容 + .timeout(20000)//超时,毫秒 + .execute().body(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/SignCheckUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/SignCheckUtil.java new file mode 100644 index 0000000..b09cd9a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/SignCheckUtil.java @@ -0,0 +1,197 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.system.entity.KVEntity; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +/** + * 签名检查和获取签名 + * https://blog.csdn.net/u011628753/article/details/110251445 + * @author leng + * + */ +public class SignCheckUtil { + // 签名字段 + public final static String SIGN = "sign"; + + /** + * 签名检查,签名参数中,sign是用于校验的加密值,其他参数按照字母顺序排序,加密,并将其内容链接起来 + * + * @param params + * @param key + * @return + */ + public static boolean signCheck(JSONObject params, String key) { + if (null != params) { + Map map = new HashMap<>(); + + params.forEach((k, v) -> { + map.put(k, v.toString()); + }); + return signCheck(map, key); + } + + return false; + } + + /** + * 签名检查,签名参数中,sign是用于校验的加密值,其他参数按照字母顺序排序,加密,并将其内容链接起来 + * + * @param params + * @param key + * 签名key不允许为空 + * @return + */ + public static boolean signCheck(Map params, String key) { + String sign = params.get(SIGN);// 签名 + if (null == sign) { + return false; + } + String signTemp = getSignString(params,key); + if (null == signTemp) { + return false; + } + return signTemp.equals(sign); + } + + /** + * 获取签名的字符串 + * + * @param params + * @param key + * @return + */ + public static String getSignString(JSONObject params, String key) { + if (null != params) { + Map map = new HashMap<>(); + + params.forEach((k, v) -> { + map.put(k, v.toString()); + }); + return getSignString(map, key); + } + + return null; + } + + /** + * 获取签名的字符串 + * + * @param params + * @param key + * @return + */ + public static String getSignString(Map params, String key) { + // 签名 + if (null == params || params.size() == 0) { + return null; + } + key = (null == key) ? "" : key; + List> list = new ArrayList<>(params.size() - 1); + + params.forEach((k, v) -> { + if (!SIGN.equals(k)) { + list.add(KVEntity.build(k, v)); + } + }); + + Collections.sort(list, (obj1, obj2) -> { + return obj1.getK().compareTo(obj2.getK()); + }); + + StringBuffer sb = new StringBuffer(); + for (KVEntity kv : list) { + String value = kv.getV(); + if (!StringUtils.isEmpty(value)) { + sb.append(kv.getV()).append("-"); + } + } + sb.append(key); + System.out.println("md5加密前的字符串 = " + sb + key); + String signTemp = SecureUtil.md5(sb.toString()).toLowerCase(); + return signTemp; + } + + /** + * 获取微信签名的字符串 + * + * 注意签名(sign)的生成方式,具体见官方文档(传参都要参与生成签名,且参数名按照字典序排序,最后接上APP_KEY,转化成大写) + * + * @param params + * @param key + * @return + */ + public static String getWXSignString(Map params, String key) { + // 签名 + if (null == params || params.size() == 0 || StringUtils.isEmpty(key)) { + return null; + } + + List> list = new ArrayList<>(params.size() - 1); + + params.forEach((k, v) -> { + if (!SIGN.equals(k)) { + list.add(KVEntity.build(k, v)); + } + }); + + Collections.sort(list, (obj1, obj2) -> { + return obj1.getK().compareTo(obj2.getK()); + }); + + StringBuffer sb = new StringBuffer(); + for (KVEntity kv : list) { + String value = kv.getV(); + if (!StringUtils.isEmpty(value)) { + sb.append(kv.getK() + "=" + value + "&"); + } + } + + sb.append("key=" + key); + String signTemp = SecureUtil.md5(sb.toString()).toLowerCase(); + return signTemp; + } + + /** + * 微信签名验证 + * @param params + * @param key + * @return + */ + public static boolean WXsignCheck(Map params, String key) { + String sign = params.get(SIGN); + if (StringUtils.isEmpty(sign)) { + return false; + } + return sign.equals(getWXSignString(params, key)); + } + + + /** + * 白名单校验 + * @param domainName abc.com + * @return true + */ + public boolean checkWhiteDomains(List whiteDomains, String domainName) { + if(whiteDomains == null){ + return true; + } + if (whiteDomains.isEmpty()) { + return true; + } + // 服务器域名白名单列表 + whiteDomains.add("server.websoft.top"); + System.out.println("whiteDomains = " + whiteDomains); + System.out.println(">>> domainName = " + domainName); + for(String item: whiteDomains){ + if(Objects.equals(item, domainName)){ + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/SpmUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/SpmUtil.java new file mode 100644 index 0000000..091ef20 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/SpmUtil.java @@ -0,0 +1,23 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.cms.entity.CmsNavigation; + +public class SpmUtil { + + // 生成spmUrl + public static String getSpmUrl(String path){ + return path.concat("?spm=c."); + } + + // 生成spmUrl + public static String getSpmUrl(String path, CmsNavigation navigation){ + return path.concat("?spm=c.".concat(navigation.getNavigationId().toString())); + } + + // 生成spmUrl +// public static String getSpmUrl(String path, T data){ +// System.out.println("json = " + data); +// return "?spm=".concat(path).concat(); +// } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java new file mode 100644 index 0000000..75b0a22 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java @@ -0,0 +1,171 @@ +package com.gxwebsoft.common.core.utils; + +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.gxwebsoft.common.core.config.ConfigProperties; + +import javax.annotation.Resource; + +/** + * 微信支付证书自动配置工具类 + * 使用RSAAutoCertificateConfig实现证书自动管理 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Slf4j +@Component +public class WechatCertAutoConfig { + + @Value("${spring.profiles.active:prod}") + private String activeProfile; + + @Resource + private ConfigProperties configProperties; + + /** + * 创建微信支付自动证书配置 + * + * @param merchantId 商户号 + * @param privateKeyPath 私钥文件路径 + * @param merchantSerialNumber 商户证书序列号 + * @param apiV3Key APIv3密钥 + * @return 微信支付配置对象 + */ + public Config createAutoConfig(String merchantId, String privateKeyPath, + String merchantSerialNumber, String apiV3Key) { + try { + log.info("创建微信支付自动证书配置..."); + log.info("商户号: {}", merchantId); + log.info("私钥路径: {}", privateKeyPath); + log.info("证书序列号: {}", merchantSerialNumber); + + Config config = new RSAAutoCertificateConfig.Builder() + .merchantId(merchantId) + .privateKeyFromPath(privateKeyPath) + .merchantSerialNumber(merchantSerialNumber) + .apiV3Key(apiV3Key) + .build(); + + log.info("✅ 微信支付自动证书配置创建成功"); + log.info("🔄 系统将自动管理平台证书的下载和更新"); + + return config; + + } catch (Exception e) { + log.error("❌ 创建微信支付自动证书配置失败: {}", e.getMessage(), e); + + // 提供详细的错误诊断信息 + log.error("🔍 错误诊断:"); + log.error("1. 请检查商户平台是否已开启API安全功能"); + log.error("2. 请确认已申请使用微信支付公钥"); + log.error("3. 请验证APIv3密钥和证书序列号是否正确"); + log.error("4. 请检查网络连接是否正常"); + log.error("5. 请确认私钥文件路径是否正确: {}", privateKeyPath); + + throw new RuntimeException("微信支付自动证书配置失败: " + e.getMessage(), e); + } + } + + /** + * 使用默认开发环境配置创建自动证书配置 + * 根据当前环境自动选择证书路径 + * 开发环境拼接规则:配置文件upload-path + dev/wechat/ + 租户ID + * + * @return 微信支付配置对象 + */ + public Config createDefaultDevConfig() { + String merchantId = "1723321338"; + String privateKeyPath; + String merchantSerialNumber = "2B933F7C35014A1C363642623E4A62364B34C4EB"; + String apiV3Key = "0kF5OlPr482EZwtn9zGufUcqa7ovgxRL"; + + // 根据环境选择证书路径 + if ("dev".equals(activeProfile)) { + // 开发环境:使用配置文件upload-path拼接证书路径 + String uploadPath = configProperties.getUploadPath(); // 配置文件路径 + String tenantId = "10550"; // 租户ID + String certPath = uploadPath + "dev/wechat/" + tenantId + "/"; + privateKeyPath = certPath + "apiclient_key.pem"; + + log.info("开发环境:使用配置文件upload-path拼接证书路径"); + log.info("配置文件upload-path: {}", uploadPath); + log.info("证书基础路径: {}", certPath); + log.info("私钥文件路径: {}", privateKeyPath); + } else { + // 生产环境:使用相对路径,由系统动态解析 + privateKeyPath = "src/main/resources/certs/dev/wechat/apiclient_key.pem"; + log.info("生产环境:使用相对证书路径 - {}", privateKeyPath); + } + + return createAutoConfig(merchantId, privateKeyPath, merchantSerialNumber, apiV3Key); + } + + /** + * 测试证书配置是否正常 + * + * @param config 微信支付配置 + * @return 是否配置成功 + */ + public boolean testConfig(Config config) { + try { + // 这里可以添加一些基本的配置验证逻辑 + log.info("🧪 测试微信支付证书配置..."); + + if (config == null) { + log.error("配置对象为空"); + return false; + } + + log.info("✅ 证书配置测试通过"); + return true; + + } catch (Exception e) { + log.error("❌ 证书配置测试失败: {}", e.getMessage(), e); + return false; + } + } + + /** + * 获取配置使用说明 + * + * @return 使用说明 + */ + public String getUsageInstructions() { + return """ + 🚀 微信支付自动证书配置使用说明 + ================================ + + ✅ 优势: + 1. 自动下载微信支付平台证书 + 2. 证书过期时自动更新 + 3. 无需手动管理 wechatpay_cert.pem 文件 + 4. 符合微信支付官方最佳实践 + + 📝 使用方法: + + // 方法1: 使用默认开发环境配置 + Config config = wechatCertAutoConfig.createDefaultDevConfig(); + + // 方法2: 自定义配置 + Config config = wechatCertAutoConfig.createAutoConfig( + "商户号", + "私钥路径", + "证书序列号", + "APIv3密钥" + ); + + 🔧 前置条件: + 1. 微信商户平台已开启API安全功能 + 2. 已申请使用微信支付公钥 + 3. 私钥文件存在且路径正确 + 4. 网络连接正常 + + 📚 更多信息: + https://pay.weixin.qq.com/doc/v3/merchant/4012153196 + """; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java new file mode 100644 index 0000000..ebe8189 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java @@ -0,0 +1,314 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + + +/** + * 微信支付证书诊断工具 + * 专门用于诊断和解决证书相关问题 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@Component +public class WechatPayCertificateDiagnostic { + + private final CertificateProperties certConfig; + private final CertificateLoader certificateLoader; + + public WechatPayCertificateDiagnostic(CertificateProperties certConfig, CertificateLoader certificateLoader) { + this.certConfig = certConfig; + this.certificateLoader = certificateLoader; + } + + /** + * 全面诊断微信支付证书配置 + * + * @param payment 支付配置 + * @param tenantId 租户ID + * @param environment 环境(dev/prod) + * @return 诊断结果 + */ + public DiagnosticResult diagnoseCertificateConfig(Payment payment, Integer tenantId, String environment) { + DiagnosticResult result = new DiagnosticResult(); + + log.info("=== 开始微信支付证书诊断 ==="); + log.info("租户ID: {}, 环境: {}", tenantId, environment); + + try { + // 1. 检查基本配置 + checkBasicConfig(payment, result); + + // 2. 检查证书文件 + checkCertificateFiles(payment, tenantId, environment, result); + + // 3. 检查证书内容 + validateCertificateContent(payment, tenantId, environment, result); + + // 4. 生成建议 + generateRecommendations(result); + + } catch (Exception e) { + result.addError("诊断过程中发生异常: " + e.getMessage()); + log.error("证书诊断异常", e); + } + + log.info("=== 证书诊断完成 ==="); + return result; + } + + /** + * 检查基本配置 + */ + private void checkBasicConfig(Payment payment, DiagnosticResult result) { + if (payment == null) { + result.addError("支付配置为空"); + return; + } + + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + result.addError("商户号未配置"); + } else { + result.addInfo("商户号: " + payment.getMchId()); + } + + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + result.addError("应用ID未配置"); + } else { + result.addInfo("应用ID: " + payment.getAppId()); + } + + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + result.addError("商户证书序列号未配置"); + } else { + result.addInfo("商户证书序列号: " + payment.getMerchantSerialNumber()); + } + + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + result.addWarning("数据库中APIv3密钥未配置,将使用配置文件默认值"); + } else { + result.addInfo("APIv3密钥: 已配置(" + payment.getApiKey().length() + "位)"); + } + } + + /** + * 检查证书文件 + */ + private void checkCertificateFiles(Payment payment, Integer tenantId, String environment, DiagnosticResult result) { + if ("dev".equals(environment)) { + // 开发环境证书检查 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + // 检查私钥文件 + if (certificateLoader.certificateExists(privateKeyPath)) { + result.addInfo("✅ 私钥文件存在: " + privateKeyPath); + try { + String privateKeyFile = certificateLoader.loadCertificatePath(privateKeyPath); + result.addInfo("私钥文件路径: " + privateKeyFile); + } catch (Exception e) { + result.addError("私钥文件加载失败: " + e.getMessage()); + } + } else { + result.addError("❌ 私钥文件不存在: " + privateKeyPath); + } + + // 检查商户证书文件 + if (certificateLoader.certificateExists(apiclientCertPath)) { + result.addInfo("✅ 商户证书文件存在: " + apiclientCertPath); + } else { + result.addWarning("⚠️ 商户证书文件不存在: " + apiclientCertPath + " (自动证书配置不需要此文件)"); + } + + } else { + // 生产环境证书检查 + if (payment.getApiclientKey() != null) { + result.addInfo("私钥文件配置: " + payment.getApiclientKey()); + } else { + result.addError("生产环境私钥文件路径未配置"); + } + + if (payment.getApiclientCert() != null) { + result.addInfo("商户证书文件配置: " + payment.getApiclientCert()); + } else { + result.addWarning("生产环境商户证书文件路径未配置 (自动证书配置不需要此文件)"); + } + } + } + + /** + * 验证证书内容 + */ + private void validateCertificateContent(Payment payment, Integer tenantId, String environment, DiagnosticResult result) { + try { + if ("dev".equals(environment)) { + String tenantCertPath = "dev/wechat/" + tenantId; + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + if (certificateLoader.certificateExists(apiclientCertPath)) { + validateX509Certificate(apiclientCertPath, payment.getMerchantSerialNumber(), result); + } + } + } catch (Exception e) { + result.addWarning("证书内容验证失败: " + e.getMessage()); + } + } + + /** + * 验证X509证书 + */ + private void validateX509Certificate(String certPath, String expectedSerialNumber, DiagnosticResult result) { + try { + String actualCertPath = certificateLoader.loadCertificatePath(certPath); + + try (InputStream inputStream = new FileInputStream(new File(actualCertPath))) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + + if (cert != null) { + String actualSerialNumber = cert.getSerialNumber().toString(16).toUpperCase(); + result.addInfo("证书序列号: " + actualSerialNumber); + result.addInfo("证书有效期: " + cert.getNotBefore() + " 至 " + cert.getNotAfter()); + result.addInfo("证书主体: " + cert.getSubjectX500Principal().toString()); + + // 检查序列号是否匹配 + if (expectedSerialNumber != null && !expectedSerialNumber.equalsIgnoreCase(actualSerialNumber)) { + result.addError("证书序列号不匹配! 配置: " + expectedSerialNumber + ", 实际: " + actualSerialNumber); + } else { + result.addInfo("✅ 证书序列号匹配"); + } + + // 检查证书是否过期 + long now = System.currentTimeMillis(); + if (now < cert.getNotBefore().getTime()) { + result.addError("证书尚未生效"); + } else if (now > cert.getNotAfter().getTime()) { + result.addError("证书已过期"); + } else { + result.addInfo("✅ 证书在有效期内"); + } + } else { + result.addError("无法解析证书文件"); + } + } + } catch (Exception e) { + result.addError("证书验证失败: " + e.getMessage()); + } + } + + /** + * 生成建议 + */ + private void generateRecommendations(DiagnosticResult result) { + if (result.hasErrors()) { + result.addRecommendation("🔧 修复建议:"); + + String errorText = result.getErrors(); + if (errorText.contains("商户号")) { + result.addRecommendation("1. 请在支付配置中设置正确的商户号"); + } + + if (errorText.contains("序列号")) { + result.addRecommendation("2. 请检查商户证书序列号是否正确,可在微信商户平台查看"); + } + + if (errorText.contains("证书文件")) { + result.addRecommendation("3. 请确保证书文件已正确放置在指定目录"); + } + + if (errorText.contains("过期")) { + result.addRecommendation("4. 请更新过期的证书文件"); + } + + result.addRecommendation("5. 建议使用RSAAutoCertificateConfig自动证书配置,可避免手动管理证书"); + result.addRecommendation("6. 确保在微信商户平台开启API安全功能并申请使用微信支付公钥"); + } else { + result.addRecommendation("✅ 证书配置正常,建议使用自动证书配置以获得最佳体验"); + } + } + + /** + * 诊断结果类 + */ + public static class DiagnosticResult { + private final StringBuilder errors = new StringBuilder(); + private final StringBuilder warnings = new StringBuilder(); + private final StringBuilder info = new StringBuilder(); + private final StringBuilder recommendations = new StringBuilder(); + + public void addError(String error) { + if (errors.length() > 0) errors.append("\n"); + errors.append(error); + } + + public void addWarning(String warning) { + if (warnings.length() > 0) warnings.append("\n"); + warnings.append(warning); + } + + public void addInfo(String information) { + if (info.length() > 0) info.append("\n"); + info.append(information); + } + + public void addRecommendation(String recommendation) { + if (recommendations.length() > 0) recommendations.append("\n"); + recommendations.append(recommendation); + } + + public boolean hasErrors() { + return errors.length() > 0; + } + + public String getErrors() { + return errors.toString(); + } + + public String getWarnings() { + return warnings.toString(); + } + + public String getInfo() { + return info.toString(); + } + + public String getRecommendations() { + return recommendations.toString(); + } + + public String getFullReport() { + StringBuilder report = new StringBuilder(); + report.append("=== 微信支付证书诊断报告 ===\n\n"); + + if (info.length() > 0) { + report.append("📋 基本信息:\n").append(info).append("\n\n"); + } + + if (warnings.length() > 0) { + report.append("⚠️ 警告:\n").append(warnings).append("\n\n"); + } + + if (errors.length() > 0) { + report.append("❌ 错误:\n").append(errors).append("\n\n"); + } + + if (recommendations.length() > 0) { + report.append("💡 建议:\n").append(recommendations).append("\n\n"); + } + + report.append("=== 诊断报告结束 ==="); + return report.toString(); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java new file mode 100644 index 0000000..af6e357 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java @@ -0,0 +1,312 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +/** + * 微信支付证书修复工具 + * 自动检测和修复常见的证书配置问题 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@Component +public class WechatPayCertificateFixer { + + private final CertificateProperties certConfig; + private final CertificateLoader certificateLoader; + + public WechatPayCertificateFixer(CertificateProperties certConfig, CertificateLoader certificateLoader) { + this.certConfig = certConfig; + this.certificateLoader = certificateLoader; + } + + /** + * 自动修复证书配置问题 + * + * @param payment 支付配置 + * @param tenantId 租户ID + * @param environment 环境 + * @return 修复结果 + */ + public FixResult autoFixCertificateIssues(Payment payment, Integer tenantId, String environment) { + FixResult result = new FixResult(); + + log.info("开始自动修复租户 {} 的证书配置问题", tenantId); + + try { + // 1. 检查并修复基本配置 + fixBasicConfiguration(payment, result); + + // 2. 检查并修复证书文件问题 + fixCertificateFiles(payment, tenantId, environment, result); + + // 3. 检查并修复序列号问题 + fixSerialNumberIssues(payment, tenantId, environment, result); + + // 4. 生成修复建议 + generateFixRecommendations(result); + + } catch (Exception e) { + result.addError("修复过程中发生异常: " + e.getMessage()); + log.error("证书修复异常", e); + } + + log.info("证书配置修复完成,成功修复 {} 个问题", result.getFixedIssues().size()); + return result; + } + + /** + * 修复基本配置问题 + */ + private void fixBasicConfiguration(Payment payment, FixResult result) { + if (payment == null) { + result.addError("支付配置为空,无法修复"); + return; + } + + // 检查商户号 + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + result.addError("商户号未配置,需要手动设置"); + } + + // 检查应用ID + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + result.addError("应用ID未配置,需要手动设置"); + } + + // 检查APIv3密钥 + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + result.addWarning("APIv3密钥未配置,将使用配置文件默认值"); + } else if (payment.getApiKey().length() != 32) { + result.addError("APIv3密钥长度错误,应为32位,实际为: " + payment.getApiKey().length()); + } + + // 检查商户证书序列号 + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + result.addError("商户证书序列号未配置,需要手动设置"); + } + } + + /** + * 修复证书文件问题 + */ + private void fixCertificateFiles(Payment payment, Integer tenantId, String environment, FixResult result) { + if ("dev".equals(environment)) { + fixDevCertificateFiles(tenantId, result); + } else { + fixProdCertificateFiles(payment, result); + } + } + + /** + * 修复开发环境证书文件问题 + */ + private void fixDevCertificateFiles(Integer tenantId, FixResult result) { + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + // 检查私钥文件 + if (!certificateLoader.certificateExists(privateKeyPath)) { + result.addError("私钥文件不存在: " + privateKeyPath); + result.addRecommendation("请将 apiclient_key.pem 文件放置到 src/main/resources/" + privateKeyPath); + } else { + result.addFixed("私钥文件存在: " + privateKeyPath); + + // 尝试加载私钥文件 + try { + String privateKeyFile = certificateLoader.loadCertificatePath(privateKeyPath); + result.addFixed("私钥文件加载成功: " + privateKeyFile); + } catch (Exception e) { + result.addError("私钥文件加载失败: " + e.getMessage()); + } + } + + // 检查商户证书文件(可选) + if (!certificateLoader.certificateExists(apiclientCertPath)) { + result.addWarning("商户证书文件不存在: " + apiclientCertPath + " (自动证书配置不需要此文件)"); + } else { + result.addFixed("商户证书文件存在: " + apiclientCertPath); + } + } + + /** + * 修复生产环境证书文件问题 + */ + private void fixProdCertificateFiles(Payment payment, FixResult result) { + if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) { + result.addError("生产环境私钥文件路径未配置"); + } else { + result.addFixed("生产环境私钥文件路径已配置: " + payment.getApiclientKey()); + } + + if (payment.getApiclientCert() == null || payment.getApiclientCert().trim().isEmpty()) { + result.addWarning("生产环境商户证书文件路径未配置 (自动证书配置不需要此文件)"); + } else { + result.addFixed("生产环境商户证书文件路径已配置: " + payment.getApiclientCert()); + } + } + + /** + * 修复序列号问题 + */ + private void fixSerialNumberIssues(Payment payment, Integer tenantId, String environment, FixResult result) { + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + result.addError("商户证书序列号未配置"); + return; + } + + // 在开发环境中,尝试从证书文件中提取序列号进行验证 + if ("dev".equals(environment)) { + try { + String tenantCertPath = "dev/wechat/" + tenantId; + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + if (certificateLoader.certificateExists(apiclientCertPath)) { + String actualCertPath = certificateLoader.loadCertificatePath(apiclientCertPath); + + try (InputStream inputStream = new FileInputStream(new File(actualCertPath))) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + + if (cert != null) { + String actualSerialNumber = cert.getSerialNumber().toString(16).toUpperCase(); + String configuredSerialNumber = payment.getMerchantSerialNumber(); + + if (!configuredSerialNumber.equalsIgnoreCase(actualSerialNumber)) { + result.addError("证书序列号不匹配! 配置: " + configuredSerialNumber + ", 实际: " + actualSerialNumber); + result.addRecommendation("建议将商户证书序列号更新为: " + actualSerialNumber); + } else { + result.addFixed("证书序列号匹配: " + actualSerialNumber); + } + } + } + } + } catch (Exception e) { + result.addWarning("无法验证证书序列号: " + e.getMessage()); + } + } + } + + /** + * 生成修复建议 + */ + private void generateFixRecommendations(FixResult result) { + if (result.hasErrors()) { + result.addRecommendation("=== 修复建议 ==="); + + if (result.getErrors().stream().anyMatch(e -> e.contains("商户号"))) { + result.addRecommendation("1. 请在微信商户平台获取商户号并在系统中配置"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("应用ID"))) { + result.addRecommendation("2. 请在微信开放平台获取应用ID并在系统中配置"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("APIv3密钥"))) { + result.addRecommendation("3. 请在微信商户平台设置32位APIv3密钥"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("证书序列号"))) { + result.addRecommendation("4. 请在微信商户平台查看正确的商户证书序列号"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("私钥文件"))) { + result.addRecommendation("5. 请从微信商户平台下载私钥文件并放置到正确位置"); + } + + result.addRecommendation("6. 建议使用RSAAutoCertificateConfig自动证书配置"); + result.addRecommendation("7. 确保在微信商户平台开启API安全功能"); + } + } + + /** + * 修复结果类 + */ + public static class FixResult { + private final List errors = new ArrayList<>(); + private final List warnings = new ArrayList<>(); + private final List fixedIssues = new ArrayList<>(); + private final List recommendations = new ArrayList<>(); + + public void addError(String error) { + errors.add(error); + } + + public void addWarning(String warning) { + warnings.add(warning); + } + + public void addFixed(String fixed) { + fixedIssues.add(fixed); + } + + public void addRecommendation(String recommendation) { + recommendations.add(recommendation); + } + + public boolean hasErrors() { + return !errors.isEmpty(); + } + + public List getErrors() { + return errors; + } + + public List getWarnings() { + return warnings; + } + + public List getFixedIssues() { + return fixedIssues; + } + + public List getRecommendations() { + return recommendations; + } + + public String getFullReport() { + StringBuilder report = new StringBuilder(); + report.append("=== 微信支付证书修复报告 ===\n\n"); + + if (!fixedIssues.isEmpty()) { + report.append("✅ 已修复的问题:\n"); + fixedIssues.forEach(issue -> report.append(" - ").append(issue).append("\n")); + report.append("\n"); + } + + if (!warnings.isEmpty()) { + report.append("⚠️ 警告:\n"); + warnings.forEach(warning -> report.append(" - ").append(warning).append("\n")); + report.append("\n"); + } + + if (!errors.isEmpty()) { + report.append("❌ 需要手动修复的问题:\n"); + errors.forEach(error -> report.append(" - ").append(error).append("\n")); + report.append("\n"); + } + + if (!recommendations.isEmpty()) { + report.append("💡 修复建议:\n"); + recommendations.forEach(rec -> report.append(" ").append(rec).append("\n")); + report.append("\n"); + } + + report.append("=== 修复报告结束 ==="); + return report.toString(); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java new file mode 100644 index 0000000..74a6fa4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java @@ -0,0 +1,243 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 微信支付配置检查器 + * 用于快速检查和验证微信支付配置状态 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@Component +public class WechatPayConfigChecker { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private CertificateLoader certificateLoader; + + @Autowired + private CertificateProperties certConfig; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + /** + * 检查租户的微信支付配置状态 + * + * @param tenantId 租户ID + * @return 配置状态报告 + */ + public ConfigStatus checkTenantConfig(Integer tenantId) { + ConfigStatus status = new ConfigStatus(); + status.tenantId = tenantId; + status.environment = activeProfile; + + try { + // 获取支付配置 + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + status.hasError = true; + status.errorMessage = "支付配置不存在"; + return status; + } + + status.merchantId = payment.getMchId(); + status.appId = payment.getAppId(); + status.serialNumber = payment.getMerchantSerialNumber(); + status.hasApiKey = payment.getApiKey() != null && !payment.getApiKey().isEmpty(); + status.apiKeyLength = payment.getApiKey() != null ? payment.getApiKey().length() : 0; + + // 检查公钥配置 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + + status.hasPublicKey = true; + status.publicKeyFile = payment.getPubKey(); + status.publicKeyId = payment.getPubKeyId(); + status.configMode = "公钥模式"; + + // 检查公钥文件是否存在 + String tenantCertPath = "dev/wechat/" + tenantId; + String pubKeyPath = tenantCertPath + "/" + payment.getPubKey(); + status.publicKeyExists = certificateLoader.certificateExists(pubKeyPath); + + } else { + status.hasPublicKey = false; + status.configMode = "自动证书模式"; + } + + // 检查私钥文件 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + status.privateKeyExists = certificateLoader.certificateExists(privateKeyPath); + + // 检查商户证书文件 + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + status.merchantCertExists = certificateLoader.certificateExists(apiclientCertPath); + + // 评估配置完整性 + evaluateConfigCompleteness(status); + + } catch (Exception e) { + status.hasError = true; + status.errorMessage = "检查配置时发生异常: " + e.getMessage(); + log.error("检查租户 {} 配置时发生异常", tenantId, e); + } + + return status; + } + + /** + * 评估配置完整性 + */ + private void evaluateConfigCompleteness(ConfigStatus status) { + if (status.hasError) { + return; + } + + // 基本配置检查 + if (status.merchantId == null || status.merchantId.isEmpty()) { + status.addIssue("商户号未配置"); + } + + if (status.appId == null || status.appId.isEmpty()) { + status.addIssue("应用ID未配置"); + } + + if (status.serialNumber == null || status.serialNumber.isEmpty()) { + status.addIssue("商户证书序列号未配置"); + } + + if (!status.hasApiKey) { + status.addIssue("APIv3密钥未配置"); + } else if (status.apiKeyLength != 32) { + status.addIssue("APIv3密钥长度错误,应为32位,实际为" + status.apiKeyLength + "位"); + } + + if (!status.privateKeyExists) { + status.addIssue("私钥文件不存在"); + } + + // 公钥模式特定检查 + if (status.hasPublicKey) { + if (!status.publicKeyExists) { + status.addIssue("公钥文件不存在"); + } + } + + // 设置配置状态 + if (status.issues.isEmpty()) { + status.configComplete = true; + status.recommendation = "✅ 配置完整,建议使用当前配置"; + } else { + status.configComplete = false; + if (status.hasPublicKey) { + status.recommendation = "⚠️ 公钥配置不完整,建议修复问题或回退到自动证书模式"; + } else { + status.recommendation = "⚠️ 自动证书配置不完整,建议配置公钥模式或修复当前问题"; + } + } + } + + /** + * 生成配置建议 + */ + public String generateConfigAdvice(Integer tenantId) { + ConfigStatus status = checkTenantConfig(tenantId); + + StringBuilder advice = new StringBuilder(); + advice.append("=== 租户 ").append(tenantId).append(" 微信支付配置建议 ===\n\n"); + + advice.append("当前配置模式: ").append(status.configMode).append("\n"); + advice.append("配置完整性: ").append(status.configComplete ? "完整" : "不完整").append("\n\n"); + + if (status.hasError) { + advice.append("❌ 错误: ").append(status.errorMessage).append("\n\n"); + return advice.toString(); + } + + // 基本信息 + advice.append("📋 基本信息:\n"); + advice.append(" 商户号: ").append(status.merchantId).append("\n"); + advice.append(" 应用ID: ").append(status.appId).append("\n"); + advice.append(" 序列号: ").append(status.serialNumber).append("\n"); + advice.append(" API密钥: ").append(status.hasApiKey ? "已配置(" + status.apiKeyLength + "位)" : "未配置").append("\n\n"); + + // 证书文件状态 + advice.append("📁 证书文件状态:\n"); + advice.append(" 私钥文件: ").append(status.privateKeyExists ? "✅ 存在" : "❌ 不存在").append("\n"); + advice.append(" 商户证书: ").append(status.merchantCertExists ? "✅ 存在" : "⚠️ 不存在").append("\n"); + + if (status.hasPublicKey) { + advice.append(" 公钥文件: ").append(status.publicKeyExists ? "✅ 存在" : "❌ 不存在").append("\n"); + advice.append(" 公钥ID: ").append(status.publicKeyId).append("\n"); + } + advice.append("\n"); + + // 问题列表 + if (!status.issues.isEmpty()) { + advice.append("⚠️ 发现的问题:\n"); + for (String issue : status.issues) { + advice.append(" - ").append(issue).append("\n"); + } + advice.append("\n"); + } + + // 建议 + advice.append("💡 建议:\n"); + advice.append(" ").append(status.recommendation).append("\n\n"); + + if (!status.hasPublicKey) { + advice.append("🔧 配置公钥模式的步骤:\n"); + advice.append(" 1. 获取微信支付平台公钥文件和公钥ID\n"); + advice.append(" 2. 将公钥文件放置到: src/main/resources/dev/wechat/").append(tenantId).append("/\n"); + advice.append(" 3. 执行SQL更新数据库配置:\n"); + advice.append(" UPDATE sys_payment SET \n"); + advice.append(" pub_key = 'wechatpay_public_key.pem',\n"); + advice.append(" pub_key_id = 'YOUR_PUBLIC_KEY_ID'\n"); + advice.append(" WHERE tenant_id = ").append(tenantId).append(" AND type = 0;\n\n"); + } + + advice.append("=== 配置建议结束 ==="); + return advice.toString(); + } + + /** + * 配置状态类 + */ + public static class ConfigStatus { + public Integer tenantId; + public String environment; + public String merchantId; + public String appId; + public String serialNumber; + public boolean hasApiKey; + public int apiKeyLength; + public boolean hasPublicKey; + public String publicKeyFile; + public String publicKeyId; + public boolean publicKeyExists; + public boolean privateKeyExists; + public boolean merchantCertExists; + public String configMode; + public boolean configComplete; + public boolean hasError; + public String errorMessage; + public String recommendation; + public java.util.List issues = new java.util.ArrayList<>(); + + public void addIssue(String issue) { + issues.add(issue); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigValidator.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigValidator.java new file mode 100644 index 0000000..23326d2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigValidator.java @@ -0,0 +1,223 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * 微信支付配置验证工具 + * + * @author 科技小王子 + * @since 2025-07-27 + */ +@Slf4j +@Component +public class WechatPayConfigValidator { + + private final CertificateProperties certConfig; + private final CertificateLoader certificateLoader; + + @Value("${spring.profiles.active}") + private String activeProfile; + + public WechatPayConfigValidator(CertificateProperties certConfig, CertificateLoader certificateLoader) { + this.certConfig = certConfig; + this.certificateLoader = certificateLoader; + } + + /** + * 验证微信支付配置 + * + * @param payment 支付配置 + * @param tenantId 租户ID + * @return 验证结果 + */ + public ValidationResult validateWechatPayConfig(Payment payment, Integer tenantId) { + ValidationResult result = new ValidationResult(); + + log.info("开始验证微信支付配置 - 租户ID: {}", tenantId); + + // 1. 验证基本配置 + if (payment == null) { + result.addError("支付配置为空"); + return result; + } + + if (!StringUtils.hasText(payment.getMchId())) { + result.addError("商户号未配置"); + } + + if (!StringUtils.hasText(payment.getAppId())) { + result.addError("应用ID未配置"); + } + + if (!StringUtils.hasText(payment.getMerchantSerialNumber())) { + result.addError("商户证书序列号未配置"); + } + + // 2. 验证 APIv3 密钥 + String apiV3Key = getValidApiV3Key(payment); + if (!StringUtils.hasText(apiV3Key)) { + result.addError("APIv3密钥未配置"); + } else { + validateApiV3Key(apiV3Key, result); + } + + // 3. 验证证书文件 + validateCertificateFiles(tenantId, result); + + // 4. 记录验证结果 + if (result.isValid()) { + log.info("✅ 微信支付配置验证通过 - 租户ID: {}", tenantId); + } else { + log.error("❌ 微信支付配置验证失败 - 租户ID: {}, 错误: {}", tenantId, result.getErrors()); + } + + return result; + } + + /** + * 获取有效的 APIv3 密钥 + * 优先使用数据库配置,如果为空则使用配置文件默认值 + */ + public String getValidApiV3Key(Payment payment) { + String apiV3Key = payment.getApiKey(); + + if (!StringUtils.hasText(apiV3Key)) { + apiV3Key = certConfig.getWechatPay().getDev().getApiV3Key(); + log.warn("数据库中APIv3密钥为空,使用配置文件默认值"); + } + + return apiV3Key; + } + + /** + * 验证 APIv3 密钥格式 + */ + private void validateApiV3Key(String apiV3Key, ValidationResult result) { + if (apiV3Key.length() != 32) { + result.addError("APIv3密钥长度错误,应为32位,实际为: " + apiV3Key.length()); + } + + if (!apiV3Key.matches("^[a-zA-Z0-9]+$")) { + result.addError("APIv3密钥格式错误,应仅包含字母和数字"); + } + + log.info("APIv3密钥验证 - 长度: {}, 格式: {}", + apiV3Key.length(), + apiV3Key.matches("^[a-zA-Z0-9]+$") ? "正确" : "错误"); + } + + /** + * 验证证书文件 + */ + private void validateCertificateFiles(Integer tenantId, ValidationResult result) { + if ("dev".equals(activeProfile)) { + // 开发环境证书验证 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + + if (!certificateLoader.certificateExists(privateKeyPath)) { + result.addError("证书文件不存在: " + privateKeyPath); + return; + } + + try { + certificateLoader.loadCertificatePath(privateKeyPath); + log.info("✅ 开发环境证书文件验证通过: {}", privateKeyPath); + } catch (Exception e) { + result.addError("证书文件加载失败: " + e.getMessage()); + } + } else { + // 生产环境证书验证 - 跳过文件存在性检查,因为证书路径来自数据库 + log.info("✅ 生产环境跳过证书文件存在性验证,使用数据库配置的证书路径"); + } + } + + /** + * 验证结果类 + */ + public static class ValidationResult { + private boolean valid = true; + private StringBuilder errors = new StringBuilder(); + + public void addError(String error) { + this.valid = false; + if (errors.length() > 0) { + errors.append("; "); + } + errors.append(error); + } + + public boolean isValid() { + return valid; + } + + public String getErrors() { + return errors.toString(); + } + + public void logErrors() { + if (!valid) { + log.error("配置验证失败: {}", errors.toString()); + } + } + } + + /** + * 生成配置诊断报告 + */ + public String generateDiagnosticReport(Payment payment, Integer tenantId) { + StringBuilder report = new StringBuilder(); + report.append("=== 微信支付配置诊断报告 ===\n"); + report.append("租户ID: ").append(tenantId).append("\n"); + + if (payment != null) { + report.append("商户号: ").append(payment.getMchId()).append("\n"); + report.append("应用ID: ").append(payment.getAppId()).append("\n"); + report.append("商户证书序列号: ").append(payment.getMerchantSerialNumber()).append("\n"); + + String dbApiKey = payment.getApiKey(); + String configApiKey = certConfig.getWechatPay().getDev().getApiV3Key(); + + report.append("数据库APIv3密钥: ").append(dbApiKey != null ? "已配置(" + dbApiKey.length() + "位)" : "未配置").append("\n"); + report.append("配置文件APIv3密钥: ").append(configApiKey != null ? "已配置(" + configApiKey.length() + "位)" : "未配置").append("\n"); + + String finalApiKey = getValidApiV3Key(payment); + report.append("最终使用APIv3密钥: ").append(finalApiKey != null ? "已配置(" + finalApiKey.length() + "位)" : "未配置").append("\n"); + + } else { + report.append("❌ 支付配置为空\n"); + } + + // 证书文件检查 + report.append("当前环境: ").append(activeProfile).append("\n"); + if ("dev".equals(activeProfile)) { + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + boolean certExists = certificateLoader.certificateExists(privateKeyPath); + + report.append("开发环境证书文件路径: ").append(privateKeyPath).append("\n"); + report.append("证书文件存在: ").append(certExists ? "是" : "否").append("\n"); + } else { + report.append("生产环境证书路径: 从数据库配置获取\n"); + if (payment != null) { + report.append("私钥文件: ").append(payment.getApiclientKey()).append("\n"); + report.append("证书文件: ").append(payment.getApiclientCert()).append("\n"); + } + } + + ValidationResult validation = validateWechatPayConfig(payment, tenantId); + report.append("配置验证结果: ").append(validation.isValid() ? "通过" : "失败").append("\n"); + if (!validation.isValid()) { + report.append("验证错误: ").append(validation.getErrors()).append("\n"); + } + + report.append("=== 诊断报告结束 ==="); + + return report.toString(); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayDiagnostic.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayDiagnostic.java new file mode 100644 index 0000000..620d506 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayDiagnostic.java @@ -0,0 +1,222 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * 微信支付配置诊断工具 + * 用于排查微信支付签名验证失败等问题 + * + * @author 科技小王子 + * @since 2025-07-27 + */ +@Slf4j +@Component +public class WechatPayDiagnostic { + + /** + * 诊断微信支付配置 + * + * @param payment 支付配置 + * @param privateKeyPath 私钥路径 + * @param environment 环境标识 + */ + public void diagnosePaymentConfig(Payment payment, String privateKeyPath, String environment) { + log.info("=== 微信支付配置诊断开始 ==="); + log.info("环境: {}", environment); + + // 1. 检查支付配置基本信息 + checkBasicConfig(payment); + + // 2. 检查证书文件 + checkCertificateFiles(payment, privateKeyPath, environment); + + // 3. 检查配置完整性 + checkConfigCompleteness(payment); + + log.info("=== 微信支付配置诊断结束 ==="); + } + + /** + * 检查基本配置信息 + */ + private void checkBasicConfig(Payment payment) { + log.info("--- 基本配置检查 ---"); + + if (payment == null) { + log.error("❌ 支付配置为空"); + return; + } + + log.info("支付配置ID: {}", payment.getId()); + log.info("支付方式名称: {}", payment.getName()); + log.info("支付类型: {}", payment.getType()); + log.info("支付代码: {}", payment.getCode()); + log.info("状态: {}", payment.getStatus()); + + // 检查关键字段 + checkField("应用ID", payment.getAppId()); + checkField("商户号", payment.getMchId()); + checkField("商户证书序列号", payment.getMerchantSerialNumber()); + checkField("API密钥", payment.getApiKey(), true); + } + + /** + * 检查证书文件 + */ + private void checkCertificateFiles(Payment payment, String privateKeyPath, String environment) { + log.info("--- 证书文件检查 ---"); + + // 检查私钥文件 + if (privateKeyPath != null) { + checkFileExists("私钥文件", privateKeyPath); + } + + // 生产环境检查证书文件 + if (!"dev".equals(environment)) { + if (payment.getApiclientCert() != null) { + log.info("商户证书文件配置: {}", payment.getApiclientCert()); + } + + if (payment.getPubKey() != null) { + log.info("公钥文件配置: {}", payment.getPubKey()); + log.info("公钥ID: {}", payment.getPubKeyId()); + } + } + } + + /** + * 检查配置完整性 + */ + private void checkConfigCompleteness(Payment payment) { + log.info("--- 配置完整性检查 ---"); + + boolean isComplete = true; + + if (isEmpty(payment.getMchId())) { + log.error("❌ 商户号未配置"); + isComplete = false; + } + + if (isEmpty(payment.getMerchantSerialNumber())) { + log.error("❌ 商户证书序列号未配置"); + isComplete = false; + } + + if (isEmpty(payment.getApiKey())) { + log.error("❌ API密钥未配置"); + isComplete = false; + } + + if (isEmpty(payment.getAppId())) { + log.error("❌ 应用ID未配置"); + isComplete = false; + } + + if (isComplete) { + log.info("✅ 配置完整性检查通过"); + } else { + log.error("❌ 配置不完整,请补充缺失的配置项"); + } + } + + /** + * 检查字段是否为空 + */ + private void checkField(String fieldName, String value) { + checkField(fieldName, value, false); + } + + /** + * 检查字段是否为空 + */ + private void checkField(String fieldName, String value, boolean isSensitive) { + if (isEmpty(value)) { + log.warn("⚠️ {}: 未配置", fieldName); + } else { + if (isSensitive) { + log.info("✅ {}: 已配置(长度:{})", fieldName, value.length()); + } else { + log.info("✅ {}: {}", fieldName, value); + } + } + } + + /** + * 检查文件是否存在 + */ + private void checkFileExists(String fileName, String filePath) { + try { + File file = new File(filePath); + if (file.exists() && file.isFile()) { + log.info("✅ {}: 文件存在 - {}", fileName, filePath); + log.info(" 文件大小: {} bytes", file.length()); + + // 检查文件内容格式 + if (filePath.endsWith(".pem")) { + checkPemFileFormat(fileName, filePath); + } + } else { + log.error("❌ {}: 文件不存在 - {}", fileName, filePath); + } + } catch (Exception e) { + log.error("❌ {}: 检查文件时出错 - {} ({})", fileName, filePath, e.getMessage()); + } + } + + /** + * 检查PEM文件格式 + */ + private void checkPemFileFormat(String fileName, String filePath) { + try { + String content = Files.readString(Paths.get(filePath)); + if (content.contains("-----BEGIN") && content.contains("-----END")) { + log.info("✅ {}: PEM格式正确", fileName); + } else { + log.warn("⚠️ {}: PEM格式可能有问题", fileName); + } + } catch (Exception e) { + log.warn("⚠️ {}: 无法读取文件内容进行格式检查 ({})", fileName, e.getMessage()); + } + } + + /** + * 检查字符串是否为空 + */ + private boolean isEmpty(String str) { + return str == null || str.trim().isEmpty(); + } + + /** + * 生成诊断报告 + */ + public String generateDiagnosticReport(Payment payment, String environment) { + StringBuilder report = new StringBuilder(); + report.append("🔍 微信支付配置诊断报告\n"); + report.append("========================\n\n"); + + report.append("环境: ").append(environment).append("\n"); + report.append("租户ID: ").append(payment != null ? payment.getTenantId() : "未知").append("\n"); + report.append("商户号: ").append(payment != null ? payment.getMchId() : "未配置").append("\n"); + report.append("应用ID: ").append(payment != null ? payment.getAppId() : "未配置").append("\n\n"); + + report.append("🚨 常见问题排查:\n"); + report.append("1. 商户证书序列号是否正确\n"); + report.append("2. APIv3密钥是否正确\n"); + report.append("3. 私钥文件是否正确\n"); + report.append("4. 微信支付平台证书是否过期\n"); + report.append("5. 网络连接是否正常\n\n"); + + report.append("💡 建议解决方案:\n"); + report.append("1. 使用自动证书配置(RSAAutoCertificateConfig)\n"); + report.append("2. 在微信商户平台重新下载证书\n"); + report.append("3. 检查商户平台API安全设置\n"); + + return report.toString(); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayUtils.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayUtils.java new file mode 100644 index 0000000..14a31e9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayUtils.java @@ -0,0 +1,111 @@ +package com.gxwebsoft.common.core.utils; + +import java.nio.charset.StandardCharsets; + +/** + * 微信支付工具类 + * 处理微信支付API的字段限制和格式要求 + * + * @author 科技小王子 + * @since 2025-01-11 + */ +public class WechatPayUtils { + + /** + * 微信支付description字段的最大字节数限制 + */ + public static final int DESCRIPTION_MAX_BYTES = 127; + + /** + * 微信支付attach字段的最大字节数限制 + */ + public static final int ATTACH_MAX_BYTES = 127; + + /** + * 截断字符串以确保字节数不超过指定限制 + * 主要用于微信支付API的字段限制处理 + * + * @param text 原始文本 + * @param maxBytes 最大字节数 + * @return 截断后的文本,确保UTF-8字符完整性 + */ + public static String truncateToByteLimit(String text, int maxBytes) { + if (text == null || text.isEmpty()) { + return text; + } + + byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + if (bytes.length <= maxBytes) { + return text; + } + + // 截断字节数组,但要确保不会截断UTF-8字符的中间 + int truncateLength = maxBytes; + while (truncateLength > 0) { + byte[] truncated = new byte[truncateLength]; + System.arraycopy(bytes, 0, truncated, 0, truncateLength); + + try { + String result = new String(truncated, StandardCharsets.UTF_8); + // 检查是否有无效字符(被截断的UTF-8字符) + if (!result.contains("\uFFFD")) { + return result; + } + } catch (Exception e) { + // 继续尝试更短的长度 + } + truncateLength--; + } + + return ""; // 如果无法安全截断,返回空字符串 + } + + /** + * 处理微信支付商品描述字段 + * 确保字节数不超过127字节 + * + * @param description 商品描述 + * @return 处理后的描述,符合微信支付要求 + */ + public static String processDescription(String description) { + return truncateToByteLimit(description, DESCRIPTION_MAX_BYTES); + } + + /** + * 处理微信支付附加数据字段 + * 确保字节数不超过127字节 + * + * @param attach 附加数据 + * @return 处理后的附加数据,符合微信支付要求 + */ + public static String processAttach(String attach) { + return truncateToByteLimit(attach, ATTACH_MAX_BYTES); + } + + /** + * 验证字符串是否符合微信支付字段的字节限制 + * + * @param text 待验证的文本 + * @param maxBytes 最大字节数限制 + * @return true如果符合限制,false如果超出限制 + */ + public static boolean isWithinByteLimit(String text, int maxBytes) { + if (text == null) { + return true; + } + return text.getBytes(StandardCharsets.UTF_8).length <= maxBytes; + } + + /** + * 获取字符串的UTF-8字节数 + * + * @param text 文本 + * @return 字节数 + */ + public static int getByteLength(String text) { + if (text == null) { + return 0; + } + return text.getBytes(StandardCharsets.UTF_8).length; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java new file mode 100644 index 0000000..52a8d2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java @@ -0,0 +1,20 @@ +package com.gxwebsoft.common.core.utils; + +import com.wechat.pay.java.core.Config; + +import java.util.HashMap; +import java.util.Map; + + +public class WxNativeUtil { + + private static final Map tenantConfigs = new HashMap<>(); + + public static void addConfig(Integer tenantId, Config config) { + tenantConfigs.put(tenantId, config); + } + + public static Config getConfig(Integer tenantId) { + return tenantConfigs.get(tenantId); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WxOfficialUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/WxOfficialUtil.java new file mode 100644 index 0000000..4f10747 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WxOfficialUtil.java @@ -0,0 +1,106 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.system.service.SettingService; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * 微信公众号工具类 + * @author 科技小王子 + * + */ +@Component +public class WxOfficialUtil { + private final StringRedisTemplate stringRedisTemplate; + private Integer tenantId; + public String appId; + public String appSecret; + public String openid; + public String unionid; + public String access_token; + public String expires_in; + public String nickname; + + + @Resource + private SettingService settingService; + @Resource + private ConfigProperties pathConfig; + @Resource + private CacheClient cacheClient; + + public WxOfficialUtil(StringRedisTemplate stringRedisTemplate){ + this.stringRedisTemplate = stringRedisTemplate; + } + + // 实例化客户端 + public WxOfficialUtil client(Integer tenantId) { + if(tenantId > 0){ + throw new BusinessException(tenantId + "123123"); + } + this.tenantId = tenantId; + this.config(); + System.out.println("this.tenantId = " + this.tenantId); + return this; + } + + // 开发者ID和秘钥 + private void config() { + String key = "cache"+ this.tenantId +":setting:wx-official"; + String wxOfficial = stringRedisTemplate.opsForValue().get(key); + JSONObject data = JSONObject.parseObject(wxOfficial); + if(data != null){ + this.appId = data.getString("appId"); + this.appSecret = data.getString("appSecret"); + } + System.out.println("this.appId = " + this.appId); + System.out.println("this.appSecret = " + this.appSecret); + } + + // 获取appId + public String getAppSecret(){ + return this.appSecret; + } + + public String getCodeUrl() throws UnsupportedEncodingException { + String encodedReturnUrl = URLEncoder.encode(pathConfig.getServerUrl() + "/open/wx-official/accessToken","UTF-8"); + return "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ this.appId +"&redirect_uri=" + encodedReturnUrl + "&response_type=code&scope=snsapi_userinfo&state="+ this.tenantId +"#wechat_redirect"; + } + + // 获取access_token + public String getAccessToken(String code) { + String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ this.appId +"&secret="+ this.appSecret +"&code="+ code +"&grant_type=authorization_code"; + System.out.println("url = " + url); + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + final JSONObject jsonObject = JSONObject.parseObject(response); + access_token = jsonObject.getString("access_token"); + if(access_token == null){ + throw new BusinessException("获取access_token失败"); + } + this.openid = jsonObject.getString("openid"); + this.unionid = jsonObject.getString("unionid"); + this.expires_in = jsonObject.getString("expires_in"); + return access_token; + } + + // 获取userinfo + public JSONObject getUserInfo(String access_token) { + String url = "https://api.weixin.qq.com/sns/userinfo?access_token="+ access_token +"&openid="+ this.openid +"&lang=zh_CN"; + System.out.println("url2 = " + url); + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + System.out.println("response = " + response); + if(response == null){ + throw new BusinessException("获取userinfo失败"); + } + return JSONObject.parseObject(response); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WxUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/WxUtil.java new file mode 100644 index 0000000..035ce91 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WxUtil.java @@ -0,0 +1,132 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.exception.BusinessException; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.concurrent.TimeUnit; + +/** + * 微信小程序工具类 + * @author 科技小王子 + * + */ +@Component +public class WxUtil { + private final StringRedisTemplate stringRedisTemplate; + private Integer tenantId; + public String appId; + public String appSecret; + public String access_token; + public String expires_in; + public String nickname; + public String userid; + public String user_ticket; + public String openid; + public String external_userid; + public String name; + public String position; + public String mobile; + public String gender; + public String email; + public String avatar; + public String thumb_avatar; + public String telephone; + public String address; + public String alias; + public String qr_code; + public String open_userid; + + @Resource + private CacheClient cacheClient; + + + public WxUtil(StringRedisTemplate stringRedisTemplate){ + this.stringRedisTemplate = stringRedisTemplate; + } + + + // 实例化客户端 + public WxUtil client(Integer tenantId) { + this.tenantId = tenantId; + this.config(); + return this; + } + + // 开发者ID和秘钥 + private void config() { + JSONObject settingInfo = cacheClient.getSettingInfo("wx-work", this.tenantId); + if(settingInfo == null){ + throw new BusinessException("微信小程序未配置"); + } + this.appId = settingInfo.getString("corpId"); + this.appSecret = settingInfo.getString("secret"); + System.out.println("this.appId = " + this.appId); + System.out.println("this.appSecret = " + this.appSecret); + } + + // 获取access_token + public void getAccessToken(String code) { + String key = "cache"+ this.tenantId +":ww:access_token"; + final String access_token = stringRedisTemplate.opsForValue().get(key); + if(access_token != null){ + this.getUserInfo(code,access_token); + }else { + String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" +this.appId+ "&corpsecret="+ this.appSecret; + System.out.println("url = " + url); + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + System.out.println("response = " + response); + final JSONObject jsonObject = JSONObject.parseObject(response); + // 获取成功 + if(jsonObject.getString("access_token") != null){ + this.access_token = jsonObject.getString("access_token"); + this.expires_in = jsonObject.getString("expires_in"); + stringRedisTemplate.opsForValue().set(key,this.access_token,7000, TimeUnit.SECONDS); + System.out.println("获取access_token成功 = " + this.access_token); + this.getUserInfo(code,this.access_token); + } + } + } + + // 获取userinfo + public void getUserInfo(String code, String access_token) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=" +access_token+ "&code=" + code; + System.out.println("url2 = " + url); + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + JSONObject jsonObject = JSONObject.parseObject(response); + final String errcode = jsonObject.getString("errcode"); + final String errmsg = jsonObject.getString("errmsg"); + if(!StrUtil.equals(errcode,"0")){ + throw new BusinessException(errmsg); + } + this.userid = jsonObject.getString("userid"); + this.user_ticket = jsonObject.getString("user_ticket"); + this.openid = jsonObject.getString("openid"); + this.external_userid = jsonObject.getString("external_userid"); + } + + public void getUserProfile(String userid, String access_token) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token="+ access_token +"&userid=" + userid; + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + JSONObject jsonObject = JSONObject.parseObject(response); + + this.name = jsonObject.getString("name"); + this.position = jsonObject.getString("position"); + this.gender = jsonObject.getString("gender"); + this.email = jsonObject.getString("email"); + this.avatar = jsonObject.getString("avatar"); + this.thumb_avatar = jsonObject.getString("thumb_avatar"); + this.telephone = jsonObject.getString("telephone"); + this.address = jsonObject.getString("address"); + this.alias = jsonObject.getString("alias"); + this.qr_code = jsonObject.getString("qr_code"); + this.open_userid = jsonObject.getString("open_userid"); + } + + +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WxWorkUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/WxWorkUtil.java new file mode 100644 index 0000000..5a4e449 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WxWorkUtil.java @@ -0,0 +1,134 @@ +package com.gxwebsoft.common.core.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.exception.BusinessException; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.concurrent.TimeUnit; + +/** + * 企业微信工具类 + * @author 科技小王子 + * + */ +@Component +public class WxWorkUtil { + private final StringRedisTemplate stringRedisTemplate; + private Integer tenantId; + public String appId; + public String appSecret; + public String access_token; + public String expires_in; + public String nickname; + public String userid; + public String user_ticket; + public String openid; + public String external_userid; + public String name; + public String position; + public String mobile; + public String gender; + public String email; + public String avatar; + public String thumb_avatar; + public String telephone; + public String address; + public String alias; + public String qr_code; + public String open_userid; + + @Resource + private CacheClient cacheClient; + + + public WxWorkUtil(StringRedisTemplate stringRedisTemplate){ + this.stringRedisTemplate = stringRedisTemplate; + } + + + // 实例化客户端 + public WxWorkUtil client(Integer tenantId) { + this.tenantId = tenantId; + this.config(); + return this; + } + + // 开发者ID和秘钥 + private void config() { + JSONObject settingInfo = cacheClient.getSettingInfo("wx-work", this.tenantId); + if(settingInfo == null){ + throw new BusinessException("企业微信未配置"); + } + this.appId = settingInfo.getString("corpId"); + this.appSecret = settingInfo.getString("secret"); + System.out.println("this.appId = " + this.appId); + System.out.println("this.appSecret = " + this.appSecret); + } + + // 获取access_token + public void getAccessToken(String code) { + String key = "cache"+ this.tenantId +":ww:access_token"; + final String access_token = stringRedisTemplate.opsForValue().get(key); + if(access_token != null){ + this.getUserInfo(code,access_token); + }else { + String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" +this.appId+ "&corpsecret="+ this.appSecret; + System.out.println("url = " + url); + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + System.out.println("response = " + response); + final JSONObject jsonObject = JSONObject.parseObject(response); + // 获取成功 + if(jsonObject.getString("access_token") != null){ + this.access_token = jsonObject.getString("access_token"); + this.expires_in = jsonObject.getString("expires_in"); + stringRedisTemplate.opsForValue().set(key,this.access_token,7000, TimeUnit.SECONDS); + System.out.println("获取access_token成功 = " + this.access_token); + this.getUserInfo(code,this.access_token); + } + } + } + + // 获取userinfo + public void getUserInfo(String code, String access_token) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=" +access_token+ "&code=" + code; + System.out.println("url2 = " + url); + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + System.out.println("response = " + response); + JSONObject jsonObject = JSONObject.parseObject(response); + final String errcode = jsonObject.getString("errcode"); + final String errmsg = jsonObject.getString("errmsg"); + if(!StrUtil.equals(errcode,"0")){ + throw new BusinessException(errmsg); + } + this.userid = jsonObject.getString("userid"); + this.user_ticket = jsonObject.getString("user_ticket"); + this.openid = jsonObject.getString("openid"); + this.external_userid = jsonObject.getString("external_userid"); + System.out.println("获取用户信息成功 = " + jsonObject); + } + + public void getUserProfile(String userid, String access_token) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token="+ access_token +"&userid=" + userid; + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + System.out.println("response3 = " + response); + JSONObject jsonObject = JSONObject.parseObject(response); + System.out.println("读取用户详细信息 = " + jsonObject); + + this.name = jsonObject.getString("name"); + this.position = jsonObject.getString("position"); + this.gender = jsonObject.getString("gender"); + this.email = jsonObject.getString("email"); + this.avatar = jsonObject.getString("avatar"); + this.thumb_avatar = jsonObject.getString("thumb_avatar"); + this.telephone = jsonObject.getString("telephone"); + this.address = jsonObject.getString("address"); + this.alias = jsonObject.getString("alias"); + this.qr_code = jsonObject.getString("qr_code"); + this.open_userid = jsonObject.getString("open_userid"); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/ApiResult.java b/src/main/java/com/gxwebsoft/common/core/web/ApiResult.java new file mode 100644 index 0000000..0313839 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/ApiResult.java @@ -0,0 +1,87 @@ +package com.gxwebsoft.common.core.web; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; + +/** + * 返回结果 + * + * @author WebSoft + * @since 2017-06-10 10:10:50 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ApiResult implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "状态码") + private Integer code; + + @Schema(description = "状态信息") + private String message; + + @Schema(description = "返回数据") + private T data; + + @Schema(description = "错误信息") + private String error; + + public ApiResult() {} + + public ApiResult(Integer code) { + this(code, null); + } + + public ApiResult(Integer code, String message) { + this(code, message, null); + } + + public ApiResult(Integer code, String message, T data) { + this(code, message, data, null); + } + + public ApiResult(Integer code, String message, T data, String error) { + setCode(code); + setMessage(message); + setData(data); + setError(error); + } + + public Integer getCode() { + return this.code; + } + + public ApiResult setCode(Integer code) { + this.code = code; + return this; + } + + public String getMessage() { + return this.message; + } + + public ApiResult setMessage(String message) { + this.message = message; + return this; + } + + public T getData() { + return this.data; + } + + public ApiResult setData(T data) { + this.data = data; + return this; + } + + public String getError() { + return this.error; + } + + public ApiResult setError(String error) { + this.error = error; + return this; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/BaseController.java b/src/main/java/com/gxwebsoft/common/core/web/BaseController.java new file mode 100644 index 0000000..46542ac --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/BaseController.java @@ -0,0 +1,333 @@ +package com.gxwebsoft.common.core.web; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.utils.SignCheckUtil; +import com.gxwebsoft.common.system.entity.User; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Controller基类 + * + * @author WebSoft + * @since 2017-06-10 10:10:19 + */ +public class BaseController { + @Resource + private HttpServletRequest request; + @Resource + private RedisUtil redisUtil; + + /** + * 获取当前登录的user + * + * @return User + */ + public User getLoginUser() { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + Object object = authentication.getPrincipal(); + if (object instanceof User) { + return (User) object; + } + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } + return null; + } + + /** + * 获取当前登录的userId + * + * @return userId + */ + public Integer getLoginUserId() { + User loginUser = getLoginUser(); + return loginUser == null ? null : loginUser.getUserId(); + } + + /** + * 获取当前登录的tenantId + * + * @return tenantId + */ + public Integer getTenantId() { + String tenantId; + // 2 从请求头拿ID + tenantId = request.getHeader("tenantId"); + if(StrUtil.isNotBlank(tenantId)){ + return Integer.valueOf(tenantId); + } + // 3 从登录用户拿tenantId + User loginUser = getLoginUser(); + if (loginUser != null) { + return loginUser.getTenantId(); + } + // 1 从域名拿ID + String Domain = request.getHeader("Domain"); + if (StrUtil.isNotBlank(Domain)) { + String key = "Domain:" + Domain; + tenantId = redisUtil.get(key); + if(tenantId != null){ + System.out.println("从域名拿ID = " + tenantId); + return Integer.valueOf(tenantId); + } + } + return null; + } + + /** + * 返回成功 + * + * @return ApiResult + */ + public ApiResult success() { + return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG); + } + + /** + * 返回成功 + * + * @param message 状态信息 + * @return ApiResult + */ + public ApiResult success(String message) { + return success().setMessage(message); + } + + /** + * 返回成功 + * + * @param data 返回数据 + * @return ApiResult + */ + public ApiResult success(T data) { + return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, data); + } + + /** + * 返回成功 + * + * @param message 状态信息 + * @return ApiResult + */ + public ApiResult success(String message, T data) { + return success(data).setMessage(message); + } + + /** + * 返回分页查询数据 + * + * @param list 当前页数据 + * @param count 总数量 + * @return ApiResult + */ + public ApiResult> success(List list, Long count) { + return success(new PageResult<>(list, count)); + } + + /** + * 返回分页查询数据 + * + * @param iPage IPage + * @return ApiResult + */ + public ApiResult> success(IPage iPage) { + return success(iPage.getRecords(), iPage.getTotal()); + } + + /** + * 返回失败 + * + * @return ApiResult + */ + public ApiResult fail() { + return new ApiResult<>(Constants.RESULT_ERROR_CODE, Constants.RESULT_ERROR_MSG); + } + + /** + * 返回失败 + * + * @param message 状态信息 + * @return ApiResult + */ + public ApiResult fail(String message) { + return fail().setMessage(message); + } + + /** + * 返回失败 + * + * @param data 返回数据 + * @return ApiResult + */ + public ApiResult fail(T data) { + return fail(Constants.RESULT_ERROR_MSG, data); + } + + /** + * 返回失败 + * + * @param message 状态信息 + * @param data 返回数据 + * @return ApiResult + */ + public ApiResult fail(String message, T data) { + return new ApiResult<>(Constants.RESULT_ERROR_CODE, message, data); + } + + /** + * 请求参数的空字符串转为null + */ + @InitBinder + public void initBinder(WebDataBinder binder) { + binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); + } + + // 自定义函数 + public String getAuthorization(){ + return request.getHeader("Authorization"); + } + + public String getAppId() { + // 兼容小写 + if(request.getHeader("appid") != null){ + return request.getHeader("appid"); + } + return request.getHeader("AppId"); + } + + public String getSign() { + return request.getParameter("sign"); + } + + /** + * 是否校验签名信息 + * 存在签名信息则需要验证 + */ + public void isCheckSign() { + if (StrUtil.isNotBlank(getSign())) { + if(getTenantId() == null){ + throw new BusinessException("签名失败:TenantId不能为空"); + } + + String timestamp1 = request.getParameter("timestamp"); + long timestamp2 = System.currentTimeMillis(); + long time = timestamp2 - Long.parseLong(timestamp1); + if(time > 600000L){ + throw new BusinessException("签名失败:请求超时"); + } + + Enumeration names = request.getParameterNames(); + //2.遍历正文名称的枚举获得请求参数 + Map params = new HashMap<>(); + while(names.hasMoreElements()){ + String name = names.nextElement(); + String value = request.getParameter(name); + params.put(name,value); + } + String signString = SignCheckUtil.getSignString(params, getAppSecret()); + System.out.println("请求的参数 = " + params); + System.out.println("正确的签名 = " + signString); + System.out.println("签名是否正确 = " + SignCheckUtil.signCheck(params, getAppSecret())); + + if (!SignCheckUtil.signCheck(params, getAppSecret())) { + throw new BusinessException("签名失败"); + } + } + + // 模拟提交参数 + // String key = "FRbMx1FkG4Qz6GZxY"; + // Map param0 = new HashMap<>(); + // param0.put("orderId", "D2018062976332656413"); + // param0.put("MainAccountID", "DC3NHPJ73S"); + // param0.put("MainAccountSN", "320"); + // param0.put("payStatus", "2"); + // param0.put("title","测试"); + // System.out.println("请求的参数 = " + param0); + // String signString0 = SignCheckUtil.getSignString(param0, key); + // System.out.println("signString0 = " + signString0); + + // return SignCheckUtil.signCheck(params, getAppSecret()); + } + + /** + * 获取当前请求租户的AppSecret + * + * @return AppSecret + */ + public String getAppSecret() { + String key = "cache5:AppSecret:" + Integer.valueOf(getAppId()); + return redisUtil.get(key); + } + + /** + * 根据账号|手机号码|邮箱查找用户ID + * @return userId + */ +// public Integer getUserIdByUsername(String username, Integer tenantId){ +// // 按账号搜素 +// User user = userService.getOne(new LambdaQueryWrapper().eq(User::getUsername, username).eq(User::getTenantId,tenantId)); +// if (user != null && user.getUserId() > 0) { +// return user.getUserId(); +// } +// // 按手机号码搜索 +// User userByPhone = userService.getOne(new LambdaQueryWrapper().eq(User::getPhone, username).eq(User::getTenantId, tenantId)); +// if (userByPhone != null && userByPhone.getUserId() > 0) { +// return userByPhone.getUserId(); +// } +// // 按邮箱搜索 +// User userByEmail = userService.getOne(new LambdaQueryWrapper().eq(User::getEmail, username).eq(User::getTenantId, tenantId)); +// if (userByEmail != null && userByEmail.getUserId() > 0) { +// return userByEmail.getUserId(); +// } +// throw new BusinessException("找不到该用户"); +// } + + /** + * 处理方法参数类型转换异常 + * 主要处理URL路径参数中传入"NaN"等无法转换为Integer的情况 + * + * @param ex 方法参数类型不匹配异常 + * @return ApiResult + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ApiResult handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex) { + String parameterName = ex.getName(); + Object value = ex.getValue(); + Class requiredType = ex.getRequiredType(); + + // 记录错误日志 + System.err.println("参数类型转换异常: 参数名=" + parameterName + + ", 传入值=" + value + + ", 期望类型=" + (requiredType != null ? requiredType.getSimpleName() : "unknown")); + + // 如果是ID参数且传入的是"NaN",返回友好的错误信息 + if ("id".equals(parameterName) && "NaN".equals(String.valueOf(value))) { + return fail("无效的ID参数,请检查传入的ID值"); + } + + // 其他类型转换错误的通用处理 + return fail("参数格式错误: " + parameterName + " 的值 '" + value + "' 无法转换为 " + + (requiredType != null ? requiredType.getSimpleName() : "目标类型")); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/BaseParam.java b/src/main/java/com/gxwebsoft/common/core/web/BaseParam.java new file mode 100644 index 0000000..9661fd8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/BaseParam.java @@ -0,0 +1,98 @@ +package com.gxwebsoft.common.core.web; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.utils.CommonUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 查询参数基本字段 + * + * @author WebSoft + * @since 2021-08-26 22:14:43 + */ +@Data +public class BaseParam implements Serializable { + private static final long serialVersionUID = 1L; + + @TableField(exist = false) + @Schema(description = "分页查询页码") + private Long page; + + @TableField(exist = false) + @Schema(description = "分页查询每页数量") + private Long limit; + + @TableField(exist = false) + @Schema(description = "国际化") + private String lang; + + @TableField(exist = false) + @Schema(description = "排序字段", example = "id asc, name desc") + private String sort; + + @TableField(exist = false) + @Schema(description = "排序方式", example = "asc或desc") + private String order; + + @QueryField(value = "create_time", type = QueryType.GE) + @TableField(exist = false) + @Schema(description = "创建时间起始值") + private String createTimeStart; + + @QueryField(value = "create_time", type = QueryType.LE) + @TableField(exist = false) + @Schema(description = "创建时间结束值") + private String createTimeEnd; + + @QueryField(value = "create_time", type = QueryType.GE) + @Schema(description = "搜索场景") + @TableField(exist = false) + private String sceneType; + + @Schema(description = "模糊搜素") + @TableField(exist = false) + private String keywords; + + @Schema(description = "token") + @TableField(exist = false) + private String token; + + @Schema(description = "租户ID") + @TableField(exist = false) + private Integer tenantId; + + @Schema(description = "商户ID") + @TableField(exist = false) + private Long merchantId; + + /** + * 获取集合中的第一条数据 + * + * @param records 集合 + * @return 第一条数据 + */ + public T getOne(List records) { + return CommonUtil.listGetOne(records); + } + + /** + * 国际化参数 + */ + public String getLang(){ + if(StrUtil.isBlank(this.lang)){ + return null; + } + if(this.lang.equals("zh")){ + return "zh_CN"; + } + return this.lang; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/BatchParam.java b/src/main/java/com/gxwebsoft/common/core/web/BatchParam.java new file mode 100644 index 0000000..cc69572 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/BatchParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.common.core.web; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.service.IService; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 批量修改通用参数 + * + * @author WebSoft + * @since 2020-03-13 00:11:06 + */ +@Data +public class BatchParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "需要修改的数据id集合") + private List ids; + + @Schema(description = "需要修改的字段和值") + private T data; + + /** + * 通用批量修改方法 + * + * @param service IService + * @param idField id字段名称 + * @return boolean + */ + public boolean update(IService service, String idField) { + if (this.data == null) { + return false; + } + return service.update(this.data, new UpdateWrapper().in(idField, this.ids)); + } + + /** + * 通用批量修改方法 + * + * @param service IService + * @param idField id字段名称 + * @return boolean + */ + public boolean update(IService service, SFunction idField) { + if (this.data == null) { + return false; + } + return service.update(this.data, new LambdaUpdateWrapper().in(idField, this.ids)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/ExistenceParam.java b/src/main/java/com/gxwebsoft/common/core/web/ExistenceParam.java new file mode 100644 index 0000000..8c51268 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/ExistenceParam.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.common.core.web; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.service.IService; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 检查是否存在通用参数 + * + * @author WebSoft + * @since 2021-09-07 22:24:39 + */ +@Data +public class ExistenceParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "检查的字段") + private String field; + + @Schema(description = "字段的值") + private String value; + + @Schema(description = "修改时的主键") + private Integer id; + + /** + * 检查是否存在 + * + * @param service IService + * @param idField 修改时的主键字段 + * @return boolean + */ + public boolean isExistence(IService service, String idField) { + return isExistence(service, idField, true); + } + + /** + * 检查是否存在 + * + * @param service IService + * @param idField 修改时的主键字段 + * @param isToUnderlineCase 是否需要把field转为下划线格式 + * @return boolean + */ + public boolean isExistence(IService service, String idField, boolean isToUnderlineCase) { + if (StrUtil.hasBlank(this.field, this.value)) { + return false; + } + String fieldName = isToUnderlineCase ? StrUtil.toUnderlineCase(field) : field; + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq(fieldName, value); + if (id != null) { + wrapper.ne(idField, id); + } + return service.count(wrapper) > 0; + } + + /** + * 检查是否存在 + * + * @param service IService + * @param idField 修改时的主键字段 + * @return boolean + */ + public boolean isExistence(IService service, SFunction idField) { + return isExistence(service, idField, true); + } + + /** + * 检查是否存在 + * + * @param service IService + * @param idField 修改时的主键字段 + * @param isToUnderlineCase 是否需要把field转为下划线格式 + * @return boolean + */ + public boolean isExistence(IService service, SFunction idField, boolean isToUnderlineCase) { + if (StrUtil.hasBlank(this.field, this.value)) { + return false; + } + String fieldName = isToUnderlineCase ? StrUtil.toUnderlineCase(field) : field; + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.apply(fieldName + " = {0}", value); + if (id != null) { + wrapper.ne(idField, id); + } + return service.count(wrapper) > 0; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/PageParam.java b/src/main/java/com/gxwebsoft/common/core/web/PageParam.java new file mode 100644 index 0000000..596ea58 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/PageParam.java @@ -0,0 +1,343 @@ +package com.gxwebsoft.common.core.web; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.utils.CommonUtil; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 分页、排序、搜索参数封装 + * + * @author WebSoft + * @since 2019-04-26 10:34:35 + */ +public class PageParam extends Page { + private static final long serialVersionUID = 1L; + + /** + * 租户id字段名称 + */ + private static final String TENANT_ID_FIELD = "tenantId"; + + /** + * 查询条件 + */ + private final U where; + + /** + * 是否把字段名称驼峰转下划线 + */ + private final boolean isToUnderlineCase; + + public PageParam() { + this(null); + } + + public PageParam(U where) { + this(where, true); + } + + public PageParam(U where, boolean isToUnderlineCase) { + super(); + this.where = where; + this.isToUnderlineCase = isToUnderlineCase; + if (where != null) { + // 获取分页页码 + if (where.getPage() != null) { + setCurrent(where.getPage()); + } + // 获取分页每页数量 + if (where.getLimit() != null) { + setSize(where.getLimit()); + } + // 获取排序方式 + if (where.getSort() != null) { + if (sortIsSQL(where.getSort())) { + setOrders(parseOrderSQL(where.getSort())); + } else { + List orderItems = new ArrayList<>(); + String column = this.isToUnderlineCase ? StrUtil.toUnderlineCase(where.getSort()) : where.getSort(); + boolean asc = !Constants.ORDER_DESC_VALUE.equals(where.getOrder()); + orderItems.add(new OrderItem(column, asc)); + setOrders(orderItems); + } + } + } + } + + /** + * 排序字段是否是sql + */ + private boolean sortIsSQL(String sort) { + return sort != null && (sort.contains(",") || sort.trim().contains(" ")); + } + + /** + * 解析排序sql + */ + private List parseOrderSQL(String orderSQL) { + List orders = new ArrayList<>(); + if (StrUtil.isNotBlank(orderSQL)) { + for (String item : orderSQL.split(",")) { + String[] temp = item.trim().split(" "); + if (!temp[0].isEmpty()) { + String column = this.isToUnderlineCase ? StrUtil.toUnderlineCase(temp[0]) : temp[0]; + boolean asc = temp.length == 1 || !temp[temp.length - 1].equals(Constants.ORDER_DESC_VALUE); + orders.add(new OrderItem(column, asc)); + } + } + } + return orders; + } + + /** + * 设置默认排序方式 + * + * @param orderItems 排序方式 + * @return PageParam + */ + public PageParam setDefaultOrder(List orderItems) { + if (orders() == null || orders().size() == 0) { + setOrders(orderItems); + } + return this; + } + + /** + * 设置默认排序方式 + * + * @param orderSQL 排序方式 + * @return PageParam + */ + public PageParam setDefaultOrder(String orderSQL) { + setDefaultOrder(parseOrderSQL(orderSQL)); + return this; + } + + /** + * 获取查询条件 + * + * @param excludes 不包含的字段 + * @return QueryWrapper + */ + public QueryWrapper getWrapper(String... excludes) { + return buildWrapper(null, Arrays.asList(excludes)); + } + + /** + * 获取查询条件 + * + * @param columns 只包含的字段 + * @return QueryWrapper + */ + public QueryWrapper getWrapperWith(String... columns) { + return buildWrapper(Arrays.asList(columns), null); + } + + /** + * 构建QueryWrapper + * + * @param columns 包含的字段 + * @param excludes 排除的字段 + * @return QueryWrapper + */ + private QueryWrapper buildWrapper(List columns, List excludes) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + Map map = BeanUtil.beanToMap(where, false, true); + for (String fieldName : map.keySet()) { + Object fieldValue = map.get(fieldName); + Field field = ReflectUtil.getField(where.getClass(), fieldName); + + // 过滤不包含的字段 + if (columns != null && !columns.contains(fieldName)) { + continue; + } + + // 过滤排除的字段 + if (excludes != null && excludes.contains(fieldName)) { + continue; + } + + // 过滤逻辑删除字段 + if (field.getAnnotation(TableLogic.class) != null) { + continue; + } + + // 过滤租户id字段 + if (fieldName.equals(TENANT_ID_FIELD)) { + continue; + } + + // 获取注解指定的查询字段及查询方式 + QueryType queryType = QueryType.LIKE; + QueryField queryField = field.getAnnotation(QueryField.class); + if (queryField != null) { + if (StrUtil.isNotEmpty(queryField.value())) { + fieldName = queryField.value(); + } + if (queryField.type() != null) { + queryType = queryField.type(); + } + } else { + // 过滤非本表的字段 + TableField tableField = field.getAnnotation(TableField.class); + if (tableField != null && !tableField.exist()) { + continue; + } + } + + // 字段名驼峰转下划线 + if (this.isToUnderlineCase) { + fieldName = StrUtil.toUnderlineCase(fieldName); + } + + // + switch (queryType) { + case EQ: + queryWrapper.eq(fieldName, fieldValue); + break; + case NE: + queryWrapper.ne(fieldName, fieldValue); + break; + case GT: + queryWrapper.gt(fieldName, fieldValue); + break; + case GE: + queryWrapper.ge(fieldName, fieldValue); + break; + case LT: + queryWrapper.lt(fieldName, fieldValue); + break; + case LE: + queryWrapper.le(fieldName, fieldValue); + break; + case LIKE: + queryWrapper.like(fieldName, fieldValue); + break; + case NOT_LIKE: + queryWrapper.notLike(fieldName, fieldValue); + break; + case LIKE_LEFT: + queryWrapper.likeLeft(fieldName, fieldValue); + break; + case LIKE_RIGHT: + queryWrapper.likeRight(fieldName, fieldValue); + break; + case IS_NULL: + queryWrapper.isNull(fieldName); + break; + case IS_NOT_NULL: + queryWrapper.isNotNull(fieldName); + break; + case IN: + queryWrapper.in(fieldName, fieldValue); + break; + case NOT_IN: + queryWrapper.notIn(fieldName, fieldValue); + break; + case IN_STR: + if (fieldValue instanceof String) { + queryWrapper.in(fieldName, Arrays.asList(((String) fieldValue).split(","))); + } + break; + case NOT_IN_STR: + if (fieldValue instanceof String) { + queryWrapper.notIn(fieldName, Arrays.asList(((String) fieldValue).split(","))); + } + break; + } + } + return queryWrapper; + } + + /** + * 获取包含排序的查询条件 + * + * @return 包含排序的QueryWrapper + */ + public QueryWrapper getOrderWrapper() { + return getOrderWrapper(getWrapper()); + } + + /** + * 获取包含排序的查询条件 + * + * @param queryWrapper 不含排序的QueryWrapper + * @return 包含排序的QueryWrapper + */ + public QueryWrapper getOrderWrapper(QueryWrapper queryWrapper) { + if (queryWrapper == null) { + queryWrapper = new QueryWrapper<>(); + } + for (OrderItem orderItem : orders()) { + if (orderItem.isAsc()) { + queryWrapper.orderByAsc(orderItem.getColumn()); + } else { + queryWrapper.orderByDesc(orderItem.getColumn()); + } + } + return queryWrapper; + } + + /** + * 获取集合中的第一条数据 + * + * @param records 集合 + * @return 第一条数据 + */ + public T getOne(List records) { + return CommonUtil.listGetOne(records); + } + + /** + * 代码排序集合 + * + * @param records 集合 + * @return 排序后的集合 + */ + public List sortRecords(List records) { + List orderItems = orders(); + if (records == null || records.size() < 2 || orderItems == null || orderItems.size() == 0) { + return records; + } + Comparator comparator = null; + for (OrderItem item : orderItems) { + if (item.getColumn() == null) { + continue; + } + String field = this.isToUnderlineCase ? StrUtil.toCamelCase(item.getColumn()) : item.getColumn(); + Function keyExtractor = t -> ReflectUtil.getFieldValue(t, field); + if (comparator == null) { + if (item.isAsc()) { + comparator = Comparator.comparing(keyExtractor); + } else { + comparator = Comparator.comparing(keyExtractor, Comparator.reverseOrder()); + } + } else { + if (item.isAsc()) { + comparator.thenComparing(keyExtractor); + } else { + comparator.thenComparing(keyExtractor, Comparator.reverseOrder()); + } + } + } + if (comparator != null) { + return records.stream().sorted(comparator).collect(Collectors.toList()); + } + return records; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/web/PageResult.java b/src/main/java/com/gxwebsoft/common/core/web/PageResult.java new file mode 100644 index 0000000..a9bc057 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/web/PageResult.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.common.core.web; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页查询返回结果 + * + * @author WebSoft + * @since 2017-06-10 10:10:02 + */ +public class PageResult implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "当前页数据") + private List list; + + @Schema(description = "总数量") + private Long count; + + public PageResult() { + } + + public PageResult(List list) { + this(list, null); + } + + public PageResult(List list, Long count) { + setList(list); + setCount(count); + } + + public List getList() { + return this.list; + } + + public void setList(List list) { + this.list = list; + } + + public Long getCount() { + return this.count; + } + + public void setCount(Long count) { + this.count = count; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/core/websocket/WebSocketConfig.java b/src/main/java/com/gxwebsoft/common/core/websocket/WebSocketConfig.java new file mode 100644 index 0000000..ba511dd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/websocket/WebSocketConfig.java @@ -0,0 +1,19 @@ +package com.gxwebsoft.common.core.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + + +@Configuration +public class WebSocketConfig { + @Bean + public ServerEndpointExporter serverEndpointExporter() { + ServerEndpointExporter exporter = new ServerEndpointExporter(); + + // 手动注册 WebSocket 端点 + exporter.setAnnotatedEndpointClasses(WebSocketServer.class); + + return exporter; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/common/core/websocket/WebSocketServer.java b/src/main/java/com/gxwebsoft/common/core/websocket/WebSocketServer.java new file mode 100644 index 0000000..c3b64fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/websocket/WebSocketServer.java @@ -0,0 +1,86 @@ +package com.gxwebsoft.common.core.websocket; + +import org.springframework.stereotype.Component; + +import javax.websocket.OnClose; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + + +@ServerEndpoint(value = "/api/chat/{userId}") +@Component +public class WebSocketServer { + + /** + * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 + */ + private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap<>(); + /** + * 与某个客户端的连接会话,需要通过它来给客户端发送数据 + */ + private Session session; + /** + * 接收userId + */ + private String userId = ""; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("userId") String userId) { + this.session = session; + this.userId = userId; + if (webSocketMap.containsKey(userId)) { + webSocketMap.remove(userId); + webSocketMap.put(userId, this); + //加入set中 + } else { + webSocketMap.put(userId, this); + } + + try { + sendMessage(userId, "连接成功"); + } catch (IOException e) { + + } + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + if (webSocketMap.containsKey(userId)) { + webSocketMap.remove(userId); + } + } + + + /** + * 实现服务器主动推送 + */ + public void sendMessage(String userId, String message) throws IOException { + if (webSocketMap.containsKey(userId)) { + Session session1 = webSocketMap.get(userId).session; + if (session1 != null) session1.getBasicRemote().sendText(message); + } + } + + + /** + * 实现服务器主动推送 + */ + public void sendAllMessage(String message) throws IOException { + ConcurrentHashMap.KeySetView userIds = webSocketMap.keySet(); + for (String userId : userIds) { + WebSocketServer webSocketServer = webSocketMap.get(userId); + webSocketServer.session.getBasicRemote().sendText(message); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/AiController.java b/src/main/java/com/gxwebsoft/common/system/controller/AiController.java new file mode 100644 index 0000000..8525a3b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/AiController.java @@ -0,0 +1,139 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.websocket.WebSocketServer; +import com.gxwebsoft.common.system.entity.ChatMessage; +import io.swagger.v3.oas.annotations.tags.Tag; +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 javax.annotation.Resource; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +@Tag(name = "AI") +@RestController +@RequestMapping("/api/chat") +public class AiController extends BaseController { + @Resource + private WebSocketServer webSocketServer; + + @PostMapping("/message") + public ApiResult message(@RequestBody ChatMessage message) throws IOException { + Map paramsJsonStr = new HashMap<>(); + paramsJsonStr.put("query", message.getQuery()); + paramsJsonStr.put("opsType", "0"); + + Map formData = new HashMap<>(); + formData.put("user", message.getUser()); + formData.put("responseMode", "streaming"); + formData.put("paramsJsonStr", JSONUtil.toJSONString(paramsJsonStr)); + formData.put("authCode", "a8cc4a0a-aea3-4ea5-811a-80316520a3d3"); + // 使用 Java 自带的 HttpURLConnection 发送流式请求 + try { + URL url = new URL("https://ai-console.gxshucheng.com/ai-console-api/run/v1"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + connection.setDoOutput(true); + connection.setConnectTimeout(600000); + connection.setReadTimeout(600000); + + // 写入请求体 + try (OutputStream os = connection.getOutputStream(); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) { + for (Map.Entry entry : formData.entrySet()) { + writeFormField(writer, entry.getKey(), entry.getValue()); + } + // 添加文件上传部分(可选) + // writeFilePart(writer, "file", "test.txt", "text/plain", "This is the file content."); + writer.append("--").append(boundary).append("--").append("\r\n"); + writer.flush(); + } + + StringBuilder responseStr = new StringBuilder(); + // 读取响应流 + try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + System.out.println("Received chunk: " + line); // 打印接收到的每一部分数据 + // 这里可以对每一部分数据进行处理,例如解析或发送给前端 + if (!line.isEmpty()) { + String[] dataList = line.split("data: "); + if (dataList.length == 2) { +// System.out.println(dataList[1]); + Map data = JSONUtil.parseObject(dataList[1], Map.class); + if (data.get("event") != null && data.get("event").equals("message")) { + String answer = (String) data.get("answer"); + String task_id = (String) data.get("task_id"); + if (answer != null && !answer.isEmpty()) { + HashMap answerData = new HashMap<>(); + answerData.put("answer", answer); + answerData.put("taskId", task_id); + webSocketServer.sendMessage(message.getUser(), JSONUtil.toJSONString(answerData)); + } + System.out.println("answer: " + answer); + responseStr.append(answer); + }else if (data.get("event") != null && data.get("event").equals("message_end")) { + String task_id = (String) data.get("task_id"); + HashMap answerData = new HashMap<>(); + answerData.put("answer", "__END__"); + answerData.put("taskId", task_id); + + webSocketServer.sendMessage(message.getUser(), JSONUtil.toJSONString(answerData)); + } + } + } + } + } + } catch (Exception e) { + System.out.println(e.getMessage()); + for (StackTraceElement stackTraceElement : e.getStackTrace()) { + System.out.println(stackTraceElement); + } + webSocketServer.sendMessage(message.getUser(), "出错了,请晚点再来提问吧~"); + return fail("出错了,请晚点再来提问吧~"); + } + + // 返回成功响应 + return success("Stream processing completed"); + } + + private static final String boundary = "---" + System.currentTimeMillis() + "---"; + + private static void writeFormField(PrintWriter writer, String fieldName, String value) { + writer.append("--").append(boundary).append("\r\n"); + writer.append("Content-Disposition: form-data; name=\"").append(fieldName).append("\"\r\n"); + writer.append("\r\n"); + writer.append(value).append("\r\n"); + } + + @PostMapping("/messageStop") + public ApiResult stop(@RequestBody Map data) { + if (data.get("taskId") == null) return success(); + String taskId = data.get("taskId").toString(); + Map postData = new HashMap<>(); + postData.put("user", getLoginUserId()); + String token = "Bearer app-UxV82WXIRrScpf53exkJ7dIw"; + if (data.get("type") != null) { + token = "Bearer app-7AFseF5UTEJpZGkW93S0wybh"; + } + String res = HttpRequest.post("http://workflow.gxshucheng.com:8010/v1/chat-messages/" + taskId + "/stop") + .header("Authorization", token) + .header("Content-Type", "application/json") + .body(JSONObject.toJSONString(postData)) + .execute().body(); + System.out.println("stop res:" + res); + return success(); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CacheController.java b/src/main/java/com/gxwebsoft/common/system/controller/CacheController.java new file mode 100644 index 0000000..d95357d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CacheController.java @@ -0,0 +1,117 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.utils.CacheClient; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Cache; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.SettingService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * 缓存控制器 + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +@Tag(name = "缓存管理") +@RestController +@RequestMapping("/api/system/cache") +public class CacheController extends BaseController { + @Resource + private SettingService settingService; + @Resource + private CacheClient cacheClient; + @Resource + private RedisUtil redisUtil; + @Resource + private StringRedisTemplate stringRedisTemplate; + + @PreAuthorize("hasAuthority('sys:cache:list')") + @Operation(summary = "查询全部缓存") + @GetMapping() + public ApiResult> list() { + String key = "cache".concat(getTenantId().toString()).concat("*"); + final Set keys = stringRedisTemplate.keys(key); + final HashMap map = new HashMap<>(); + final ArrayList list = new ArrayList<>(); + assert keys != null; + keys.forEach(d -> { + final Cache cache = new Cache(); + cache.setKey(d); + try { + final String content = stringRedisTemplate.opsForValue().get(d); + if(content != null){ + cache.setContent(stringRedisTemplate.opsForValue().get(d)); + } + } catch (Exception e) { + e.printStackTrace(); + } + list.add(cache); + }); + map.put("count",keys.size()); + map.put("list",list); + return success(map); + } + + @PreAuthorize("hasAuthority('sys:cache:list')") + @Operation(summary = "根据key查询缓存信息") + @GetMapping("/{key}") + public ApiResult get(@PathVariable("key") String key) { + final String s = redisUtil.get(key + getTenantId()); + if(StrUtil.isNotBlank(s)){ + return success("读取成功", JSONObject.parseObject(s)); + } + return fail("缓存不存在!"); + } + + @PreAuthorize("hasAuthority('sys:cache:save')") + @Operation(summary = "添加缓存") + @PostMapping() + public ApiResult add(@RequestBody Cache cache) { + if (cache.getExpireTime() != null) { + redisUtil.set(cache.getKey() + ":" + getTenantId(),cache.getContent(),cache.getExpireTime(), TimeUnit.MINUTES); + return success("缓存成功"); + } + redisUtil.set(cache.getKey() + ":" + getTenantId(),cache.getContent()); + return success("缓存成功"); + } + + @PreAuthorize("hasAuthority('sys:cache:save')") + @Operation(summary = "删除缓存") + @DeleteMapping("/{key}") + public ApiResult remove(@PathVariable("key") String key) { + if (Boolean.TRUE.equals(stringRedisTemplate.delete(key))) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:cache:save')") + @Operation(summary = "缓存皮肤") + @PostMapping("/theme") + public ApiResult saveTheme(@RequestBody Cache cache) { + final User loginUser = getLoginUser(); + final String username = loginUser.getUsername(); + if (username.equals("admin")) { + redisUtil.set(cache.getKey() + ":" + getTenantId(),cache.getContent()); + return success("缓存成功"); + } + return success("缓存失败"); + } + + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CompanyCommentController.java b/src/main/java/com/gxwebsoft/common/system/controller/CompanyCommentController.java new file mode 100644 index 0000000..5f460b4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CompanyCommentController.java @@ -0,0 +1,131 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyComment; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.param.CompanyCommentParam; +import com.gxwebsoft.common.system.service.CompanyCommentService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用评论控制器 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Tag(name = "应用评论管理") +@RestController +@RequestMapping("/api/system/company-comment") +public class CompanyCommentController extends BaseController { + @Resource + private CompanyCommentService companyCommentService; + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "分页查询应用评论") + @GetMapping("/page") + public ApiResult> page(CompanyCommentParam param) { + // 使用关联查询 + return success(companyCommentService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "查询全部应用评论") + @GetMapping() + public ApiResult> list(CompanyCommentParam param) { + // 使用关联查询 + return success(companyCommentService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "根据id查询应用评论") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(companyCommentService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "添加应用评论") + @PostMapping() + public ApiResult save(@RequestBody CompanyComment companyComment) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + companyComment.setUserId(loginUser.getUserId()); + } + if (companyCommentService.save(companyComment)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "修改应用评论") + @PutMapping() + public ApiResult update(@RequestBody CompanyComment companyComment) { + if (companyCommentService.updateById(companyComment)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "删除应用评论") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (companyCommentService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "批量添加应用评论") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (companyCommentService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "批量修改应用评论") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(companyCommentService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "批量删除应用评论") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (companyCommentService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CompanyContentController.java b/src/main/java/com/gxwebsoft/common/system/controller/CompanyContentController.java new file mode 100644 index 0000000..cc63569 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CompanyContentController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyContent; +import com.gxwebsoft.common.system.param.CompanyContentParam; +import com.gxwebsoft.common.system.service.CompanyContentService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用详情控制器 + * + * @author 科技小王子 + * @since 2024-10-16 13:41:21 + */ +@Tag(name = "应用详情管理") +@RestController +@RequestMapping("/api/system/company-content") +public class CompanyContentController extends BaseController { + @Resource + private CompanyContentService companyContentService; + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "分页查询应用详情") + @GetMapping("/page") + public ApiResult> page(CompanyContentParam param) { + // 使用关联查询 + return success(companyContentService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "查询全部应用详情") + @GetMapping() + public ApiResult> list(CompanyContentParam param) { + // 使用关联查询 + return success(companyContentService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "根据id查询应用详情") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(companyContentService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "添加应用详情") + @PostMapping() + public ApiResult save(@RequestBody CompanyContent companyContent) { + if (companyContentService.save(companyContent)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "修改应用详情") + @PutMapping() + public ApiResult update(@RequestBody CompanyContent companyContent) { + if (companyContentService.updateById(companyContent)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "删除应用详情") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (companyContentService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "批量添加应用详情") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (companyContentService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "批量修改应用详情") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(companyContentService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "批量删除应用详情") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (companyContentService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CompanyController.java b/src/main/java/com/gxwebsoft/common/system/controller/CompanyController.java new file mode 100644 index 0000000..6a65b19 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CompanyController.java @@ -0,0 +1,367 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.DesensitizedUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.system.entity.*; +import com.gxwebsoft.common.system.mapper.CompanyMapper; +import com.gxwebsoft.common.system.mapper.TenantMapper; +import com.gxwebsoft.common.system.param.CompanyParam; +import com.gxwebsoft.common.system.service.*; +import com.gxwebsoft.shop.entity.ShopMerchantApply; +import com.gxwebsoft.shop.service.ShopMerchantApplyService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 企业信息控制器 + * + * @author 科技小王子 + * @since 2023-05-27 14:57:34 + */ +@Tag(name = "企业") +@RestController +@RequestMapping("/api/system/company") +public class CompanyController extends BaseController { + @Resource + private CompanyService companyService; + @Resource + private ShopMerchantApplyService shopMerchantApplyService; + @Resource + private CompanyContentService companyContentService; + @Resource + private CompanyUrlService companyUrlService; + @Resource + private CompanyParameterService companyParameterService; + @Resource + private TenantService tenantService; + @Resource + private CompanyMapper companyMapper; + @Resource + private TenantMapper tenantMapper; + @Resource + private DomainService domainService; + @Resource + private UserCollectionService userCollectionService; + @Resource + private RedisUtil redisUtil; + + + @Operation(summary = "分页查询企业信息不限租户") + @GetMapping("/pageAll") + public ApiResult> pageAll(CompanyParam param) { + final PageResult result = companyService.pageRelAll(param); + result.getList().forEach(d -> { + d.setPhone(DesensitizedUtil.mobilePhone(d.getPhone())); + d.setCompanyCode(DesensitizedUtil.idCardNum(d.getCompanyCode(),1,2)); + }); + final User loginUser = getLoginUser(); + if(loginUser != null){ + // 我的收藏 + final List myFocus = userCollectionService.list(new LambdaQueryWrapper().eq(UserCollection::getUserId, getLoginUserId())); + if (!CollectionUtils.isEmpty(myFocus)) { + final Set collect = myFocus.stream().map(UserCollection::getTid).collect(Collectors.toSet()); + if (param.getVersion() != null) { + // 我的收藏 + if (param.getVersion().equals(99)) { + param.setVersion(null); + param.setCompanyIds(collect); + } + } + result.getList().forEach(d -> { + d.setCollection(collect.contains(d.getCompanyId())); + }); + return success(result); + } + } + // 使用关联查询 + return success(result); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @Operation(summary = "分页查询企业信息") + @GetMapping("/page") + public ApiResult> page(CompanyParam param) { + // 使用关联查询 + return success(companyService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "查询全部企业信息") + @GetMapping() + public ApiResult> list(CompanyParam param) { + // 使用关联查询 + return success(companyService.listRel(param)); + } + + @Operation(summary = "根据id查询企业信息") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + final Company company = companyService.getByIdRel(id); + if (ObjectUtil.isNotEmpty(company)) { + // 应用详情 + final CompanyContent content = companyContentService.getOne(new LambdaQueryWrapper().eq(CompanyContent::getCompanyId, company.getCompanyId()).last("limit 1")); + if (ObjectUtil.isNotEmpty(content)) { + company.setContent(content.getContent()); + } + // 应用链接 + company.setLinks(companyUrlService.list(new LambdaQueryWrapper().eq(CompanyUrl::getCompanyId, company.getCompanyId()))); + // 应用参数 + company.setParameters(companyParameterService.list(new LambdaQueryWrapper().eq(CompanyParameter::getCompanyId, company.getCompanyId()))); + + } + return success(company); + } + + @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE) + @PreAuthorize("hasAuthority('sys:company:save')") + @Operation(summary = "添加企业信息") + @PostMapping() + public ApiResult save(@RequestBody Company company) { + Tenant tenant = new Tenant(); + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + company.setUserId(loginUser.getUserId()); + tenant.setUserId(loginUser.getUserId()); + } + tenant.setTenantName(company.getShortName()); + tenant.setTenantCode(CommonUtil.randomUUID16()); + tenant.setComments(company.getComments()); + tenantService.save(tenant); + company.setTenantId(tenant.getTenantId()); + company.setTid(tenant.getTenantId()); + company.setAuthoritative(true); + // 添加租户并初始化 +// final Company result = tenantService.initialization(company); +// if (result != null) { +// return success("添加成功",result); +// } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "修改企业信息") + @PutMapping() + public ApiResult update(@RequestBody Company company) { + // 授权新的免费域名 + if (StrUtil.isNotBlank(company.getFreeDomain())) { + // 待授权的二级域名 + String domain = company.getFreeDomain().concat(".websoft.top"); + // 删除旧授权域名 + final Domain one = domainService.getOne(new LambdaQueryWrapper().eq(Domain::getType, 2).eq(Domain::getCompanyId, company.getCompanyId()).eq(Domain::getDeleted,0).last("limit 1")); + if(one != null){ + redisUtil.delete("Domain:".concat(one.getDomain())); + domainService.removeById(one); + } + // 保存记录 + final Domain sysDomain = new Domain(); + sysDomain.setDomain(domain); + sysDomain.setType(2); + sysDomain.setSortNumber(100); + sysDomain.setCompanyId(company.getCompanyId()); + sysDomain.setTenantId(company.getTenantId()); + domainService.save(sysDomain); + company.setDomain(domain); + // 写入缓存 + redisUtil.set("Domain:".concat(domain), company.getTenantId()); + } + // 同步更新租户表 + if(StrUtil.isNotBlank(company.getShortName())){ + final Tenant tenant = new Tenant(); + tenant.setTenantId(company.getTenantId()); + tenant.setTenantName(company.getShortName()); + tenantService.updateById(tenant); + } + if (companyService.updateById(company)) { + // 清除缓存 + redisUtil.delete("TenantInfo:".concat(company.getTenantId().toString())); + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "删除企业信息") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + final Company company = companyService.getById(id); + tenantService.removeById(company.getTenantId()); + if (companyService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "批量添加企业信息") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (companyService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "批量修改企业信息") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(companyService, "company_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "批量删除企业信息") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (companyService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "根据id查询企业信息") + @GetMapping("/profile") + public ApiResult profile() { + final User loginUser = getLoginUser(); + if(loginUser != null){ + final Company company = companyService.getOne(new LambdaQueryWrapper().eq(Company::getTenantId, loginUser.getTenantId()).eq(Company::getAuthoritative, true).last("limit 1")); + if (ObjectUtil.isNotEmpty(company)) { + final ShopMerchantApply apply = shopMerchantApplyService.getOne(new LambdaQueryWrapper().eq(ShopMerchantApply::getTenantId, loginUser.getTenantId()).last("limit 1")); + if (ObjectUtil.isNotEmpty(apply)) { + company.setCompanyName(apply.getMerchantName()); + company.setCompanyType(apply.getShopType()); + if (apply.getStatus().equals(1)) { + company.setAuthentication(1); + } + } + LocalDateTime now = LocalDateTime.now(); + // 即将过期(一周内过期的) + company.setSoon(company.getExpirationTime().minusDays(7).compareTo(now)); + // 是否过期 -1已过期 大于0 未过期 + company.setStatus(company.getExpirationTime().compareTo(now)); + return success(company); + } + } + return fail("企业不存在",null); + } + + @PreAuthorize("hasAuthority('sys:company:profile')") + @OperationLog + @Operation(summary = "根据id查询企业信息不限租户") + @GetMapping("/profileAll/{companyId}") + public ApiResult profileAll(@PathVariable("companyId") Integer companyId) { + return success(companyMapper.getCompanyAll(companyId)); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "销毁租户") + @DeleteMapping("/destruction/{id}") + public ApiResult destruction(@PathVariable("id") Integer id) { + final User loginUser = getLoginUser(); + if (!loginUser.getUsername().equals("admin")) { + throw new BusinessException("只有超级管理员才能操作"); + } + final Integer tenantId = getTenantId(); + if (tenantService.removeById(tenantId)) { + return success("删除成功",tenantId); + } + return fail("删除失败"); + } + @Operation(summary = "检查企业是否存在") + @GetMapping("/existence") + public ApiResult existence(ExistenceParam param) { + CompanyParam companyParam = new CompanyParam(); + if (param.getField().equals("shortName")) { + companyParam.setAppName(param.getValue()); + List count = companyMapper.getCount(companyParam); + if (!CollectionUtils.isEmpty(count)) { + return success(param.getValue() + "已存在"); + } + } + if (param.getField().equals("email")) { + companyParam.setEmail(param.getValue()); + List count = companyMapper.getCount(companyParam); + if (!CollectionUtils.isEmpty(count)) { + return success(param.getValue() + "已存在"); + } + } + if (param.getField().equals("phone")) { + companyParam.setPhone(param.getValue()); + List count = companyMapper.getCount(companyParam); + if (!CollectionUtils.isEmpty(count)) { + return success(param.getValue() + "已存在"); + } + } + return fail(param.getValue() + "不存在"); + } + + @Operation(summary = "根据id查询企业信息不限租户不带token") + @GetMapping("/companyInfoAll/{companyId}") + public ApiResult companyInfoAll(@PathVariable("companyId") Integer companyId) { + return success(companyMapper.getCompanyAll(companyId)); + } + + @PreAuthorize("hasAuthority('sys:company:updateAll')") + @OperationLog + @Operation(summary = "修改企业信息") + @PutMapping("/updateCompanyAll") + public ApiResult updateCompanyAll(@RequestBody Company company) { + if (companyMapper.updateByIdAll(company)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:removeAll')") + @OperationLog + @Operation(summary = "删除企业信息") + @DeleteMapping("/removeAll/{id}") + public ApiResult removeAll(@PathVariable("id") Integer id) { + if (companyMapper.removeCompanyAll(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:company:removeAll')") + @OperationLog + @Operation(summary = "恢复租户") + @DeleteMapping("/undeleteAll/{id}") + public ApiResult undeleteAll(@PathVariable("id") Integer id) { + if (companyMapper.undeleteAll(id)) { + return success("恢复成功"); + } + return fail("恢复失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CompanyGitController.java b/src/main/java/com/gxwebsoft/common/system/controller/CompanyGitController.java new file mode 100644 index 0000000..451118c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CompanyGitController.java @@ -0,0 +1,122 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyGit; +import com.gxwebsoft.common.system.param.CompanyGitParam; +import com.gxwebsoft.common.system.service.CompanyGitService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 代码仓库控制器 + * + * @author 科技小王子 + * @since 2024-10-19 18:08:51 + */ +@Tag(name = "代码仓库管理") +@RestController +@RequestMapping("/api/system/company-git") +public class CompanyGitController extends BaseController { + @Resource + private CompanyGitService companyGitService; + + @PreAuthorize("hasAuthority('sys:companyGit:list')") + @Operation(summary = "分页查询代码仓库") + @GetMapping("/page") + public ApiResult> page(CompanyGitParam param) { + // 使用关联查询 + return success(companyGitService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:companyGit:list')") + @Operation(summary = "查询全部代码仓库") + @GetMapping() + public ApiResult> list(CompanyGitParam param) { + // 使用关联查询 + return success(companyGitService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:companyGit:list')") + @Operation(summary = "根据id查询代码仓库") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(companyGitService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:companyGit:save')") + @OperationLog + @Operation(summary = "添加代码仓库") + @PostMapping() + public ApiResult save(@RequestBody CompanyGit companyGit) { + if (companyGitService.save(companyGit)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:companyGit:update')") + @OperationLog + @Operation(summary = "修改代码仓库") + @PutMapping() + public ApiResult update(@RequestBody CompanyGit companyGit) { + if (companyGitService.updateById(companyGit)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:companyGit:remove')") + @OperationLog + @Operation(summary = "删除代码仓库") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (companyGitService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:companyGit:save')") + @OperationLog + @Operation(summary = "批量添加代码仓库") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (companyGitService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:companyGit:update')") + @OperationLog + @Operation(summary = "批量修改代码仓库") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(companyGitService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:companyGit:remove')") + @OperationLog + @Operation(summary = "批量删除代码仓库") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (companyGitService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CompanyParameterController.java b/src/main/java/com/gxwebsoft/common/system/controller/CompanyParameterController.java new file mode 100644 index 0000000..086964f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CompanyParameterController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyParameter; +import com.gxwebsoft.common.system.param.CompanyParameterParam; +import com.gxwebsoft.common.system.service.CompanyParameterService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用参数控制器 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Tag(name = "应用参数管理") +@RestController +@RequestMapping("/api/system/company-parameter") +public class CompanyParameterController extends BaseController { + @Resource + private CompanyParameterService companyParameterService; + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "分页查询应用参数") + @GetMapping("/page") + public ApiResult> page(CompanyParameterParam param) { + // 使用关联查询 + return success(companyParameterService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "查询全部应用参数") + @GetMapping() + public ApiResult> list(CompanyParameterParam param) { + // 使用关联查询 + return success(companyParameterService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "根据id查询应用参数") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(companyParameterService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "添加应用参数") + @PostMapping() + public ApiResult save(@RequestBody CompanyParameter companyParameter) { + if (companyParameterService.save(companyParameter)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "修改应用参数") + @PutMapping() + public ApiResult update(@RequestBody CompanyParameter companyParameter) { + if (companyParameterService.updateById(companyParameter)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "删除应用参数") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (companyParameterService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "批量添加应用参数") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (companyParameterService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "批量修改应用参数") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(companyParameterService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "批量删除应用参数") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (companyParameterService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/CompanyUrlController.java b/src/main/java/com/gxwebsoft/common/system/controller/CompanyUrlController.java new file mode 100644 index 0000000..6de5078 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/CompanyUrlController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyUrl; +import com.gxwebsoft.common.system.param.CompanyUrlParam; +import com.gxwebsoft.common.system.service.CompanyUrlService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用域名控制器 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Tag(name = "应用域名管理") +@RestController +@RequestMapping("/api/system/company-url") +public class CompanyUrlController extends BaseController { + @Resource + private CompanyUrlService companyUrlService; + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "分页查询应用域名") + @GetMapping("/page") + public ApiResult> page(CompanyUrlParam param) { + // 使用关联查询 + return success(companyUrlService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "查询全部应用域名") + @GetMapping() + public ApiResult> list(CompanyUrlParam param) { + // 使用关联查询 + return success(companyUrlService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:company:list')") + @OperationLog + @Operation(summary = "根据id查询应用域名") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(companyUrlService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "添加应用域名") + @PostMapping() + public ApiResult save(@RequestBody CompanyUrl companyUrl) { + if (companyUrlService.save(companyUrl)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "修改应用域名") + @PutMapping() + public ApiResult update(@RequestBody CompanyUrl companyUrl) { + if (companyUrlService.updateById(companyUrl)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "删除应用域名") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (companyUrlService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:company:save')") + @OperationLog + @Operation(summary = "批量添加应用域名") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (companyUrlService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:company:update')") + @OperationLog + @Operation(summary = "批量修改应用域名") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(companyUrlService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:company:remove')") + @OperationLog + @Operation(summary = "批量删除应用域名") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (companyUrlService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/DictController.java b/src/main/java/com/gxwebsoft/common/system/controller/DictController.java new file mode 100644 index 0000000..27fb3fe --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/DictController.java @@ -0,0 +1,177 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Dict; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.param.DictDataParam; +import com.gxwebsoft.common.system.param.DictParam; +import com.gxwebsoft.common.system.service.DictDataService; +import com.gxwebsoft.common.system.service.DictService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 字典控制器 + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +@Tag(name = "字典管理(业务类)") +@RestController +@RequestMapping("/api/system/dict") +public class DictController extends BaseController { + @Resource + private DictService dictService; + @Resource + private DictDataService dictDataService; + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "分页查询字典") + @GetMapping("/page") + public ApiResult> page(DictParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return success(dictService.page(page, page.getWrapper())); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "查询全部字典") + @GetMapping() + public ApiResult> list(DictParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return success(dictService.list(page.getOrderWrapper())); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "查询全部字典") + @GetMapping("/tree") + public ApiResult tree() { + final HashMap result = new HashMap<>(); + final List dictData = dictDataService.listRel(new DictDataParam()); + final Map> dataCollect = dictData.stream().collect(Collectors.groupingBy(DictData::getDictCode)); + for (String code : dataCollect.keySet()) { + Dict dict = new Dict(); + dict.setDictCode(code); + final Set> list = new LinkedHashSet<>(); + Set codes = new LinkedHashSet<>(); + for(DictData item : dictData){ + if (item.getDictCode().equals(code)) { + codes.add(item.getDictDataCode()); + } + } + list.add(codes); + dict.setItems(list); + result.put(code,dict.getItems()); + } + return success(result); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "根据id查询字典") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(dictService.getById(id)); + } + + @PreAuthorize("hasAuthority('sys:dict:save')") + @Operation(summary = "添加字典") + @PostMapping() + public ApiResult add(@RequestBody Dict dict) { + if (dictService.count(new LambdaQueryWrapper() + .eq(Dict::getDictCode, dict.getDictCode())) > 0) { + return fail("字典标识已存在"); + } + if (dictService.count(new LambdaQueryWrapper() + .eq(Dict::getDictName, dict.getDictName())) > 0) { + return fail("字典名称已存在"); + } + if (dictService.save(dict)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:update')") + @Operation(summary = "修改字典") + @PutMapping() + public ApiResult update(@RequestBody Dict dict) { + if (dictService.count(new LambdaQueryWrapper() + .eq(Dict::getDictCode, dict.getDictCode()) + .ne(Dict::getDictId, dict.getDictId())) > 0) { + return fail("字典标识已存在"); + } + if (dictService.count(new LambdaQueryWrapper() + .eq(Dict::getDictName, dict.getDictName()) + .ne(Dict::getDictId, dict.getDictId())) > 0) { + return fail("字典名称已存在"); + } + if (dictService.updateById(dict)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:remove')") + @Operation(summary = "删除字典") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (dictService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:save')") + @Operation(summary = "批量添加字典") + @PostMapping("/batch") + public ApiResult> saveBatch(@RequestBody List list) { + if (CommonUtil.checkRepeat(list, Dict::getDictCode)) { + return fail("字典标识不能重复", null); + } + if (CommonUtil.checkRepeat(list, Dict::getDictName)) { + return fail("字典名称不能重复", null); + } + List codeExists = dictService.list(new LambdaQueryWrapper() + .in(Dict::getDictCode, list.stream().map(Dict::getDictCode) + .collect(Collectors.toList()))); + if (codeExists.size() > 0) { + return fail("字典标识已存在", codeExists.stream().map(Dict::getDictCode) + .collect(Collectors.toList())).setCode(2); + } + List nameExists = dictService.list(new LambdaQueryWrapper() + .in(Dict::getDictName, list.stream().map(Dict::getDictCode) + .collect(Collectors.toList()))); + if (nameExists.size() > 0) { + return fail("字典名称已存在", nameExists.stream().map(Dict::getDictName) + .collect(Collectors.toList())).setCode(3); + } + if (dictService.saveBatch(list)) { + return success("添加成功", null); + } + return fail("添加失败", null); + } + + @PreAuthorize("hasAuthority('sys:dict:remove')") + @Operation(summary = "批量删除字典") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (dictService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/DictDataController.java b/src/main/java/com/gxwebsoft/common/system/controller/DictDataController.java new file mode 100644 index 0000000..1942f67 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/DictDataController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.param.DictDataParam; +import com.gxwebsoft.common.system.service.DictDataService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 字典数据控制器 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Tag(name = "字典数据管理(业务类)") +@RestController +@RequestMapping("/api/system/dict-data") +public class DictDataController extends BaseController { + @Resource + private DictDataService dictDataService; + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "分页查询字典数据") + @GetMapping("/page") + public ApiResult> page(DictDataParam param) { + return success(dictDataService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "查询全部字典数据") + @GetMapping() + public ApiResult> list(DictDataParam param) { + return success(dictDataService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "根据id查询字典数据") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(dictDataService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:dict:save')") + @Operation(summary = "添加字典数据") + @PostMapping() + public ApiResult add(@RequestBody DictData dictData) { + if (dictDataService.count(new LambdaQueryWrapper() + .eq(DictData::getDictId, dictData.getDictId()) + .eq(DictData::getDictDataName, dictData.getDictDataName())) > 0) { + return fail("字典数据名称已存在"); + } + if (dictDataService.count(new LambdaQueryWrapper() + .eq(DictData::getDictId, dictData.getDictId()) + .eq(DictData::getDictDataCode, dictData.getDictDataCode())) > 0) { + return fail("字典数据标识已存在"); + } + if (dictDataService.save(dictData)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:update')") + @Operation(summary = "修改字典数据") + @PutMapping() + public ApiResult update(@RequestBody DictData dictData) { + if (dictDataService.count(new LambdaQueryWrapper() + .eq(DictData::getDictId, dictData.getDictId()) + .eq(DictData::getDictDataName, dictData.getDictDataName()) + .ne(DictData::getDictDataId, dictData.getDictDataId())) > 0) { + return fail("字典数据名称已存在"); + } + if (dictDataService.count(new LambdaQueryWrapper() + .eq(DictData::getDictId, dictData.getDictId()) + .eq(DictData::getDictDataCode, dictData.getDictDataCode()) + .ne(DictData::getDictDataId, dictData.getDictDataId())) > 0) { + return fail("字典数据标识已存在"); + } + if (dictDataService.updateById(dictData)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:remove')") + @Operation(summary = "删除字典数据") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (dictDataService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:save')") + @Operation(summary = "批量添加字典数据") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List dictDataList) { + if (dictDataService.saveBatch(dictDataList)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:remove')") + @Operation(summary = "批量删除字典数据") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (dictDataService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/DictionaryController.java b/src/main/java/com/gxwebsoft/common/system/controller/DictionaryController.java new file mode 100644 index 0000000..ecbeb6d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/DictionaryController.java @@ -0,0 +1,148 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Dictionary; +import com.gxwebsoft.common.system.param.DictionaryParam; +import com.gxwebsoft.common.system.service.DictionaryService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 字典控制器 + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +@Tag(name = "字典管理(系统类)") +@RestController +@RequestMapping("/api/system/dictionary") +public class DictionaryController extends BaseController { + @Resource + private DictionaryService dictionaryService; + + @PreAuthorize("hasAuthority('sys:dictionary:list')") + @Operation(summary = "分页查询字典") + @GetMapping("/page") + public ApiResult> page(DictionaryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return success(dictionaryService.page(page, page.getWrapper())); + } + + @PreAuthorize("hasAuthority('sys:dictionary:list')") + @Operation(summary = "查询全部字典") + @GetMapping() + public ApiResult> list(DictionaryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return success(dictionaryService.list(page.getOrderWrapper())); + } + + @PreAuthorize("hasAuthority('sys:dictionary:list')") + @Operation(summary = "根据id查询字典") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(dictionaryService.getById(id)); + } + + @PreAuthorize("hasAuthority('sys:dictionary:save')") + @Operation(summary = "添加字典") + @PostMapping() + public ApiResult add(@RequestBody Dictionary dictionary) { + if (dictionaryService.count(new LambdaQueryWrapper() + .eq(Dictionary::getDictCode, dictionary.getDictCode())) > 0) { + return fail("字典标识已存在"); + } + if (dictionaryService.count(new LambdaQueryWrapper() + .eq(Dictionary::getDictName, dictionary.getDictName())) > 0) { + return fail("字典名称已存在"); + } + if (dictionaryService.save(dictionary)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:dictionary:update')") + @Operation(summary = "修改字典") + @PutMapping() + public ApiResult update(@RequestBody Dictionary dictionary) { + if (dictionaryService.count(new LambdaQueryWrapper() + .eq(Dictionary::getDictCode, dictionary.getDictCode()) + .ne(Dictionary::getDictId, dictionary.getDictId())) > 0) { + return fail("字典标识已存在"); + } + if (dictionaryService.count(new LambdaQueryWrapper() + .eq(Dictionary::getDictName, dictionary.getDictName()) + .ne(Dictionary::getDictId, dictionary.getDictId())) > 0) { + return fail("字典名称已存在"); + } + if (dictionaryService.updateById(dictionary)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:dictionary:remove')") + @Operation(summary = "删除字典") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (dictionaryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:dictionary:save')") + @Operation(summary = "批量添加字典") + @PostMapping("/batch") + public ApiResult> saveBatch(@RequestBody List list) { + if (CommonUtil.checkRepeat(list, Dictionary::getDictCode)) { + return fail("字典标识不能重复", null); + } + if (CommonUtil.checkRepeat(list, Dictionary::getDictName)) { + return fail("字典名称不能重复", null); + } + List codeExists = dictionaryService.list(new LambdaQueryWrapper() + .in(Dictionary::getDictCode, list.stream().map(Dictionary::getDictCode) + .collect(Collectors.toList()))); + if (codeExists.size() > 0) { + return fail("字典标识已存在", codeExists.stream().map(Dictionary::getDictCode) + .collect(Collectors.toList())).setCode(2); + } + List nameExists = dictionaryService.list(new LambdaQueryWrapper() + .in(Dictionary::getDictName, list.stream().map(Dictionary::getDictCode) + .collect(Collectors.toList()))); + if (nameExists.size() > 0) { + return fail("字典名称已存在", nameExists.stream().map(Dictionary::getDictName) + .collect(Collectors.toList())).setCode(3); + } + if (dictionaryService.saveBatch(list)) { + return success("添加成功", null); + } + return fail("添加失败", null); + } + + @PreAuthorize("hasAuthority('sys:dictionary:remove')") + @Operation(summary = "批量删除字典") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (dictionaryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/DictionaryDataController.java b/src/main/java/com/gxwebsoft/common/system/controller/DictionaryDataController.java new file mode 100644 index 0000000..df16554 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/DictionaryDataController.java @@ -0,0 +1,123 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.system.entity.DictionaryData; +import com.gxwebsoft.common.system.param.DictionaryDataParam; +import com.gxwebsoft.common.system.service.DictionaryDataService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 字典数据控制器 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Tag(name = "字典数据管理(系统类)") +@RestController +@RequestMapping("/api/system/dictionary-data") +public class DictionaryDataController extends BaseController { + @Resource + private DictionaryDataService dictionaryDataService; + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "分页查询字典数据") + @GetMapping("/page") + public ApiResult> page(DictionaryDataParam param) { + return success(dictionaryDataService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "查询全部字典数据") + @GetMapping() + public ApiResult> list(DictionaryDataParam param) { + return success(dictionaryDataService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:dict:list')") + @Operation(summary = "根据id查询字典数据") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(dictionaryDataService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:dict:save')") + @Operation(summary = "添加字典数据") + @PostMapping() + public ApiResult add(@RequestBody DictionaryData dictionaryData) { + if (dictionaryDataService.count(new LambdaQueryWrapper() + .eq(DictionaryData::getDictId, dictionaryData.getDictId()) + .eq(DictionaryData::getDictDataName, dictionaryData.getDictDataName())) > 0) { + return fail("字典数据名称已存在"); + } + if (dictionaryDataService.count(new LambdaQueryWrapper() + .eq(DictionaryData::getDictId, dictionaryData.getDictId()) + .eq(DictionaryData::getDictDataCode, dictionaryData.getDictDataCode())) > 0) { + return fail("字典数据标识已存在"); + } + if (dictionaryDataService.save(dictionaryData)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:update')") + @Operation(summary = "修改字典数据") + @PutMapping() + public ApiResult update(@RequestBody DictionaryData dictionaryData) { + if (dictionaryDataService.count(new LambdaQueryWrapper() + .eq(DictionaryData::getDictId, dictionaryData.getDictId()) + .eq(DictionaryData::getDictDataName, dictionaryData.getDictDataName()) + .ne(DictionaryData::getDictDataId, dictionaryData.getDictDataId())) > 0) { + return fail("字典数据名称已存在"); + } + if (dictionaryDataService.count(new LambdaQueryWrapper() + .eq(DictionaryData::getDictId, dictionaryData.getDictId()) + .eq(DictionaryData::getDictDataCode, dictionaryData.getDictDataCode()) + .ne(DictionaryData::getDictDataId, dictionaryData.getDictDataId())) > 0) { + return fail("字典数据标识已存在"); + } + if (dictionaryDataService.updateById(dictionaryData)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:remove')") + @Operation(summary = "删除字典数据") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (dictionaryDataService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:save')") + @Operation(summary = "批量添加字典数据") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List dictDataList) { + if (dictionaryDataService.saveBatch(dictDataList)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:dict:remove')") + @Operation(summary = "批量删除字典数据") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (dictionaryDataService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/DomainController.java b/src/main/java/com/gxwebsoft/common/system/controller/DomainController.java new file mode 100644 index 0000000..66b95cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/DomainController.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Domain; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.param.DomainParam; +import com.gxwebsoft.common.system.service.DomainService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 授权域名控制器 + * + * @author 科技小王子 + * @since 2024-09-19 23:56:33 + */ +@Tag(name = "授权域名管理") +@RestController +@RequestMapping("/api/system/domain") +public class DomainController extends BaseController { + @Resource + private DomainService domainService; + + @Operation(summary = "分页查询授权域名") + @GetMapping("/page") + public ApiResult> page(DomainParam param) { + // 使用关联查询 + return success(domainService.pageRel(param)); + } + + @Operation(summary = "查询全部授权域名") + @GetMapping() + public ApiResult> list(DomainParam param) { + // 使用关联查询 + return success(domainService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:domain:list')") + @OperationLog + @Operation(summary = "根据id查询授权域名") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(domainService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:domain:save')") + @OperationLog + @Operation(summary = "添加授权域名") + @PostMapping() + public ApiResult save(@RequestBody Domain domain) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + domain.setUserId(loginUser.getUserId()); + } + if (domainService.save(domain)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:domain:update')") + @OperationLog + @Operation(summary = "修改授权域名") + @PutMapping() + public ApiResult update(@RequestBody Domain domain) { + if (domainService.updateById(domain)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:domain:remove')") + @OperationLog + @Operation(summary = "删除授权域名") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (domainService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:domain:save')") + @OperationLog + @Operation(summary = "批量添加授权域名") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (domainService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:domain:update')") + @OperationLog + @Operation(summary = "批量修改授权域名") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(domainService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:domain:remove')") + @OperationLog + @Operation(summary = "批量删除授权域名") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (domainService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/EmailController.java b/src/main/java/com/gxwebsoft/common/system/controller/EmailController.java new file mode 100644 index 0000000..cf1f9cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/EmailController.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.EmailRecord; +import com.gxwebsoft.common.system.service.EmailRecordService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +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 javax.annotation.Resource; +import javax.mail.MessagingException; + +/** + * 邮件功能控制器 + * + * @author WebSoft + * @since 2020-03-21 00:37:11 + */ +@Tag(name = "邮件功能") +@RestController +@RequestMapping("/api/system/email") +public class EmailController extends BaseController { + @Resource + private EmailRecordService emailRecordService; + + @PreAuthorize("hasAuthority('sys:email:send')") + @Operation(summary = "发送邮件") + @PostMapping() + public ApiResult send(@RequestBody EmailRecord emailRecord) { + try { + emailRecordService.sendFullTextEmail(emailRecord.getTitle(), emailRecord.getContent(), + emailRecord.getReceiver().split(",")); + emailRecord.setCreateUserId(getLoginUserId()); + emailRecordService.save(emailRecord); + return success("发送成功"); + } catch (MessagingException e) { + e.printStackTrace(); + } + return fail("发送失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/FileController.java b/src/main/java/com/gxwebsoft/common/system/controller/FileController.java new file mode 100644 index 0000000..a6dc268 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/FileController.java @@ -0,0 +1,341 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.utils.FileServerUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.param.FileRecordParam; +import com.gxwebsoft.common.system.service.FileRecordService; +import io.swagger.v3.oas.annotations.tags.Tag; + +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + * 文件上传下载控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:24 + */ +@Tag(name = "文件上传下载") +@RestController +@RequestMapping("/api/file") +public class FileController extends BaseController { + @Resource + private ConfigProperties config; + @Resource + private RedisUtil redisUtil; + @Resource + private FileRecordService fileRecordService; + + @PreAuthorize("hasAuthority('sys:file:upload')") + @Operation(summary = "上传文件") + @PostMapping("/upload") + public ApiResult upload(@RequestParam MultipartFile file, HttpServletRequest request) { + FileRecord result = null; + try { + String dir = getUploadDir(); + File upload = FileServerUtil.upload(file, dir, config.getUploadUuidName()); + String path = upload.getAbsolutePath().replace("\\", "/").substring(dir.length() - 1); +// String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/upload"); + String requestURL = config.getFileServer() + "/api/file"; + String originalName = file.getOriginalFilename(); + result = new FileRecord(); + result.setCreateUserId(getLoginUserId()); + result.setName(StrUtil.isBlank(originalName) ? upload.getName() : originalName); + result.setLength(upload.length()); + result.setPath(path); + result.setUrl(requestURL + path); + String contentType = FileServerUtil.getContentType(upload); + result.setContentType(contentType); + if (FileServerUtil.isImage(contentType)) { + result.setThumbnail(requestURL + "/thumbnail" + path); + } + result.setDownloadUrl(config.getFileServer() + "/download" + path); + // 云存储配置 + final String s = redisUtil.get("setting:upload:" + getTenantId()); + final JSONObject jsonObject = JSONObject.parseObject(s); + final String uploadMethod = jsonObject.getString("uploadMethod"); + final String bucketDomain = jsonObject.getString("bucketDomain"); + if(!uploadMethod.equals("file")){ + path = bucketDomain + path; + } + result.setUrl(path); + fileRecordService.save(result); + return success(result); + } catch (Exception e) { + e.printStackTrace(); + return fail("上传失败", result).setError(e.toString()); + } + } + + @PreAuthorize("hasAuthority('sys:file:upload')") + @Operation(summary = "上传base64文件") + @PostMapping("/upload/base64") + public ApiResult uploadBase64(String base64, String fileName, HttpServletRequest request) { + FileRecord result = null; + try { + String dir = getUploadDir(); + File upload = FileServerUtil.upload(base64, fileName, getUploadDir()); + String path = upload.getAbsolutePath().substring(dir.length()).replace("\\", "/"); + String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/upload/base64"); + result = new FileRecord(); + result.setCreateUserId(getLoginUserId()); + result.setName(StrUtil.isBlank(fileName) ? upload.getName() : fileName); + result.setLength(upload.length()); + result.setPath(path); + result.setUrl(requestURL + path); + result.setThumbnail(FileServerUtil.isImage(upload) ? (requestURL + "/thumbnail" + path) : null); + fileRecordService.save(result); + return success(result); + } catch (Exception e) { + e.printStackTrace(); + return fail("上传失败", result).setError(e.toString()); + } + } + + @PreAuthorize("hasAuthority('sys:file:upload')") + @Operation(summary = "上传图片") + @PostMapping("/image") + public HashMap image(@RequestParam MultipartFile file, HttpServletRequest request) { + FileRecord result = null; + try { + String dir = getUploadDir(); + File upload = FileServerUtil.upload(file, dir, config.getUploadUuidName()); + String path = upload.getAbsolutePath().replace("\\", "/").substring(dir.length() - 1); +// System.out.println("request.getRequestURL() = " + request.getRequestURL()); +// String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/image"); + String requestURL = config.getFileServer() + "/api/file"; +// System.out.println("requestURL = " + requestURL); +// config.getServerUrl() + String originalName = file.getOriginalFilename(); + result = new FileRecord(); + result.setCreateUserId(getLoginUserId()); + result.setName(StrUtil.isBlank(originalName) ? upload.getName() : originalName); + result.setLength(upload.length()); + result.setPath(path); + result.setUrl(path); + String contentType = FileServerUtil.getContentType(upload); + result.setContentType(contentType); + if (FileServerUtil.isImage(contentType)) { + result.setThumbnail(requestURL + "/thumbnail" + path); + } + result.setDownloadUrl(requestURL + "/download" + path); + final HashMap map = new HashMap<>(); + map.put("name",result.getName()); + map.put("status","done"); + map.put("thumbUrl",result.getThumbnail()); + map.put("downloadUrl",result.getDownloadUrl()); + map.put("url",result.getUrl()); + fileRecordService.save(result); + return map; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @PreAuthorize("hasAuthority('sys:file:list')") + @Operation(summary = "根据id查询文件") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(fileRecordService.getByIdRel(id)); + } + + @Operation(summary = "查看原文件") + @GetMapping("/{dir}/{name:.+}") + public void preview(@PathVariable("dir") String dir, @PathVariable("name") String name, + HttpServletResponse response, HttpServletRequest request) { + File file = new File(getUploadDir(), dir + "/" + name); + FileServerUtil.preview(file, getPdfOutDir(), config.getOpenOfficeHome(), response, request); + } + + @Operation(summary = "下载原文件") + @GetMapping("/download/{dir}/{name:.+}") + public void download(@PathVariable("dir") String dir, @PathVariable("name") String name, + HttpServletResponse response, HttpServletRequest request) { + String path = dir + "/" + name; + FileRecord record = fileRecordService.getByIdPath(path); + File file = new File(getUploadDir(), path); + String fileName = record == null ? file.getName() : record.getName(); + FileServerUtil.preview(file, true, fileName, null, null, response, request); + } + + @Operation(summary = "查看缩略图") + @GetMapping("/thumbnail/{dir}/{name:.+}") + public void thumbnail(@PathVariable("dir") String dir, @PathVariable("name") String name, + HttpServletResponse response, HttpServletRequest request) { + File file = new File(getUploadDir(), dir + "/" + name); + File thumbnail = new File(getUploadSmDir(), dir + "/" + name); + FileServerUtil.previewThumbnail(file, thumbnail, config.getThumbnailSize(), response, request); + } + + @PreAuthorize("hasAuthority('sys:file:remove')") + @Operation(summary = "删除文件") + @DeleteMapping("/remove/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + FileRecord record = fileRecordService.getById(id); + if (fileRecordService.removeById(id)) { + if (StrUtil.isNotBlank(record.getPath())) { + fileRecordService.deleteFileAsync(Arrays.asList( + new File(getUploadDir(), record.getPath()), + new File(getUploadSmDir(), record.getPath()) + )); + } + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:file:remove')") + @Operation(summary = "批量删除文件") + @DeleteMapping("/remove/batch") + public ApiResult deleteBatch(@RequestBody List ids) { + List fileRecords = fileRecordService.listByIds(ids); + if (fileRecordService.removeByIds(ids)) { + List files = new ArrayList<>(); + for (FileRecord record : fileRecords) { + if (StrUtil.isNotBlank(record.getPath())) { + files.add(new File(getUploadDir(), record.getPath())); + files.add(new File(getUploadSmDir(), record.getPath())); + } + } + fileRecordService.deleteFileAsync(files); + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:file:list')") + @Operation(summary = "分页查询文件") + @GetMapping("/page") + public ApiResult> page(FileRecordParam param, HttpServletRequest request) { + PageResult result = fileRecordService.pageRel(param); +// String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/page"); + String requestURL = config.getFileServer(); + for (FileRecord record : result.getList()) { + if (StrUtil.isNotBlank(record.getPath())) { + record.setUrl(requestURL + record.getPath()); + if (FileServerUtil.isImage(record.getContentType())) { + record.setThumbnail(requestURL + "/thumbnail" + record.getPath()); + } + record.setDownloadUrl(requestURL + "/download" + record.getPath()); + } + } + return success(result); + } + + @PreAuthorize("hasAuthority('sys:file:list')") + @Operation(summary = "查询全部文件") + @GetMapping("/list") + public ApiResult> list(FileRecordParam param, HttpServletRequest request) { + List records = fileRecordService.listRel(param); +// String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/list"); + String requestURL = config.getFileServer(); + for (FileRecord record : records) { + if (StrUtil.isNotBlank(record.getPath())) { + record.setUrl(requestURL + record.getPath()); + if (FileServerUtil.isImage(record.getContentType())) { + record.setThumbnail(requestURL + "/thumbnail" + record.getPath()); + } + record.setDownloadUrl(requestURL + "/download" + record.getPath()); + } + } + return success(records); + } + + /** + * 文件上传基目录 + */ + private String getUploadBaseDir() { + return config.getUploadPath() + "file/"; + } + + /** + * 文件上传位置(服务器) + */ + private String getUploadDir() { + return config.getUploadPath() + "file/"; + } + + /** + * 文件上传位置(本地) + */ +// private String getUploadDir() { +// return "/Users/gxwebsoft/Documents/uploads/"; +// } + + /** + * 缩略图生成位置 + */ + private String getUploadSmDir() { + return getUploadBaseDir() + "thumbnail/"; + } + + /** + * office转pdf输出位置 + */ + private String getPdfOutDir() { + return getUploadBaseDir() + "pdf/"; + } + + @PreAuthorize("hasAuthority('sys:file:upload')") + @Operation(summary = "添加文件") + @PostMapping() + public ApiResult save(@RequestBody FileRecord fileRecord) { + if (fileRecordService.save(fileRecord)) { + return success("上传成功"); + } + return fail("上传失败"); + } + + @PreAuthorize("hasAuthority('sys:file:update')") + @Operation(summary = "修改文件") + @PutMapping() + public ApiResult update(@RequestBody FileRecord fileRecord) { + if (fileRecordService.updateById(fileRecord)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + public FileRecord upload(MultipartFile file, Integer tenantId) { + FileRecord fileRecord = new FileRecord(); + if (file == null || file.isEmpty()) { + return fileRecord; + } + try { + String requestURL = config.getServerUrl() + "/oss/upload"; + String response = HttpRequest.post(requestURL) + .header("Tenantid", tenantId != null ? tenantId.toString() : getTenantId().toString()) + .form("file", file.getBytes(), file.getOriginalFilename()) + .execute() + .body(); + JSONObject data = JSONObject.parseObject(response).getJSONObject("data"); + fileRecord = data != null ? BeanUtil.copyProperties(data, FileRecord.class) : fileRecord; + } catch (Exception e) { + e.printStackTrace(); + } + return fileRecord; + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/LoginRecordController.java b/src/main/java/com/gxwebsoft/common/system/controller/LoginRecordController.java new file mode 100644 index 0000000..91175ca --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/LoginRecordController.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.LoginRecord; +import com.gxwebsoft.common.system.param.LoginRecordParam; +import com.gxwebsoft.common.system.service.LoginRecordService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +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; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 登录日志控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:31 + */ +@Tag(name = "登录日志") +@RestController +@RequestMapping("/api/system/login-record") +public class LoginRecordController extends BaseController { + @Resource + private LoginRecordService loginRecordService; + + @PreAuthorize("hasAuthority('sys:login-record:list')") + @Operation(summary = "分页查询登录日志") + @GetMapping("/page") + public ApiResult> page(LoginRecordParam param) { + return success(loginRecordService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:login-record:list')") + @Operation(summary = "查询全部登录日志") + @GetMapping() + public ApiResult> list(LoginRecordParam param) { + return success(loginRecordService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:login-record:list')") + @Operation(summary = "根据id查询登录日志") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(loginRecordService.getByIdRel(id)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/MainController.java b/src/main/java/com/gxwebsoft/common/system/controller/MainController.java new file mode 100644 index 0000000..07ab5b3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/MainController.java @@ -0,0 +1,314 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.aliyuncs.CommonRequest; +import com.aliyuncs.CommonResponse; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.aliyuncs.http.MethodType; +import com.aliyuncs.profile.DefaultProfile; +import com.google.gson.Gson; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.security.JwtSubject; +import com.gxwebsoft.common.core.security.JwtUtil; +import com.gxwebsoft.common.core.utils.CacheClient; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.ExistenceParam; +import com.gxwebsoft.common.system.entity.LoginRecord; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.param.LoginParam; +import com.gxwebsoft.common.system.param.SmsCaptchaParam; +import com.gxwebsoft.common.system.param.UpdatePasswordParam; +import com.gxwebsoft.common.system.result.CaptchaResult; +import com.gxwebsoft.common.system.result.LoginResult; +import com.gxwebsoft.common.system.service.*; +import com.wf.captcha.SpecCaptcha; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * 登录认证控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:11 + */ +@Tag(name = "登录认证") +@RestController +@RequestMapping("/api") +public class MainController extends BaseController { + @Resource + private ConfigProperties configProperties; + @Resource + private UserService userService; + @Resource + private RoleMenuService roleMenuService; + @Resource + private LoginRecordService loginRecordService; + @Resource + private CacheClient cacheClient; + @Resource + private RedisUtil redisUtil; + + @Operation(summary = "检查用户是否存在") + @GetMapping("/existence") + public ApiResult existence(ExistenceParam param) { + if (param.isExistence(userService, User::getUserId)) { + return success("已存在", param.getValue()); + } + return fail("不存在"); + } + + @Operation(summary = "获取登录用户信息") + @GetMapping("/auth/user") + public ApiResult userInfo() { + final Integer loginUserId = getLoginUserId(); + if(loginUserId != null){ + return success(userService.getByIdRel(getLoginUserId())); + } + return fail("loginUserId不存在",null); + } + + @Operation(summary = "获取登录用户菜单") + @GetMapping("/auth/menu") + public ApiResult> userMenu() { + List menus = roleMenuService.listMenuByUserId(getLoginUserId(), Menu.TYPE_MENU); + return success(CommonUtil.toTreeData(menus, 0, Menu::getParentId, Menu::getMenuId, Menu::setChildren)); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "修改个人信息") + @PutMapping("/auth/user") + public ApiResult updateInfo(@RequestBody User user) { + user.setUserId(getLoginUserId()); + // 不能修改的字段 + user.setUsername(null); + user.setPassword(null); + user.setEmailVerified(null); + user.setOrganizationId(null); + user.setStatus(null); + if (userService.updateById(user)) { + return success(userService.getByIdRel(user.getUserId())); + } + return fail("保存失败", null); + } + + @PreAuthorize("hasAuthority('sys:auth:password')") + @Operation(summary = "修改自己密码") + @PutMapping("/auth/password") + public ApiResult updatePassword(@RequestBody UpdatePasswordParam param) { + if (StrUtil.hasBlank(param.getOldPassword(), param.getPassword())) { + return fail("参数不能为空"); + } + Integer userId = getLoginUserId(); + if (userId == null) { + return fail("未登录"); + } + if (!userService.comparePassword(userService.getById(userId).getPassword(), param.getOldPassword())) { + return fail("原密码输入不正确"); + } + User user = new User(); + user.setUserId(userId); + user.setPassword(userService.encodePassword(param.getPassword())); + if (userService.updateById(user)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "图形验证码") + @GetMapping("/captcha") + public ApiResult captcha() { + SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5); + return success(new CaptchaResult(specCaptcha.toBase64(), specCaptcha.text().toLowerCase())); + } + + @Operation(summary = "企业微信登录链接") + @GetMapping("/wxWorkQrConnect") + public ApiResult wxWorkQrConnect() throws UnsupportedEncodingException { + final JSONObject settingInfo = cacheClient.getSettingInfo("wx-work", 10048); + final String corpId = settingInfo.getString("corpId"); + String encodedReturnUrl = URLEncoder.encode("https://oa.gxwebsoft.com/api/open/wx-work/login","UTF-8"); + String url = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=" +corpId+ "&redirect_uri=" +encodedReturnUrl+ "&state=ww_login@gxwebsoft&usertype=admin"; + return success("获取成功",url); + } + + @Operation(summary = "短信验证码") + @PostMapping("/sendSmsCaptcha") + public ApiResult sendSmsCaptcha(@RequestBody SmsCaptchaParam param) { + // 读取短信配置信息 + String string = redisUtil.get("setting:sms:" + getTenantId()); + JSONObject jsonObject = JSONObject.parseObject(string); + String accessKeyId = jsonObject.getString("accessKeyId"); + String accessKeySecret = jsonObject.getString("accessKeySecret"); + String userTemplateId = jsonObject.getString("userTemplateId"); + String sign = jsonObject.getString("sign"); + if(accessKeyId != null){ + DefaultProfile profile = DefaultProfile.getProfile("regionld", accessKeyId, accessKeySecret); + IAcsClient client = new DefaultAcsClient(profile); + CommonRequest request = new CommonRequest(); + request.setSysMethod(MethodType.POST); + request.setSysDomain("dysmsapi.aliyuncs.com"); + request.setSysVersion("2017-05-25"); + request.setSysAction("SendSms"); + request.putQueryParameter("RegionId", "cn-hangzhou"); + request.putQueryParameter("PhoneNumbers", param.getPhone()); + request.putQueryParameter("SignName", sign); + request.putQueryParameter("TemplateCode", userTemplateId); + // 生成短信验证码 + Random randObj = new Random(); + String code = Integer.toString(100000 + randObj.nextInt(900000)); + request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}"); + try { + CommonResponse response = client.getCommonResponse(request); + System.out.println("response = " + response); + String json = response.getData(); + System.out.println("json = " + json); + Gson g = new Gson(); + HashMap result = g.fromJson(json, HashMap.class); + System.out.println("result = " + result); + if("OK".equals(result.get("Message"))) { + System.out.println("======================== = " + result); + cacheClient.set(param.getPhone(),code,5L,TimeUnit.MINUTES); + String key = "code:" + param.getPhone(); + redisUtil.set(key,code,5L,TimeUnit.MINUTES); + return success("发送成功",result.get("Message")); + }else{ + return fail("发送失败"); + } + } catch (ServerException e) { + e.printStackTrace(); + } catch (ClientException e) { + e.printStackTrace(); + } + } + return fail("发送失败"); + } + + @Operation(summary = "重置密码") + @PutMapping("/password") + public ApiResult resetPassword(@RequestBody User user) { + if (user.getPassword() == null) { + return fail("参数不正确"); + } + if (user.getCode() == null) { + return fail("验证码不能为空"); + } + // 短信验证码校验 + String code = cacheClient.get(user.getPhone(), String.class); + if (!StrUtil.equals(code,user.getCode())) { + return fail("验证码不正确"); + } + + user.setUserId(getLoginUserId()); + user.setPassword(userService.encodePassword(user.getPassword())); + if (userService.updateById(user)) { + return success("密码修改成功"); + } else { + return fail("密码修改失败"); + } + } + + @Operation(summary = "短信验证码登录") + @PostMapping("/loginBySms") + public ApiResult loginBySms(@RequestBody LoginParam param, HttpServletRequest request) { + final String phone = param.getPhone(); + final Integer tenantId = param.getTenantId(); + final String code = param.getCode(); + + User user = userService.getByUsername(phone, tenantId); + // 验证码校验 + String key = "code:" + param.getPhone(); + if(!code.equals(redisUtil.get(key))){ + String message = "验证码不正确"; + loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId,request); + return fail(message, null); + } + if (user == null) { + String message = "账号不存在"; + loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId,request); + return fail(message, null); + } + if (!user.getStatus().equals(0)) { + String message = "账号被冻结"; + loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request); + return fail(message, null); + } + loginRecordService.saveAsync(phone, LoginRecord.TYPE_LOGIN, null, tenantId, request); + + // 设置过期时间 + Long tokenExpireTime = configProperties.getTokenExpireTime(); + final JSONObject register = cacheClient.getSettingInfo("register", tenantId); + if(register != null){ + final String ExpireTime = register.getString("tokenExpireTime"); + if (ExpireTime != null) { + tokenExpireTime = Long.valueOf(ExpireTime); + } + } + + // 签发token + String access_token = JwtUtil.buildToken(new JwtSubject(phone, tenantId), + tokenExpireTime, configProperties.getTokenKey()); + return success("登录成功", new LoginResult(access_token, user)); + } + + @Operation(summary = "会员注册") + @PostMapping("/register") + public ApiResult register(@RequestBody LoginParam param, HttpServletRequest request) { + final String phone = param.getPhone(); + final Integer tenantId = param.getTenantId(); + final String code = param.getCode(); + + User user = userService.getByUsername(phone, tenantId); + // 验证码校验 + String key = "code:" + param.getPhone(); + if(!code.equals(redisUtil.get(key))){ + String message = "验证码不正确"; + loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId,request); + return fail(message, null); + } + if (user == null) { + String message = "账号不存在"; + loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId,request); + return fail(message, null); + } + if (!user.getStatus().equals(0)) { + String message = "账号被冻结"; + loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request); + return fail(message, null); + } + loginRecordService.saveAsync(phone, LoginRecord.TYPE_LOGIN, null, tenantId, request); + + // 设置过期时间 + Long tokenExpireTime = configProperties.getTokenExpireTime(); + final JSONObject register = cacheClient.getSettingInfo("register", tenantId); + if(register != null){ + final String ExpireTime = register.getString("tokenExpireTime"); + if (ExpireTime != null) { + tokenExpireTime = Long.valueOf(ExpireTime); + } + } + + // 签发token + String access_token = JwtUtil.buildToken(new JwtSubject(phone, tenantId), + tokenExpireTime, configProperties.getTokenKey()); + return success("登录成功", new LoginResult(access_token, user)); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/MenuController.java b/src/main/java/com/gxwebsoft/common/system/controller/MenuController.java new file mode 100644 index 0000000..ba11820 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/MenuController.java @@ -0,0 +1,145 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.Plug; +import com.gxwebsoft.common.system.param.MenuParam; +import com.gxwebsoft.common.system.service.MenuService; +import com.gxwebsoft.common.system.service.PlugService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 菜单控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:23 + */ +@Tag(name = "菜单管理") +@RestController +@RequestMapping("/api/system/menu") +public class MenuController extends BaseController { + @Resource + private MenuService menuService; + @Resource + private PlugService plugService; + + @PreAuthorize("hasAuthority('sys:menu:list')") + @Operation(summary = "分页查询菜单") + @GetMapping("/page") + public ApiResult> page(MenuParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return success(menuService.page(page, page.getWrapper())); + } + + @PreAuthorize("hasAuthority('sys:menu:list')") + @Operation(summary = "查询全部菜单") + @GetMapping() + public ApiResult> list(MenuParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return success(menuService.list(page.getOrderWrapper())); + } + + @PreAuthorize("hasAuthority('sys:menu:list')") + @Operation(summary = "根据id查询菜单") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(menuService.getById(id)); + } + + @PreAuthorize("hasAuthority('sys:menu:save')") + @Operation(summary = "添加菜单") + @PostMapping() + public ApiResult add(@RequestBody Menu menu) { + if (menu.getParentId() == null) { + menu.setParentId(0); + } + if (menuService.save(menu)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:update')") + @Operation(summary = "修改菜单") + @PutMapping() + public ApiResult update(@RequestBody Menu menu) { + if (menuService.updateById(menu)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:remove')") + @Operation(summary = "删除菜单") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (menuService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:save')") + @Operation(summary = "批量添加菜单") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List menus) { + if (menuService.saveBatch(menus)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:update')") + @Operation(summary = "批量修改菜单") + @PutMapping("/batch") + public ApiResult updateBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(menuService, "menu_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:remove')") + @Operation(summary = "批量删除菜单") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (menuService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:update')") + @Operation(summary = "菜单克隆") + @PostMapping("/clone") + public ApiResult onClone(@RequestBody MenuParam param){ + if(menuService.cloneMenu(param)){ + return success("克隆成功,请刷新"); + } + return fail("克隆失败"); + } + + @PreAuthorize("hasAuthority('sys:menu:update')") + @Operation(summary = "安装插件") + @GetMapping("/install/{id}") + public ApiResult install(@PathVariable("id") Integer id){ + if(menuService.install(id)){ + // 更新安装次数 + final Plug plug = plugService.getOne(new LambdaQueryWrapper().eq(Plug::getMenuId, id)); + plug.setInstalls(plug.getInstalls() + 1); + plugService.updateById(plug); + return success("安装成功"); + } + return fail("安装失败",id); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/OperationRecordController.java b/src/main/java/com/gxwebsoft/common/system/controller/OperationRecordController.java new file mode 100644 index 0000000..ec54bc4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/OperationRecordController.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.OperationRecord; +import com.gxwebsoft.common.system.param.OperationRecordParam; +import com.gxwebsoft.common.system.service.OperationRecordService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 操作日志控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:12 + */ +@Tag(name = "操作日志") +@RestController +@RequestMapping("/api/system/operation-record") +public class OperationRecordController extends BaseController { + @Resource + private OperationRecordService operationRecordService; + + /** + * 分页查询操作日志 + */ + @PreAuthorize("hasAuthority('sys:operation-record:list')") + @Operation(summary = "分页查询操作日志") + @GetMapping("/page") + public ApiResult> page(OperationRecordParam param) { + return success(operationRecordService.pageRel(param)); + } + + /** + * 查询全部操作日志 + */ + @PreAuthorize("hasAuthority('sys:operation-record:list')") + @Operation(summary = "查询全部操作日志") + @GetMapping() + public ApiResult> list(OperationRecordParam param) { + return success(operationRecordService.listRel(param)); + } + + /** + * 根据id查询操作日志 + */ + @PreAuthorize("hasAuthority('sys:operation-record:list')") + @Operation(summary = "根据id查询操作日志") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(operationRecordService.getByIdRel(id)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/OrganizationController.java b/src/main/java/com/gxwebsoft/common/system/controller/OrganizationController.java new file mode 100644 index 0000000..4174b04 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/OrganizationController.java @@ -0,0 +1,130 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.system.entity.Organization; +import com.gxwebsoft.common.system.param.OrganizationParam; +import com.gxwebsoft.common.system.service.OrganizationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 组织机构控制器 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Tag(name = "组织机构管理") +@RestController +@RequestMapping("/api/system/organization") +public class OrganizationController extends BaseController { + @Resource + private OrganizationService organizationService; + + @PreAuthorize("hasAuthority('sys:org:list')") + @Operation(summary = "分页查询组织机构") + @GetMapping("/page") + public ApiResult> page(OrganizationParam param) { + return success(organizationService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:org:list')") + @Operation(summary = "查询全部组织机构") + @GetMapping() + public ApiResult> list(OrganizationParam param) { + return success(organizationService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:org:list')") + @Operation(summary = "根据id查询组织机构") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(organizationService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:org:save')") + @Operation(summary = "添加组织机构") + @PostMapping() + public ApiResult add(@RequestBody Organization organization) { + if (organization.getParentId() == null) { + organization.setParentId(0); + } + if (organizationService.count(new LambdaQueryWrapper() + .eq(Organization::getOrganizationName, organization.getOrganizationName()) + .eq(Organization::getParentId, organization.getParentId())) > 0) { + return fail("机构名称已存在"); + } + if (organizationService.save(organization)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:org:update')") + @Operation(summary = "修改组织机构") + @PutMapping() + public ApiResult update(@RequestBody Organization organization) { + if (organization.getOrganizationName() != null) { + if (organization.getParentId() == null) { + organization.setParentId(0); + } + if (organizationService.count(new LambdaQueryWrapper() + .eq(Organization::getOrganizationName, organization.getOrganizationName()) + .eq(Organization::getParentId, organization.getParentId()) + .ne(Organization::getOrganizationId, organization.getOrganizationId())) > 0) { + return fail("机构名称已存在"); + } + } + if (organizationService.updateById(organization)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:org:remove')") + @Operation(summary = "删除组织机构") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (organizationService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:org:save')") + @Operation(summary = "批量添加组织机构") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List organizationList) { + if (organizationService.saveBatch(organizationList)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:org:update')") + @Operation(summary = "批量修改组织机构") + @PutMapping("/batch") + public ApiResult updateBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(organizationService, Organization::getOrganizationId)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:org:remove')") + @Operation(summary = "批量删除组织机构") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (organizationService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/PaymentController.java b/src/main/java/com/gxwebsoft/common/system/controller/PaymentController.java new file mode 100644 index 0000000..f68c414 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/PaymentController.java @@ -0,0 +1,235 @@ +package com.gxwebsoft.common.system.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.utils.RequestUtil; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.entity.UserBalanceLog; +import com.gxwebsoft.common.system.param.PaymentParam; +import com.gxwebsoft.common.system.service.PaymentService; +import com.gxwebsoft.common.system.service.UserBalanceLogService; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction; +import com.wechat.pay.java.service.partnerpayments.model.TransactionAmount; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static com.gxwebsoft.common.core.constants.BalanceConstants.BALANCE_USE; + +/** + * 支付方式控制器 + * + * @author 科技小王子 + * @since 2024-05-11 12:39:11 + */ +@Tag(name = "支付") +@RestController +@RequestMapping("/api/system/payment") +public class PaymentController extends BaseController { + @Resource + private PaymentService paymentService; + @Resource + private UserService userService; + @Resource + private UserBalanceLogService userBalanceLogService; + @Resource + private RedisUtil redisUtil; + @Resource + private RequestUtil requestUtil; + @Resource + private PaymentCacheService paymentCacheService; + + @Operation(summary = "余额支付接口") + @PostMapping("/balancePay") + public ApiResult balancePay(@RequestBody ShopOrder order) { + System.out.println("使用余额支付 >>> 订单信息 " + order); + + // 查询购买者信息 + final User buyer = userService.getById(order.getUserId()); + + if (buyer.getBalance().compareTo(order.getPayPrice()) < 0) { + return fail("余额不足"); + } + + // 扣除余额 + final BigDecimal subtract = buyer.getBalance().subtract(order.getTotalPrice()); +// final BigDecimal multiply = subtract.multiply(new BigDecimal(100)); + buyer.setBalance(subtract); + final boolean updateUser = userService.updateUser(buyer); + + // 记录余额明细 + UserBalanceLog userBalanceLog = new UserBalanceLog(); + userBalanceLog.setUserId(buyer.getUserId()); + userBalanceLog.setScene(BALANCE_USE); + userBalanceLog.setMoney(order.getPayPrice()); + BigDecimal balance = buyer.getBalance().add(order.getPayPrice()); + userBalanceLog.setBalance(balance); + userBalanceLog.setComments(order.getMerchantName()); + userBalanceLog.setTransactionId(UUID.randomUUID().toString()); + userBalanceLog.setOrderNo(order.getOrderNo()); + final boolean save = userBalanceLogService.save(userBalanceLog); + System.out.println("save = " + save); + + // 推送微信官方支付结果(携带租户ID的POST请求) + final Transaction transaction = new Transaction(); + transaction.setOutTradeNo(order.getOrderNo()); + transaction.setTransactionId(order.getOrderNo()); + final TransactionAmount amount = new TransactionAmount(); + // 计算金额 + BigDecimal decimal = order.getTotalPrice(); + final BigDecimal multiply = decimal.multiply(new BigDecimal(100)); + // 将 BigDecimal 转换为 Integer + Integer money = multiply.intValue(); + amount.setTotal(money); + amount.setCurrency("CNY"); + transaction.setAmount(amount); + // 获取支付配置信息用于解密 - 使用缓存服务 + Payment payment = paymentCacheService.getWechatPayConfig(order.getTenantId()); + System.out.println("获取到支付配置: " + payment.getMchId()); + requestUtil.pushBalancePayNotify(transaction, payment); + + return success("支付成功",order.getOrderNo()); + } + + @Operation(summary = "选择支付方式") + @GetMapping("/select") + public ApiResult select(PaymentParam param) { + String key = "SelectPayment:".concat(getTenantId().toString()); + final String string = redisUtil.get(key); + final List paymentList = JSONObject.parseArray(string, Payment.class); + if (!CollectionUtils.isEmpty(paymentList)) { + return success(paymentList); + } + // 使用关联查询 + final List list = paymentService.list(new LambdaUpdateWrapper().eq(Payment::getStatus, true)); + if (!CollectionUtils.isEmpty(list)) { + list.forEach(d -> { + d.setApiKey(null); + d.setApiclientCert(null); + d.setApiclientKey(null); + d.setMerchantSerialNumber(null); + }); + } + redisUtil.set(key,list,1L, TimeUnit.DAYS); + return success(list); + } + + @PreAuthorize("hasAuthority('sys:payment:list')") + @Operation(summary = "分页查询支付方式") + @GetMapping("/page") + public ApiResult> page(PaymentParam param) { + // 使用关联查询 + return success(paymentService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:payment:list')") + @Operation(summary = "查询全部支付方式") + @GetMapping() + public ApiResult> list(PaymentParam param) { + // 使用关联查询 + return success(paymentService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:payment:list')") + @Operation(summary = "根据id查询支付方式") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(paymentService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:payment:save')") + @OperationLog + @Operation(summary = "添加支付方式") + @PostMapping() + public ApiResult save(@RequestBody Payment payment) { + if (paymentService.count(new LambdaQueryWrapper().eq(Payment::getCode,payment.getCode())) > 0) { + return fail(payment.getName() + "已存在"); + } + if (paymentService.save(payment)) { + // 使用缓存服务统一管理缓存 + paymentCacheService.cachePaymentConfig(payment, getTenantId()); + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:payment:update')") + @OperationLog + @Operation(summary = "修改支付方式") + @PutMapping() + public ApiResult update(@RequestBody Payment payment) { + if (paymentService.updateById(payment)) { + // 使用缓存服务统一管理缓存 + paymentCacheService.cachePaymentConfig(payment, getTenantId()); + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:payment:remove')") + @OperationLog + @Operation(summary = "删除支付方式") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + final Payment payment = paymentService.getById(id); + System.out.println("payment = " + payment); + + // 使用缓存服务统一管理缓存删除 + paymentCacheService.removePaymentConfig(payment.getCode(), getTenantId()); + if (paymentService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:payment:save')") + @OperationLog + @Operation(summary = "批量添加支付方式") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (paymentService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:payment:update')") + @OperationLog + @Operation(summary = "批量修改支付方式") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(paymentService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:payment:remove')") + @OperationLog + @Operation(summary = "批量删除支付方式") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (paymentService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/PlugController.java b/src/main/java/com/gxwebsoft/common/system/controller/PlugController.java new file mode 100644 index 0000000..2429e99 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/PlugController.java @@ -0,0 +1,161 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.Plug; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.param.PlugParam; +import com.gxwebsoft.common.system.service.MenuService; +import com.gxwebsoft.common.system.service.PlugService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 插件扩展控制器 + * + * @author 科技小王子 + * @since 2023-05-18 11:57:37 + */ +@Tag(name = "插件扩展管理") +@RestController +@RequestMapping("/api/system/plug") +public class PlugController extends BaseController { + @Resource + private PlugService plugService; + @Resource + private MenuService menuService; + + @PreAuthorize("hasAuthority('sys:plug:list')") + @Operation(summary = "分页查询插件扩展") + @GetMapping("/page") + public ApiResult> page(PlugParam param) { + // 如果不传userId,只显示审核通过的插件 + if (param.getUserId() == null) { + param.setStatus(20); + } + // 使用关联查询 + return success(plugService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:plug:list')") + @Operation(summary = "查询全部插件扩展") + @GetMapping() + public ApiResult> list(PlugParam param) { + // 使用关联查询 + return success(plugService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:plug:list')") + @Operation(summary = "根据id查询插件扩展") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(plugService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:plug:save')") + @Operation(summary = "添加插件扩展") + @PostMapping() + public ApiResult save(@RequestBody Plug plug) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + plug.setUserId(loginUser.getUserId()); + } + if (plugService.save(plug)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:plug:update')") + @Operation(summary = "修改插件扩展") + @PutMapping() + public ApiResult update(@RequestBody Plug plug) { + if (plugService.updateById(plug)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:plug:remove')") + @Operation(summary = "删除插件扩展") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (plugService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:plug:save')") + @Operation(summary = "批量添加插件扩展") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (plugService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:plug:update')") + @Operation(summary = "批量修改插件扩展") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(plugService, "menu_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:plug:remove')") + @Operation(summary = "批量删除插件扩展") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (plugService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:plug:save')") + @Operation(summary = "发布插件") + @PostMapping("/plug") + public ApiResult plug(@RequestBody Plug plug){ + final Integer menuId = plug.getParentId(); + // 查重 + final long count = plugService.count(new LambdaQueryWrapper().eq(Plug::getMenuId, menuId)); + if(count > 0){ + return fail("请勿重复发布"); + } + // 准备数据 + final Menu menu = menuService.getById(menuId); + plug.setUserId(getLoginUserId()); + plug.setMenuId(menuId); + plug.setTenantId(getTenantId()); + plug.setIcon(menu.getIcon()); + plug.setPath(menu.getPath()); + plug.setComponent(menu.getComponent()); + plug.setAuthority(menu.getAuthority()); + plug.setTitle(menu.getTitle()); + plug.setMenuType(menu.getMenuType()); + plug.setMeta(menu.getMeta()); + plug.setParentId(menu.getParentId()); + plug.setHide(menu.getHide()); + plug.setSortNumber(menu.getSortNumber()); + if(plugService.save(plug)){ + return success("发布成功"); + } + return fail("发布失败"); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/RedisUtilController.java b/src/main/java/com/gxwebsoft/common/system/controller/RedisUtilController.java new file mode 100644 index 0000000..3e1cac5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/RedisUtilController.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.utils.CacheClient; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/redis-util") +@Tag(name = "Redis缓存工具接口") +public class RedisUtilController extends BaseController { + private CacheClient cacheClient; + private final StringRedisTemplate redisTemplate; + private static final String SPLIT = ":"; + private static final String PREFIX_ENTITY_LIKE = "focus:user"; + private static final String PREFIX_USER_LIKE = "like:user"; + private static final String PREFIX_FOLLOWEE = "followee"; + private static final String PREFIX_FOLLOWER = "follower"; + + + public RedisUtilController(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Operation(summary = "添加关注") + @PostMapping("/addFocus") + public ApiResult addFocus(@RequestBody User user) { + final Integer userId = user.getUserId(); + redisTemplate.opsForZSet().incrementScore(getFocusKey(userId), userId.toString(), 1); + return success("关注成功"); + } + + /** + * 某个用户的关注数 + * @return like:entity:[entityId] ->set(userId) + */ + public static String getFocusKey(Integer userId) { + return PREFIX_ENTITY_LIKE + SPLIT + userId; + } + + /** + * 某个用户的赞 + * @return like:entity:[entityId] ->set(userId) + */ + public static String getEntityLikeKey(int entityType, int entityId) { + return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId; + } + + /** + * 某个用户的赞 + * @return like:user:[userId] ->int + */ + public static String getUserLikeKey(int userId) { + return PREFIX_USER_LIKE + SPLIT + userId; + } + + /** + * 某个用户关注的实体(键:用户Id,值:实体Id) + * @return followee:[userId:entityType] ->zSet(entityId,now) + */ + public static String getFolloweeKey(int userId, int entityType) { + return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType; + } + + /** + * 某个实体拥有的粉丝(键:实体Id,值:用户Id) + * @return follower:[entityType:entityId] ->zSet(entityId,now) + */ + public static String getFollowerKey(int entityType, int entityId) { + return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/RoleController.java b/src/main/java/com/gxwebsoft/common/system/controller/RoleController.java new file mode 100644 index 0000000..eece032 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/RoleController.java @@ -0,0 +1,144 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.param.RoleParam; +import com.gxwebsoft.common.system.service.RoleService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 角色控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:02 + */ +@Tag(name = "角色管理") +@RestController +@RequestMapping("/api/system/role") +public class RoleController extends BaseController { + @Resource + private RoleService roleService; + + @PreAuthorize("hasAuthority('sys:role:list')") + @Operation(summary = "分页查询角色") + @GetMapping("/page") + public ApiResult> page(RoleParam param) { + PageParam page = new PageParam<>(param); + return success(roleService.page(page, page.getWrapper())); + } + + @PreAuthorize("hasAuthority('sys:role:list')") + @Operation(summary = "查询全部角色") + @GetMapping() + public ApiResult> list(RoleParam param) { + PageParam page = new PageParam<>(param); + return success(roleService.list(page.getOrderWrapper())); + } + + @PreAuthorize("hasAuthority('sys:role:list')") + @Operation(summary = "根据id查询角色") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(roleService.getById(id)); + } + + @PreAuthorize("hasAuthority('sys:role:save')") + @Operation(summary = "添加角色") + @PostMapping() + public ApiResult save(@RequestBody Role role) { + if (roleService.count(new LambdaQueryWrapper().eq(Role::getRoleCode, role.getRoleCode())) > 0) { + return fail("角色标识已存在"); + } + if (roleService.count(new LambdaQueryWrapper().eq(Role::getRoleName, role.getRoleName())) > 0) { + return fail("角色名称已存在"); + } + if (roleService.save(role)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:role:update')") + @Operation(summary = "修改角色") + @PutMapping() + public ApiResult update(@RequestBody Role role) { + if (role.getRoleCode() != null && roleService.count(new LambdaQueryWrapper() + .eq(Role::getRoleCode, role.getRoleCode()) + .ne(Role::getRoleId, role.getRoleId())) > 0) { + return fail("角色标识已存在"); + } + if (role.getRoleName() != null && roleService.count(new LambdaQueryWrapper() + .eq(Role::getRoleName, role.getRoleName()) + .ne(Role::getRoleId, role.getRoleId())) > 0) { + return fail("角色名称已存在"); + } + if (roleService.updateById(role)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:role:remove')") + @Operation(summary = "删除角色") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (roleService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:role:save')") + @Operation(summary = "批量添加角色") + @PostMapping("/batch") + public ApiResult> saveBatch(@RequestBody List list) { + // 校验是否重复 + if (CommonUtil.checkRepeat(list, Role::getRoleName)) { + return fail("角色名称存在重复", null); + } + if (CommonUtil.checkRepeat(list, Role::getRoleCode)) { + return fail("角色标识存在重复", null); + } + // 校验是否存在 + List codeExists = roleService.list(new LambdaQueryWrapper().in(Role::getRoleCode, + list.stream().map(Role::getRoleCode).collect(Collectors.toList()))); + if (codeExists.size() > 0) { + return fail("角色标识已存在", codeExists.stream().map(Role::getRoleCode) + .collect(Collectors.toList())).setCode(2); + } + List nameExists = roleService.list(new LambdaQueryWrapper().in(Role::getRoleName, + list.stream().map(Role::getRoleCode).collect(Collectors.toList()))); + if (nameExists.size() > 0) { + return fail("角色标识已存在", nameExists.stream().map(Role::getRoleCode) + .collect(Collectors.toList())).setCode(3); + } + if (roleService.saveBatch(list)) { + return success("添加成功", null); + } + return fail("添加失败", null); + } + + @PreAuthorize("hasAuthority('sys:role:remove')") + @Operation(summary = "批量删除角色") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (roleService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/RoleMenuController.java b/src/main/java/com/gxwebsoft/common/system/controller/RoleMenuController.java new file mode 100644 index 0000000..fdc186a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/RoleMenuController.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.RoleMenu; +import com.gxwebsoft.common.system.service.MenuService; +import com.gxwebsoft.common.system.service.RoleMenuService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * 角色菜单控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:01 + */ +@Tag(name = "角色菜单管理") +@RestController +@RequestMapping("/api/system/role-menu") +public class RoleMenuController extends BaseController { + @Resource + private RoleMenuService roleMenuService; + @Resource + private MenuService menuService; + + @PreAuthorize("hasAuthority('sys:role:list')") + @Operation(summary = "查询角色菜单") + @GetMapping("/{id}") + public ApiResult> list(@PathVariable("id") Integer roleId) { + List menus = menuService.list(new LambdaQueryWrapper().orderByAsc(Menu::getSortNumber)); + List roleMenus = roleMenuService.list(new LambdaQueryWrapper() + .eq(RoleMenu::getRoleId, roleId)); + for (Menu menu : menus) { + menu.setChecked(roleMenus.stream().anyMatch((d) -> d.getMenuId().equals(menu.getMenuId()))); + } + return success(menus); + } + + @Transactional(rollbackFor = {Exception.class}) + @PreAuthorize("hasAuthority('sys:role:update')") + @Operation(summary = "修改角色菜单") + @PutMapping("/{id}") + public ApiResult update(@PathVariable("id") Integer roleId, @RequestBody List menuIds) { + roleMenuService.remove(new LambdaUpdateWrapper().eq(RoleMenu::getRoleId, roleId)); + if (menuIds != null && menuIds.size() > 0) { + List roleMenuList = new ArrayList<>(); + for (Integer menuId : menuIds) { + RoleMenu roleMenu = new RoleMenu(); + roleMenu.setRoleId(roleId); + roleMenu.setMenuId(menuId); + roleMenuList.add(roleMenu); + } + if (!roleMenuService.saveBatch(roleMenuList)) { + throw new BusinessException("保存失败"); + } + } + return success("保存成功"); + } + + @PreAuthorize("hasAuthority('sys:role:update')") + @Operation(summary = "添加角色菜单") + @PostMapping("/{id}") + public ApiResult addRoleAuth(@PathVariable("id") Integer roleId, @RequestBody Integer menuId) { + RoleMenu roleMenu = new RoleMenu(); + roleMenu.setRoleId(roleId); + roleMenu.setMenuId(menuId); + if (roleMenuService.save(roleMenu)) { + return success(); + } + return fail(); + } + + @PreAuthorize("hasAuthority('sys:role:update')") + @Operation(summary = "移除角色菜单") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer roleId, @RequestBody Integer menuId) { + if (roleMenuService.remove(new LambdaUpdateWrapper() + .eq(RoleMenu::getRoleId, roleId).eq(RoleMenu::getMenuId, menuId))) { + return success(); + } + return fail(); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/SettingController.java b/src/main/java/com/gxwebsoft/common/system/controller/SettingController.java new file mode 100644 index 0000000..51e670d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/SettingController.java @@ -0,0 +1,178 @@ +package com.gxwebsoft.common.system.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.system.entity.Setting; +import com.gxwebsoft.common.system.entity.Tenant; +import com.gxwebsoft.common.system.param.SettingParam; +import com.gxwebsoft.common.system.service.SettingService; +import com.gxwebsoft.common.system.service.TenantService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统设置控制器 + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +@Tag(name = "系统设置管理") +@RestController +@RequestMapping("/api/system/setting") +public class SettingController extends BaseController { + @Resource + private SettingService settingService; + @Resource + private TenantService tenantService; + @Resource + private RedisUtil redisUtil; + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "分页查询系统设置") + @GetMapping("/page") + public ApiResult> page(SettingParam param) { + // 使用关联查询 + return success(settingService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "查询全部系统设置") + @GetMapping() + public ApiResult> list(SettingParam param) { + // 使用关联查询 + return success(settingService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "根据id查询系统设置") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(settingService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "添加系统设置") + @PostMapping() + public ApiResult save(@RequestBody Setting setting) { + if (settingService.save(setting)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "修改系统设置") + @PutMapping() + public ApiResult update(@RequestBody Setting setting) { + if (settingService.updateById(setting)) { + // 更新系统设置信息到缓存 + String key = "setting:" + setting.getSettingKey() + ":" + getTenantId(); + System.out.println("key = " + key); + redisUtil.set(key, JSON.parseObject(setting.getContent())); + // 创建微信支付Bean +// settingService.initConfig(setting); + // 更新租户信息 + if (setting.getSettingKey().equals("setting")) { + System.out.println("修改系统设置 = " + setting.getContent()); + final String content = setting.getContent(); + final JSONObject jsonObject = JSONObject.parseObject(content); + final String siteName = jsonObject.getString("siteName"); + final String logo = jsonObject.getString("logo"); + System.out.println("siteName = " + siteName); + final Tenant tenant = new Tenant(); + tenant.setTenantName(siteName); + tenant.setTenantId(getTenantId()); + tenant.setLogo(logo); + tenantService.updateById(tenant); + } + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:setting:remove')") + @Operation(summary = "删除系统设置") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (settingService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "批量添加系统设置") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (settingService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:setting:update')") + @Operation(summary = "批量修改系统设置") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(settingService, "setting_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:setting:remove')") + @Operation(summary = "批量删除系统设置") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (settingService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:setting:data')") + @Operation(summary = "查询租户设置信息") + @GetMapping("/data") + public ApiResult data() { + return success(settingService.getData("setting")); + } + + @PreAuthorize("hasAuthority('sys:setting:save')") + @Operation(summary = "更新主题皮肤") + @PutMapping("/theme") + public ApiResult theme(@RequestBody Setting setting) { + String key = "theme:".concat(getTenantId().toString()); + // 新增 + final Setting one = settingService.getOne(new LambdaQueryWrapper().eq(Setting::getSettingKey, setting.getSettingKey())); + if(one == null){ + settingService.save(setting); + redisUtil.set(key,setting.getContent()); + return success("保存成功"); + } + // 更新 + final Setting update = settingService.getOne(new LambdaQueryWrapper().eq(Setting::getSettingKey, setting.getSettingKey())); + update.setContent(setting.getContent()); + if (settingService.updateById(update)) { + redisUtil.set(key,setting.getContent()); + return success("更新成功"); + } + return fail("更新失败"); + } + + @Operation(summary = "更新主题皮肤") + @GetMapping("/getTheme") + public ApiResult getTheme() { + String key = "theme:".concat(getTenantId().toString()); + return success("获取成功",redisUtil.get(key)); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/TenantController.java b/src/main/java/com/gxwebsoft/common/system/controller/TenantController.java new file mode 100644 index 0000000..4114e04 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/TenantController.java @@ -0,0 +1,158 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.RoleMenu; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.MenuService; +import com.gxwebsoft.common.system.service.RoleMenuService; +import com.gxwebsoft.common.system.service.TenantService; +import com.gxwebsoft.common.system.entity.Tenant; +import com.gxwebsoft.common.system.param.TenantParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 租户控制器 + * + * @author 科技小王子 + * @since 2023-07-17 17:49:53 + */ +@Tag(name = "租户管理") +@RestController +@RequestMapping("/api/system/tenant") +public class TenantController extends BaseController { + @Resource + private TenantService tenantService; + @Resource + private MenuService menuService; + @Resource + private RoleMenuService roleMenuService; + + @PreAuthorize("hasAuthority('sys:tenant:list')") + @Operation(summary = "分页查询租户") + @GetMapping("/page") + public ApiResult> page(TenantParam param) { + // 使用关联查询 + return success(tenantService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:tenant:list')") + @Operation(summary = "查询全部租户") + @GetMapping() + public ApiResult> list(TenantParam param) { + // 使用关联查询 + return success(tenantService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:tenant:list')") + @Operation(summary = "根据id查询租户") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(tenantService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:tenant:save')") + @Operation(summary = "添加租户") + @PostMapping() + public ApiResult save(@RequestBody Tenant tenant) { + System.out.println("tenant = " + tenant); + if (tenantService.save(tenant)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:tenant:update')") + @Operation(summary = "修改租户") + @PutMapping() + public ApiResult update(@RequestBody Tenant tenant) { + if (tenantService.updateById(tenant)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:tenant:remove')") + @Operation(summary = "删除租户") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (tenantService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:tenant:save')") + @Operation(summary = "批量添加租户") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (tenantService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:tenant:update')") + @Operation(summary = "批量修改租户") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(tenantService, "tenant_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:tenant:remove')") + @Operation(summary = "批量删除租户") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (tenantService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "租户角色权限初始化") + @GetMapping("/role-menu/{id}") + public ApiResult> initialization(@PathVariable("id") Integer roleId) { + List menus = menuService.list(new LambdaQueryWrapper().orderByAsc(Menu::getSortNumber)); + List roleMenus = roleMenuService.list(new LambdaQueryWrapper() + .eq(RoleMenu::getRoleId, roleId)); + for (Menu menu : menus) { + menu.setChecked(roleMenus.stream().anyMatch((d) -> d.getMenuId().equals(menu.getMenuId()))); + } + List menuIds = menus.stream().map(Menu::getMenuId).collect(Collectors.toList()); + roleMenuService.remove(new LambdaUpdateWrapper().eq(RoleMenu::getRoleId, roleId)); + if (menuIds.size() > 0) { + List roleMenuList = new ArrayList<>(); + for (Integer menuId : menuIds) { + RoleMenu roleMenu = new RoleMenu(); + roleMenu.setRoleId(roleId); + roleMenu.setMenuId(menuId); + roleMenuList.add(roleMenu); + } + if (!roleMenuService.saveBatch(roleMenuList)) { + throw new BusinessException("保存失败"); + } + } + return success(menus); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/UserCollectionController.java b/src/main/java/com/gxwebsoft/common/system/controller/UserCollectionController.java new file mode 100644 index 0000000..0585272 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/UserCollectionController.java @@ -0,0 +1,135 @@ +package com.gxwebsoft.common.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.entity.UserCollection; +import com.gxwebsoft.common.system.param.UserCollectionParam; +import com.gxwebsoft.common.system.service.UserCollectionService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 我的收藏控制器 + * + * @author 科技小王子 + * @since 2024-04-28 18:08:32 + */ +@Tag(name = "用户收藏") +@RestController +@RequestMapping("/api/system/user-collection") +public class UserCollectionController extends BaseController { + @Resource + private UserCollectionService userCollectionService; + + @PreAuthorize("hasAuthority('sys:userCollection:list')") + @Operation(summary = "分页查询我的收藏") + @GetMapping("/page") + public ApiResult> page(UserCollectionParam param) { + // 使用关联查询 + return success(userCollectionService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:userCollection:list')") + @Operation(summary = "查询全部我的收藏") + @GetMapping() + public ApiResult> list(UserCollectionParam param) { + // 使用关联查询 + return success(userCollectionService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:userCollection:list')") + @Operation(summary = "根据id查询我的收藏") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(userCollectionService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:userCollection:save')") + @OperationLog + @Operation(summary = "添加和取消收藏") + @PostMapping() + public ApiResult save(@RequestBody UserCollection userCollection) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + userCollection.setUserId(loginUser.getUserId()); + userCollection.setTid(userCollection.getTid()); + final UserCollection one = userCollectionService.getOne(new LambdaQueryWrapper().eq(UserCollection::getUserId, loginUser.getUserId()).eq(UserCollection::getTid, userCollection.getTid()).last("limit 1")); + if (one != null) { + userCollectionService.removeById(one.getId()); + return success("已取消收藏"); + } + if (userCollectionService.save(userCollection)) { + return success("已添加收藏"); + } + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:userCollection:update')") + @OperationLog + @Operation(summary = "修改我的收藏") + @PutMapping() + public ApiResult update(@RequestBody UserCollection userCollection) { + if (userCollectionService.updateById(userCollection)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:userCollection:remove')") + @OperationLog + @Operation(summary = "删除我的收藏") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (userCollectionService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:userCollection:save')") + @OperationLog + @Operation(summary = "批量添加我的收藏") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (userCollectionService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:userCollection:update')") + @OperationLog + @Operation(summary = "批量修改我的收藏") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(userCollectionService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:userCollection:remove')") + @OperationLog + @Operation(summary = "批量删除我的收藏") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (userCollectionService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/UserController.java b/src/main/java/com/gxwebsoft/common/system/controller/UserController.java new file mode 100644 index 0000000..19b99bc --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/UserController.java @@ -0,0 +1,401 @@ +package com.gxwebsoft.common.system.controller; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.*; +import com.gxwebsoft.common.system.entity.*; +import com.gxwebsoft.common.system.param.UserImportParam; +import com.gxwebsoft.common.system.param.UserParam; +import com.gxwebsoft.common.system.service.DictionaryDataService; +import com.gxwebsoft.common.system.service.OrganizationService; +import com.gxwebsoft.common.system.service.RoleService; +import com.gxwebsoft.common.system.service.UserService; +import io.swagger.v3.oas.annotations.tags.Tag; + +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 用户控制器 + * + * @author WebSoft + * @since 2018-12-24 16:10:41 + */ +@Tag(name = "用户管理") +@RestController +@RequestMapping("/api/system/user") +public class UserController extends BaseController { + @Resource + private UserService userService; + @Resource + private RoleService roleService; + @Resource + private OrganizationService organizationService; + @Resource + private DictionaryDataService dictionaryDataService; + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "分页查询用户") + @GetMapping("/page") + public ApiResult> page(UserParam param) { + return success(userService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "分页查询用户") + @GetMapping("/pageAdminByPhone") + public ApiResult> pageAdminByPhone(UserParam param) { + return success(userService.pageAdminByPhone(param)); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "查询全部用户") + @GetMapping() + public ApiResult> list(UserParam param) { + return success(userService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "根据id查询用户") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(userService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "根据手机号码查询用户") + @GetMapping("/getByPhone/{phone}") + public ApiResult getByPhone(@PathVariable("phone") String phone) { + return success(userService.getByPhone(phone)); + } + + @PreAuthorize("hasAuthority('sys:user:save')") + @Operation(summary = "添加用户") + @PostMapping() + public ApiResult add(@RequestBody User user) { + user.setStatus(0); + user.setPassword(userService.encodePassword(user.getPassword())); + if (userService.saveUser(user)) { + return success("添加成功",user.getUserId()); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:user:save')") + @Operation(summary = "批量添加用户") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List userList) { + userList.forEach(d -> { + d.setStatus(0); + if (d.getPassword() != null) { + d.setPassword(userService.encodePassword(d.getPassword())); + } + }); + if (userService.saveBatch(userList)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:user:save')") + @Operation(summary = "批量添加用户并返回userId") + @PostMapping("/batchBackUserId") + public ApiResult saveBatchBackUserId(@RequestBody List userList) { + userList.forEach(d -> { + d.setStatus(0); + d.setPassword(userService.encodePassword(d.getPassword())); + }); + final Set phones = userList.stream().map(User::getPhone).collect(Collectors.toSet()); + if (userService.saveBatch(userList)) { + final UserParam userParam = new UserParam(); + userParam.setPhones(phones); + userParam.setLimit(500L); + final PageResult result = userService.pageRel(userParam); + final Set collect = result.getList().stream().map(User::getUserId).collect(Collectors.toSet()); + return success("添加成功",collect); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "修改用户") + @PutMapping() + public ApiResult update(@RequestBody User user) { + user.setStatus(null); + user.setUsername(null); + user.setPassword(null); + if (userService.updateUser(user)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:user:remove')") + @OperationLog + @Operation(summary = "删除用户") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (userService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "批量修改用户") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(userService, User::getUserId)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:user:remove')") + @OperationLog + @Operation(summary = "批量删除用户") + @Transactional(rollbackFor = {Exception.class}) + @DeleteMapping("/batch") + public ApiResult deleteBatch(@RequestBody List ids) { + if (userService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "修改用户状态") + @PutMapping("/status") + public ApiResult updateStatus(@RequestBody User user) { + if (user.getUserId() == null || user.getStatus() == null || !Arrays.asList(0, 1).contains(user.getStatus())) { + return fail("参数不正确"); + } + User u = new User(); + u.setUserId(user.getUserId()); + u.setStatus(user.getStatus()); + if (userService.updateById(u)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "修改推荐状态") + @PutMapping("/recommend") + public ApiResult updateRecommend(@RequestBody User user) { + if (user.getUserId() == null || user.getRecommend() == null || !Arrays.asList(0, 1).contains(user.getRecommend())) { + return fail("参数不正确"); + } + User u = new User(); + u.setUserId(user.getUserId()); + u.setRecommend(user.getRecommend()); + if (userService.updateById(u)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "批量修改用户状态") + @PutMapping("/status/batch") + public ApiResult updateStatusBatch(@RequestBody BatchParam batchParam) { + if (!Arrays.asList(0, 1).contains(batchParam.getData())) { + return fail("状态值不正确"); + } + if (userService.update(new LambdaUpdateWrapper() + .in(User::getUserId, batchParam.getIds()) + .set(User::getStatus, batchParam.getData()))) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "重置密码") + @PutMapping("/password") + public ApiResult resetPassword(@RequestBody User user) { + if (user.getUserId() == null || StrUtil.isBlank(user.getPassword())) { + return fail("参数不正确"); + } + User u = new User(); + u.setUserId(user.getUserId()); + u.setPassword(userService.encodePassword(user.getPassword())); + if (userService.updateById(u)) { + return success("重置成功"); + } else { + return fail("重置失败"); + } + } + + @PreAuthorize("hasAuthority('sys:user:update')") + @OperationLog + @Operation(summary = "批量重置密码") + @PutMapping("/password/batch") + public ApiResult resetPasswordBatch(@RequestBody BatchParam batchParam) { + if (batchParam.getIds() == null || batchParam.getIds().size() == 0) { + return fail("请选择用户"); + } + if (batchParam.getData() == null) { + return fail("请输入密码"); + } + if (userService.update(new LambdaUpdateWrapper() + .in(User::getUserId, batchParam.getIds()) + .set(User::getPassword, userService.encodePassword(batchParam.getData())))) { + return success("重置成功"); + } else { + return fail("重置失败"); + } + } + + @Operation(summary = "检查用户是否存在") + @GetMapping("/existence") + public ApiResult existence(ExistenceParam param) { + if (param.isExistence(userService, User::getUserId)) { + return success(param.getValue() + "已存在"); + } + return fail(param.getValue() + "不存在"); + } + + /** + * excel导入用户 + */ + @PreAuthorize("hasAuthority('sys:user:save')") + @Operation(summary = "导入用户") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/import") + public ApiResult> importBatch(MultipartFile file) { + ImportParams importParams = new ImportParams(); + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), + UserImportParam.class, importParams); + // 校验是否重复 + if (CommonUtil.checkRepeat(list, UserImportParam::getUsername)) { + return fail("账号存在重复", null); + } + if (CommonUtil.checkRepeat(list, UserImportParam::getPhone)) { + return fail("手机号存在重复", null); + } + // 校验是否存在 + List usernameExists = userService.list(new LambdaQueryWrapper().in(User::getUsername, + list.stream().map(UserImportParam::getUsername).collect(Collectors.toList()))); + if (usernameExists.size() > 0) { + return fail("账号已经存在", + usernameExists.stream().map(User::getUsername).collect(Collectors.toList())); + } + List phoneExists = userService.list(new LambdaQueryWrapper().in(User::getPhone, + list.stream().map(UserImportParam::getPhone).collect(Collectors.toList()))); + if (phoneExists.size() > 0) { + return fail("手机号已经存在", + phoneExists.stream().map(User::getPhone).collect(Collectors.toList())); + } + // 添加 + List users = new ArrayList<>(); + for (UserImportParam one : list) { + User u = new User(); + u.setStatus(0); + u.setUsername(one.getUsername()); + u.setPassword(userService.encodePassword(one.getPassword())); + u.setNickname(one.getNickname()); + u.setPhone(one.getPhone()); + Role role = roleService.getOne(new QueryWrapper() + .eq("role_name", one.getRoleName()), false); + if (role == null) { + return fail("角色不存在", Collections.singletonList(one.getRoleName())); + } else { + u.setRoles(Collections.singletonList(role)); + } + Organization organization = organizationService.getOne(new QueryWrapper() + .eq("organization_full_name", one.getOrganizationName()), false); + if (organization == null) { + return fail("机构不存在", Collections.singletonList(one.getOrganizationName())); + } else { + u.setOrganizationId(organization.getOrganizationId()); + } + DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName()); + if (sex == null) { + return fail("性别不存在", Collections.singletonList(one.getSexName())); + } else { + u.setSex(sex.getDictDataCode()); + } + } + if (userService.saveBatch(users)) { + return success("导入成功", null); + } + } catch (Exception e) { + e.printStackTrace(); + } + return fail("导入失败", null); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @PostMapping("/getAvatarByMpWx") + @Operation(summary = "更新微信头像") + public ApiResult getAvatarByMpWx(@RequestBody User user){ + user.setAvatar("https://oa.gxwebsoft.com/assets/logo.7ccfefb9.svg"); + if (userService.updateUser(user)) { + return success("更新成功"); + } + return fail("更新失败"); + } + + @PostMapping("/updatePointsBySign") + @Operation(summary = "签到成功累加积分") + public ApiResult updatePointsBySign(){ + final User loginUser = getLoginUser(); + loginUser.setPoints(loginUser.getPoints() + 1); + if (userService.updateUser(loginUser)) { + return success("签到成功"); + } + return fail("签到失败"); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @PutMapping("/updateUserBalance") + @Operation(summary = "更新用户余额") + public ApiResult updateUserBalance(@RequestBody User user){ + if (getLoginUser() == null) { + return fail("请先登录"); + } + if (userService.updateUser(user)) { + return success("操作成功"); + } + return fail("操作失败"); + } + + @PreAuthorize("hasAuthority('sys:user:list')") + @OperationLog + @Operation(summary = "统计用户余额") + @GetMapping("/countUserBalance") + public ApiResult countUserBalance(User param) { + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.gt(User::getBalance, 0); + if (!param.getOrganizationId().equals(0)) { + wrapper.eq(User::getOrganizationId,param.getOrganizationId()); + } + final List list = userService.list(wrapper); + final BigDecimal totalBalance = list.stream().map(User::getBalance).reduce(BigDecimal.ZERO, BigDecimal::add); + // System.out.println("统计用户余额 = " + totalBalance); + return success("统计成功",totalBalance); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/UserFileController.java b/src/main/java/com/gxwebsoft/common/system/controller/UserFileController.java new file mode 100644 index 0000000..b56214d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/UserFileController.java @@ -0,0 +1,158 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.utils.FileServerUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.service.UserFileService; +import com.gxwebsoft.common.system.entity.UserFile; +import com.gxwebsoft.common.system.param.UserFileParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 用户文件控制器 + * + * @author WebSoft + * @since 2022-07-21 14:34:40 + */ +@Tag(name = "用户文件管理") +@RestController +@RequestMapping("/api/system/user-file") +public class UserFileController extends BaseController { + @Resource + private UserFileService userFileService; + + @Operation(summary = "分页查询用户文件") + @GetMapping("/page") + public ApiResult> page(UserFileParam param, HttpServletRequest request) { + param.setUserId(getLoginUserId()); + PageParam page = new PageParam<>(param); + page.setDefaultOrder("is_directory desc"); + PageParam result = userFileService.page(page, page.getWrapper()); + List records = result.getRecords(); + String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/system/user-file") + "/file"; + for (UserFile record : records) { + if (StrUtil.isNotBlank(record.getPath())) { + record.setUrl(requestURL + "/" + record.getPath()); + if (FileServerUtil.isImage(record.getContentType())) { + record.setThumbnail(requestURL + "/thumbnail/" + record.getPath()); + } + record.setDownloadUrl(requestURL + "/download/" + record.getPath()); + } + } + return success(records, result.getTotal()); + } + + @Operation(summary = "查询全部用户文件") + @GetMapping() + public ApiResult> list(UserFileParam param, HttpServletRequest request) { + param.setUserId(getLoginUserId()); + PageParam page = new PageParam<>(param); + page.setDefaultOrder("is_directory desc"); + List records = userFileService.list(page.getOrderWrapper()); + String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/system/user-file") + "/file"; + for (UserFile record : records) { + if (StrUtil.isNotBlank(record.getPath())) { + record.setUrl(requestURL + "/" + record.getPath()); + if (FileServerUtil.isImage(record.getContentType())) { + record.setThumbnail(requestURL + "/thumbnail/" + record.getPath()); + } + record.setDownloadUrl(requestURL + "/download/" + record.getPath()); + } + } + return success(records); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "添加用户文件") + @PostMapping() + public ApiResult save(@RequestBody UserFile userFile) { + userFile.setUserId(getLoginUserId()); + if (userFile.getParentId() == null) { + userFile.setParentId(0); + } + if (userFile.getIsDirectory() != null && userFile.getIsDirectory().equals(1)) { + if (userFileService.count(new LambdaQueryWrapper() + .eq(UserFile::getName, userFile.getName()) + .eq(UserFile::getParentId, userFile.getParentId()) + .eq(UserFile::getUserId, userFile.getUserId())) > 0) { + return fail("文件夹名称已存在"); + } + } + if (userFileService.save(userFile)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "修改用户文件") + @PutMapping() + public ApiResult update(@RequestBody UserFile userFile) { + Integer loginUserId = getLoginUserId(); + UserFile old = userFileService.getById(userFile.getId()); + UserFile entity = new UserFile(); + if (StrUtil.isNotBlank(userFile.getName())) { + entity.setName(userFile.getName()); + } + if (userFile.getParentId() != null) { + entity.setParentId(userFile.getParentId()); + } + if (!old.getUserId().equals(loginUserId) || + (entity.getName() == null && entity.getParentId() == null)) { + return fail("修改失败"); + } + if (old.getIsDirectory() != null && old.getIsDirectory().equals(1)) { + if (userFileService.count(new LambdaQueryWrapper() + .eq(UserFile::getName, entity.getName() == null ? old.getName() : entity.getName()) + .eq(UserFile::getParentId, entity.getParentId() == null ? old.getParentId() : entity.getParentId()) + .eq(UserFile::getUserId, loginUserId) + .ne(UserFile::getId, old.getId())) > 0) { + return fail("文件夹名称已存在"); + } + } + if (userFileService.update(entity, new LambdaUpdateWrapper() + .eq(UserFile::getId, userFile.getId()) + .eq(UserFile::getUserId, loginUserId))) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "删除用户文件") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (userFileService.remove(new LambdaUpdateWrapper() + .eq(UserFile::getId, id) + .eq(UserFile::getUserId, getLoginUserId()))) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:auth:user')") + @Operation(summary = "批量删除用户文件") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (userFileService.remove(new LambdaUpdateWrapper() + .in(UserFile::getId, ids) + .eq(UserFile::getUserId, getLoginUserId()))) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/UserRefereeController.java b/src/main/java/com/gxwebsoft/common/system/controller/UserRefereeController.java new file mode 100644 index 0000000..6cd1463 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/UserRefereeController.java @@ -0,0 +1,183 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.entity.UserReferee; +import com.gxwebsoft.common.system.param.UserRefereeParam; +import com.gxwebsoft.common.system.service.UserRefereeService; +import com.gxwebsoft.common.system.service.UserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用户推荐关系表控制器 + * + * @author 科技小王子 + * @since 2023-10-07 22:56:36 + */ +@Tag(name = "用户") +@RestController +@RequestMapping("/api/system/user-referee") +public class UserRefereeController extends BaseController { + @Resource + private UserRefereeService userRefereeService; + @Resource + private UserService userService; + + @PreAuthorize("hasAuthority('sys:userReferee:list')") + @OperationLog + @Operation(summary = "分页查询用户推荐关系表") + @GetMapping("/page") + public ApiResult> page(UserRefereeParam param) { + // 使用关联查询 + return success(userRefereeService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('sys:userReferee:list')") + @OperationLog + @Operation(summary = "查询全部用户推荐关系表") + @GetMapping() + public ApiResult> list(UserRefereeParam param) { + // 使用关联查询 + return success(userRefereeService.listRel(param)); + } + + @PreAuthorize("hasAuthority('sys:userReferee:list')") + @OperationLog + @Operation(summary = "根据id查询用户推荐关系表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(userRefereeService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('sys:userReferee:save')") + @OperationLog + @Operation(summary = "添加用户推荐关系表") + @PostMapping() + public ApiResult save(@RequestBody UserReferee userReferee) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + userReferee.setUserId(loginUser.getUserId()); + } + if (userRefereeService.save(userReferee)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:userReferee:update')") + @OperationLog + @Operation(summary = "修改用户推荐关系表") + @PutMapping() + public ApiResult update(@RequestBody UserReferee userReferee) { + if (userRefereeService.updateById(userReferee)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:userReferee:remove')") + @OperationLog + @Operation(summary = "删除用户推荐关系表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (userRefereeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('sys:userReferee:save')") + @OperationLog + @Operation(summary = "批量添加用户推荐关系表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (userRefereeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('sys:userReferee:update')") + @OperationLog + @Operation(summary = "批量修改用户推荐关系表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(userRefereeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('sys:userReferee:remove')") + @OperationLog + @Operation(summary = "批量删除用户推荐关系表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (userRefereeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "查询推荐人信息") + @GetMapping("/getReferee/{id}") + public ApiResult getReferee(@PathVariable("id") Integer id) { + if (id == null) { + return fail("参数错误", null); + } + + final UserReferee referee = userRefereeService.getOne(new LambdaQueryWrapper() + .eq(UserReferee::getUserId, id) + .eq(UserReferee::getDeleted, 0)); + + if (ObjectUtil.isEmpty(referee)) { + return fail("查询失败", null); + } + + final User user = userService.getByIdRel(referee.getDealerId()); + if (ObjectUtil.isNotEmpty(user)) { + return success(user); + } + return fail("查询失败", null); + } + + @Operation(summary = "查询推荐人列表") + @GetMapping("/getRefereeList/{id}") + public ApiResult> getRefereeList(@PathVariable("id") Integer id) { + if (id == null) { + return fail("参数错误", null); + } + + final List refereeList = userRefereeService.list(new LambdaQueryWrapper() + .eq(UserReferee::getDealerId, id) + .eq(UserReferee::getDeleted, 0)); + + if (ObjectUtil.isEmpty(refereeList)) { + return fail("查询失败", null); + } + + final List users = userService.list( + new LambdaQueryWrapper() + .in(User::getUserId, refereeList.stream().map(UserReferee::getUserId).toList()) + ); + if (ObjectUtil.isNotEmpty(users)) { + return success(users); + } + return fail("查询失败", null); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java new file mode 100644 index 0000000..1fa9e7b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java @@ -0,0 +1,731 @@ +package com.gxwebsoft.common.system.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.security.JwtSubject; +import com.gxwebsoft.common.core.security.JwtUtil; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.utils.RequestUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.*; +import com.gxwebsoft.common.system.param.UserParam; +import com.gxwebsoft.common.system.result.LoginResult; +import com.gxwebsoft.common.system.service.*; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import okhttp3.*; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RequestBody; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeUnit; + +import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN; +import static com.gxwebsoft.common.core.constants.RedisConstants.ACCESS_TOKEN_KEY; + +@RestController +@RequestMapping("/api/wx-login") +@Tag(name = "微信小程序登录API") +public class WxLoginController extends BaseController { + private final StringRedisTemplate redisTemplate; + private final OkHttpClient http = new OkHttpClient(); + private final ObjectMapper om = new ObjectMapper(); + private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒 + @Resource + private SettingService settingService; + @Resource + private UserService userService; + @Resource + private ConfigProperties configProperties; + @Resource + private UserRoleService userRoleService; + @Resource + private LoginRecordService loginRecordService; + @Resource + private RoleService roleService; + @Resource + private RedisUtil redisUtil; + @Resource + private RequestUtil requestUtil; + @Resource + private ConfigProperties config; + @Resource + private UserRefereeService userRefereeService; + + + + public WxLoginController(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Operation(summary = "获取微信AccessToken") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/getAccessToken") + public ApiResult getMpAccessToken() { + return success("操作成功", getAccessToken()); + } + + @Operation(summary = "获取微信openId") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/getOpenId") + public ApiResult getOpenId(@RequestBody UserParam userParam, HttpServletRequest request) { + // 1.获取openid + JSONObject result = getOpenIdByCode(userParam); + String openid = result.getString("openid"); + String unionid = result.getString("unionid"); + if (openid == null) { + return fail("获取openid失败", null); + } + // 2.通过openid查询用户是否已存在 + User user = userService.getByOauthId(userParam); + // 3.存在则签发token并返回登录成功,不存在则注册新用户 + if (user == null) { + user = addUser(userParam); + } + // 4.签发token + loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request); + String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()), + configProperties.getTokenExpireTime(), configProperties.getTokenKey()); + return success("登录成功", new LoginResult(access_token, user)); + } + + @Operation(summary = "微信授权手机号码并登录") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/loginByMpWxPhone") + public ApiResult loginByMpWxPhone(@RequestBody UserParam userParam, HttpServletRequest request) { + // 获取手机号码 + String phone = getPhoneByCode(userParam); + if (phone == null) { + String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString()); + redisTemplate.delete(key); + throw new BusinessException("授权失败,请重试"); + } + // 查询是否存在 + User user = userService.getByPhone(phone); + // 不存在则注册 + if (user == null) { + if ((userParam.getOpenid() == null || userParam.getOpenid().isEmpty()) && userParam.getAuthCode() != null) { + UserParam userParam2 = new UserParam(); + userParam2.setCode(userParam.getAuthCode()); + JSONObject result = getOpenIdByCode(userParam2); + String openid = result.getString("openid"); +// String unionid = result.getString("unionid"); + userParam.setOpenid(openid); + } + userParam.setPhone(phone); + user = addUser(userParam); + user.setRecommend(1); + }else { + // 存在则检查绑定上级 + if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) { + UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId()); + if (check == null) { + UserReferee userReferee = new UserReferee(); + userReferee.setDealerId(userParam.getRefereeId()); + userReferee.setUserId(user.getUserId()); + userRefereeService.save(userReferee); + } + } + } + // 签发token + String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()), + configProperties.getTokenExpireTime(), configProperties.getTokenKey()); + loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request); + // 附加体育中心项目用户信息 +// user.setBookingUser(); + return success("登录成功", new LoginResult(access_token, user)); + } + + @Operation(summary = "微信授权手机号码并更新") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/updatePhoneByMpWx") + public ApiResult updatePhoneByMpWx(@RequestBody UserParam userParam) { + // 获取微信授权手机号 + String phone = getPhoneByCode(userParam); + // 查询当前用户 + User user = userService.getById(userParam.getUserId()); + if (user != null && phone != null) { + user.setPhone(phone); + userService.updateUser(user); + return success("更新成功", phone); + } + return fail("更新失败"); + } + + /** + * 新用户注册 + */ + private User addUser(UserParam userParam) { + User addUser = new User(); + // 注册用户 + addUser.setStatus(0); + addUser.setUsername(createUsername("wx_")); + addUser.setNickname("微信用户"); + addUser.setPlatform(MP_WEIXIN); + addUser.setGradeId(2); + if (userParam.getGradeId() != null) { + addUser.setGradeId(userParam.getGradeId()); + } + if (userParam.getPhone() != null) { + addUser.setPhone(userParam.getPhone()); + } + if (StrUtil.isNotBlank(userParam.getOpenid())) { + addUser.setOpenid(userParam.getOpenid()); + } + if (StrUtil.isNotBlank(userParam.getUnionid())) { + addUser.setUnionid(userParam.getUnionid()); + } + addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16())); + addUser.setTenantId(getTenantId()); + addUser.setRecommend(1); + Role role = roleService.getOne(new QueryWrapper().eq("role_code", "user"), false); + addUser.setRoleId(role.getRoleId()); + if (userService.saveUser(addUser)) { + // 添加用户角色 + final UserRole userRole = new UserRole(); + userRole.setUserId(addUser.getUserId()); + userRole.setTenantId(addUser.getTenantId()); + userRole.setRoleId(addUser.getRoleId()); + userRoleService.save(userRole); + } + // 绑定关系 + if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) { + UserReferee check = userRefereeService.check(addUser.getUserId(), userParam.getRefereeId()); + if (check == null) { + UserReferee userReferee = new UserReferee(); + userReferee.setDealerId(userParam.getRefereeId()); + userReferee.setUserId(addUser.getUserId()); + userRefereeService.save(userReferee); + } + } + return addUser; + } + + // 获取openid + private JSONObject getOpenIdByCode(UserParam userParam) { + // 获取微信小程序配置信息 + JSONObject setting = settingService.getBySettingKey("mp-weixin",getTenantId()); + // 获取openId + String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + setting.getString("appId") + "&secret=" + setting.getString("appSecret") + "&js_code=" + userParam.getCode() + "&grant_type=authorization_code"; + // 执行get请求 + String result = HttpUtil.get(apiUrl); + // 解析access_token + return JSON.parseObject(result); + } + + /** + * 获取微信手机号码 + * + * @param userParam 需要传微信凭证code + */ + private String getPhoneByCode(UserParam userParam) { + // 获取手机号码 + String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(); + HashMap paramMap = new HashMap<>(); + if (StrUtil.isBlank(userParam.getCode())) { + throw new BusinessException("code不能为空"); + } + paramMap.put("code", userParam.getCode()); + // 执行post请求 + String post = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap)); + JSONObject json = JSON.parseObject(post); + if (json.get("errcode").equals(0)) { + JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info")); + // 微信用户的手机号码 + final String phoneNumber = phoneInfo.getString("phoneNumber"); + // 验证手机号码 +// if (userParam.getNotVerifyPhone() == null && !Validator.isMobile(phoneNumber)) { +// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString()); +// redisTemplate.delete(key); +// throw new BusinessException("手机号码格式不正确"); +// } + return phoneNumber; + } + return null; + } + + /** + * 生成随机账号 + * + * @return username + */ + private String createUsername(String type) { + return type.concat(RandomUtil.randomString(12)); + } + + /** + * 获取接口调用凭据AccessToken + * ... + */ + public String getAccessToken() { + Integer tenantId = getTenantId(); + String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString()); + + // 使用跨租户方式获取微信小程序配置信息 + JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId); + if (setting == null) { + throw new BusinessException("请先配置小程序"); + } + + // 从缓存获取access_token + String value = redisTemplate.opsForValue().get(key); + if (value != null) { + // 解析access_token + JSONObject response = JSON.parseObject(value); + String accessToken = response.getString("access_token"); + if (accessToken != null) { + return accessToken; + } + } + + // 微信获取凭证接口 + String apiUrl = "https://api.weixin.qq.com/cgi-bin/token"; + // 组装url参数 + String url = apiUrl.concat("?grant_type=client_credential") + .concat("&appid=").concat(setting.getString("appId")) + .concat("&secret=").concat(setting.getString("appSecret")); + + // 执行get请求 + String result = HttpUtil.get(url); + // 解析access_token + JSONObject response = JSON.parseObject(result); + if (response.getString("access_token") != null) { + // 存入缓存 + redisTemplate.opsForValue().set(key, result, 7000L, TimeUnit.SECONDS); + return response.getString("access_token"); + } + throw new BusinessException("小程序配置不正确"); + } + + @Operation(summary = "获取微信openId并更新") + @PostMapping("/getWxOpenId") + public ApiResult getWxOpenId(@RequestBody UserParam userParam) { + final User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录"); + } + // 已存在直接返回 + if (StrUtil.isNotBlank(loginUser.getOpenid())) { + return success(loginUser); + } + // 请求微信接口获取openid + String apiUrl = "https://api.weixin.qq.com/sns/jscode2session"; + final HashMap map = new HashMap<>(); + final JSONObject setting = settingService.getBySettingKey("mp-weixin",getTenantId()); + final String appId = setting.getString("appId"); + final String appSecret = setting.getString("appSecret"); + map.put("appid", appId); + map.put("secret", appSecret); + map.put("js_code", userParam.getCode()); + map.put("grant_type", "authorization_code"); + final String response = HttpUtil.get(apiUrl, map); + final JSONObject jsonObject = JSONObject.parseObject(response); + String openid = jsonObject.getString("openid"); + String sessionKey = jsonObject.getString("session_key"); + String unionid = jsonObject.getString("unionid"); + // 保存openID + if (loginUser.getOpenid() == null || StrUtil.isBlank(loginUser.getOpenid())) { + loginUser.setOpenid(openid); + loginUser.setUnionid(unionid); + requestUtil.updateUser(loginUser); +// userService.updateById(loginUser); + } + return success("获取成功", jsonObject); + } + + @Operation(summary = "仅获取微信openId") + @PostMapping("/getWxOpenIdOnly") + public ApiResult getWxOpenIdOnly(@RequestBody UserParam userParam) { + + String apiUrl = "https://api.weixin.qq.com/sns/jscode2session"; + final HashMap map = new HashMap<>(); + final JSONObject setting = settingService.getBySettingKey("mp-weixin",getTenantId()); + final String appId = setting.getString("appId"); + final String appSecret = setting.getString("appSecret"); + map.put("appid", appId); + map.put("secret", appSecret); + map.put("js_code", userParam.getCode()); + map.put("grant_type", "authorization_code"); + final String response = HttpUtil.get(apiUrl, map); + final JSONObject jsonObject = JSONObject.parseObject(response); + return success("获取成功", jsonObject); + } + + @Operation(summary = "获取微信小程序码-用户ID") + @GetMapping("/getUserQRCode") + public ApiResult getQRCode() { + String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); + final HashMap map = new HashMap<>(); + map.put("path", "/package/user/qrcode?user_id=" + getLoginUserId()); +// map.put("env_version","trial"); + // 获取图片 Buffer + byte[] qrCode = HttpRequest.post(apiUrl) + .body(JSON.toJSONString(map)) + .execute().bodyBytes(); + + // 保存的文件名称 + final String fileName = CommonUtil.randomUUID8().concat(".png"); + // 保存路径 + String filePath = getUploadDir().concat("qrcode/") + fileName; + File file = FileUtil.writeBytes(qrCode, filePath); + if (file != null) { + return success(config.getFileServer().concat("/qrcode/").concat(fileName)); + } + return fail("获取失败", null); + } + + @Operation(summary = "获取微信小程序码-订单核销码") + @GetMapping("/getOrderQRCode/{orderNo}") + public ApiResult getOrderQRCode(@PathVariable("orderNo") String orderNo) { + String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); + final HashMap map = new HashMap<>(); + map.put("path", "/package/admin/order-scan?orderNo=".concat(orderNo)); + map.put("env_version", "release"); + // 获取图片 Buffer + byte[] qrCode = HttpRequest.post(apiUrl) + .body(JSON.toJSONString(map)) + .execute().bodyBytes(); + + // 保存的文件名称 + final String fileName = CommonUtil.randomUUID8().concat(".png"); + // 保存路径 + String filePath = getUploadDir().concat("qrcode/") + fileName; + File file = FileUtil.writeBytes(qrCode, filePath); + if (file != null) { + return success(config.getFileServer().concat("/qrcode/").concat(fileName)); + } + return fail("获取失败", null); + } + + @Operation(summary = "获取微信小程序码-订单核销码-数量极多的业务场景") + @GetMapping("/getOrderQRCodeUnlimited/{scene}") + public void getOrderQRCodeUnlimited(@PathVariable("scene") String scene, HttpServletResponse response) throws IOException { + try { + // 从scene参数中解析租户ID + Integer tenantId = extractTenantIdFromScene(scene); + System.out.println("从scene参数中解析租户ID = " + tenantId); + if (tenantId == null) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("{\"error\":\"无法从scene参数中获取租户信息\"}"); + return; + } + + // 使用指定租户ID获取 access_token + String accessToken = getAccessTokenForTenant(tenantId); + String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken; + + final HashMap map = new HashMap<>(); + map.put("scene", scene); + map.put("page", "pages/index/index"); + map.put("env_version", "release"); + + String jsonBody = JSON.toJSONString(map); + System.out.println("请求的 JSON body = " + jsonBody); + + // 获取微信 API 响应 + cn.hutool.http.HttpResponse httpResponse = HttpRequest.post(apiUrl) + .body(jsonBody) + .execute(); + + byte[] responseBytes = httpResponse.bodyBytes(); + String contentType = httpResponse.header("Content-Type"); + + // 检查响应内容类型,判断是否为错误响应 + if (contentType != null && contentType.contains("application/json")) { + // 微信返回了错误信息(JSON格式) + String errorResponse = new String(responseBytes, "UTF-8"); + System.err.println("微信 API 错误响应: " + errorResponse); + + // 返回错误信息给前端 + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write(errorResponse); + return; + } + + // 成功获取二维码图片 + response.setContentType("image/png"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Disposition", "inline; filename=qrcode.png"); + + // 输出图片 + response.getOutputStream().write(responseBytes); + System.out.println("二维码生成成功,大小: " + responseBytes.length + " bytes"); + + } catch (Exception e) { + System.err.println("生成二维码失败: " + e.getMessage()); + e.printStackTrace(); + + // 返回错误信息 + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("{\"error\":\"生成二维码失败: " + e.getMessage() + "\"}"); + } + } + + @Operation(summary = "获取微信小程序码-用户ID") + @GetMapping("/getQRCodeText") + public byte[] getQRCodeText(String scene, String page, Integer width, + Boolean isHyaline, String envVersion) throws IOException { + HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/wxa/getwxacodeunlimit") + .newBuilder() + .addQueryParameter("access_token", getLocalAccessToken()) + .build(); + + System.out.println("page = " + page); + // 构造请求 JSON + // 注意:scene 仅支持可见字符,长度上限 32,尽量 URL-safe(字母数字下划线等) + // page 必须是已发布小程序内的路径(不带开头斜杠也可) + var root = om.createObjectNode(); + root.put("scene", scene); + if (page != null) root.put("page", page); + if (width != null) root.put("width", width); // 默认 430,建议 280~1280 + if (isHyaline != null) root.put("is_hyaline", isHyaline); + if (envVersion != null) root.put("env_version", envVersion); // release/trial/develop + + okhttp3.RequestBody reqBody = okhttp3.RequestBody.create( + root.toString(), MediaType.parse("application/json; charset=utf-8")); + Request req = new Request.Builder().url(url).post(reqBody).build(); + + try (Response resp = http.newCall(req).execute()) { + if (!resp.isSuccessful()) { + throw new IOException("HTTP " + resp.code() + " calling getwxacodeunlimit"); + } + MediaType ct = resp.body().contentType(); + byte[] bytes = resp.body().bytes(); + // 微信出错时返回 JSON,需要识别一下 + if (ct != null && ct.subtype() != null && ct.subtype().contains("json")) { + String err = new String(bytes); + throw new IOException("WeChat error: " + err); + } + return bytes; // 成功就是图片二进制(PNG) + } + } + + /** 获取/刷新 access_token */ + public String getLocalAccessToken() throws IOException { + long now = Instant.now().getEpochSecond(); + String key = "AccessToken:Local:" + getTenantId(); + + if (redisUtil.get(key) != null && now < tokenExpireEpoch - 60) { + return redisUtil.get(key); + } + + // 使用跨租户方式获取微信小程序配置信息 + Integer tenantId = getTenantId(); + JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId); + if (setting == null) { + throw new IOException("请先配置小程序"); + } + + String appId = setting.getString("appId"); + String appSecret = setting.getString("appSecret"); + + if (appId == null || appSecret == null) { + throw new IOException("小程序配置不完整,缺少 appId 或 appSecret"); + } + + HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token") + .newBuilder() + .addQueryParameter("grant_type", "client_credential") + .addQueryParameter("appid", appId) + .addQueryParameter("secret", appSecret) + .build(); + + Request req = new Request.Builder().url(url).get().build(); + try (Response resp = http.newCall(req).execute()) { + String body = resp.body().string(); + JsonNode json = om.readTree(body); + if (json.has("access_token")) { + String token = json.get("access_token").asText(); + long expiresIn = json.get("expires_in").asInt(7200); + redisUtil.set(key, token, expiresIn, TimeUnit.SECONDS); + tokenExpireEpoch = now + expiresIn; + return token; + } else { + throw new IOException("Get access_token failed: " + body); + } + } + } + + /** + * 文件上传位置(服务器) + */ + private String getUploadDir() { + return config.getUploadPath() + "file/"; + } + + @Operation(summary = "调试:检查微信小程序配置") + @GetMapping("/debug/checkWxConfig") + public ApiResult debugCheckWxConfig() { + Integer tenantId = getTenantId(); + Map result = new HashMap<>(); + result.put("tenantId", tenantId); + + try { + // 尝试获取配置 + JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId); + result.put("hasConfig", true); + result.put("config", setting); + } catch (Exception e) { + result.put("hasConfig", false); + result.put("error", e.getMessage()); + + // 提供创建配置的建议 + Map suggestion = new HashMap<>(); + suggestion.put("message", "请在系统设置中创建微信小程序配置"); + suggestion.put("configKey", "mp-weixin"); + suggestion.put("tenantId", tenantId); + suggestion.put("sampleConfig", createSampleWxConfig()); + result.put("suggestion", suggestion); + } + + return success("配置检查完成", result); + } + + @Operation(summary = "调试:创建示例微信小程序配置") + @PostMapping("/debug/createSampleWxConfig") + public ApiResult debugCreateSampleWxConfig(@RequestBody Map params) { + Integer tenantId = getTenantId(); + + String appId = params.get("appId"); + String appSecret = params.get("appSecret"); + + if (appId == null || appSecret == null) { + return fail("请提供 appId 和 appSecret", null); + } + + try { + // 创建配置对象 + Setting setting = new Setting(); + setting.setSettingKey("mp-weixin"); + setting.setTenantId(tenantId); + + // 创建配置内容 + Map config = new HashMap<>(); + config.put("appId", appId); + config.put("appSecret", appSecret); + setting.setContent(JSON.toJSONString(config)); + setting.setComments("微信小程序配置"); + setting.setSortNumber(1); + + // 保存配置 + settingService.save(setting); + + return success("微信小程序配置创建成功", setting); + } catch (Exception e) { + return fail("创建配置失败: " + e.getMessage(), null); + } + } + + private Map createSampleWxConfig() { + Map sample = new HashMap<>(); + sample.put("appId", "wx_your_app_id_here"); + sample.put("appSecret", "your_app_secret_here"); + return sample; + } + + /** + * 从scene参数中提取租户ID + * scene格式可能是: uid_33103 或其他包含用户ID的格式 + */ + private Integer extractTenantIdFromScene(String scene) { + try { + System.out.println("解析scene参数: " + scene); + + // 如果scene包含uid_前缀,提取用户ID + if (scene != null && scene.startsWith("uid_")) { + String userIdStr = scene.substring(4); // 去掉"uid_"前缀 + Integer userId = Integer.parseInt(userIdStr); + + // 根据用户ID查询用户信息,获取租户ID + User user = userService.getByIdIgnoreTenant(userId); + if (user != null) { + System.out.println("从用户ID " + userId + " 获取到租户ID: " + user.getTenantId()); + return user.getTenantId(); + } else { + System.err.println("未找到用户ID: " + userId); + } + } + + // 如果无法解析,默认使用租户10550 + System.out.println("无法解析scene参数,使用默认租户ID: 10550"); + return 10550; + + } catch (Exception e) { + System.err.println("解析scene参数异常: " + e.getMessage()); + // 出现异常时,默认使用租户10550 + return 10550; + } + } + + /** + * 为指定租户获取AccessToken + */ + private String getAccessTokenForTenant(Integer tenantId) { + try { + String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString()); + + // 使用跨租户方式获取微信小程序配置信息 + JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId); + if (setting == null) { + throw new RuntimeException("租户 " + tenantId + " 的小程序未配置"); + } + + // 从缓存获取access_token + String accessToken = redisTemplate.opsForValue().get(key); + if (accessToken != null) { + System.out.println("从缓存获取到access_token"); + return accessToken; + } + + // 缓存中没有,重新获取 + String appId = setting.getString("appId"); + String appSecret = setting.getString("appSecret"); + + String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; + String result = HttpUtil.get(apiUrl); + JSONObject json = JSON.parseObject(result); + + if (json.containsKey("access_token")) { + accessToken = json.getString("access_token"); + Integer expiresIn = json.getInteger("expires_in"); + + // 缓存access_token,提前5分钟过期 + redisTemplate.opsForValue().set(key, accessToken, expiresIn - 300, TimeUnit.SECONDS); + + System.out.println("获取新的access_token成功,租户ID: " + tenantId); + return accessToken; + } else { + throw new RuntimeException("获取access_token失败: " + result); + } + + } catch (Exception e) { + System.err.println("获取access_token异常,租户ID: " + tenantId + ", 错误: " + e.getMessage()); + throw new RuntimeException("获取access_token失败: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java b/src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java new file mode 100644 index 0000000..8a0cab9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java @@ -0,0 +1,39 @@ +package com.gxwebsoft.common.system.dto; + +import lombok.Data; +import java.io.Serializable; + +/** + * 支付配置缓存DTO + * 专门用于Redis缓存,不包含时间字段,避免序列化问题 + * + * @author 科技小王子 + * @since 2025-01-13 + */ +@Data +public class PaymentCacheDTO implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private String name; + private Integer type; + private String code; + private String image; + private Integer wechatType; + private String appId; + private String mchId; + private String apiKey; + private String apiclientCert; + private String apiclientKey; + private String pubKey; + private String pubKeyId; + private String merchantSerialNumber; + private String notifyUrl; + private String comments; + private Integer sortNumber; + private Boolean status; + private Integer deleted; + private Integer tenantId; + + // 不包含 createTime 和 updateTime 字段,避免序列化问题 +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Cache.java b/src/main/java/com/gxwebsoft/common/system/entity/Cache.java new file mode 100644 index 0000000..232c183 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Cache.java @@ -0,0 +1,34 @@ +package com.gxwebsoft.common.system.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * 缓存管理 + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Setting对象", description = "缓存管理") +public class Cache implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "key") + private String key; + + @Schema(description = "设置内容(json格式)") + private String content; + + @Schema(description = "过期时间(秒)") + private Long expireTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/ChatMessage.java b/src/main/java/com/gxwebsoft/common/system/entity/ChatMessage.java new file mode 100644 index 0000000..2102b4d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/ChatMessage.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Map; + +/** + * 机构 + * + * @author LX + * @since 2025-04-14 00:35:34 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "LawOrg对象", description = "机构") +@TableName("law_org") +public class ChatMessage implements Serializable { + private static final long serialVersionUID = 1L; + + @TableField(exist = false) + private String query; + + @TableField(exist = false) + private String inputs; + + @TableField(exist = false) + private String responseMode; + + @TableField(exist = false) + private String user; + + @TableField(exist = false) + private String conversationId; + + @TableField(exist = false) + private String type; + + @TableField(exist = false) + private Integer requestType; + + @TableField(exist = false) + private Map files; +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Company.java b/src/main/java/com/gxwebsoft/common/system/entity/Company.java new file mode 100644 index 0000000..9e37455 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Company.java @@ -0,0 +1,337 @@ +package com.gxwebsoft.common.system.entity; + +import cn.hutool.core.util.DesensitizedUtil; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; + +/** + * 企业信息 + * + * @author 科技小王子 + * @since 2023-05-27 14:57:34 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Company对象", description = "企业信息") +@TableName("sys_company") +public class Company implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "企业id") + @TableId(value = "company_id", type = IdType.AUTO) + private Integer companyId; + + @Schema(description = "应用类型") + private Integer type; + + @Schema(description = "企业简称") + private String shortName; + + @Schema(description = "企业全称") + @TableField(exist = false) + private String companyName; + + @Schema(description = "企业标识") + private String companyCode; + + @Schema(description = "企业类型") + private String companyType; + + @Schema(description = "是否官方") + private Boolean official; + + @Schema(description = "企业类型 多选") + private String companyTypeMultiple; + + @Schema(description = "应用标识") + private String companyLogo; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "应用详情") + @TableField(exist = false) + private String content; + + @Schema(description = "栏目分类") + private Integer categoryId; + + @Schema(description = "栏目名称") + @TableField(exist = false) + private String categoryName; + + @Schema(description = "应用截图") + private String files; + + @Schema(description = "顶级域名") + private String domain; + + @Schema(description = "免费域名") + private String freeDomain; + + @Schema(description = "销售价格") + private BigDecimal price; + + @Schema(description = "计费方式") + private Integer chargingMethod; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "公司座机") + private String tel; + + @Schema(description = "电子邮箱") + private String email; + + @Schema(description = "企业法人") + private String businessEntity; + + @Schema(description = "发票抬头") + @TableField("Invoice_header") + private String invoiceHeader; + + @Schema(description = "服务开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "服务到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "即将过期") + private Integer soon; + + @Schema(description = "应用版本 10体验版 20授权版 30旗舰版") + private Integer version; + + @Schema(description = "版本名称") + private String versionName; + + @Schema(description = "版本号") + private String versionCode; + + @Schema(description = "评分") + private BigDecimal rate; + + @Schema(description = "企业成员(当前)") + private Integer users; + + @Schema(description = "成员数量(上限)") + private Integer members; + + @Schema(description = "浏览数量") + private Long clicks; + + @Schema(description = "点赞数量") + private Long likes; + + @Schema(description = "购买数量") + private Long buys; + + @Schema(description = "存储空间") + private Long storage; + + @Schema(description = "存储空间(上限)") + private Long storageMax; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "部门数量") + private Integer departments; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否实名认证") + private Integer authentication; + + @Schema(description = "主控端节点") + private String serverUrl; + + @Schema(description = "模块节点") + private String modulesUrl; + + @Schema(description = "重定向节点") + private String redirectUrl; + + @Schema(description = "request合法域名") + private String requestUrl; + + @Schema(description = "socket合法域名") + private String socketUrl; + + @Schema(description = "总后台管理入口") + private String adminUrl; + + @Schema(description = "商户端管理入口") + private String merchantUrl; + + @Schema(description = "默认网站URL") + private String websiteUrl; + + @Schema(description = "微信小程序二维码") + private String mpWeixinCode; + + @Schema(description = "支付宝小程序二维码") + private String mpAlipayCode; + + @Schema(description = "H5端应用二维码") + private String h5Code; + + @Schema(description = "安卓APP二维码") + private String androidUrl; + + @Schema(description = "苹果APP二维码") + private String iosUrl; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序") + private Integer sortNumber; + + @Schema(description = "插件ID(菜单根节点)") + private Integer menuId; + + @Schema(description = "当前使用的租户模板") + private Integer planId; + + @Schema(description = "是否开启网站") + private Boolean websiteStatus; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否含税") + private Boolean isTax; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "是否默认企业主体") + private Boolean authoritative; + + @Schema(description = "是否推荐") + private Boolean recommend; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "租户ID") + private Integer tid; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "租户名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "租户编号") + @TableField(exist = false) + private String tenantCode; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "配置信息") + @TableField(exist = false) + private Object config; + + @Schema(description = "是否已收藏") + @TableField(exist = false) + private Boolean collection; + + @Schema(description = "新注册的密码") + @TableField(exist = false) + private String password; + + @Schema(description = "手机号(脱敏)") + @TableField(exist = false) + private String mobile; + + @Schema(description = "是否已购买") + @TableField(exist = false) + private Boolean isBuy; + + @Schema(description = "用户是否已安装了该插件") + @TableField(exist = false) + private Boolean installed; + + @Schema(description = "产品参数") + @TableField(exist = false) + private List parameters; + + @Schema(description = "产品按钮及链接") + @TableField(exist = false) + private List links; + + @Schema(description = "购买数量") + @TableField(exist = false) + private Integer num; + + @Schema(description = "角色列表") + @TableField(exist = false) + private List roles; + + @Schema(description = "权限列表") + @TableField(exist = false) + private List authorities; + + @Schema(description = "记录克隆的模板ID") + @TableField(exist = false) + private Integer templateId; + + public String getMobile() { + return DesensitizedUtil.mobilePhone(this.phone); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/CompanyComment.java b/src/main/java/com/gxwebsoft/common/system/entity/CompanyComment.java new file mode 100644 index 0000000..5ad0b6b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/CompanyComment.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 应用评论 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CompanyComment对象", description = "应用评论") +@TableName("sys_company_comment") +public class CompanyComment implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "父级ID") + private Integer parentId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "评分") + private BigDecimal rate; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "评论内容") + private String comments; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/CompanyContent.java b/src/main/java/com/gxwebsoft/common/system/entity/CompanyContent.java new file mode 100644 index 0000000..1230838 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/CompanyContent.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 应用详情 + * + * @author 科技小王子 + * @since 2024-10-16 13:41:21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CompanyContent对象", description = "应用详情") +@TableName("sys_company_content") +public class CompanyContent implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "详细内容") + private String content; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/CompanyGit.java b/src/main/java/com/gxwebsoft/common/system/entity/CompanyGit.java new file mode 100644 index 0000000..b8d482b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/CompanyGit.java @@ -0,0 +1,142 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 代码仓库 + * + * @author 科技小王子 + * @since 2024-10-19 18:08:51 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CompanyGit对象", description = "代码仓库") +@TableName("sys_company_git") +public class CompanyGit implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "仓库名称") + private String title; + + @Schema(description = "厂商") + private String brand; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "仓库地址") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "仓库描述") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "租户id") + private Integer tenantId; + + /** + * 用户余额变动明细表 + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ + @Data + @EqualsAndHashCode(callSuper = false) + @Schema(name = "UserBalanceLog对象", description = "用户余额变动明细表") + @TableName("sys_user_balance_log") + public static class UserBalanceLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "log_id", type = IdType.AUTO) + private Integer logId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "余额变动场景(10用户充值 20用户消费 30管理员操作 40订单退款)") + private Integer scene; + + @Schema(description = "变动金额") + private BigDecimal money; + + @Schema(description = "变动后余额") + private BigDecimal balance; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "支付流水号") + private String transactionId; + + @Schema(description = "管理员备注") + private String remark; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/CompanyParameter.java b/src/main/java/com/gxwebsoft/common/system/entity/CompanyParameter.java new file mode 100644 index 0000000..e2c0718 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/CompanyParameter.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 应用参数 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CompanyParameter对象", description = "应用参数") +@TableName("sys_company_parameter") +public class CompanyParameter implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "参数名称") + private String name; + + @Schema(description = "参数内容") + private String value; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/CompanyUrl.java b/src/main/java/com/gxwebsoft/common/system/entity/CompanyUrl.java new file mode 100644 index 0000000..1bee66a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/CompanyUrl.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 应用域名 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "CompanyUrl对象", description = "应用域名") +@TableName("sys_company_url") +public class CompanyUrl implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "域名类型") + private String type; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "二维码") + private String qrcode; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Dict.java b/src/main/java/com/gxwebsoft/common/system/entity/Dict.java new file mode 100644 index 0000000..35f9c61 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Dict.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.Set; + +/** + * 字典 + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +@Data +@Schema(description = "字典(业务类)") +@TableName("sys_dict") +public class Dict implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "字典id") + @TableId(type = IdType.AUTO) + private Integer dictId; + + @Schema(description = "字典标识") + private String dictCode; + + @Schema(description = "字典名称") + private String dictName; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "字典项列表") + @TableField(exist = false) + private Set> items; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/DictData.java b/src/main/java/com/gxwebsoft/common/system/entity/DictData.java new file mode 100644 index 0000000..e9bf6e9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/DictData.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 字典数据 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Data +@Schema(description = "字典数据(业务类)") +@TableName("sys_dict_data") +public class DictData implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "字典数据id") + @TableId(type = IdType.AUTO) + private Integer dictDataId; + + @Schema(description = "字典id") + private Integer dictId; + + @Schema(description = "字典数据标识") + private String dictDataCode; + + @Schema(description = "字典数据名称") + private String dictDataName; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "字典代码") + @TableField(exist = false) + private String dictCode; + + @Schema(description = "字典名称") + @TableField(exist = false) + private String dictName; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Dictionary.java b/src/main/java/com/gxwebsoft/common/system/entity/Dictionary.java new file mode 100644 index 0000000..b184758 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Dictionary.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; + +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 字典 + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +@Data +@Schema(description = "字典(系统类)") +@TableName("sys_dictionary") +public class Dictionary implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "字典id") + @TableId(type = IdType.AUTO) + private Integer dictId; + + @Schema(description = "字典标识") + private String dictCode; + + @Schema(description = "字典名称") + private String dictName; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/DictionaryData.java b/src/main/java/com/gxwebsoft/common/system/entity/DictionaryData.java new file mode 100644 index 0000000..9f9ed86 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/DictionaryData.java @@ -0,0 +1,67 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; + +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 字典数据 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Data +@Schema(description = "字典数据(系统类)") +@TableName("sys_dictionary_data") +public class DictionaryData implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "字典数据id") + @TableId(type = IdType.AUTO) + private Integer dictDataId; + + @Schema(description = "字典id") + private Integer dictId; + + @Schema(description = "字典数据标识") + private String dictDataCode; + + @Schema(description = "字典数据名称") + private String dictDataName; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "字典代码") + @TableField(exist = false) + private String dictCode; + + @Schema(description = "字典名称") + @TableField(exist = false) + private String dictName; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Domain.java b/src/main/java/com/gxwebsoft/common/system/entity/Domain.java new file mode 100644 index 0000000..c9fc263 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Domain.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 授权域名 + * + * @author 科技小王子 + * @since 2024-09-19 23:56:33 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Domain对象", description = "授权域名") +@TableName("sys_domain") +public class Domain implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "主机记录") + private String hostName; + + @Schema(description = "记录值") + private String hostValue; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "类型 0常规 1后台 2商家端") + private Integer type; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/EmailRecord.java b/src/main/java/com/gxwebsoft/common/system/entity/EmailRecord.java new file mode 100644 index 0000000..1831835 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/EmailRecord.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 邮件发送记录 + * + * @author WebSoft + * @since 2021-08-29 12:36:35 + */ +@Data +@Schema(description = "邮件发送记录") +@TableName("sys_email_record") +public class EmailRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @TableId(type = IdType.AUTO) + private Integer id; + + @Schema(description = "邮件标题") + private String title; + + @Schema(description = "邮件内容") + private String content; + + @Schema(description = "收件邮箱") + private String receiver; + + @Schema(description = "发件邮箱") + private String sender; + + @Schema(description = "创建人") + private Integer createUserId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/FileRecord.java b/src/main/java/com/gxwebsoft/common/system/entity/FileRecord.java new file mode 100644 index 0000000..f846af5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/FileRecord.java @@ -0,0 +1,94 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 文件上传记录 + * + * @author WebSoft + * @since 2021-08-29 12:36:32 + */ +@Data +@Schema(description = "文件上传记录") +@TableName("sys_file_record") +public class FileRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @TableId(type = IdType.AUTO) + private Integer id; + + @Schema(description = "分组ID") + private String groupId; + + @Schema(description = "文件名称") + private String name; + + @Schema(description = "文件存储路径") + private String path; + + @Schema(description = "文件大小") + private Long length; + + @Schema(description = "文件类型") + private String contentType; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "创建人") + private Integer createUserId; + + @Schema(description = "AppId") + private Integer appId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户编号") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "文件访问地址") + @TableField(exist = false) + private String url; + + @Schema(description = "文件缩略图访问地址") + @TableField(exist = false) + private String thumbnail; + + @Schema(description = "文件下载地址") + @TableField(exist = false) + private String downloadUrl; + + @Schema(description = "创建人账号") + @TableField(exist = false) + private String createUsername; + + @Schema(description = "创建人名称") + @TableField(exist = false) + private String createNickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/KVEntity.java b/src/main/java/com/gxwebsoft/common/system/entity/KVEntity.java new file mode 100644 index 0000000..c1fa9ab --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/KVEntity.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 租户 + * + * @author WebSoft + * @since 2021-08-28 11:31:06 + */ +@Data +@Schema(description = "实体") +public class KVEntity implements Serializable { + private static final long serialVersionUID = 1L; + protected K k; + protected V v; + public KVEntity() { + super(); + } + + public KVEntity(K k, V v) { + super(); + this.k = k; + this.v = v; + } + + public static KVEntity build(K k, V v) { + return new KVEntity<>(k, v); + } + + public K getK() { + return k; + } + + public void setK(K k) { + this.k = k; + } + + public V getV() { + return v; + } + + public void setV(V v) { + this.v = v; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/LoginRecord.java b/src/main/java/com/gxwebsoft/common/system/entity/LoginRecord.java new file mode 100644 index 0000000..b516166 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/LoginRecord.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 登录日志 + * + * @author WebSoft + * @since 2018-12-24 16:10:41 + */ +@Data +@Schema(description = "登录日志") +@TableName("sys_login_record") +public class LoginRecord implements Serializable { + private static final long serialVersionUID = 1L; + public static final int TYPE_LOGIN = 0; // 登录成功 + public static final int TYPE_ERROR = 1; // 登录失败 + public static final int TYPE_LOGOUT = 2; // 退出登录 + public static final int TYPE_REFRESH = 3; // 续签token + public static final int TYPE_REGISTER = 4; // 注册成功 + + @Schema(description = "主键id") + @TableId(type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户账号") + private String username; + + @Schema(description = "操作系统") + private String os; + + @Schema(description = "设备名称") + private String device; + + @Schema(description = "浏览器类型") + private String browser; + + @Schema(description = "ip地址") + private String ip; + + @Schema(description = "操作类型, 0登录成功, 1登录失败, 2退出登录, 3续签token") + private Integer loginType; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "操作时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "用户id") + @TableField(exist = false) + private Integer userId; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Menu.java b/src/main/java/com/gxwebsoft/common/system/entity/Menu.java new file mode 100644 index 0000000..479e2f4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Menu.java @@ -0,0 +1,87 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; + +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; + +/** + * 菜单 + * + * @author WebSoft + * @since 2018-12-24 16:10:17 + */ +@Data +@Schema(description = "菜单") +@TableName("sys_menu") +public class Menu implements GrantedAuthority { + private static final long serialVersionUID = 1L; + public static final int TYPE_MENU = 0; // 菜单类型 + public static final int TYPE_BTN = 1; // 按钮类型 + + @Schema(description = "菜单id") + @TableId(type = IdType.AUTO) + private Integer menuId; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址") + private String component; + + @Schema(description = "菜单类型, 0菜单, 1按钮") + private Integer menuType; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示左侧菜单)") + private Integer hide; + + @Schema(description = "路由元信息") + private String meta; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "关联应用") + private Integer appId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "子菜单") + @TableField(exist = false) + private List children; + + @Schema(description = "角色权限树选中状态") + @TableField(exist = false) + private Boolean checked; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/OperationRecord.java b/src/main/java/com/gxwebsoft/common/system/entity/OperationRecord.java new file mode 100644 index 0000000..8ffb3bf --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/OperationRecord.java @@ -0,0 +1,98 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 操作日志 + * + * @author WebSoft + * @since 2018-12-24 16:10:33 + */ +@Data +@Schema(description = "操作日志") +@TableName("sys_operation_record") +public class OperationRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @TableId(type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "操作模块") + private String module; + + @Schema(description = "操作功能") + private String description; + + @Schema(description = "请求地址") + private String url; + + @Schema(description = "请求方式") + private String requestMethod; + + @Schema(description = "调用方法") + private String method; + + @Schema(description = "请求参数") + private String params; + + @Schema(description = "返回结果") + private String result; + + @Schema(description = "异常信息") + private String error; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "消耗时间, 单位毫秒") + private Long spendTime; + + @Schema(description = "操作系统") + private String os; + + @Schema(description = "设备名称") + private String device; + + @Schema(description = "浏览器类型") + private String browser; + + @Schema(description = "ip地址") + private String ip; + + @Schema(description = "状态, 0成功, 1异常") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "操作时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户账号") + @TableField(exist = false) + private String username; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Organization.java b/src/main/java/com/gxwebsoft/common/system/entity/Organization.java new file mode 100644 index 0000000..215a64c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Organization.java @@ -0,0 +1,92 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; + +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 组织机构 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Data +@Schema(description = "组织机构") +@TableName("sys_organization") +public class Organization implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "机构id") + @TableId(type = IdType.AUTO) + private Integer organizationId; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "机构名称") + private String organizationName; + + @Schema(description = "机构全称") + private String organizationFullName; + + @Schema(description = "机构代码") + private String organizationCode; + + @Schema(description = "机构类型, 字典标识") + private String organizationType; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "邮政编码") + private String zipCode; + + @Schema(description = "负责人id") + private Integer leaderId; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "机构类型名称") + @TableField(exist = false) + private String organizationTypeName; + + @Schema(description = "负责人姓名") + @TableField(exist = false) + private String leaderNickname; + + @Schema(description = "负责人账号") + @TableField(exist = false) + private String leaderUsername; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Payment.java b/src/main/java/com/gxwebsoft/common/system/entity/Payment.java new file mode 100644 index 0000000..819ff36 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Payment.java @@ -0,0 +1,100 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * 支付方式 + * + * @author 科技小王子 + * @since 2024-05-11 12:39:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Payment对象", description = "支付方式") +@TableName("sys_payment") +public class Payment implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "支付方式") + private String name; + + @Schema(description = "类型") + private Integer type; + + @Schema(description = "标识") + private String code; + + @Schema(description = "支付图标") + private String image; + + @Schema(description = "微信商户号类型 1普通商户2子商户") + private Integer wechatType; + + @Schema(description = "应用ID") + private String appId; + + @Schema(description = "商户号") + private String mchId; + + @Schema(description = "设置APIv3密钥") + private String apiKey; + + @Schema(description = "证书文件 (CERT)") + private String apiclientCert; + + @Schema(description = "证书文件 (KEY)") + private String apiclientKey; + + @Schema(description = "公钥文件 (KEY)") + private String pubKey; + + @Schema(description = "公钥ID") + private String pubKeyId; + + @Schema(description = "商户证书序列号") + private String merchantSerialNumber; + + @Schema(description = "支付结果通知") + private String notifyUrl; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "文章排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0未启用, 1启用") + private Boolean status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonIgnore // 缓存时忽略此字段 + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonIgnore // 缓存时忽略此字段 + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Plug.java b/src/main/java/com/gxwebsoft/common/system/entity/Plug.java new file mode 100644 index 0000000..b8ef784 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Plug.java @@ -0,0 +1,144 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; + +/** + * 插件扩展 + * + * @author 科技小王子 + * @since 2023-05-18 11:57:37 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Plug对象", description = "插件扩展") +@TableName("sys_plug") +public class Plug implements Serializable { + private static final long serialVersionUID = 1L; + public static final int TYPE_MENU = 0; // 菜单类型 + public static final int TYPE_BTN = 1; // 按钮类型 + + @Schema(description = "插件id") + @TableId(value = "plug_id", type = IdType.AUTO) + private Integer plugId; + + @Schema(description = "菜单ID") + private Integer menuId; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "类型, 0菜单, 1按钮") + private Integer menuType; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "图标颜色") + private String color; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "插件描述") + private String comments; + + @Schema(description = "插件详情") + private String content; + + @Schema(description = "评分") + private BigDecimal score; + + @Schema(description = "插件价格") + private BigDecimal price; + + @Schema(description = "浏览次数") + private Integer clicks; + + @Schema(description = "安装次数") + private Integer installs; + + @Schema(description = "关联应用ID") + private Integer appId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "子菜单") + @TableField(exist = false) + private List children; + + @Schema(description = "角色权限树选中状态") + @TableField(exist = false) + private Boolean checked; + + @Schema(description = "租户名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "企业名称") + @TableField(exist = false) + private String companyName; + + @Schema(description = "企业简称") + @TableField(exist = false) + private String shortName; + + @Schema(description = "企业域名") + @TableField(exist = false) + private String domain; +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Role.java b/src/main/java/com/gxwebsoft/common/system/entity/Role.java new file mode 100644 index 0000000..2859ce9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Role.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 角色 + * + * @author WebSoft + * @since 2018-12-24 16:10:01 + */ +@Data +@Schema(description = "角色") +@TableName("sys_role") +public class Role implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "角色id") + @TableId(type = IdType.AUTO) + private Integer roleId; + + @Schema(description = "角色标识") + private String roleCode; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(hidden = true) + @TableField(exist = false) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/RoleMenu.java b/src/main/java/com/gxwebsoft/common/system/entity/RoleMenu.java new file mode 100644 index 0000000..14781c7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/RoleMenu.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 角色菜单 + * + * @author WebSoft + * @since 2018-12-24 16:10:54 + */ +@Data +@Schema(description = "角色权限") +@TableName("sys_role_menu") +public class RoleMenu implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @TableId(type = IdType.AUTO) + private Integer id; + + @Schema(description = "角色id") + private Integer roleId; + + @Schema(description = "菜单id") + private Integer menuId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Setting.java b/src/main/java/com/gxwebsoft/common/system/entity/Setting.java new file mode 100644 index 0000000..336bc2e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Setting.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 系统设置 + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Setting对象", description = "系统设置") +@TableName("sys_setting") +public class Setting implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @TableId(value = "setting_id", type = IdType.AUTO) + private Integer settingId; + + @Schema(description = "设置项标示") + private String settingKey; + + @Schema(description = "设置内容(json格式)") + private String content; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "修改租户名称") + @TableField(exist = false) + private String tenantName; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Tenant.java b/src/main/java/com/gxwebsoft/common/system/entity/Tenant.java new file mode 100644 index 0000000..fc5f804 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Tenant.java @@ -0,0 +1,78 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; + +/** + * 租户 + * + * @author 科技小王子 + * @since 2023-07-17 17:49:53 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Tenant对象", description = "租户") +@TableName("sys_tenant") +public class Tenant implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "租户id") + @TableId(value = "tenant_id", type = IdType.AUTO) + private Integer tenantId; + + @Schema(description = "租户名称") + private String tenantName; + + @Schema(description = "租户编号") + private String tenantCode; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "logo") + @TableField(exist = false) + private String logo; + + @Schema(description = "游客") + @TableField(exist = false) + private String username; + + @Schema(description = "游客身份") + @TableField(exist = false) + private String token; + + @Schema(description = "当前登录用户") + @TableField(exist = false) + private User loginUser; + + @Schema(description = "菜单信息") + @TableField(exist = false) + private List menu; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/User.java b/src/main/java/com/gxwebsoft/common/system/entity/User.java new file mode 100644 index 0000000..ca83722 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/User.java @@ -0,0 +1,317 @@ +package com.gxwebsoft.common.system.entity; + +import cn.hutool.core.util.DesensitizedUtil; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.gxwebsoft.cms.entity.CmsWebsite; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.security.core.userdetails.UserDetails; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 用户 + * + * @author WebSoft + * @since 2018-12-24 16:10:13 + */ +@Data +@Schema(description = "用户") +@TableName("sys_user") +public class User implements UserDetails { + private static final long serialVersionUID = 1L; + + @Schema(description = "用户id") + @TableId(type = IdType.AUTO) + private Integer userId; + + @Schema(description = "用户类型, 0普通用户") + private Integer type; + + @Schema(description = "用户编码") + private String userCode; + + @Schema(description = "账号") + private String username; + + @Schema(description = "密码") + private String password; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "头像") + private String avatar; + + @Schema(description = "头像") + private String bgImage; + + @Schema(description = "性别, 字典标识") + private String sex; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "支付密码") + private String payPassword; + + @Schema(description = "职务") + private String position; + + @Schema(description = "邮箱是否验证, 0否, 1是") + private Integer emailVerified; + + @Schema(description = "别名") + private String alias; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "身份证号(脱敏)") + private String idCard; + + @Schema(description = "身份证号") + @TableField(exist = false) + private String idCardNo; + + @Schema(description = "出生日期") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime birthday; + + @Schema(description = "年龄") + private Integer age; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "用户可用余额") + private BigDecimal balance; + + @Schema(description = "用户可用积分") + private Integer points; + + @Schema(description = "用户总支付的金额") + private String payMoney; + + @Schema(description = "实际消费的金额(不含退款)") + private BigDecimal expendMoney; + + @Schema(description = "会员等级ID") + private Integer gradeId; + + @Schema(description = "个人简介") + private String introduction; + + @Schema(description = "机构ID") + private Integer organizationId; + + @Schema(description = "会员分组ID") + private Integer groupId; + + @Schema(description = "会员分组") + @TableField(exist = false) + private String groupName; + + @Schema(description = "客户ID") + @TableField(exist = false) + private Integer customerId; + + @Schema(description = "企业ID") + @TableField(exist = false) + private Integer companyId; + + @Schema(description = "模板ID") + @TableField(exist = false) + private Integer templateId; + + @Schema(description = "注册来源客户端") + private String platform; + + @Schema(description = "是否下线会员") + private Integer offline; + + @Schema(description = "关注数") + private Integer followers; + + @Schema(description = "粉丝数") + private Integer fans; + + @Schema(description = "获赞数") + private Integer likes; + + @Schema(description = "客户端ID") + private String clientId; + + @Schema(description = "可管理的商户") + private String merchants; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "是否管理员") + private Boolean isAdmin; + + @Schema(description = "评论数") + private Integer commentNumbers; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "租户名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "最后结算时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime settlementTime; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "公司名称") + @TableField(exist = false) + private String companyName; + + @Schema(description = "是否已实名认证") + private Integer certification; + + @Schema(description = "机构名称") + @TableField(exist = false) + private String organizationName; + + @Schema(description = "性别名称") + @TableField(exist = false) + private String sexName; + + @Schema(description = "会员等级") + @TableField(exist = false) + private String gradeName; + + @Schema(description = "默认注册的角色ID") + @TableField(exist = false) + private Integer roleId; + + @Schema(description = "角色列表") + @TableField(exist = false) + private List roles; + + @Schema(description = "权限列表") + @TableField(exist = false) + private List authorities; + + @Schema(description = "微信凭证") + @TableField(exist = false) + private String code; + + @Schema(description = "推荐人ID") + @TableField(exist = false) + private Integer dealerId; + + @Schema(description = "微信openid") + private String openid; + + @Schema(description = "公众号openid") + private String officeOpenid; + + @Schema(description = "微信unionid") + private String unionid; + + @Schema(description = "关联用户ID") + @TableField(exist = false) + private Integer sysUserId; + + @Schema(description = "ico文件") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建的应用数量") + @TableField(exist = false) + private Double apps; + + @Schema(description = "租户设置信息") + @TableField(exist = false) + private String setting; + + @Schema(description = "手机号(脱敏)") + @TableField(exist = false) + private String mobile; + + @Schema(description = "网站信息") + @TableField(exist = false) + private CmsWebsite website; + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return this.status != null && this.status == 0; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public String getMobile(){ + return DesensitizedUtil.mobilePhone(this.phone); + } + + public String getIdCardNo(){ + return DesensitizedUtil.idCardNum(this.idCard,4,4); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/UserBalanceLog.java b/src/main/java/com/gxwebsoft/common/system/entity/UserBalanceLog.java new file mode 100644 index 0000000..bd62f51 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/UserBalanceLog.java @@ -0,0 +1,87 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 用户余额变动明细表 + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "UserBalanceLog对象", description = "用户余额变动明细表") +@TableName("sys_user_balance_log") +public class UserBalanceLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "log_id", type = IdType.AUTO) + private Integer logId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "余额变动场景(10用户充值 20用户消费 30管理员操作 40订单退款)") + private Integer scene; + + @Schema(description = "变动金额") + private BigDecimal money; + + @Schema(description = "变动后余额") + private BigDecimal balance; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "支付流水号") + private String transactionId; + + @Schema(description = "管理员备注") + private String remark; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/UserCollection.java b/src/main/java/com/gxwebsoft/common/system/entity/UserCollection.java new file mode 100644 index 0000000..89133e9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/UserCollection.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 我的收藏 + * + * @author 科技小王子 + * @since 2024-04-28 18:08:32 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "UserCollection对象", description = "我的收藏") +@TableName("sys_user_collection") +public class UserCollection implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "租户ID") + private Integer tid; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/UserFile.java b/src/main/java/com/gxwebsoft/common/system/entity/UserFile.java new file mode 100644 index 0000000..f99ec96 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/UserFile.java @@ -0,0 +1,79 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户文件 + * + * @author WebSoft + * @since 2022-07-21 14:34:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "UserFile对象", description = "用户文件") +@TableName("sys_user_file") +public class UserFile implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "文件名称") + private String name; + + @Schema(description = "是否是文件夹, 0否, 1是") + private Integer isDirectory; + + @Schema(description = "上级id") + private Integer parentId; + + @Schema(description = "文件路径") + private String path; + + @Schema(description = "文件大小") + private Integer length; + + @Schema(description = "文件类型") + private String contentType; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "文件访问地址") + @TableField(exist = false) + private String url; + + @Schema(description = "文件缩略图访问地址") + @TableField(exist = false) + private String thumbnail; + + @Schema(description = "文件下载地址") + @TableField(exist = false) + private String downloadUrl; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/UserInfo.java b/src/main/java/com/gxwebsoft/common/system/entity/UserInfo.java new file mode 100644 index 0000000..ecf46eb --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/UserInfo.java @@ -0,0 +1,260 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 用户 + * + * @author WebSoft + * @since 2018-12-24 16:10:13 + */ +@Data +@Schema(description = "用户") +@TableName("sys_user") +public class UserInfo { + private static final long serialVersionUID = 1L; + + @Schema(description = "用户id") + @TableId(type = IdType.AUTO) + private Integer userId; + + @Schema(description = "用户类型, 0普通用户 6开发者 10企业用户") + private Integer type; + + @Schema(description = "用户编码") + private String userCode; + + @Schema(description = "账号") + private String username; + + @Schema(description = "密码") + private String password; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "头像") + private String avatar; + + @Schema(description = "头像") + private String bgImage; + + @Schema(description = "性别, 字典标识") + private String sex; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "职务") + private String position; + + @Schema(description = "邮箱是否验证, 0否, 1是") + private Integer emailVerified; + + @Schema(description = "别名") + private String alias; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "身份证号") + private String idCard; + + @Schema(description = "出生日期") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime birthday; + + @Schema(description = "年龄") + private Integer age; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "用户可用余额") + private BigDecimal balance; + + @Schema(description = "用户可用积分") + private Integer points; + + @Schema(description = "用户总支付的金额") + private String payMoney; + + @Schema(description = "实际消费的金额(不含退款)") + private String expendMoney; + + @Schema(description = "会员等级ID") + private Integer gradeId; + + @Schema(description = "个人简介") + private String introduction; + + @Schema(description = "机构ID") + private Integer organizationId; + + @Schema(description = "客户ID") + private Integer customerId; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "注册来源客户端") + private String platform; + + @Schema(description = "兴趣爱好") + private String interest; + + @Schema(description = "身高") + private String height; + + @Schema(description = "体重") + private String weight; + + @Schema(description = "学历") + private String education; + + @Schema(description = "月薪") + private String monthlyPay; + + @Schema(description = "是否下线会员") + private Integer offline; + + @Schema(description = "关注数") + private Integer followers; + + @Schema(description = "粉丝数") + private Integer fans; + + @Schema(description = "获赞数") + private Integer likes; + + @Schema(description = "评论数") + private Integer commentNumbers; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "租户名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "最后结算时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime settlementTime; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "公司名称") + private String companyName; + + @Schema(description = "是否已实名认证") + private Integer certification; + + @Schema(description = "机构名称") + @TableField(exist = false) + private String organizationName; + + @Schema(description = "性别名称") + @TableField(exist = false) + private String sexName; + + @Schema(description = "会员等级") + @TableField(exist = false) + private String gradeName; + + @Schema(description = "默认注册的角色ID") + @TableField(exist = false) + private Integer roleId; + + @Schema(description = "角色列表") + @TableField(exist = false) + private List roles; + + @Schema(description = "权限列表") + @TableField(exist = false) + private List authorities; + + @Schema(description = "微信凭证") + @TableField(exist = false) + private String code; + + @Schema(description = "推荐人ID") + @TableField(exist = false) + private Integer dealerId; + + @Schema(description = "微信openid") + @TableField(exist = false) + private String openid; + + @Schema(description = "微信unionid") + @TableField(exist = false) + private String unionid; + + @Schema(description = "所属商户名称") + @TableField(exist = false) + private String merchantName; + + @Schema(description = "ico文件") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建的应用数量") + @TableField(exist = false) + private Double apps; + + @Schema(description = "租户设置信息") + @TableField(exist = false) + private String setting; + + @Schema(description = "手机号(脱敏)") + @TableField(exist = false) + private String mobile; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/UserReferee.java b/src/main/java/com/gxwebsoft/common/system/entity/UserReferee.java new file mode 100644 index 0000000..b836af8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/UserReferee.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 用户推荐关系表 + * + * @author 科技小王子 + * @since 2023-10-07 22:56:36 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "UserReferee对象", description = "用户推荐关系表") +@TableName("sys_user_referee") +public class UserReferee implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "推荐人ID") + private Integer dealerId; + + @Schema(description = "用户id(被推荐人)") + private Integer userId; + + @Schema(description = "推荐关系层级(1,2,3)") + private Integer level; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @TableField(exist = false) + private User user; +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/UserRole.java b/src/main/java/com/gxwebsoft/common/system/entity/UserRole.java new file mode 100644 index 0000000..9308dd9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/UserRole.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.common.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 用户角色 + * + * @author WebSoft + * @since 2018-12-24 16:10:23 + */ +@Data +@Schema(description = "用户角色") +@TableName("sys_user_role") +public class UserRole implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @TableId(type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "角色id") + private Integer roleId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "角色名称") + @TableField(exist = false) + private String roleName; + + @Schema(description = "租户ID") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/CompanyCommentMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyCommentMapper.java new file mode 100644 index 0000000..d80c603 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyCommentMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.CompanyComment; +import com.gxwebsoft.common.system.param.CompanyCommentParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用评论Mapper + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +public interface CompanyCommentMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CompanyCommentParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CompanyCommentParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/CompanyContentMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyContentMapper.java new file mode 100644 index 0000000..3a75de9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyContentMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.CompanyContent; +import com.gxwebsoft.common.system.param.CompanyContentParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用详情Mapper + * + * @author 科技小王子 + * @since 2024-10-16 13:41:21 + */ +public interface CompanyContentMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CompanyContentParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CompanyContentParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/CompanyGitMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyGitMapper.java new file mode 100644 index 0000000..345650f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyGitMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.CompanyGit; +import com.gxwebsoft.common.system.param.CompanyGitParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 代码仓库Mapper + * + * @author 科技小王子 + * @since 2024-10-19 18:08:51 + */ +public interface CompanyGitMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CompanyGitParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CompanyGitParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/CompanyMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyMapper.java new file mode 100644 index 0000000..28b534f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyMapper.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.system.entity.Company; +import com.gxwebsoft.common.system.param.CompanyParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 企业信息Mapper + * + * @author 科技小王子 + * @since 2023-05-27 14:57:34 + */ +public interface CompanyMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CompanyParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CompanyParam param); + + @InterceptorIgnore(tenantLine = "true") + List getCount(@Param("param") CompanyParam param); + + @InterceptorIgnore(tenantLine = "true") + List selectPageRelAll(PageParam page, CompanyParam param); + + @InterceptorIgnore(tenantLine = "true") + Company getCompanyAll(@Param("companyId") Integer companyId); + + @InterceptorIgnore(tenantLine = "true") + void updateByCompanyId(@Param("param") Company company); + + @InterceptorIgnore(tenantLine = "true") + boolean removeCompanyAll(Integer id); + + @InterceptorIgnore(tenantLine = "true") + boolean undeleteAll(Integer id); + + @InterceptorIgnore(tenantLine = "true") + boolean updateByIdAll(Company company); + + @InterceptorIgnore(tenantLine = "true") + Company getByTenantId(Integer tenantId); +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/CompanyParameterMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyParameterMapper.java new file mode 100644 index 0000000..c55b5f0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyParameterMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.CompanyParameter; +import com.gxwebsoft.common.system.param.CompanyParameterParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用参数Mapper + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +public interface CompanyParameterMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CompanyParameterParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CompanyParameterParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/CompanyUrlMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyUrlMapper.java new file mode 100644 index 0000000..117ed2b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/CompanyUrlMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.CompanyUrl; +import com.gxwebsoft.common.system.param.CompanyUrlParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用域名Mapper + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +public interface CompanyUrlMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") CompanyUrlParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") CompanyUrlParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/DictDataMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/DictDataMapper.java new file mode 100644 index 0000000..f36039f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/DictDataMapper.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.param.DictDataParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典数据Mapper + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface DictDataMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") DictDataParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") DictDataParam param); + + /** + * 根据dictCode和dictDataName查询 + * + * @param dictCode 字典标识 + * @param dictDataName 字典项名称 + * @return List + */ + List getByDictCodeAndName(@Param("dictCode") String dictCode, + @Param("dictDataName") String dictDataName); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/DictMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/DictMapper.java new file mode 100644 index 0000000..ce19779 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/DictMapper.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.Dict; + +/** + * 字典Mapper + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +public interface DictMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/DictionaryDataMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/DictionaryDataMapper.java new file mode 100644 index 0000000..519c2ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/DictionaryDataMapper.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.DictionaryData; +import com.gxwebsoft.common.system.param.DictionaryDataParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典数据Mapper + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface DictionaryDataMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") DictionaryDataParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") DictionaryDataParam param); + + /** + * 根据dictCode和dictDataName查询 + * + * @param dictCode 字典标识 + * @param dictDataName 字典项名称 + * @return List + */ + List getByDictCodeAndName(@Param("dictCode") String dictCode, + @Param("dictDataName") String dictDataName); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/DictionaryMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/DictionaryMapper.java new file mode 100644 index 0000000..7c2cbac --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/DictionaryMapper.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.Dictionary; + +/** + * 字典Mapper + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +public interface DictionaryMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/DomainMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/DomainMapper.java new file mode 100644 index 0000000..2853001 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/DomainMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.Domain; +import com.gxwebsoft.common.system.param.DomainParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 授权域名Mapper + * + * @author 科技小王子 + * @since 2024-09-19 23:56:33 + */ +public interface DomainMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") DomainParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") DomainParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/EmailRecordMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/EmailRecordMapper.java new file mode 100644 index 0000000..02611c9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/EmailRecordMapper.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.EmailRecord; + +/** + * 邮件记录Mapper + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface EmailRecordMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/FileRecordMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/FileRecordMapper.java new file mode 100644 index 0000000..fe9f0b8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/FileRecordMapper.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.param.FileRecordParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 文件上传记录Mapper + * + * @author WebSoft + * @since 2021-08-30 11:18:04 + */ +public interface FileRecordMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") FileRecordParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") FileRecordParam param); + + /** + * 根据path查询 + * + * @param path 文件路径 + * @return FileRecord + */ + @InterceptorIgnore(tenantLine = "true") + List getByIdPath(@Param("path") String path); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/LoginRecordMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/LoginRecordMapper.java new file mode 100644 index 0000000..4409fdd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/LoginRecordMapper.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.LoginRecord; +import com.gxwebsoft.common.system.param.LoginRecordParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 登录日志Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:11 + */ +public interface LoginRecordMapper extends BaseMapper { + + /** + * 添加, 排除租户拦截 + * + * @param entity LoginRecord + * @return int + */ + @Override + @InterceptorIgnore(tenantLine = "true") + int insert(LoginRecord entity); + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") LoginRecordParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") LoginRecordParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/MenuMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/MenuMapper.java new file mode 100644 index 0000000..938ef17 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/MenuMapper.java @@ -0,0 +1,22 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.param.MenuParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 菜单Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:32 + */ +public interface MenuMapper extends BaseMapper { + @InterceptorIgnore(tenantLine = "true") + List getMenuByClone(@Param("param") MenuParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/OperationRecordMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/OperationRecordMapper.java new file mode 100644 index 0000000..8750c2a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/OperationRecordMapper.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.OperationRecord; +import com.gxwebsoft.common.system.param.OperationRecordParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 操作日志Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:03 + */ +public interface OperationRecordMapper extends BaseMapper { + + /** + * 添加, 排除租户拦截 + * + * @param entity OperationRecord + * @return int + */ + @Override + @InterceptorIgnore(tenantLine = "true") + int insert(OperationRecord entity); + + /** + * 分页查询 + * + * @param page 分页参数 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OperationRecordParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OperationRecordParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/OrganizationMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/OrganizationMapper.java new file mode 100644 index 0000000..6f06689 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/OrganizationMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.Organization; +import com.gxwebsoft.common.system.param.OrganizationParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 组织机构Mapper + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface OrganizationMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OrganizationParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OrganizationParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/PaymentMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/PaymentMapper.java new file mode 100644 index 0000000..c58aeb8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/PaymentMapper.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.param.PaymentParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 支付方式Mapper + * + * @author 科技小王子 + * @since 2024-05-11 12:39:11 + */ +public interface PaymentMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") PaymentParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") PaymentParam param); + + @InterceptorIgnore(tenantLine = "true") + Payment getByType(@Param("param") PaymentParam paymentParam); +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/PlugMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/PlugMapper.java new file mode 100644 index 0000000..600b1a2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/PlugMapper.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.Plug; +import com.gxwebsoft.common.system.param.PlugParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 插件扩展Mapper + * + * @author 科技小王子 + * @since 2023-05-18 11:57:37 + */ +public interface PlugMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + List selectPageRel(@Param("page") IPage page, + @Param("param") PlugParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") PlugParam param); + + @InterceptorIgnore(tenantLine = "true") + List getMenuByClone(@Param("param") PlugParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/RoleMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/RoleMapper.java new file mode 100644 index 0000000..b00f275 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/RoleMapper.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.Role; + +/** + * 角色Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:44 + */ +public interface RoleMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java new file mode 100644 index 0000000..a225765 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/RoleMenuMapper.java @@ -0,0 +1,39 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.RoleMenu; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 角色菜单Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:21 + */ +public interface RoleMenuMapper extends BaseMapper { + + /** + * 查询用户的菜单 + * + * @param userId 用户id + * @param menuType 菜单类型 + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + List listMenuByUserId(@Param("userId") Integer userId, @Param("menuType") Integer menuType); + + /** + * 根据角色id查询菜单 + * + * @param roleIds 角色id + * @param menuType 菜单类型 + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + List listMenuByRoleIds(@Param("roleIds") List roleIds, @Param("menuType") Integer menuType); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/SettingMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/SettingMapper.java new file mode 100644 index 0000000..ffc7f55 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/SettingMapper.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.Setting; +import com.gxwebsoft.common.system.param.SettingParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 系统设置Mapper + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +public interface SettingMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") SettingParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") SettingParam param); + + @InterceptorIgnore(tenantLine = "true") + Setting getBySettingKeyIgnore(@Param("param") SettingParam param); +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/TenantMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/TenantMapper.java new file mode 100644 index 0000000..17f4a25 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/TenantMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.Tenant; +import com.gxwebsoft.common.system.param.TenantParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 租户Mapper + * + * @author 科技小王子 + * @since 2023-07-17 17:49:53 + */ +public interface TenantMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") TenantParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") TenantParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserBalanceLogMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserBalanceLogMapper.java new file mode 100644 index 0000000..f986c44 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserBalanceLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.UserBalanceLog; +import com.gxwebsoft.common.system.param.UserBalanceLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户余额变动明细表Mapper + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ +public interface UserBalanceLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") UserBalanceLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") UserBalanceLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserCollectionMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserCollectionMapper.java new file mode 100644 index 0000000..b7330da --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserCollectionMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.UserCollection; +import com.gxwebsoft.common.system.param.UserCollectionParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 我的收藏Mapper + * + * @author 科技小王子 + * @since 2024-04-28 18:08:32 + */ +public interface UserCollectionMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") UserCollectionParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") UserCollectionParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserFileMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserFileMapper.java new file mode 100644 index 0000000..d9e4bb4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserFileMapper.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.UserFile; + +/** + * 用户文件Mapper + * + * @author WebSoft + * @since 2022-07-21 14:34:40 + */ +public interface UserFileMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserMapper.java new file mode 100644 index 0000000..41c8be0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserMapper.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.param.UserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:14 + */ +public interface UserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") UserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") UserParam param); + + /** + * 根据账号查询 + * + * @param username 账号 + * @param tenantId 租户id + * @return User + */ + @InterceptorIgnore(tenantLine = "true") + User selectByUsername(@Param("username") String username, @Param("tenantId") Integer tenantId); + + @InterceptorIgnore(tenantLine = "true") + List getOne(@Param("param") UserParam param); + + List selectListStatisticsRel(@Param("param") UserParam param); + + @InterceptorIgnore(tenantLine = "true") + void updateByUserId(@Param("param") User param); + + /** + * 根据用户ID查询用户(忽略租户隔离) + * @param userId 用户ID + * @return User + */ + @InterceptorIgnore(tenantLine = "true") + User selectByIdIgnoreTenant(@Param("userId") Integer userId); + + @InterceptorIgnore(tenantLine = "true") + List pageAdminByPhone(@Param("param") UserParam param); + + @InterceptorIgnore(tenantLine = "true") + List listByAlert(); +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserRefereeMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserRefereeMapper.java new file mode 100644 index 0000000..e729045 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserRefereeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.UserReferee; +import com.gxwebsoft.common.system.param.UserRefereeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户推荐关系表Mapper + * + * @author 科技小王子 + * @since 2023-10-07 22:56:36 + */ +public interface UserRefereeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") UserRefereeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") UserRefereeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java b/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java new file mode 100644 index 0000000..51b38c8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/UserRoleMapper.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.common.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.entity.UserRole; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户角色Mapper + * + * @author WebSoft + * @since 2018-12-24 16:10:02 + */ +public interface UserRoleMapper extends BaseMapper { + + /** + * 批量添加用户角色 + * + * @param userId 用户id + * @param roleIds 角色id集合 + * @return int + */ + int insertBatch(@Param("userId") Integer userId, @Param("roleIds") List roleIds); + + /** + * 根据用户id查询角色 + * + * @param userId 用户id + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + List selectByUserId(@Param("userId") Integer userId); + + /** + * 批量根据用户id查询角色 + * + * @param userIds 用户id集合 + * @return List + */ + List selectByUserIds(@Param("userIds") List userIds); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyCommentMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyCommentMapper.xml new file mode 100644 index 0000000..0e04c48 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyCommentMapper.xml @@ -0,0 +1,53 @@ + + + + + + + SELECT a.* + FROM sys_company_comment a + + + AND a.id = #{param.id} + + + AND a.parent_id = #{param.parentId} + + + AND a.user_id = #{param.userId} + + + AND a.company_id = #{param.companyId} + + + AND a.rate = #{param.rate} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyContentMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyContentMapper.xml new file mode 100644 index 0000000..06207e5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyContentMapper.xml @@ -0,0 +1,38 @@ + + + + + + + SELECT a.* + FROM sys_company_content a + + + AND a.id = #{param.id} + + + AND a.company_id = #{param.companyId} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyGitMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyGitMapper.xml new file mode 100644 index 0000000..bf8c616 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyGitMapper.xml @@ -0,0 +1,65 @@ + + + + + + + SELECT a.* + FROM sys_company_git a + + + AND a.id = #{param.id} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.brand = #{param.brand} + + + AND a.company_id = #{param.companyId} + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND ( + a.title LIKE CONCAT('%', #{param.keywords}, '%') + OR a.domain LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyMapper.xml new file mode 100644 index 0000000..ad61d39 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyMapper.xml @@ -0,0 +1,198 @@ + + + + + + + SELECT a.*,b.tenant_id,b.tenant_name,b.tenant_code,c.title as categoryName + FROM gxwebsoft_core.sys_company a + LEFT JOIN gxwebsoft_core.sys_tenant b ON a.tenant_id = b.tenant_id + LEFT JOIN gxwebsoft_core.cms_navigation c ON a.category_id = c.navigation_id + + + AND a.company_id = #{param.companyId} + + + AND a.type = #{param.type} + + + AND a.official = #{param.official} + + + AND a.category_id = #{param.categoryId} + + + AND a.short_name LIKE CONCAT('%', #{param.shortName}, '%') + + + AND a.company_name LIKE CONCAT('%', #{param.companyName}, '%') + + + AND a.company_type = #{param.companyType} + + + AND a.company_logo LIKE CONCAT('%', #{param.companyLogo}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.phone = #{param.phone} + + + AND a.Invoice_header LIKE CONCAT('%', #{param.invoiceHeader}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.version = #{param.version} + + + AND a.members = #{param.members} + + + AND a.industry_parent LIKE CONCAT('%', #{param.industryParent}, '%') + + + AND a.industry_child LIKE CONCAT('%', #{param.industryChild}, '%') + + + AND a.departments = #{param.departments} + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.authentication = #{param.authentication} + + + AND a.status = #{param.status} + + + AND a.app_type = #{param.appType} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.authoritative = #{param.authoritative} + + + AND a.recommend = 1 + + + AND a.short_name = #{param.appName} + + + AND a.email = #{param.email} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.company_id IN + + #{item} + + + + AND (a.company_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.short_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.tenant_id = #{param.keywords} + OR a.phone = #{param.keywords} + OR a.domain LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + + + + + + + + + + UPDATE sys_company SET storage = #{param.storage} WHERE company_id = #{param.companyId} + + + + + UPDATE sys_company SET deleted = 1 WHERE company_id = #{param.companyId} + + + + UPDATE sys_company SET deleted = 0 WHERE company_id = #{param.companyId} + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyParameterMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyParameterMapper.xml new file mode 100644 index 0000000..eabfba2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyParameterMapper.xml @@ -0,0 +1,50 @@ + + + + + + + SELECT a.* + FROM sys_company_parameter a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.value LIKE CONCAT('%', #{param.value}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyUrlMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyUrlMapper.xml new file mode 100644 index 0000000..6db9462 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/CompanyUrlMapper.xml @@ -0,0 +1,59 @@ + + + + + + + SELECT a.* + FROM sys_company_url a + + + AND a.id = #{param.id} + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.qrcode LIKE CONCAT('%', #{param.qrcode}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictDataMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictDataMapper.xml new file mode 100644 index 0000000..a831629 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictDataMapper.xml @@ -0,0 +1,71 @@ + + + + + + + SELECT a.*, + b.dict_code, + b.dict_name + FROM gxwebsoft_core.sys_dict_data a + LEFT JOIN gxwebsoft_core.sys_dict b ON a.dict_id = b.dict_id + + AND a.deleted = 0 + + AND a.dict_data_id = #{param.dictDataId} + + + AND a.dict_id = #{param.dictId} + + + AND a.dict_data_code LIKE CONCAT('%', #{param.dictDataCode}, '%') + + + AND a.dict_data_name LIKE CONCAT('%', #{param.dictDataName}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND b.dict_code = #{param.dictCode} + + + AND b.dict_name = #{param.dictName} + + + AND ( + a.dict_data_code LIKE CONCAT('%', #{param.keywords}, '%') + OR a.dict_data_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictMapper.xml new file mode 100644 index 0000000..db709ae --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryDataMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryDataMapper.xml new file mode 100644 index 0000000..886303b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryDataMapper.xml @@ -0,0 +1,71 @@ + + + + + + + SELECT a.*, + b.dict_code, + b.dict_name + FROM sys_dictionary_data a + LEFT JOIN gxwebsoft_core.sys_dictionary b ON a.dict_id = b.dict_id + + AND a.deleted = 0 + + AND a.dict_data_id = #{param.dictDataId} + + + AND a.dict_id = #{param.dictId} + + + AND a.dict_data_code LIKE CONCAT('%', #{param.dictDataCode}, '%') + + + AND a.dict_data_name LIKE CONCAT('%', #{param.dictDataName}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND b.dict_code = #{param.dictCode} + + + AND b.dict_name = #{param.dictName} + + + AND ( + a.dict_data_code LIKE CONCAT('%', #{param.keywords}, '%') + OR a.dict_data_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryMapper.xml new file mode 100644 index 0000000..8cd0cff --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DictionaryMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/DomainMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DomainMapper.xml new file mode 100644 index 0000000..1299cf7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/DomainMapper.xml @@ -0,0 +1,62 @@ + + + + + + + SELECT a.* + FROM sys_domain a + + + AND a.id = #{param.id} + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.host_name LIKE CONCAT('%', #{param.hostName}, '%') + + + AND a.host_value LIKE CONCAT('%', #{param.hostValue}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.type = #{param.type} + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/EmailRecordMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/EmailRecordMapper.xml new file mode 100644 index 0000000..7b5ad62 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/EmailRecordMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/FileRecordMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/FileRecordMapper.xml new file mode 100644 index 0000000..1a7dd22 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/FileRecordMapper.xml @@ -0,0 +1,76 @@ + + + + + + + SELECT a.*, + b.username create_username, + b.nickname create_nickname, + b.avatar, + c.merchant_code + FROM sys_file_record a + LEFT JOIN gxwebsoft_core.sys_user b ON a.create_user_id = b.user_id + LEFT JOIN shop_merchant c ON a.merchant_code = c.merchant_code + + + AND a.id = #{param.id} + + + AND a.`name` LIKE CONCAT('%', #{param.name}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.create_user_id = #{param.createUserId} + + + AND a.group_id = #{param.groupId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND b.username = #{param.createUsername} + + + AND b.nickname LIKE CONCAT('%', #{param.createNickname}, '%') + + + AND a.content_type LIKE CONCAT('%', #{param.contentType}, '%') + + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/LoginRecordMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/LoginRecordMapper.xml new file mode 100644 index 0000000..397c525 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/LoginRecordMapper.xml @@ -0,0 +1,62 @@ + + + + + + + SELECT a.*, + b.user_id, + b.nickname + FROM sys_login_record a + LEFT JOIN gxwebsoft_core.sys_user b ON a.username = b.username + + + AND a.id = #{param.id} + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.os LIKE CONCAT('%', #{param.os}, '%') + + + AND a.device LIKE CONCAT('%', #{param.device}, '%') + + + AND a.browser LIKE CONCAT('%', #{param.browser}, '%') + + + AND a.ip LIKE CONCAT('%', #{param.ip}, '%') + + + AND a.login_type LIKE CONCAT('%', #{param.loginType}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND b.user_id = #{param.userId} + + + AND b.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/MenuMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/MenuMapper.xml new file mode 100644 index 0000000..0897489 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/MenuMapper.xml @@ -0,0 +1,32 @@ + + + + + + + SELECT a.* + FROM sys_menu a + + + AND a.menu_id = #{param.menuId} + + + AND a.parent_id = #{param.parentId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.tenant_id = #{param.tenantId} + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/OperationRecordMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/OperationRecordMapper.xml new file mode 100644 index 0000000..56b7fad --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/OperationRecordMapper.xml @@ -0,0 +1,71 @@ + + + + + + + SELECT a.*, + b.nickname, + b.username + FROM gxwebsoft_core.sys_operation_record a + LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.module LIKE CONCAT('%', #{param.module}, '%') + + + AND a.description LIKE CONCAT('%', #{param.description}, '%') + + + AND a.url LIKE CONCAT('%', #{param.url}, '%') + + + AND a.request_method = #{param.requestMethod} + + + AND a.method LIKE CONCAT('%', #{param.method}, '%') + + + AND a.description LIKE CONCAT('%', #{param.description}, '%') + + + AND a.ip LIKE CONCAT('%', #{param.ip}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.`status` = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND b.username LIKE CONCAT('%', #{param.username}, '%') + + + AND b.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/OrganizationMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/OrganizationMapper.xml new file mode 100644 index 0000000..ed5d3c8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/OrganizationMapper.xml @@ -0,0 +1,98 @@ + + + + + + + SELECT ta.* + FROM sys_dictionary_data ta + LEFT JOIN gxwebsoft_core.sys_dictionary tb + ON ta.dict_id = tb.dict_id + AND tb.deleted = 0 + WHERE ta.deleted = 0 + AND tb.dict_code = 'organization_type' + + + + + SELECT a.*, + b.dict_data_name organization_type_name, + c.nickname leader_nickname, + c.username leader_username + FROM sys_organization a + LEFT JOIN ( + + ) b ON a.organization_type = b.dict_data_code + LEFT JOIN gxwebsoft_core.sys_user c ON a.leader_id = c.user_id + + AND a.deleted = 0 + + AND a.organization_id = #{param.organizationId} + + + AND a.parent_id = #{param.parentId} + + + AND a.organization_name LIKE CONCAT('%', #{param.organizationName}, '%') + + + AND a.organization_full_name LIKE CONCAT('%', #{param.organizationFullName}, '%') + + + AND a.organization_code LIKE CONCAT('%', #{param.organizationCode}, '%') + + + AND a.organization_type = #{param.organizationType} + + + AND a.province = #{param.province} + + + AND a.city = #{param.city} + + + AND a.region = #{param.province} + + + AND a.zip_code = #{param.zipCode} + + + AND a.leader_id = #{param.leaderId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND b.dict_data_name LIKE CONCAT('%', #{param.organizationTypeName}, '%') + + + AND c.nickname LIKE CONCAT('%', #{param.leaderNickname}, '%') + + + AND c.username LIKE CONCAT('%', #{param.leaderUsername}, '%') + + + AND ( + a.organization_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/PaymentMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/PaymentMapper.xml new file mode 100644 index 0000000..d54c4bc --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/PaymentMapper.xml @@ -0,0 +1,90 @@ + + + + + + + SELECT a.* + FROM gxwebsoft_core.sys_payment a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.type = #{param.type} + + + AND a.code = #{param.code} + + + AND a.wechat_type = #{param.wechatType} + + + AND a.app_id LIKE CONCAT('%', #{param.appId}, '%') + + + AND a.mch_id LIKE CONCAT('%', #{param.mchId}, '%') + + + AND a.api_key LIKE CONCAT('%', #{param.apiKey}, '%') + + + AND a.apiclient_cert LIKE CONCAT('%', #{param.apiclientCert}, '%') + + + AND a.apiclient_key LIKE CONCAT('%', #{param.apiclientKey}, '%') + + + AND a.merchant_serial_number LIKE CONCAT('%', #{param.merchantSerialNumber}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.tenant_id = #{param.tenantId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/PlugMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/PlugMapper.xml new file mode 100644 index 0000000..45e3aac --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/PlugMapper.xml @@ -0,0 +1,105 @@ + + + + + + + SELECT a.*,b.tenant_name,c.company_name,c.short_name,c.domain + FROM sys_plug a + LEFT JOIN gxwebsoft_core.sys_tenant b ON a.tenant_id = b.tenant_id + LEFT JOIN gxwebsoft_core.sys_company c ON a.tenant_id = c.tenant_id + + + AND a.plug_id = #{param.plugId} + + + AND a.menu_id = #{param.menuId} + + + AND a.parent_id = #{param.parentId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.menu_type = #{param.menuType} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.authority LIKE CONCAT('%', #{param.authority}, '%') + + + AND a.target LIKE CONCAT('%', #{param.target}, '%') + + + AND a.icon LIKE CONCAT('%', #{param.icon}, '%') + + + AND a.color LIKE CONCAT('%', #{param.color}, '%') + + + AND a.hide = #{param.hide} + + + AND a.active LIKE CONCAT('%', #{param.active}, '%') + + + AND a.meta LIKE CONCAT('%', #{param.meta}, '%') + + + AND a.app_id = #{param.appId} + + + AND a.user_id = #{param.userId} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.merchant_code LIKE CONCAT('%', #{param.merchantCode}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.title LIKE CONCAT('%', #{param.keywords}, '%') + OR a.menu_id = #{param.keywords} + OR c.company_name LIKE CONCAT('%', #{param.keywords}, '%') + OR c.short_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMapper.xml new file mode 100644 index 0000000..9f6facc --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml new file mode 100644 index 0000000..141c53c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/RoleMenuMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/SettingMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/SettingMapper.xml new file mode 100644 index 0000000..f99a88f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/SettingMapper.xml @@ -0,0 +1,33 @@ + + + + + + + SELECT a.* + FROM gxwebsoft_core.sys_setting a + + + AND a.setting_key = #{param.settingKey} + + + AND a.setting_id = #{param.settingId} + + + AND a.tenant_id = #{param.tenantId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/TenantMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/TenantMapper.xml new file mode 100644 index 0000000..83ba5b2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/TenantMapper.xml @@ -0,0 +1,56 @@ + + + + + + + SELECT a.* + FROM sys_tenant a + + + AND a.tenant_id = #{param.tenantId} + + + AND a.tenant_name LIKE CONCAT('%', #{param.tenantName}, '%') + + + AND a.tenant_code LIKE CONCAT('%', #{param.tenantCode}, '%') + + + AND a.logo LIKE CONCAT('%', #{param.logo}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserCollectionMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserCollectionMapper.xml new file mode 100644 index 0000000..9b60a69 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserCollectionMapper.xml @@ -0,0 +1,38 @@ + + + + + + + SELECT a.* + FROM sys_user_collection a + + + AND a.id = #{param.id} + + + AND a.tid = #{param.tid} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserFileMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserFileMapper.xml new file mode 100644 index 0000000..872b232 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserFileMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserMapper.xml new file mode 100644 index 0000000..c16fae8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserMapper.xml @@ -0,0 +1,264 @@ + + + + + + + SELECT ta.* + FROM gxwebsoft_core.sys_dictionary_data ta + LEFT JOIN gxwebsoft_core.sys_dictionary tb + ON ta.dict_id = tb.dict_id + AND tb.deleted = 0 + WHERE ta.deleted = 0 + AND tb.dict_code = 'sex' + + + + + SELECT a.user_id, + GROUP_CONCAT(b.role_name) role_name + FROM gxwebsoft_core.sys_user_role a + LEFT JOIN gxwebsoft_core.sys_role b ON a.role_id = b.role_id + GROUP BY a.user_id + + + + + SELECT a.*, + c.dict_data_name sex_name, + e.tenant_name, + h.dealer_id + FROM gxwebsoft_core.sys_user a + LEFT JOIN ( + + ) c ON a.sex = c.dict_data_code + LEFT JOIN( + + ) d ON a.user_id = d.user_id + LEFT JOIN gxwebsoft_core.sys_tenant e ON a.tenant_id = e.tenant_id + LEFT JOIN gxwebsoft_core.sys_user_referee h ON a.user_id = h.user_id and h.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.type = #{param.type} + + + AND a.sex = #{param.sex} + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.email LIKE CONCAT('%', #{param.email}, '%') + + + AND a.email_verified = #{param.emailVerified} + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.company_name LIKE CONCAT('%', #{param.companyName}, '%') + + + AND a.id_card LIKE CONCAT('%', #{param.idCard}, '%') + + + AND a.birthday LIKE CONCAT('%', #{param.birthday}, '%') + + + AND a.organization_id = #{param.organizationId} + + + AND a.organization_id > 0 + + + AND a.platform = #{param.platform} + + + AND a.`status` = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.recommend = #{param.recommend} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id IN (SELECT user_id FROM sys_user_role WHERE role_id=#{param.roleId}) + + + AND a.user_id IN + + #{item} + + + + AND a.phones IN + + #{item} + + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND i.city_mate LIKE CONCAT('%', #{param.cityMate}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND c.dict_data_name = #{param.sexName} + + + AND ( + a.username LIKE CONCAT('%', #{param.keywords}, '%') + OR a.user_id = #{param.keywords} + OR a.nickname LIKE CONCAT('%', #{param.keywords}, '%') + OR a.real_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.alias LIKE CONCAT('%', #{param.keywords}, '%') + OR a.phone LIKE CONCAT('%', #{param.keywords}, '%') + OR a.email LIKE CONCAT('%', #{param.keywords}, '%') + OR c.dict_data_name LIKE CONCAT('%', #{param.keywords}, '%') + OR d.role_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + AND a.organization_id IN (SELECT organization_id FROM sys_organization WHERE parent_id=#{param.parentId}) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UPDATE gxwebsoft_core.sys_user SET grade_id = #{param.gradeId} WHERE user_id = #{param.userId} + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRefereeMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRefereeMapper.xml new file mode 100644 index 0000000..67943f7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRefereeMapper.xml @@ -0,0 +1,50 @@ + + + + + + + SELECT a.* + FROM sys_user_referee a + + + AND a.id = #{param.id} + + + AND a.dealer_id = #{param.dealerId} + + + AND a.user_id = #{param.userId} + + + AND a.level = #{param.level} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml new file mode 100644 index 0000000..78e3a87 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/mapper/xml/UserRoleMapper.xml @@ -0,0 +1,35 @@ + + + + + + INSERT INTO sys_user_role(user_id, role_id) VALUES + + (#{userId}, #{roleId}) + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/common/system/param/AlipayParam.java b/src/main/java/com/gxwebsoft/common/system/param/AlipayParam.java new file mode 100644 index 0000000..9888a3c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/AlipayParam.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 登录参数 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "登录参数") +public class AlipayParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "支付宝授权码") + private String authCode; + + @Schema(description = "登录账号") + private String username; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CacheParam.java b/src/main/java/com/gxwebsoft/common/system/param/CacheParam.java new file mode 100644 index 0000000..082e278 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CacheParam.java @@ -0,0 +1,25 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 缓存管理 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "缓存管理") +public class CacheParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "key") + private String key; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CompanyCommentParam.java b/src/main/java/com/gxwebsoft/common/system/param/CompanyCommentParam.java new file mode 100644 index 0000000..b2fdcf8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CompanyCommentParam.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 应用评论查询参数 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CompanyCommentParam对象", description = "应用评论查询参数") +public class CompanyCommentParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "父级ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "评分") + @QueryField(type = QueryType.EQ) + private BigDecimal rate; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "评论内容") + private String comments; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CompanyContentParam.java b/src/main/java/com/gxwebsoft/common/system/param/CompanyContentParam.java new file mode 100644 index 0000000..1a5ff5e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CompanyContentParam.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用详情查询参数 + * + * @author 科技小王子 + * @since 2024-10-16 13:41:21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CompanyContentParam对象", description = "应用详情查询参数") +public class CompanyContentParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "详细内容") + private String content; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CompanyGitParam.java b/src/main/java/com/gxwebsoft/common/system/param/CompanyGitParam.java new file mode 100644 index 0000000..7786857 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CompanyGitParam.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 代码仓库查询参数 + * + * @author 科技小王子 + * @since 2024-10-19 18:08:51 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CompanyGitParam对象", description = "代码仓库查询参数") +public class CompanyGitParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "仓库名称") + private String title; + + @Schema(description = "厂商") + @QueryField(type = QueryType.EQ) + private String brand; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "仓库地址") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "仓库描述") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CompanyParam.java b/src/main/java/com/gxwebsoft/common/system/param/CompanyParam.java new file mode 100644 index 0000000..6c48c51 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CompanyParam.java @@ -0,0 +1,167 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Set; + +/** + * 企业信息查询参数 + * + * @author 科技小王子 + * @since 2023-05-27 14:57:34 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CompanyParam对象", description = "企业信息查询参数") +public class CompanyParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "企业id") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "应用类型") + private Integer type; + + @Schema(description = "是否官方") + private Boolean official; + + @Schema(description = "企业简称") + private String shortName; + + @Schema(description = "企业全称") + private String companyName; + + @Schema(description = "企业标识") + private String companyCode; + + @Schema(description = "类型 10企业 20政府单位") + @QueryField(type = QueryType.EQ) + private String companyType; + + @Schema(description = "企业类型 多选") + private String companyTypeMultiple; + + @Schema(description = "应用标识") + private String companyLogo; + + @Schema(description = "栏目分类") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "企业域名") + private String domain; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "电子邮箱") + @QueryField(type = QueryType.EQ) + private String email; + + @Schema(description = "企业法人") + private String businessEntity; + + @Schema(description = "发票抬头") + private String invoiceHeader; + + @Schema(description = "服务开始时间") + private String startTime; + + @Schema(description = "服务到期时间") + private String expirationTime; + + @Schema(description = "应用版本 10体验版 20授权版 30旗舰版") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "成员数量") + @QueryField(type = QueryType.EQ) + private Integer members; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "部门数量") + @QueryField(type = QueryType.EQ) + private Integer departments; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否实名认证") + @QueryField(type = QueryType.EQ) + private Integer authentication; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Boolean recommend; + + @Schema(description = "应用类型 app应用 plug插件") + private String appType; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "是否默认企业主体") + @QueryField(type = QueryType.EQ) + private Boolean authoritative; + + @Schema(description = "租户号") + private Integer tenantId; + + @Schema(description = "应用名称") + @QueryField(type = QueryType.EQ) + private String appName; + + @Schema(description = "企业id集合") + @TableField(exist = false) + private Set companyIds; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CompanyParameterParam.java b/src/main/java/com/gxwebsoft/common/system/param/CompanyParameterParam.java new file mode 100644 index 0000000..91c97b0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CompanyParameterParam.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数查询参数 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CompanyParameterParam对象", description = "应用参数查询参数") +public class CompanyParameterParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "参数名称") + private String name; + + @Schema(description = "参数内容") + private String value; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/CompanyUrlParam.java b/src/main/java/com/gxwebsoft/common/system/param/CompanyUrlParam.java new file mode 100644 index 0000000..e0576e4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/CompanyUrlParam.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用域名查询参数 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "CompanyUrlParam对象", description = "应用域名查询参数") +public class CompanyUrlParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "域名类型") + private String type; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "二维码") + private String qrcode; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/DictDataParam.java b/src/main/java/com/gxwebsoft/common/system/param/DictDataParam.java new file mode 100644 index 0000000..19e4c0a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/DictDataParam.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典数据查询参数 + * + * @author WebSoft + * @since 2021-08-28 22:12:02 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "字典数据查询参数") +public class DictDataParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "字典数据id") + @QueryField(type = QueryType.EQ) + private Integer dictDataId; + + @Schema(description = "字典id") + @QueryField(type = QueryType.EQ) + private Integer dictId; + + @Schema(description = "字典数据标识") + private String dictDataCode; + + @Schema(description = "字典数据名称") + private String dictDataName; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "字典代码") + @TableField(exist = false) + private String dictCode; + + @Schema(description = "字典名称") + @TableField(exist = false) + private String dictName; + + @Schema(description = "字典数据代码或字典数据名称") + @TableField(exist = false) + private String keywords; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/DictParam.java b/src/main/java/com/gxwebsoft/common/system/param/DictParam.java new file mode 100644 index 0000000..2957409 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/DictParam.java @@ -0,0 +1,38 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典查询参数 + * + * @author WebSoft + * @since 2021-08-28 22:12:01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "字典查询参数") +public class DictParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + @Schema(description = "字典id") + private Integer dictId; + + @Schema(description = "字典标识") + private String dictCode; + + @Schema(description = "字典名称") + private String dictName; + + @Schema(description = "备注") + private String comments; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/DictionaryDataParam.java b/src/main/java/com/gxwebsoft/common/system/param/DictionaryDataParam.java new file mode 100644 index 0000000..f9cbc21 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/DictionaryDataParam.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典数据查询参数 + * + * @author WebSoft + * @since 2021-08-28 22:12:02 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "字典数据查询参数") +public class DictionaryDataParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "字典数据id") + @QueryField(type = QueryType.EQ) + private Integer dictDataId; + + @Schema(description = "字典id") + @QueryField(type = QueryType.EQ) + private Integer dictId; + + @Schema(description = "字典数据标识") + private String dictDataCode; + + @Schema(description = "字典数据名称") + private String dictDataName; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "字典代码") + @TableField(exist = false) + private String dictCode; + + @Schema(description = "字典名称") + @TableField(exist = false) + private String dictName; + + @Schema(description = "字典数据代码或字典数据名称") + @TableField(exist = false) + private String keywords; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/DictionaryParam.java b/src/main/java/com/gxwebsoft/common/system/param/DictionaryParam.java new file mode 100644 index 0000000..0e70378 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/DictionaryParam.java @@ -0,0 +1,38 @@ +package com.gxwebsoft.common.system.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典查询参数 + * + * @author WebSoft + * @since 2021-08-28 22:12:01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "字典查询参数") +public class DictionaryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + @Schema(description = "字典id") + private Integer dictId; + + @Schema(description = "字典标识") + private String dictCode; + + @Schema(description = "字典名称") + private String dictName; + + @Schema(description = "备注") + private String comments; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/DomainParam.java b/src/main/java/com/gxwebsoft/common/system/param/DomainParam.java new file mode 100644 index 0000000..c842644 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/DomainParam.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 授权域名查询参数 + * + * @author 科技小王子 + * @since 2024-09-19 23:56:33 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "DomainParam对象", description = "授权域名查询参数") +public class DomainParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "主机记录") + private String hostName; + + @Schema(description = "记录值") + private String hostValue; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "类型 0常规 1后台 2商家端") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/FileRecordParam.java b/src/main/java/com/gxwebsoft/common/system/param/FileRecordParam.java new file mode 100644 index 0000000..bb8d33e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/FileRecordParam.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文件上传记录查询参数 + * + * @author WebSoft + * @since 2021-08-30 11:29:31 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "文件上传记录查询参数") +public class FileRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + @Schema(description = "主键id") + private Integer id; + + @QueryField(type = QueryType.EQ) + @Schema(description = "分组ID") + private String groupId; + + @Schema(description = "文件名称") + private String name; + + @Schema(description = "文件存储路径") + private String path; + + @QueryField(type = QueryType.EQ) + @Schema(description = "创建人") + private Integer createUserId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "文件类型") + private String contentType; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建人账号") + @TableField(exist = false) + private String createUsername; + + @Schema(description = "创建人名称") + @TableField(exist = false) + private String createNickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "商户编号") + private String merchantCode; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/LoginParam.java b/src/main/java/com/gxwebsoft/common/system/param/LoginParam.java new file mode 100644 index 0000000..039db8b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/LoginParam.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 登录参数 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "登录参数") +public class LoginParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "账号") + private String username; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "短信验证码") + private String code; + + @Schema(description = "密码") + private String password; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/LoginRecordParam.java b/src/main/java/com/gxwebsoft/common/system/param/LoginRecordParam.java new file mode 100644 index 0000000..833b056 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/LoginRecordParam.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 登录日志查询参数 + * + * @author WebSoft + * @since 2021-08-29 19:09:23 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "登录日志查询参数") +public class LoginRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + @Schema(description = "主键id") + private Integer id; + + @Schema(description = "用户账号") + private String username; + + @Schema(description = "操作系统") + private String os; + + @Schema(description = "设备名") + private String device; + + @Schema(description = "浏览器类型") + private String browser; + + @Schema(description = "ip地址") + private String ip; + + @QueryField(type = QueryType.EQ) + @Schema(description = "操作类型, 0登录成功, 1登录失败, 2退出登录, 3续签token") + private Integer loginType; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户id") + @TableField(exist = false) + private Integer userId; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/MenuParam.java b/src/main/java/com/gxwebsoft/common/system/param/MenuParam.java new file mode 100644 index 0000000..69b70fb --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/MenuParam.java @@ -0,0 +1,68 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 菜单查询参数 + * + * @author WebSoft + * @since 2021-08-29 19:36:10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "菜单查询参数") +public class MenuParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "菜单id") + @QueryField(type = QueryType.EQ) + private Integer menuId; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "菜单路由关键字") + private String path; + + @Schema(description = "菜单组件地址") + private String component; + + @Schema(description = "菜单类型, 0菜单, 1按钮") + @QueryField(type = QueryType.EQ) + private Integer menuType; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "关联应用") + private Integer appId; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户ID") + @QueryField(type = QueryType.EQ) + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/OperationRecordParam.java b/src/main/java/com/gxwebsoft/common/system/param/OperationRecordParam.java new file mode 100644 index 0000000..26b6478 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/OperationRecordParam.java @@ -0,0 +1,67 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 操作日志参数 + * + * @author WebSoft + * @since 2021-08-29 20:35:09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "操作日志参数") +public class OperationRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "操作模块") + private String module; + + @Schema(description = "操作功能") + private String description; + + @Schema(description = "请求地址") + private String url; + + @Schema(description = "请求方式") + private String requestMethod; + + @Schema(description = "调用方法") + private String method; + + @Schema(description = "ip地址") + private String ip; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0成功, 1异常") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "用户账号") + @TableField(exist = false) + private String username; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/OrganizationParam.java b/src/main/java/com/gxwebsoft/common/system/param/OrganizationParam.java new file mode 100644 index 0000000..5081089 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/OrganizationParam.java @@ -0,0 +1,81 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 组织机构查询参数 + * + * @author WebSoft + * @since 2021-08-29 20:35:09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "组织机构查询参数") +public class OrganizationParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "机构名称") + private String organizationName; + + @Schema(description = "机构全称") + private String organizationFullName; + + @Schema(description = "机构代码") + private String organizationCode; + + @Schema(description = "机构类型(字典代码)") + private String organizationType; + + @Schema(description = "所在省份") + @QueryField(type = QueryType.EQ) + private String province; + + @Schema(description = "所在城市") + @QueryField(type = QueryType.EQ) + private String city; + + @Schema(description = "所在辖区") + @QueryField(type = QueryType.EQ) + private String region; + + @Schema(description = "邮政编码") + @QueryField(type = QueryType.EQ) + private String zipCode; + + @Schema(description = "负责人id") + @QueryField(type = QueryType.EQ) + private Integer leaderId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "机构类型名称") + @TableField(exist = false) + private String organizationTypeName; + + @Schema(description = "负责人姓名") + @TableField(exist = false) + private String leaderNickname; + + @Schema(description = "负责人账号") + @TableField(exist = false) + private String leaderUsername; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/PaymentParam.java b/src/main/java/com/gxwebsoft/common/system/param/PaymentParam.java new file mode 100644 index 0000000..c814fcd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/PaymentParam.java @@ -0,0 +1,81 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 支付方式查询参数 + * + * @author 科技小王子 + * @since 2024-05-11 12:39:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "PaymentParam对象", description = "支付方式查询参数") +public class PaymentParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "支付方式") + private String name; + + @Schema(description = "类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "标识") + @QueryField(type = QueryType.EQ) + private String code; + + @Schema(description = "微信商户号类型 1普通商户2子商户") + @QueryField(type = QueryType.EQ) + private Integer wechatType; + + @Schema(description = "应用ID") + private String appId; + + @Schema(description = "商户号") + private String mchId; + + @Schema(description = "设置APIv3密钥") + private String apiKey; + + @Schema(description = "证书文件 (CERT)") + private String apiclientCert; + + @Schema(description = "证书文件 (KEY)") + private String apiclientKey; + + @Schema(description = "商户证书序列号") + private String merchantSerialNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "文章排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0启用, 1禁用") + @QueryField(type = QueryType.EQ) + private Boolean status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "租户ID") + @QueryField(type = QueryType.EQ) + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/PlugParam.java b/src/main/java/com/gxwebsoft/common/system/param/PlugParam.java new file mode 100644 index 0000000..3c32d42 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/PlugParam.java @@ -0,0 +1,95 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 插件扩展查询参数 + * + * @author 科技小王子 + * @since 2023-05-18 11:57:37 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "PlugParam对象", description = "插件扩展查询参数") +public class PlugParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "插件id") + @QueryField(type = QueryType.EQ) + private Integer plugId; + + @Schema(description = "菜单id") + @QueryField(type = QueryType.EQ) + private Integer menuId; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "类型, 0菜单, 1按钮") + @QueryField(type = QueryType.EQ) + private Integer menuType; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "图标颜色") + private String color; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "关联应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/RoleParam.java b/src/main/java/com/gxwebsoft/common/system/param/RoleParam.java new file mode 100644 index 0000000..d07e3b5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/RoleParam.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.common.system.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 角色查询参数 + * + * @author WebSoft + * @since 2021-08-29 20:35:09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "角色查询参数") +public class RoleParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "角色id") + @QueryField(type = QueryType.EQ) + private Integer roleId; + + @Schema(description = "角色标识") + private String roleCode; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "租户ID") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/SettingParam.java b/src/main/java/com/gxwebsoft/common/system/param/SettingParam.java new file mode 100644 index 0000000..324195b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/SettingParam.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 系统设置查询参数 + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "SettingParam对象", description = "系统设置查询参数") +public class SettingParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @QueryField(type = QueryType.EQ) + private Integer settingId; + + @Schema(description = "设置项标示") + @QueryField(type = QueryType.EQ) + private String settingKey; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "同步更新租户名称") + private String tenantName; + + @Schema(description = "租户名称") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/SmsCaptchaParam.java b/src/main/java/com/gxwebsoft/common/system/param/SmsCaptchaParam.java new file mode 100644 index 0000000..7d1f5b5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/SmsCaptchaParam.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 发送短信验证码参数 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "发送短信验证码参数") +public class SmsCaptchaParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "短信签名") + private String signName; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "短信模板") + private String TemplateParam; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/TenantParam.java b/src/main/java/com/gxwebsoft/common/system/param/TenantParam.java new file mode 100644 index 0000000..03b57c3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/TenantParam.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 租户查询参数 + * + * @author 科技小王子 + * @since 2023-07-17 17:49:53 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "TenantParam对象", description = "租户查询参数") +public class TenantParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "租户名称") + private String tenantName; + + @Schema(description = "租户编号") + private String tenantCode; + + @Schema(description = "logo") + private String logo; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "租户id") + @QueryField(type = QueryType.EQ) + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UpdatePasswordParam.java b/src/main/java/com/gxwebsoft/common/system/param/UpdatePasswordParam.java new file mode 100644 index 0000000..8b67b74 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UpdatePasswordParam.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 修改密码参数 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "修改密码参数") +public class UpdatePasswordParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "原始密码") + private String oldPassword; + + @Schema(description = "新密码") + private String password; + + @Schema(description = "手机号码") + private String phone; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserBalanceLogParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserBalanceLogParam.java new file mode 100644 index 0000000..1977e31 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UserBalanceLogParam.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 用户余额变动明细表查询参数 + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "UserBalanceLogParam对象", description = "用户余额变动明细表查询参数") +public class UserBalanceLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer logId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "余额变动场景(10用户充值 20用户消费 30管理员操作 40订单退款)") + @QueryField(type = QueryType.EQ) + private Integer scene; + + @Schema(description = "变动金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "变动后余额") + @QueryField(type = QueryType.EQ) + private BigDecimal balance; + + @Schema(description = "描述/说明") + private String describe; + + @Schema(description = "管理员备注") + private String remark; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "余额变动场景筛选") + private String sceneMultiple; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserCollectionParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserCollectionParam.java new file mode 100644 index 0000000..85106e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UserCollectionParam.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 我的收藏查询参数 + * + * @author 科技小王子 + * @since 2024-04-28 18:08:32 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "UserCollectionParam对象", description = "我的收藏查询参数") +public class UserCollectionParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "租户ID") + @QueryField(type = QueryType.EQ) + private Integer tid; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserFileParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserFileParam.java new file mode 100644 index 0000000..5404354 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UserFileParam.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.common.system.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户文件查询参数 + * + * @author WebSoft + * @since 2022-07-21 14:34:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "UserFileParam对象", description = "用户文件查询参数") +public class UserFileParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键id") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "文件名称") + private String name; + + @Schema(description = "上级id") + @QueryField(type = QueryType.EQ) + private Integer parentId; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java new file mode 100644 index 0000000..153a783 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.param; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户导入参数 + * + * @author WebSoft + * @since 2011-10-15 17:33:34 + */ +@Data +public class UserImportParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Excel(name = "账号") + private String username; + + @Excel(name = "密码") + private String password; + + @Excel(name = "昵称") + private String nickname; + + @Excel(name = "手机号") + private String phone; + + @Excel(name = "邮箱") + private String email; + + @Excel(name = "组织机构") + private String organizationName; + + @Excel(name = "性别") + private String sexName; + + @Excel(name = "角色") + private String roleName; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserParam.java new file mode 100644 index 0000000..2ade1fd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UserParam.java @@ -0,0 +1,249 @@ +package com.gxwebsoft.common.system.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 用户查询参数 + * + * @author WebSoft + * @since 2021-08-29 20:35:09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "用户查询参数") +public class UserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "用户类型, 0普通用户 10企业用户") + private Integer type; + + @Schema(description = "账号") + private String username; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "用户编码") + private String userCode; + + @Schema(description = "性别(字典)") + @QueryField(type = QueryType.EQ) + private String sex; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "邮箱是否验证, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer emailVerified; + + @Schema(description = "别名") + private String alias; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "身份证号") + private String idCard; + + @Schema(description = "出生日期") + private String birthday; + + @Schema(description = "年龄") + private Integer age; + + @Schema(description = "可用余额") + private BigDecimal balance; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "用户分组ID") + @QueryField(type = QueryType.EQ) + private Integer groupId; + + @Schema(description = "注册来源客户端") + @QueryField(type = QueryType.EQ) + private String platform; + + @Schema(description = "是否下线会员") + private Integer offline; + + @Schema(description = "上级机构ID") + @QueryField(type = QueryType.IN) + private Integer parentId; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "角色id") + @TableField(exist = false) + private Integer roleId; + + @Schema(description = "角色标识") + @TableField(exist = false) + private String roleCode; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "关注数") + private Integer followers; + + @Schema(description = "粉丝数") + private Integer fans; + + @Schema(description = "获赞数") + private Integer likes; + + @Schema(description = "评论数") + private Integer commentNumbers; + + @Schema(description = "择偶区域") + @TableField(exist = false) + private String cityMate; + + @Schema(description = "机构名称") + @TableField(exist = false) + private String organizationName; + + @Schema(description = "公司名称") + @TableField(exist = false) + private String companyName; + + @Schema(description = "公司名称") + private String customerName; + + @Schema(description = "性别名称") + @TableField(exist = false) + private String sexName; + + @Schema(description = "推荐状态") + @TableField(exist = false) + private Integer recommend; + + @Schema(description = "搜索关键字") + @TableField(exist = false) + private String keywords; + + @Schema(description = "会员等级") + @TableField(exist = false) + private Integer gradeId; + + @Schema(description = "按角色搜索") + @TableField(exist = false) + private String roleIds; + + @Schema(description = "用户类型 sys系统用户 org机构职员 member商城会员 ") + @TableField(exist = false) + private String userType; + + @Schema(description = "支付宝授权码") + @TableField(exist = false) + private String authCode; + + @Schema(description = "微信凭证code") + @TableField(exist = false) + private String code; + + @Schema(description = "推荐人ID") + @QueryField(type = QueryType.IN) + private Integer refereeId; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "二维码类型") + @TableField(exist = false) + private String codeType; + + @Schema(description = "二维码内容 填网址扫码后可跳转") + @TableField(exist = false) + private String codeContent; + + @Schema(description = "是否内部职员") + @TableField(exist = false) + private Boolean isStaff; + + @Schema(description = "是否管理员") + @TableField(exist = false) + private Boolean isAdmin; + + @Schema(description = "openid") + private String openid; + + @Schema(description = "unionid") + private String unionid; + + @Schema(description = "最后结算时间") + @TableField(exist = false) + private String settlementTime; + + @Schema(description = "报餐时间") + @TableField(exist = false) + private String deliveryTime; + + @Schema(description = "用户ID集合") + @TableField(exist = false) + private Set userIds; + + @Schema(description = "用户手机号码集合") + @TableField(exist = false) + private Set phones; + + @Schema(description = "是否查询用户详细资料表") + @TableField(exist = false) + private Boolean showProfile; + + @Schema(description = "openId") + @TableField(exist = false) + private String openId; + + @Schema(description = "可管理的商户") + @QueryField(type = QueryType.LIKE) + private String merchants; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "关联用户ID") + @TableField(exist = false) + private Integer sysUserId; +} diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserRefereeParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserRefereeParam.java new file mode 100644 index 0000000..3d5389c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/param/UserRefereeParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.common.system.param; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户推荐关系表查询参数 + * + * @author 科技小王子 + * @since 2023-10-07 22:56:36 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "UserRefereeParam对象", description = "用户推荐关系表查询参数") +public class UserRefereeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "推荐人ID") + @QueryField(type = QueryType.EQ) + private Integer dealerId; + + @Schema(description = "用户id(被推荐人)") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "推荐关系层级(1,2,3)") + @QueryField(type = QueryType.EQ) + private Integer level; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/result/CaptchaResult.java b/src/main/java/com/gxwebsoft/common/system/result/CaptchaResult.java new file mode 100644 index 0000000..46bd08f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/result/CaptchaResult.java @@ -0,0 +1,30 @@ +package com.gxwebsoft.common.system.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 验证码返回结果 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "验证码返回结果") +public class CaptchaResult implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "图形验证码base64数据") + private String base64; + + @Schema(description = "验证码文本") + private String text; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/result/LoginResult.java b/src/main/java/com/gxwebsoft/common/system/result/LoginResult.java new file mode 100644 index 0000000..940d8e9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/result/LoginResult.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.system.result; + +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 登录返回结果 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "登录返回结果") +public class LoginResult implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "access_token") + private String access_token; + + @Schema(description = "用户信息") + private User user; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/result/RedisResult.java b/src/main/java/com/gxwebsoft/common/system/result/RedisResult.java new file mode 100644 index 0000000..f53282b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/result/RedisResult.java @@ -0,0 +1,34 @@ +package com.gxwebsoft.common.system.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Redis缓存数据 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "缓存数据返回") +public class RedisResult implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "key") + private String key; + + @Schema(description = "数据") + private T data; + + @Schema(description = "过期时间") + private LocalDateTime expireTime; + +} diff --git a/src/main/java/com/gxwebsoft/common/system/result/SmsCaptchaResult.java b/src/main/java/com/gxwebsoft/common/system/result/SmsCaptchaResult.java new file mode 100644 index 0000000..8f5bb2a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/result/SmsCaptchaResult.java @@ -0,0 +1,26 @@ +package com.gxwebsoft.common.system.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 短信验证码返回结果 + * + * @author WebSoft + * @since 2021-08-30 17:35:16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "短信验证码返回结果") +public class SmsCaptchaResult implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "短信验证码") + private String text; +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/CompanyCommentService.java b/src/main/java/com/gxwebsoft/common/system/service/CompanyCommentService.java new file mode 100644 index 0000000..10ca95a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/CompanyCommentService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyComment; +import com.gxwebsoft.common.system.param.CompanyCommentParam; + +import java.util.List; + +/** + * 应用评论Service + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +public interface CompanyCommentService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CompanyCommentParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CompanyCommentParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return CompanyComment + */ + CompanyComment getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/CompanyContentService.java b/src/main/java/com/gxwebsoft/common/system/service/CompanyContentService.java new file mode 100644 index 0000000..e4e1ab4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/CompanyContentService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyContent; +import com.gxwebsoft.common.system.param.CompanyContentParam; + +import java.util.List; + +/** + * 应用详情Service + * + * @author 科技小王子 + * @since 2024-10-16 13:41:21 + */ +public interface CompanyContentService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CompanyContentParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CompanyContentParam param); + + /** + * 根据id查询 + * + * @param id + * @return CompanyContent + */ + CompanyContent getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/CompanyGitService.java b/src/main/java/com/gxwebsoft/common/system/service/CompanyGitService.java new file mode 100644 index 0000000..f3ac263 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/CompanyGitService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyGit; +import com.gxwebsoft.common.system.param.CompanyGitParam; + +import java.util.List; + +/** + * 代码仓库Service + * + * @author 科技小王子 + * @since 2024-10-19 18:08:51 + */ +public interface CompanyGitService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CompanyGitParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CompanyGitParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CompanyGit + */ + CompanyGit getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/CompanyParameterService.java b/src/main/java/com/gxwebsoft/common/system/service/CompanyParameterService.java new file mode 100644 index 0000000..4ffa4c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/CompanyParameterService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyParameter; +import com.gxwebsoft.common.system.param.CompanyParameterParam; + +import java.util.List; + +/** + * 应用参数Service + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +public interface CompanyParameterService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CompanyParameterParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CompanyParameterParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CompanyParameter + */ + CompanyParameter getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/CompanyService.java b/src/main/java/com/gxwebsoft/common/system/service/CompanyService.java new file mode 100644 index 0000000..d675f59 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/CompanyService.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Company; +import com.gxwebsoft.common.system.param.CompanyParam; + +import java.util.List; + +/** + * 企业信息Service + * + * @author 科技小王子 + * @since 2023-05-27 14:57:34 + */ +public interface CompanyService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CompanyParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CompanyParam param); + + /** + * 根据id查询 + * + * @param companyId 企业id + * @return Company + */ + Company getByIdRel(Integer companyId); + + Company getByTenantIdRel(Integer tenantId); + + PageResult pageRelAll(CompanyParam param); + + void updateByCompanyId(Company company); + + boolean removeCompanyAll(Integer companyId); + + boolean undeleteAll(Integer companyId); +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/CompanyUrlService.java b/src/main/java/com/gxwebsoft/common/system/service/CompanyUrlService.java new file mode 100644 index 0000000..c2b2479 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/CompanyUrlService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyUrl; +import com.gxwebsoft.common.system.param.CompanyUrlParam; + +import java.util.List; + +/** + * 应用域名Service + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +public interface CompanyUrlService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(CompanyUrlParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(CompanyUrlParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return CompanyUrl + */ + CompanyUrl getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/DictDataService.java b/src/main/java/com/gxwebsoft/common/system/service/DictDataService.java new file mode 100644 index 0000000..86b94bf --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/DictDataService.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.*; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.param.DictDataParam; + +import java.util.List; + +/** + * 字典数据Service + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface DictDataService extends IService { + + /** + * 关联分页查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(DictDataParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(DictDataParam param); + + /** + * 根据id查询 + * + * @param dictDataId 字典数据id + * @return DictData + */ + DictData getByIdRel(Integer dictDataId); + + /** + * 根据dictCode和dictDataName查询 + * + * @param dictCode 字典标识 + * @param dictDataName 字典项名称 + * @return DictData + */ + DictData getByDictCodeAndName(String dictCode, String dictDataName); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/DictService.java b/src/main/java/com/gxwebsoft/common/system/service/DictService.java new file mode 100644 index 0000000..8aef5ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/DictService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.Dict; + +/** + * 字典Service + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +public interface DictService extends IService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/DictionaryDataService.java b/src/main/java/com/gxwebsoft/common/system/service/DictionaryDataService.java new file mode 100644 index 0000000..881fd5a --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/DictionaryDataService.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.DictionaryData; +import com.gxwebsoft.common.system.param.DictionaryDataParam; + +import java.util.List; + +/** + * 字典数据Service + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface DictionaryDataService extends IService { + + /** + * 关联分页查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(DictionaryDataParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(DictionaryDataParam param); + + /** + * 根据id查询 + * + * @param dictDataId 字典数据id + * @return DictionaryData + */ + DictionaryData getByIdRel(Integer dictDataId); + + /** + * 根据dictCode和dictDataName查询 + * + * @param dictCode 字典标识 + * @param dictDataName 字典项名称 + * @return DictionaryData + */ + DictionaryData getByDictCodeAndName(String dictCode, String dictDataName); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/DictionaryService.java b/src/main/java/com/gxwebsoft/common/system/service/DictionaryService.java new file mode 100644 index 0000000..4705494 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/DictionaryService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.Dictionary; + +/** + * 字典Service + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +public interface DictionaryService extends IService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/DomainService.java b/src/main/java/com/gxwebsoft/common/system/service/DomainService.java new file mode 100644 index 0000000..2996471 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/DomainService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Domain; +import com.gxwebsoft.common.system.param.DomainParam; + +import java.util.List; + +/** + * 授权域名Service + * + * @author 科技小王子 + * @since 2024-09-19 23:56:33 + */ +public interface DomainService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(DomainParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(DomainParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return Domain + */ + Domain getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/DomainServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/DomainServiceImpl.java new file mode 100644 index 0000000..9323228 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/DomainServiceImpl.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Domain; +import com.gxwebsoft.common.system.mapper.DomainMapper; +import com.gxwebsoft.common.system.param.DomainParam; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 授权域名Service实现 + * + * @author 科技小王子 + * @since 2024-09-19 23:56:33 + */ +@Service +public class DomainServiceImpl extends ServiceImpl implements DomainService { + + @Override + public PageResult pageRel(DomainParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(DomainParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public Domain getByIdRel(Integer id) { + DomainParam param = new DomainParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/EmailRecordService.java b/src/main/java/com/gxwebsoft/common/system/service/EmailRecordService.java new file mode 100644 index 0000000..b99a195 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/EmailRecordService.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.EmailRecord; + +import javax.mail.MessagingException; +import java.io.IOException; +import java.util.Map; + +/** + * 邮件发送记录Service + * + * @author WebSoft + * @since 2019-06-19 04:07:02 + */ +public interface EmailRecordService extends IService { + + /** + * 发送普通邮件 + * + * @param title 标题 + * @param content 内容 + * @param toEmails 收件人 + */ + void sendTextEmail(String title, String content, String[] toEmails); + + /** + * 发送富文本邮件 + * + * @param title 标题 + * @param html 富文本 + * @param toEmails 收件人 + * @throws MessagingException MessagingException + */ + void sendFullTextEmail(String title, String html, String[] toEmails) throws MessagingException; + + /** + * 发送模板邮件 + * + * @param title 标题 + * @param path 模板路径 + * @param map 填充数据 + * @param toEmails 收件人 + * @throws MessagingException MessagingException + * @throws IOException IOException + */ + void sendHtmlEmail(String title, String path, Map map, String[] toEmails) + throws MessagingException, IOException; + + void sendEmail(String title, String content, String receiver); +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/FileRecordService.java b/src/main/java/com/gxwebsoft/common/system/service/FileRecordService.java new file mode 100644 index 0000000..3dd09ac --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/FileRecordService.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.param.FileRecordParam; + +import java.io.File; +import java.util.List; + +/** + * 文件上传记录Service + * + * @author WebSoft + * @since 2021-08-30 11:20:15 + */ +public interface FileRecordService extends IService { + + /** + * 关联分页查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(FileRecordParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(FileRecordParam param); + + /** + * 根据id查询 + * + * @param id id + * @return FileRecord + */ + FileRecord getByIdRel(Integer id); + + /** + * 根据path查询 + * + * @param path 文件路径 + * @return FileRecord + */ + FileRecord getByIdPath(String path); + + /** + * 异步删除文件 + * + * @param files 文件数组 + */ + void deleteFileAsync(List files); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/LoginRecordService.java b/src/main/java/com/gxwebsoft/common/system/service/LoginRecordService.java new file mode 100644 index 0000000..0c4adbf --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/LoginRecordService.java @@ -0,0 +1,54 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.LoginRecord; +import com.gxwebsoft.common.system.param.LoginRecordParam; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 登录日志Service + * + * @author WebSoft + * @since 2018-12-24 16:10:41 + */ +public interface LoginRecordService extends IService { + + /** + * 关联分页查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(LoginRecordParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(LoginRecordParam param); + + /** + * 根据id查询 + * + * @param id id + * @return LoginRecord + */ + LoginRecord getByIdRel(Integer id); + + /** + * 异步添加 + * + * @param username 用户账号 + * @param type 操作类型 + * @param comments 备注 + * @param tenantId 租户id + * @param request HttpServletRequest + */ + void saveAsync(String username, Integer type, String comments, Integer tenantId, HttpServletRequest request); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/MenuService.java b/src/main/java/com/gxwebsoft/common/system/service/MenuService.java new file mode 100644 index 0000000..db8e967 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/MenuService.java @@ -0,0 +1,18 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.param.MenuParam; + +/** + * 菜单Service + * + * @author WebSoft + * @since 2018-12-24 16:10:31 + */ +public interface MenuService extends IService { + + Boolean cloneMenu(MenuParam param); + + Boolean install(Integer id); +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/OperationRecordService.java b/src/main/java/com/gxwebsoft/common/system/service/OperationRecordService.java new file mode 100644 index 0000000..227c4e6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/OperationRecordService.java @@ -0,0 +1,49 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.OperationRecord; +import com.gxwebsoft.common.system.param.OperationRecordParam; + +import java.util.List; + +/** + * 操作日志Service + * + * @author WebSoft + * @since 2018-12-24 16:10:01 + */ +public interface OperationRecordService extends IService { + + /** + * 关联分页查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OperationRecordParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OperationRecordParam param); + + /** + * 根据id查询 + * + * @param id id + * @return OperationRecord + */ + OperationRecord getByIdRel(Integer id); + + /** + * 异步添加 + * + * @param operationRecord OperationRecord + */ + void saveAsync(OperationRecord operationRecord); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/OrganizationService.java b/src/main/java/com/gxwebsoft/common/system/service/OrganizationService.java new file mode 100644 index 0000000..94d3407 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/OrganizationService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Organization; +import com.gxwebsoft.common.system.param.OrganizationParam; + +import java.util.List; + +/** + * 组织机构Service + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +public interface OrganizationService extends IService { + + /** + * 关联分页查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OrganizationParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OrganizationParam param); + + /** + * 根据id查询 + * + * @param organizationId 机构id + * @return Organization + */ + Organization getByIdRel(Integer organizationId); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/PaymentService.java b/src/main/java/com/gxwebsoft/common/system/service/PaymentService.java new file mode 100644 index 0000000..ae4f571 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/PaymentService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.param.PaymentParam; + +import java.util.List; + +/** + * 支付方式Service + * + * @author 科技小王子 + * @since 2024-05-11 12:39:11 + */ +public interface PaymentService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(PaymentParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(PaymentParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return Payment + */ + Payment getByIdRel(Integer id); + + Payment getByType(PaymentParam paymentParam); +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/PlugService.java b/src/main/java/com/gxwebsoft/common/system/service/PlugService.java new file mode 100644 index 0000000..18d1194 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/PlugService.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Plug; +import com.gxwebsoft.common.system.param.PlugParam; + +import java.util.List; + +/** + * 插件扩展Service + * + * @author 科技小王子 + * @since 2023-05-18 11:57:37 + */ +public interface PlugService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(PlugParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(PlugParam param); + + /** + * 根据id查询 + * + * @param menuId 菜单id + * @return Plug + */ + Plug getByIdRel(Integer menuId); + + Boolean cloneMenu(PlugParam param); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/RoleMenuService.java b/src/main/java/com/gxwebsoft/common/system/service/RoleMenuService.java new file mode 100644 index 0000000..05a3d4f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/RoleMenuService.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.RoleMenu; + +import java.util.List; + +/** + * 角色菜单Service + * + * @author WebSoft + * @since 2018-12-24 16:10:44 + */ +public interface RoleMenuService extends IService { + + /** + * 查询用户对应的菜单 + * + * @param userId 用户id + * @param menuType 菜单类型 + * @return List + */ + List listMenuByUserId(Integer userId, Integer menuType); + + /** + * 查询用户对应的菜单 + * + * @param roleIds 角色id + * @param menuType 菜单类型 + * @return List + */ + List listMenuByRoleIds(List roleIds, Integer menuType); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/RoleService.java b/src/main/java/com/gxwebsoft/common/system/service/RoleService.java new file mode 100644 index 0000000..3e76263 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/RoleService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.Role; + +/** + * 角色Service + * + * @author WebSoft + * @since 2018-12-24 16:10:32 + */ +public interface RoleService extends IService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/SettingService.java b/src/main/java/com/gxwebsoft/common/system/service/SettingService.java new file mode 100644 index 0000000..1478c90 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/SettingService.java @@ -0,0 +1,67 @@ +package com.gxwebsoft.common.system.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Setting; +import com.gxwebsoft.common.system.param.SettingParam; +import com.wechat.pay.java.core.Config; + +import java.util.List; + +/** + * 系统设置Service + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +public interface SettingService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(SettingParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(SettingParam param); + + /** + * 根据id查询 + * + * @param settingId id + * @return Setting + */ + Setting getByIdRel(Integer settingId); + + /** + * 通过key获取设置内容 + * @param key key + * @return Setting + */ + JSONObject getBySettingKey(String key,Integer tenantId); + + /** + * 跨租户获取设置内容 + * @param key 设置键 + * @param tenantId 租户ID + * @return JSONObject + */ + JSONObject getBySettingKeyIgnoreTenant(String key, Integer tenantId); + + Setting getData(String settingKey); + + JSONObject getCache(String key); + + void initConfig(Setting setting); + + Config getConfig(Integer tenantId); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/TenantService.java b/src/main/java/com/gxwebsoft/common/system/service/TenantService.java new file mode 100644 index 0000000..42614a4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/TenantService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Tenant; +import com.gxwebsoft.common.system.param.TenantParam; + +import java.util.List; + +/** + * 租户Service + * + * @author 科技小王子 + * @since 2023-07-17 17:49:53 + */ +public interface TenantService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(TenantParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(TenantParam param); + + /** + * 根据id查询 + * + * @param tenantId 租户id + * @return Tenant + */ + Tenant getByIdRel(Integer tenantId); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserBalanceLogService.java b/src/main/java/com/gxwebsoft/common/system/service/UserBalanceLogService.java new file mode 100644 index 0000000..eaa4e0d --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserBalanceLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserBalanceLog; +import com.gxwebsoft.common.system.param.UserBalanceLogParam; + +import java.util.List; + +/** + * 用户余额变动明细表Service + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ +public interface UserBalanceLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(UserBalanceLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(UserBalanceLogParam param); + + /** + * 根据id查询 + * + * @param logId 主键ID + * @return UserBalanceLog + */ + UserBalanceLog getByIdRel(Integer logId); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserCollectionService.java b/src/main/java/com/gxwebsoft/common/system/service/UserCollectionService.java new file mode 100644 index 0000000..421c682 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserCollectionService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserCollection; +import com.gxwebsoft.common.system.param.UserCollectionParam; + +import java.util.List; + +/** + * 我的收藏Service + * + * @author 科技小王子 + * @since 2024-04-28 18:08:32 + */ +public interface UserCollectionService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(UserCollectionParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(UserCollectionParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return UserCollection + */ + UserCollection getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserCollectionServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/UserCollectionServiceImpl.java new file mode 100644 index 0000000..f5c174f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserCollectionServiceImpl.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserCollection; +import com.gxwebsoft.common.system.mapper.UserCollectionMapper; +import com.gxwebsoft.common.system.param.UserCollectionParam; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 我的收藏Service实现 + * + * @author 科技小王子 + * @since 2024-04-28 18:08:32 + */ +@Service +public class UserCollectionServiceImpl extends ServiceImpl implements UserCollectionService { + + @Override + public PageResult pageRel(UserCollectionParam param) { + PageParam page = new PageParam<>(param); + //page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(UserCollectionParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + //page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public UserCollection getByIdRel(Integer id) { + UserCollectionParam param = new UserCollectionParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserFileService.java b/src/main/java/com/gxwebsoft/common/system/service/UserFileService.java new file mode 100644 index 0000000..2c8abe6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserFileService.java @@ -0,0 +1,14 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.UserFile; + +/** + * 用户文件Service + * + * @author WebSoft + * @since 2022-07-21 14:34:40 + */ +public interface UserFileService extends IService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserRefereeService.java b/src/main/java/com/gxwebsoft/common/system/service/UserRefereeService.java new file mode 100644 index 0000000..e715c4f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserRefereeService.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserReferee; +import com.gxwebsoft.common.system.param.UserRefereeParam; + +import java.util.List; + +/** + * 用户推荐关系表Service + * + * @author 科技小王子 + * @since 2023-10-07 22:56:36 + */ +public interface UserRefereeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(UserRefereeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(UserRefereeParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return UserReferee + */ + UserReferee getByIdRel(Integer id); + + UserReferee check(Integer dealerId, Integer userId); + + UserReferee getByUserId(Integer userId); +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserRoleService.java b/src/main/java/com/gxwebsoft/common/system/service/UserRoleService.java new file mode 100644 index 0000000..c87365b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserRoleService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.entity.UserRole; + +import java.util.List; + +/** + * 用户角色Service + * + * @author WebSoft + * @since 2018-12-24 16:10:35 + */ +public interface UserRoleService extends IService { + + /** + * 批量添加用户角色 + * + * @param userId 用户id + * @param roleIds 角色id集合 + * @return int + */ + int saveBatch(Integer userId, List roleIds); + + /** + * 根据用户id查询角色 + * + * @param userId 用户id + * @return List + */ + List listByUserId(Integer userId); + + /** + * 批量根据用户id查询角色 + * + * @param userIds 用户id集合 + * @return List + */ + List listByUserIds(List userIds); + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/UserService.java b/src/main/java/com/gxwebsoft/common/system/service/UserService.java new file mode 100644 index 0000000..2230d94 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/UserService.java @@ -0,0 +1,123 @@ +package com.gxwebsoft.common.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.param.UserParam; +import org.springframework.security.core.userdetails.UserDetailsService; + +import java.util.List; + +/** + * 用户Service + * + * @author WebSoft + * @since 2018-12-24 16:10:52 + */ +public interface UserService extends IService, UserDetailsService { + + /** + * 关联分页查询用户 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(UserParam param); + + /** + * 关联查询全部用户 + * + * @param param 查询参数 + * @return List + */ + List listRel(UserParam param); + + /** + * 根据id查询用户 + * + * @param userId 用户id + * @return User + */ + User getByIdRel(Integer userId); + + /** + * 根据账号查询用户 + * + * @param username 账号 + * @return User + */ + User getByUsername(String username); + + /** + * 根据账号查询用户 + * + * @param username 账号 + * @param tenantId 租户id + * @return User + */ + User getByUsername(String username, Integer tenantId); + + /** + * 添加用户 + * + * @param user 用户信息 + * @return boolean + */ + boolean saveUser(User user); + + /** + * 修改用户 + * + * @param user 用户信息 + * @return boolean + */ + boolean updateUser(User user); + + /** + * 比较用户密码 + * + * @param dbPassword 数据库存储的密码 + * @param inputPassword 用户输入的密码 + * @return boolean + */ + boolean comparePassword(String dbPassword, String inputPassword); + + /** + * md5加密用户密码 + * + * @param password 密码明文 + * @return 密文 + */ + String encodePassword(String password); + + /** + * 跟进手机号码查询用户 + * @param phone 手机号码 + * @return 用户信息 + */ + User getByPhone(String phone); + + User getByUnionId(UserParam userParam); + + User getByOauthId(UserParam userParam); + + List listStatisticsRel(UserParam param); + + /** + * 更新会员不限租户 + * @param user 用户信息 + */ + void updateByUserId(User user); + + /** + * 根据用户ID查询用户(忽略租户隔离) + * @param userId 用户ID + * @return User + */ + User getByIdIgnoreTenant(Integer userId); + + List pageAdminByPhone(UserParam param); + + List listByAlert(); +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyCommentServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyCommentServiceImpl.java new file mode 100644 index 0000000..e945e66 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyCommentServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyComment; +import com.gxwebsoft.common.system.mapper.CompanyCommentMapper; +import com.gxwebsoft.common.system.param.CompanyCommentParam; +import com.gxwebsoft.common.system.service.CompanyCommentService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用评论Service实现 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Service +public class CompanyCommentServiceImpl extends ServiceImpl implements CompanyCommentService { + + @Override + public PageResult pageRel(CompanyCommentParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CompanyCommentParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CompanyComment getByIdRel(Integer id) { + CompanyCommentParam param = new CompanyCommentParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyContentServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyContentServiceImpl.java new file mode 100644 index 0000000..b8604a6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyContentServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyContent; +import com.gxwebsoft.common.system.mapper.CompanyContentMapper; +import com.gxwebsoft.common.system.param.CompanyContentParam; +import com.gxwebsoft.common.system.service.CompanyContentService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用详情Service实现 + * + * @author 科技小王子 + * @since 2024-10-16 13:41:21 + */ +@Service +public class CompanyContentServiceImpl extends ServiceImpl implements CompanyContentService { + + @Override + public PageResult pageRel(CompanyContentParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CompanyContentParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CompanyContent getByIdRel(Integer id) { + CompanyContentParam param = new CompanyContentParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyGitServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyGitServiceImpl.java new file mode 100644 index 0000000..a954d4b --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyGitServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyGit; +import com.gxwebsoft.common.system.mapper.CompanyGitMapper; +import com.gxwebsoft.common.system.param.CompanyGitParam; +import com.gxwebsoft.common.system.service.CompanyGitService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 代码仓库Service实现 + * + * @author 科技小王子 + * @since 2024-10-19 18:08:51 + */ +@Service +public class CompanyGitServiceImpl extends ServiceImpl implements CompanyGitService { + + @Override + public PageResult pageRel(CompanyGitParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CompanyGitParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CompanyGit getByIdRel(Integer id) { + CompanyGitParam param = new CompanyGitParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyParameterServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyParameterServiceImpl.java new file mode 100644 index 0000000..4b77612 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyParameterServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyParameter; +import com.gxwebsoft.common.system.mapper.CompanyParameterMapper; +import com.gxwebsoft.common.system.param.CompanyParameterParam; +import com.gxwebsoft.common.system.service.CompanyParameterService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用参数Service实现 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Service +public class CompanyParameterServiceImpl extends ServiceImpl implements CompanyParameterService { + + @Override + public PageResult pageRel(CompanyParameterParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CompanyParameterParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CompanyParameter getByIdRel(Integer id) { + CompanyParameterParam param = new CompanyParameterParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyServiceImpl.java new file mode 100644 index 0000000..8bdb503 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyServiceImpl.java @@ -0,0 +1,86 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Company; +import com.gxwebsoft.common.system.mapper.CompanyMapper; +import com.gxwebsoft.common.system.param.CompanyParam; +import com.gxwebsoft.common.system.service.CompanyService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 企业信息Service实现 + * + * @author 科技小王子 + * @since 2023-05-27 14:57:34 + */ +@Service +public class CompanyServiceImpl extends ServiceImpl implements CompanyService { + + @Override + public PageResult pageRel(CompanyParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number desc,create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CompanyParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public Company getByIdRel(Integer companyId) { + CompanyParam param = new CompanyParam(); + param.setCompanyId(companyId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public Company getByTenantIdRel(Integer tenantId) { + CompanyParam param = new CompanyParam(); +// final Company one = param.getOne(baseMapper.selectListRel(param)); + param.setAuthoritative(true); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public PageResult pageRelAll(CompanyParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number desc,create_time desc"); + List list = baseMapper.selectPageRelAll(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public void updateByCompanyId(Company company) { + baseMapper.updateByCompanyId(company); + } + + @Override + public boolean removeCompanyAll(Integer companyId){ + if (baseMapper.removeCompanyAll(companyId)) { + return true; + } + return false; + } + + @Override + public boolean undeleteAll(Integer companyId){ + if (baseMapper.undeleteAll(companyId)) { + return true; + } + return false; + } + + + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyUrlServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyUrlServiceImpl.java new file mode 100644 index 0000000..a7922cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/CompanyUrlServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.CompanyUrl; +import com.gxwebsoft.common.system.mapper.CompanyUrlMapper; +import com.gxwebsoft.common.system.param.CompanyUrlParam; +import com.gxwebsoft.common.system.service.CompanyUrlService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用域名Service实现 + * + * @author 科技小王子 + * @since 2024-10-17 15:30:24 + */ +@Service +public class CompanyUrlServiceImpl extends ServiceImpl implements CompanyUrlService { + + @Override + public PageResult pageRel(CompanyUrlParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(CompanyUrlParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public CompanyUrl getByIdRel(Integer id) { + CompanyUrlParam param = new CompanyUrlParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/DictDataServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/DictDataServiceImpl.java new file mode 100644 index 0000000..e30a0fe --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/DictDataServiceImpl.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.mapper.DictDataMapper; +import com.gxwebsoft.common.system.param.DictDataParam; +import com.gxwebsoft.common.system.service.DictDataService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 字典数据Service实现 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Service +public class DictDataServiceImpl extends ServiceImpl + implements DictDataService { + + @Override + public PageResult pageRel(DictDataParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return new PageResult<>(baseMapper.selectPageRel(page, param), page.getTotal()); + } + + @Override + public List listRel(DictDataParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return page.sortRecords(baseMapper.selectListRel(param)); + } + + @Override + public DictData getByIdRel(Integer dictDataId) { + DictDataParam param = new DictDataParam(); + param.setDictDataId(dictDataId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public DictData getByDictCodeAndName(String dictCode, String dictDataName) { + List list = baseMapper.getByDictCodeAndName(dictCode, dictDataName); + return CommonUtil.listGetOne(list); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/DictServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/DictServiceImpl.java new file mode 100644 index 0000000..6b09f90 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/DictServiceImpl.java @@ -0,0 +1,18 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.Dict; +import com.gxwebsoft.common.system.mapper.DictMapper; +import com.gxwebsoft.common.system.service.DictService; +import org.springframework.stereotype.Service; + +/** + * 字典Service实现 + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +@Service +public class DictServiceImpl extends ServiceImpl implements DictService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryDataServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryDataServiceImpl.java new file mode 100644 index 0000000..0f26dd2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryDataServiceImpl.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.DictionaryData; +import com.gxwebsoft.common.system.mapper.DictionaryDataMapper; +import com.gxwebsoft.common.system.param.DictionaryDataParam; +import com.gxwebsoft.common.system.service.DictionaryDataService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 字典数据Service实现 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Service +public class DictionaryDataServiceImpl extends ServiceImpl + implements DictionaryDataService { + + @Override + public PageResult pageRel(DictionaryDataParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return new PageResult<>(baseMapper.selectPageRel(page, param), page.getTotal()); + } + + @Override + public List listRel(DictionaryDataParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return page.sortRecords(baseMapper.selectListRel(param)); + } + + @Override + public DictionaryData getByIdRel(Integer dictDataId) { + DictionaryDataParam param = new DictionaryDataParam(); + param.setDictDataId(dictDataId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public DictionaryData getByDictCodeAndName(String dictCode, String dictDataName) { + List list = baseMapper.getByDictCodeAndName(dictCode, dictDataName); + return CommonUtil.listGetOne(list); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryServiceImpl.java new file mode 100644 index 0000000..74d6fe1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/DictionaryServiceImpl.java @@ -0,0 +1,18 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.Dictionary; +import com.gxwebsoft.common.system.mapper.DictionaryMapper; +import com.gxwebsoft.common.system.service.DictionaryService; +import org.springframework.stereotype.Service; + +/** + * 字典Service实现 + * + * @author WebSoft + * @since 2020-03-14 11:29:03 + */ +@Service +public class DictionaryServiceImpl extends ServiceImpl implements DictionaryService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/EmailRecordServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/EmailRecordServiceImpl.java new file mode 100644 index 0000000..6cf05fe --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/EmailRecordServiceImpl.java @@ -0,0 +1,99 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.EmailRecord; +import com.gxwebsoft.common.system.mapper.EmailRecordMapper; +import com.gxwebsoft.common.system.service.EmailRecordService; +import org.beetl.core.Configuration; +import org.beetl.core.GroupTemplate; +import org.beetl.core.Template; +import org.beetl.core.resource.ClasspathResourceLoader; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.io.IOException; +import java.util.Map; + +/** + * 邮件发送记录Service实现 + * + * @author WebSoft + * @since 2019-06-19 04:07:54 + */ +@Service +public class EmailRecordServiceImpl extends ServiceImpl + implements EmailRecordService { + // 发件邮箱 + @Value("${spring.mail.username:}") + private String formEmail; + @Autowired(required = false) + private JavaMailSender mailSender; + + @Override + public void sendTextEmail(String title, String content, String[] toEmails) { + if (mailSender == null) { + System.out.println("邮件服务未配置,跳过发送邮件: " + title); + return; + } + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(formEmail); + message.setTo(toEmails); + message.setSubject(title); + message.setText(content); + mailSender.send(message); + } + + @Override + public void sendFullTextEmail(String title, String html, String[] toEmails) throws MessagingException { + if (mailSender == null) { + System.out.println("邮件服务未配置,跳过发送邮件: " + title); + return; + } + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + helper.setFrom(formEmail); + helper.setTo(toEmails); + helper.setSubject(title); + // 发送邮件 + helper.setText(html, true); + mailSender.send(mimeMessage); + } + + @Override + public void sendHtmlEmail(String title, String path, Map map, String[] toEmails) + throws MessagingException, IOException { + ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("templates/"); + Configuration cfg = Configuration.defaultConfiguration(); + GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); + Template t = gt.getTemplate(path); // 加载html模板 + t.binding(map); // 填充数据 + String html = t.render(); // 获得渲染后的html + sendFullTextEmail(title, html, toEmails); // 发送邮件 + } + + @Async + @Override + public void sendEmail(String title, String content, String receiver) { + // 发送邮件通知 + EmailRecord emailRecord = new EmailRecord(); + emailRecord.setTitle(title); + emailRecord.setContent(content); + emailRecord.setReceiver(receiver); + emailRecord.setCreateUserId(42); + if (mailSender != null) { + sendTextEmail(title,content,receiver.split(",")); + } else { + System.out.println("邮件服务未配置,跳过发送邮件: " + title); + } + save(emailRecord); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java new file mode 100644 index 0000000..72c9c27 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/FileRecordServiceImpl.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.mapper.FileRecordMapper; +import com.gxwebsoft.common.system.param.FileRecordParam; +import com.gxwebsoft.common.system.service.FileRecordService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; + +/** + * 文件上传记录Service实现 + * + * @author WebSoft + * @since 2021-08-30 11:21:01 + */ +@Service +public class FileRecordServiceImpl extends ServiceImpl implements FileRecordService { + + @Override + public PageResult pageRel(FileRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return new PageResult<>(baseMapper.selectPageRel(page, param), page.getTotal()); + } + + @Override + public List listRel(FileRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(baseMapper.selectListRel(param)); + } + + @Override + public FileRecord getByIdRel(Integer id) { + FileRecordParam param = new FileRecordParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public FileRecord getByIdPath(String path) { + return CommonUtil.listGetOne(baseMapper.getByIdPath(path)); + } + + @Async + @Override + public void deleteFileAsync(List files) { + for (File file : files) { + try { + file.delete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/LoginRecordServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/LoginRecordServiceImpl.java new file mode 100644 index 0000000..28497e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/LoginRecordServiceImpl.java @@ -0,0 +1,78 @@ +package com.gxwebsoft.common.system.service.impl; + +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.LoginRecord; +import com.gxwebsoft.common.system.mapper.LoginRecordMapper; +import com.gxwebsoft.common.system.param.LoginRecordParam; +import com.gxwebsoft.common.system.service.LoginRecordService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 登录日志Service实现 + * + * @author WebSoft + * @since 2018-12-24 16:10:14 + */ +@Service +public class LoginRecordServiceImpl extends ServiceImpl + implements LoginRecordService { + + @Override + public PageResult pageRel(LoginRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return new PageResult<>(baseMapper.selectPageRel(page, param), page.getTotal()); + } + + @Override + public List listRel(LoginRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(baseMapper.selectListRel(param)); + } + + @Override + public LoginRecord getByIdRel(Integer id) { + LoginRecordParam param = new LoginRecordParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Async + @Override + public void saveAsync(String username, Integer type, String comments, Integer tenantId, + HttpServletRequest request) { + if (username == null) { + return; + } + LoginRecord loginRecord = new LoginRecord(); + loginRecord.setUsername(username); + loginRecord.setLoginType(type); + loginRecord.setComments(comments); + loginRecord.setTenantId(tenantId); + UserAgent ua = UserAgentUtil.parse(ServletUtil.getHeaderIgnoreCase(request, "User-Agent")); + if (ua != null) { + if (ua.getPlatform() != null) { + loginRecord.setOs(ua.getPlatform().toString()); + } + if (ua.getOs() != null) { + loginRecord.setDevice(ua.getOs().toString()); + } + if (ua.getBrowser() != null) { + loginRecord.setBrowser(ua.getBrowser().toString()); + } + } + loginRecord.setIp(ServletUtil.getClientIP(request)); + baseMapper.insert(loginRecord); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/MenuServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/MenuServiceImpl.java new file mode 100644 index 0000000..969aee3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/MenuServiceImpl.java @@ -0,0 +1,139 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.entity.RoleMenu; +import com.gxwebsoft.common.system.mapper.MenuMapper; +import com.gxwebsoft.common.system.param.MenuParam; +import com.gxwebsoft.common.system.service.MenuService; +import com.gxwebsoft.common.system.service.RoleMenuService; +import com.gxwebsoft.common.system.service.RoleService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 菜单Service实现 + * + * @author WebSoft + * @since 2018-12-24 16:10:10 + */ +@Service +public class MenuServiceImpl extends ServiceImpl implements MenuService { + private Integer plugMenuId; + @Resource + private RoleService roleService; + @Resource + private RoleMenuService roleMenuService; + + @Override + @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE) + public Boolean cloneMenu(MenuParam param) { +// System.out.println("准备待克隆的菜单数据 = " + param); + // 删除本项目菜单 + baseMapper.delete(new LambdaQueryWrapper().eq(Menu::getDeleted,0)); + // 顶级栏目 + param.setParentId(0); +// final List list = baseMapper.getMenuByClone(param); +//// final List menuIds = list.stream().map(Menu::getMenuId).collect(Collectors.toList()); + doCloneMenu(baseMapper.getMenuByClone(param)); + return true; + } + + @Override + @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE) + public Boolean install(Integer id) { + // 1.插件绑定的菜单ID + final MenuParam param = new MenuParam(); + param.setMenuId(id); + final List list = baseMapper.getMenuByClone(param); + // TODO 克隆当前插件到顶级菜单 + doCloneMenu(list); + + // 2.查找当前租户的超管权限的roleId + final Role superAdmin = roleService.getOne(new LambdaQueryWrapper().eq(Role::getRoleCode, "superAdmin")); + final Integer roleId = superAdmin.getRoleId(); + final Integer tenantId = superAdmin.getTenantId(); + // 3.勾选菜单根权限 + final RoleMenu roleMenu0 = new RoleMenu(); + roleMenu0.setRoleId(roleId); + roleMenu0.setMenuId(this.plugMenuId); + roleMenuService.save(roleMenu0); + + // 4.勾选根节点下的子菜单权限 + final MenuParam menuParam = new MenuParam(); + menuParam.setParentId(this.plugMenuId); + menuParam.setTenantId(tenantId); + final List menuList = baseMapper.getMenuByClone(menuParam); + menuList.forEach(d->{ + RoleMenu roleMenu = new RoleMenu(); + roleMenu.setRoleId(roleId); + roleMenu.setMenuId(d.getMenuId()); + roleMenuService.save(roleMenu); + }); + // 5.调整新插件的排序 + final Menu menu = baseMapper.selectById(this.plugMenuId); + menu.setSortNumber(100); + baseMapper.updateById(menu); + return true; + } + + // 克隆菜单 + private void doCloneMenu(List list) { + final MenuParam param = new MenuParam(); + list.forEach(d -> { + Menu menu = new Menu(); + menu.setParentId(0); + menu.setTitle(d.getTitle()); + menu.setPath(d.getPath()); + menu.setComponent(d.getComponent()); + menu.setMenuType(d.getMenuType()); + menu.setSortNumber(d.getSortNumber()); + menu.setAuthority(d.getAuthority()); + menu.setIcon(d.getIcon()); + menu.setHide(d.getHide()); + menu.setMeta(d.getMeta()); + save(menu); + this.plugMenuId = menu.getMenuId(); + // 二级菜单 + param.setParentId(d.getMenuId()); + final List list1 = baseMapper.getMenuByClone(param); + list1.forEach(d1 -> { + final Menu menu1 = new Menu(); + menu1.setParentId(menu.getMenuId()); + menu1.setTitle(d1.getTitle()); + menu1.setPath(d1.getPath()); + menu1.setComponent(d1.getComponent()); + menu1.setMenuType(d1.getMenuType()); + menu1.setSortNumber(d1.getSortNumber()); + menu1.setAuthority(d1.getAuthority()); + menu1.setIcon(d1.getIcon()); + menu1.setHide(d1.getHide()); + menu1.setMeta(d1.getMeta()); + save(menu1); + // 三级菜单 + param.setParentId(d1.getMenuId()); + final List list2 = baseMapper.getMenuByClone(param); + list2.forEach(d2 -> { + final Menu menu2 = new Menu(); + menu2.setParentId(menu1.getMenuId()); + menu2.setTitle(d2.getTitle()); + menu2.setPath(d2.getPath()); + menu2.setComponent(d2.getComponent()); + menu2.setMenuType(d2.getMenuType()); + menu2.setSortNumber(d2.getSortNumber()); + menu2.setAuthority(d2.getAuthority()); + menu2.setIcon(d2.getIcon()); + menu2.setHide(d2.getHide()); + menu2.setMeta(d2.getMeta()); + save(menu2); + }); + }); + }); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/OperationRecordServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/OperationRecordServiceImpl.java new file mode 100644 index 0000000..8095bf4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/OperationRecordServiceImpl.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.OperationRecord; +import com.gxwebsoft.common.system.mapper.OperationRecordMapper; +import com.gxwebsoft.common.system.param.OperationRecordParam; +import com.gxwebsoft.common.system.service.OperationRecordService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 操作日志Service实现 + * + * @author WebSoft + * @since 2018-12-24 16:10:02 + */ +@Service +public class OperationRecordServiceImpl extends ServiceImpl + implements OperationRecordService { + + @Override + public PageResult pageRel(OperationRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return new PageResult<>(baseMapper.selectPageRel(page, param), page.getTotal()); + } + + @Override + public List listRel(OperationRecordParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(baseMapper.selectListRel(param)); + } + + @Override + public OperationRecord getByIdRel(Integer id) { + OperationRecordParam param = new OperationRecordParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Async + @Override + public void saveAsync(OperationRecord operationRecord) { + baseMapper.insert(operationRecord); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/OrganizationServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/OrganizationServiceImpl.java new file mode 100644 index 0000000..b2bb53f --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/OrganizationServiceImpl.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Organization; +import com.gxwebsoft.common.system.mapper.OrganizationMapper; +import com.gxwebsoft.common.system.param.OrganizationParam; +import com.gxwebsoft.common.system.service.OrganizationService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 组织机构Service实现 + * + * @author WebSoft + * @since 2020-03-14 11:29:04 + */ +@Service +public class OrganizationServiceImpl extends ServiceImpl + implements OrganizationService { + + @Override + public PageResult pageRel(OrganizationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return new PageResult<>(baseMapper.selectPageRel(page, param), page.getTotal()); + } + + @Override + public List listRel(OrganizationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number"); + return page.sortRecords(baseMapper.selectListRel(param)); + } + + @Override + public Organization getByIdRel(Integer organizationId) { + OrganizationParam param = new OrganizationParam(); + param.setOrganizationId(organizationId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/PaymentServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/PaymentServiceImpl.java new file mode 100644 index 0000000..9e2d798 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/PaymentServiceImpl.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.mapper.PaymentMapper; +import com.gxwebsoft.common.system.param.PaymentParam; +import com.gxwebsoft.common.system.service.PaymentService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 支付方式Service实现 + * + * @author 科技小王子 + * @since 2024-05-11 12:39:11 + */ +@Service +public class PaymentServiceImpl extends ServiceImpl implements PaymentService { + + @Override + public PageResult pageRel(PaymentParam param) { + PageParam page = new PageParam<>(param); + //page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(PaymentParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + //page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public Payment getByIdRel(Integer id) { + PaymentParam param = new PaymentParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public Payment getByType(PaymentParam paymentParam) { + return baseMapper.getByType(paymentParam); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/PlugServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/PlugServiceImpl.java new file mode 100644 index 0000000..aee07a6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/PlugServiceImpl.java @@ -0,0 +1,112 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Plug; +import com.gxwebsoft.common.system.mapper.PlugMapper; +import com.gxwebsoft.common.system.param.PlugParam; +import com.gxwebsoft.common.system.service.PlugService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 插件扩展Service实现 + * + * @author 科技小王子 + * @since 2023-05-18 11:57:37 + */ +@Service +public class PlugServiceImpl extends ServiceImpl implements PlugService { + + @Override + public PageResult pageRel(PlugParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(PlugParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public Plug getByIdRel(Integer menuId) { + PlugParam param = new PlugParam(); + param.setMenuId(menuId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE) + public Boolean cloneMenu(PlugParam param) { +// System.out.println("准备待克隆的菜单数据 = " + param); + // 删除本项目菜单 + baseMapper.delete(new LambdaQueryWrapper().eq(Plug::getDeleted,0)); + // 顶级栏目 + param.setParentId(0); + final List list = baseMapper.getMenuByClone(param); +// final List menuIds = list.stream().map(Menu::getMenuId).collect(Collectors.toList()); + + list.forEach(d -> { + Plug plug = new Plug(); + plug.setParentId(0); + plug.setTitle(d.getTitle()); + plug.setPath(d.getPath()); + plug.setComponent(d.getComponent()); + plug.setMenuType(d.getMenuType()); + plug.setSortNumber(d.getSortNumber()); + plug.setAuthority(d.getAuthority()); + plug.setIcon(d.getIcon()); + plug.setHide(d.getHide()); + plug.setMeta(d.getMeta()); + save(plug); + // 二级菜单 + param.setParentId(d.getMenuId()); + final List list1 = baseMapper.getMenuByClone(param); + list1.forEach(d1 -> { + final Plug menu1 = new Plug(); + menu1.setParentId(plug.getMenuId()); + menu1.setTitle(d1.getTitle()); + menu1.setPath(d1.getPath()); + menu1.setComponent(d1.getComponent()); + menu1.setMenuType(d1.getMenuType()); + menu1.setSortNumber(d1.getSortNumber()); + menu1.setAuthority(d1.getAuthority()); + menu1.setIcon(d1.getIcon()); + menu1.setHide(d1.getHide()); + menu1.setMeta(d1.getMeta()); + save(menu1); + // 三级菜单 + param.setParentId(d1.getMenuId()); + final List list2 = baseMapper.getMenuByClone(param); + list2.forEach(d2 -> { + final Plug menu2 = new Plug(); + menu2.setParentId(menu1.getMenuId()); + menu2.setTitle(d2.getTitle()); + menu2.setPath(d2.getPath()); + menu2.setComponent(d2.getComponent()); + menu2.setMenuType(d2.getMenuType()); + menu2.setSortNumber(d2.getSortNumber()); + menu2.setAuthority(d2.getAuthority()); + menu2.setIcon(d2.getIcon()); + menu2.setHide(d2.getHide()); + menu2.setMeta(d2.getMeta()); + save(menu2); + }); + }); + }); + return true; + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/RoleMenuServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/RoleMenuServiceImpl.java new file mode 100644 index 0000000..737c5e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/RoleMenuServiceImpl.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.Menu; +import com.gxwebsoft.common.system.entity.RoleMenu; +import com.gxwebsoft.common.system.mapper.RoleMenuMapper; +import com.gxwebsoft.common.system.service.RoleMenuService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 角色菜单Service实现 + * + * @author WebSoft + * @since 2018-12-24 16:10:12 + */ +@Service +public class RoleMenuServiceImpl extends ServiceImpl implements RoleMenuService { + + @Override + public List listMenuByUserId(Integer userId, Integer menuType) { + return baseMapper.listMenuByUserId(userId, menuType); + } + + @Override + public List listMenuByRoleIds(List roleIds, Integer menuType) { + return baseMapper.listMenuByRoleIds(roleIds, menuType); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/RoleServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..f543abd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/RoleServiceImpl.java @@ -0,0 +1,18 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.mapper.RoleMapper; +import com.gxwebsoft.common.system.service.RoleService; +import org.springframework.stereotype.Service; + +/** + * 角色服务实现类 + * + * @author WebSoft + * @since 2018-12-24 16:10:11 + */ +@Service +public class RoleServiceImpl extends ServiceImpl implements RoleService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java new file mode 100644 index 0000000..2a7895c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java @@ -0,0 +1,291 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.annotation.IgnoreTenant; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Setting; +import com.gxwebsoft.common.system.mapper.SettingMapper; +import com.gxwebsoft.common.system.param.SettingParam; +import com.gxwebsoft.common.system.service.SettingService; +import com.gxwebsoft.cms.entity.CmsWebsiteField; +import com.gxwebsoft.cms.service.CmsWebsiteFieldService; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAConfig; +import com.wechat.pay.java.service.payments.jsapi.JsapiService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 系统设置Service实现 + * + * @author WebSoft + * @since 2022-11-19 13:54:27 + */ +@Service +public class SettingServiceImpl extends ServiceImpl implements SettingService { + // 本地缓存 + public static Map configMap = new HashMap<>(); + public static JsapiService service = null; + public static Config config = null; + @Resource + private ConfigProperties pathConfig; + @Resource + private StringRedisTemplate stringRedisTemplate; + @Resource + private CmsWebsiteFieldService cmsWebsiteFieldService; + + @Value("${spring.profiles.active:prod}") + private String activeProfile; + @Autowired + private SettingService settingService; + + @Override + public PageResult pageRel(SettingParam param) { + PageParam page = new PageParam<>(param); + //page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(SettingParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + //page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public Setting getByIdRel(Integer settingId) { + SettingParam param = new SettingParam(); + param.setSettingId(settingId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public JSONObject getBySettingKey(String key, Integer tenantId) { + System.out.println("tenantId = " + tenantId); + final JSONObject settingKey = settingService.getBySettingKey("setting_key", tenantId); + System.out.println("settingKey = " + settingKey); + Setting setting = this.getOne(new QueryWrapper().eq("setting_key", key), false); + System.out.println("setting1 = " + setting); + if(setting == null){ + if ("mp-weixin".equals(key)) { + throw new BusinessException("小程序未配置1"); + } + if ("payment".equals(key)) { + throw new BusinessException("支付未配置"); + } + if ("sms".equals(key)) { + throw new BusinessException("短信未配置"); + } + if ("wx-work".equals(key)){ + throw new BusinessException("企业微信未配置"); + } + if ("setting".equals(key)) { + throw new BusinessException("基本信息未配置"); + } + if ("wx-official".equals(key)) { + throw new BusinessException("微信公众号未配置"); + } + if ("printer".equals(key)) { + throw new BusinessException("打印机未配置"); + } + } + return JSON.parseObject(setting.getContent()); + } + + @Override + @IgnoreTenant("跨租户获取指定租户的设置配置") + public JSONObject getBySettingKeyIgnoreTenant(String key, Integer tenantId) { + System.out.println("跨租户查询设置 - key: " + key + ", tenantId: " + tenantId); + final List list = list(new LambdaQueryWrapper().eq(Setting::getTenantId, tenantId)); + System.out.println("list = " + list); + + // 使用跨租户查询,指定租户ID + Setting setting = this.getOne(new QueryWrapper() + .eq("setting_key", key) + .eq("tenant_id", tenantId), false); + + System.out.println("跨租户查询结果: " + setting); + + if(setting == null){ + if ("mp-weixin".equals(key)) { + // 尝试从cms_website_field表中读取微信小程序配置 + JSONObject websiteFieldConfig = getWeixinConfigFromWebsiteField(tenantId); + if (websiteFieldConfig != null) { + System.out.println("从cms_website_field表获取到微信小程序配置: " + websiteFieldConfig); + return websiteFieldConfig; + } + throw new BusinessException("租户 " + tenantId + " 的小程序未配置,请先在系统设置中配置微信小程序信息"); + } + if ("payment".equals(key)) { + throw new BusinessException("租户 " + tenantId + " 的支付未配置"); + } + if ("sms".equals(key)) { + throw new BusinessException("租户 " + tenantId + " 的短信未配置"); + } + if ("wx-work".equals(key)){ + throw new BusinessException("租户 " + tenantId + " 的企业微信未配置"); + } + if ("setting".equals(key)) { + throw new BusinessException("租户 " + tenantId + " 的基本信息未配置"); + } + if ("wx-official".equals(key)) { + throw new BusinessException("租户 " + tenantId + " 的微信公众号未配置"); + } + if ("printer".equals(key)) { + throw new BusinessException("租户 " + tenantId + " 的打印机未配置"); + } + throw new BusinessException("租户 " + tenantId + " 的配置项 " + key + " 未找到"); + } + + return JSON.parseObject(setting.getContent()); + } + + @Override + public Setting getData(String settingKey) { + return query().eq("setting_key", settingKey).one(); + } + + @Override + public JSONObject getCache(String key) { + final String cache = stringRedisTemplate.opsForValue().get(key); + final JSONObject jsonObject = JSONObject.parseObject(cache); + if(jsonObject == null){ + throw new BusinessException("域名未配置"); + } + return jsonObject; + } + + @Override + public void initConfig(Setting data) { + if (data.getSettingKey().equals("payment")) { + final JSONObject jsonObject = JSONObject.parseObject(data.getContent()); + final String mchId = jsonObject.getString("mchId"); + final String apiclientKey = jsonObject.getString("apiclientKey"); + final String privateKey = pathConfig.getUploadPath().concat("file").concat(apiclientKey); + final String apiclientCert = pathConfig.getUploadPath().concat("file").concat(jsonObject.getString("apiclientCert")); + final String merchantSerialNumber = jsonObject.getString("merchantSerialNumber"); + final String apiV3key = jsonObject.getString("wechatApiKey"); + if(config == null){ + // 根据环境选择不同的证书路径配置 + if ("dev".equals(activeProfile)) { + // 开发环境:使用配置文件的upload-path拼接证书路径 - 租户ID 10550 + System.out.println("=== 开发环境:使用配置文件upload-path拼接证书路径 ==="); + String uploadPath = pathConfig.getUploadPath(); // 获取配置的upload-path + String tenantId = "10550"; // 租户ID + String certBasePath = uploadPath + "dev/wechat/" + tenantId + "/"; + String devPrivateKeyPath = certBasePath + "apiclient_key.pem"; + String devCertPath = certBasePath + "apiclient_cert.pem"; + + System.out.println("配置的upload-path: " + uploadPath); + System.out.println("证书基础路径: " + certBasePath); + System.out.println("私钥文件路径: " + devPrivateKeyPath); + System.out.println("证书文件路径: " + devCertPath); + + config = new RSAConfig.Builder() + .merchantId("1246610101") + .privateKeyFromPath(devPrivateKeyPath) + .merchantSerialNumber("2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7") + .wechatPayCertificatesFromPath(devCertPath) + .build(); + System.out.println("开发环境证书路径配置完成"); + } else { + // 生产环境:使用数据库存储的路径 + System.out.println("=== 生产环境:使用数据库存储的证书路径 ==="); + config = new RSAConfig.Builder() + .merchantId(mchId) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(merchantSerialNumber) + .wechatPayCertificatesFromPath(apiclientCert) + .build(); + System.out.println("生产环境证书路径: " + privateKey); + } + configMap.put(data.getTenantId().toString(),config); + System.out.println("当前环境: " + activeProfile); + System.out.println("config = " + config); + } + if (service == null) { + service = new JsapiService.Builder().config(config).build(); + } + } + } + + @Override + public Config getConfig(Integer tenantId) { + if(configMap.get(tenantId.toString()) == null){ + final Setting payment = getOne(new LambdaQueryWrapper().eq(Setting::getSettingKey, "payment")); + this.initConfig(payment); + return configMap.get(tenantId.toString()); + } + return configMap.get(tenantId.toString()); + } + + /** + * 从cms_website_field表中获取微信小程序配置 + * @param tenantId 租户ID + * @return 微信小程序配置JSON对象 + */ + private JSONObject getWeixinConfigFromWebsiteField(Integer tenantId) { + try { + System.out.println("尝试从cms_website_field表获取微信小程序配置 - 租户ID: " + tenantId); + + // 查询AppID + CmsWebsiteField appIdField = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, "AppID") + .eq(CmsWebsiteField::getTenantId, tenantId) + .eq(CmsWebsiteField::getDeleted, 0) + ); + + // 查询AppSecret + CmsWebsiteField appSecretField = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, "AppSecret") + .eq(CmsWebsiteField::getTenantId, tenantId) + .eq(CmsWebsiteField::getDeleted, 0) + ); + + System.out.println("AppID字段查询结果: " + appIdField); + System.out.println("AppSecret字段查询结果: " + appSecretField); + + if (appIdField != null && appSecretField != null + && appIdField.getValue() != null && !appIdField.getValue().trim().isEmpty() + && appSecretField.getValue() != null && !appSecretField.getValue().trim().isEmpty()) { + + // 构建微信小程序配置JSON + JSONObject config = new JSONObject(); + config.put("appId", appIdField.getValue().trim()); + config.put("appSecret", appSecretField.getValue().trim()); + + System.out.println("成功从cms_website_field表构建微信小程序配置: " + config); + return config; + } else { + System.out.println("cms_website_field表中未找到完整的AppID和AppSecret配置"); + return null; + } + + } catch (Exception e) { + System.err.println("从cms_website_field表获取微信小程序配置异常: " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java new file mode 100644 index 0000000..99c423e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/TenantServiceImpl.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.mapper.TenantMapper; +import com.gxwebsoft.common.system.service.TenantService; +import com.gxwebsoft.common.system.entity.Tenant; +import com.gxwebsoft.common.system.param.TenantParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 租户Service实现 + * + * @author 科技小王子 + * @since 2023-07-17 17:49:53 + */ +@Service +public class TenantServiceImpl extends ServiceImpl implements TenantService { + + @Override + public PageResult pageRel(TenantParam param) { + PageParam page = new PageParam<>(param); + //page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(TenantParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + //page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public Tenant getByIdRel(Integer tenantId) { + TenantParam param = new TenantParam(); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/UserBalanceLogServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/UserBalanceLogServiceImpl.java new file mode 100644 index 0000000..2286471 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/UserBalanceLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserBalanceLog; +import com.gxwebsoft.common.system.mapper.UserBalanceLogMapper; +import com.gxwebsoft.common.system.param.UserBalanceLogParam; +import com.gxwebsoft.common.system.service.UserBalanceLogService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户余额变动明细表Service实现 + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ +@Service +public class UserBalanceLogServiceImpl extends ServiceImpl implements UserBalanceLogService { + + @Override + public PageResult pageRel(UserBalanceLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(UserBalanceLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public UserBalanceLog getByIdRel(Integer logId) { + UserBalanceLogParam param = new UserBalanceLogParam(); + param.setLogId(logId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/UserFileServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/UserFileServiceImpl.java new file mode 100644 index 0000000..b5712c3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/UserFileServiceImpl.java @@ -0,0 +1,18 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.mapper.UserFileMapper; +import com.gxwebsoft.common.system.service.UserFileService; +import com.gxwebsoft.common.system.entity.UserFile; +import org.springframework.stereotype.Service; + +/** + * 用户文件Service实现 + * + * @author WebSoft + * @since 2022-07-21 14:34:40 + */ +@Service +public class UserFileServiceImpl extends ServiceImpl implements UserFileService { + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/UserRefereeServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/UserRefereeServiceImpl.java new file mode 100644 index 0000000..3055b98 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/UserRefereeServiceImpl.java @@ -0,0 +1,74 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserReferee; +import com.gxwebsoft.common.system.mapper.UserRefereeMapper; +import com.gxwebsoft.common.system.param.UserRefereeParam; +import com.gxwebsoft.common.system.service.UserRefereeService; +import com.gxwebsoft.common.system.service.UserService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用户推荐关系表Service实现 + * + * @author 科技小王子 + * @since 2023-10-07 22:56:36 + */ +@Service +public class UserRefereeServiceImpl extends ServiceImpl implements UserRefereeService { + + @Resource + private UserService userService; + + @Override + public PageResult pageRel(UserRefereeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + for (UserReferee userReferee : list) { + userReferee.setUser(userService.getById(userReferee.getUserId())); + } + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(UserRefereeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public UserReferee getByIdRel(Integer id) { + UserRefereeParam param = new UserRefereeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public UserReferee check(Integer dealerId, Integer userId) { + return getOne( + new LambdaQueryWrapper() + .eq(UserReferee::getDealerId, dealerId) + .eq(UserReferee::getUserId, userId) + ); + } + + @Override + public UserReferee getByUserId(Integer userId) { + return getOne( + new LambdaQueryWrapper() + .eq(UserReferee::getUserId, userId) + .last("limit 1") + ); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/UserRoleServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/UserRoleServiceImpl.java new file mode 100644 index 0000000..a2a3d11 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/UserRoleServiceImpl.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.common.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.entity.UserRole; +import com.gxwebsoft.common.system.mapper.UserRoleMapper; +import com.gxwebsoft.common.system.service.UserRoleService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户角色Service实现 + * + * @author WebSoft + * @since 2018-12-24 16:10:36 + */ +@Service +public class UserRoleServiceImpl extends ServiceImpl implements UserRoleService { + + + @Override + public int saveBatch(Integer userId, List roleIds) { + return baseMapper.insertBatch(userId, roleIds); + } + + @Override + public List listByUserId(Integer userId) { + return baseMapper.selectByUserId(userId); + } + + @Override + public List listByUserIds(List userIds) { + return baseMapper.selectByUserIds(userIds); + } + +} diff --git a/src/main/java/com/gxwebsoft/common/system/service/impl/UserServiceImpl.java b/src/main/java/com/gxwebsoft/common/system/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..7a04c19 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/service/impl/UserServiceImpl.java @@ -0,0 +1,258 @@ +package com.gxwebsoft.common.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.cms.param.CmsWebsiteParam; +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.Company; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.entity.UserRole; +import com.gxwebsoft.common.system.mapper.UserMapper; +import com.gxwebsoft.common.system.param.CompanyParam; +import com.gxwebsoft.common.system.param.UserParam; +import com.gxwebsoft.common.system.service.CompanyService; +import com.gxwebsoft.common.system.service.RoleMenuService; +import com.gxwebsoft.common.system.service.UserRoleService; +import com.gxwebsoft.common.system.service.UserService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 用户Service实现 + * + * @author WebSoft + * @since 2018-12-24 16:10:14 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + @Resource + private UserRoleService userRoleService; + @Resource + private RoleMenuService roleMenuService; + @Resource + private BCryptPasswordEncoder bCryptPasswordEncoder; + @Resource + private CmsWebsiteService cmsWebsiteService; + @Resource + private CompanyService companyService; + @Resource + private RedisUtil redisUtil; + + @Override + public PageResult pageRel(UserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + // 查询用户的角色 + selectUserRoles(list); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(UserParam param) { + List list = baseMapper.selectListRel(param); + // 查询用户的角色 + selectUserRoles(list); + // 排序 + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public User getByIdRel(Integer userId) { + UserParam param = new UserParam(); + param.setUserId(userId); + User user = param.getOne(baseMapper.selectListRel(param)); + if (user != null) { + user.setRoles(userRoleService.listByUserId(user.getUserId())); + user.setAuthorities(roleMenuService.listMenuByUserId(user.getUserId(), null)); + // 系统配置信息 +// Map map = new HashMap<>(); + // 1)云存储 +// String key = "setting:upload:" + user.getTenantId(); +// final String upload = redisUtil.get(key); +// if(upload != null){ +// final JSONObject object = JSONObject.parseObject(upload); +// map.put("uploadMethod",object.getString("uploadMethod")); +// map.put("bucketDomain",object.getString("bucketDomain")); +// map.put("fileUrl",object.getString("fileUrl") + "/"); +// user.setSystem(map); +// } + } + return user; + } + + @Override + public User getByUsername(String username) { + return getByUsername(username, null); + } + + @Override + public User getByUsername(String username, Integer tenantId) { + if (StrUtil.isBlank(username)) { + return null; + } + User user = baseMapper.selectByUsername(username, tenantId); + if (user != null) { + user.setRoles(userRoleService.listByUserId(user.getUserId())); + user.setAuthorities(roleMenuService.listMenuByUserId(user.getUserId(), null)); + } + return user; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return getByUsername(username); + } + + @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE) + @Override + public boolean saveUser(User user) { + if (StrUtil.isNotEmpty(user.getUsername()) && baseMapper.selectCount(new LambdaQueryWrapper() + .eq(User::getUsername, user.getUsername())) > 0) { + throw new BusinessException("账号已存在"); + } + if (StrUtil.isNotEmpty(user.getPhone()) && baseMapper.selectCount(new LambdaQueryWrapper() + .eq(User::getPhone, user.getPhone())) > 0) { + throw new BusinessException("手机号已存在"); + } + if (StrUtil.isNotEmpty(user.getEmail()) && baseMapper.selectCount(new LambdaQueryWrapper() + .eq(User::getEmail, user.getEmail())) > 0) { + throw new BusinessException("邮箱已存在"); + } + boolean result = baseMapper.insert(user) > 0; + if (result && user.getRoles() != null && user.getRoles().size() > 0) { + List roleIds = user.getRoles().stream().map(Role::getRoleId).collect(Collectors.toList()); + if (userRoleService.saveBatch(user.getUserId(), roleIds) < roleIds.size()) { + throw new BusinessException("用户角色添加失败"); + } + } + return result; + } + + @Transactional(rollbackFor = {Exception.class}) + @Override + public boolean updateUser(User user) { + if (StrUtil.isNotEmpty(user.getUsername()) && baseMapper.selectCount(new LambdaQueryWrapper() + .eq(User::getUsername, user.getUsername()) + .ne(User::getUserId, user.getUserId())) > 0) { + throw new BusinessException("账号已存在"); + } + if (StrUtil.isNotEmpty(user.getPhone()) && baseMapper.selectCount(new LambdaQueryWrapper() + .eq(User::getPhone, user.getPhone()) + .ne(User::getUserId, user.getUserId())) > 0) { + throw new BusinessException("手机号已存在"); + } + if (StrUtil.isNotEmpty(user.getEmail()) && baseMapper.selectCount(new LambdaQueryWrapper() + .eq(User::getEmail, user.getEmail()) + .ne(User::getUserId, user.getUserId())) > 0) { + throw new BusinessException("邮箱已存在"); + } + boolean result = baseMapper.updateById(user) > 0; + if (result && user.getRoles() != null && user.getRoles().size() > 0) { + userRoleService.remove(new LambdaUpdateWrapper().eq(UserRole::getUserId, user.getUserId())); + List roleIds = user.getRoles().stream().map(Role::getRoleId).collect(Collectors.toList()); + if (userRoleService.saveBatch(user.getUserId(), roleIds) < roleIds.size()) { + throw new BusinessException("用户角色添加失败"); + } + } + return result; + } + + @Override + public boolean comparePassword(String dbPassword, String inputPassword) { + return bCryptPasswordEncoder.matches(inputPassword, dbPassword); + } + + @Override + public String encodePassword(String password) { + return password == null ? null : bCryptPasswordEncoder.encode(password); + } + + @Override + public User getByPhone(String phone) { + return query().eq("phone", phone).one(); + } + + @Override + public User getByUnionId(UserParam param) { + return param.getOne(baseMapper.getOne(param)); + } + + @Override + public User getByOauthId(UserParam userParam) { + return userParam.getOne(baseMapper.getOne(userParam)); + } + + @Override + public List listStatisticsRel(UserParam param) { + List list = baseMapper.selectListStatisticsRel(param); + return list; + } + + /** + * 更新用户信息(跨租户) + * + * @param user 用户信息 + */ + @Override + public void updateByUserId(User user) { + baseMapper.updateByUserId(user); + } + + @Override + public List pageAdminByPhone(UserParam param) { + return baseMapper.pageAdminByPhone(param); + } + + @Override + public List listByAlert() { + return baseMapper.listByAlert(); + } + + @Override + public User getByIdIgnoreTenant(Integer userId) { + if (userId == null) { + return null; + } + return baseMapper.selectByIdIgnoreTenant(userId); + } + + /** + * 批量查询用户的角色 + * + * @param users 用户集合 + */ + private void selectUserRoles(List users) { + if (users != null && users.size() > 0) { + List userIds = users.stream().map(User::getUserId).collect(Collectors.toList()); + List userRoles = userRoleService.listByUserIds(userIds); + for (User user : users) { + List roles = userRoles.stream().filter(d -> user.getUserId().equals(d.getUserId())) + .collect(Collectors.toList()); + user.setRoles(roles); + } + } + } +} diff --git a/src/main/java/com/gxwebsoft/enterprise/controller/EnterpriseController.java b/src/main/java/com/gxwebsoft/enterprise/controller/EnterpriseController.java new file mode 100644 index 0000000..819568a --- /dev/null +++ b/src/main/java/com/gxwebsoft/enterprise/controller/EnterpriseController.java @@ -0,0 +1,147 @@ +package com.gxwebsoft.enterprise.controller; + +import cn.hutool.core.util.ObjectUtil; +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.extension.plugins.pagination.Page; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.enterprise.entity.Enterprise; +import com.gxwebsoft.enterprise.service.EnterpriseService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * + * @author GIIT-YC + * + */ +@Tag(name = "企业信息管理") +@RestController +@RequestMapping("/api/enterprise/enterprise") +public class EnterpriseController extends BaseController { + + @Resource + private EnterpriseService enterpriseService; + +// @PreAuthorize("hasAuthority('enterprise:enterprise:list')") + @Operation(summary = "分页查询企业信息") + @GetMapping("/page") + public ApiResult> page(Enterprise enterprise) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(StrUtil.isNotBlank(enterprise.getName()), Enterprise::getName, enterprise.getName()); + wrapper.like(StrUtil.isNotBlank(enterprise.getCreditCode()), Enterprise::getCreditCode, enterprise.getCreditCode()); + wrapper.orderByAsc(Enterprise::getName); + + final Page page = new Page<>(enterprise.getPage(), enterprise.getLimit()); + final IPage p = enterpriseService.page(page, wrapper); + + return success(new PageResult(p.getRecords(), p.getTotal())); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:list')") + @Operation(summary = "查询全部企业信息") + @GetMapping("/list") + public ApiResult> list(Enterprise enterprise) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(StrUtil.isNotBlank(enterprise.getName()), Enterprise::getName, enterprise.getName()); + wrapper.like(StrUtil.isNotBlank(enterprise.getCreditCode()), Enterprise::getCreditCode, enterprise.getCreditCode()); + return success(enterpriseService.list(wrapper)); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:list')") + @Operation(summary = "根据id查询企业信息") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(enterpriseService.getById(id)); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:list')") + @Operation(summary = "根据CreditCode查询企业信息") + @GetMapping("/creditCode/{creditCode}") + public ApiResult getByCreditCode(@PathVariable("creditCode") String creditCode) { + Enterprise enterprise = enterpriseService.getOne(new LambdaQueryWrapper().eq(Enterprise::getCreditCode, creditCode)); + return success(enterprise); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:save')") + @OperationLog + @Operation(summary = "添加企业信息") + @PostMapping() + public ApiResult save(@RequestBody Enterprise enterprise) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + enterprise.setUserId(loginUser.getUserId()); + final Enterprise one = enterpriseService.getOne(new LambdaQueryWrapper().eq(Enterprise::getCreditCode, enterprise.getCreditCode())); + if (!ObjectUtil.isEmpty(one)) { + return fail("企业统一信用代码已存在"); + } + if (enterpriseService.save(enterprise)) { + //TODO 查询知识库(kb_name=enterprise.getCreditCode) + + //TODO 新建知识库 + String kbId = "pggi9mpair"; + + //绑定知识库 + enterprise.setKbId(kbId); + enterpriseService.updateById(enterprise); + return success("添加成功"); + } + } + return fail("添加失败"); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:update')") + @OperationLog + @Operation(summary = "修改企业信息") + @PutMapping() + public ApiResult update(@RequestBody Enterprise enterprise) { + if(StrUtil.isEmpty(enterprise.getKbId())) { + //TODO 查询知识库 + + //TODO 新建知识库 + String kbId = "pggi9mpair"; + + //绑定知识库 + enterprise.setKbId(kbId); + } + if (enterpriseService.updateById(enterprise)) { + return success("修改成功"); + } + return fail("修改失败"); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:remove')") + @OperationLog + @Operation(summary = "删除企业信息") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (enterpriseService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +// @PreAuthorize("hasAuthority('enterprise:enterprise:remove')") + @OperationLog + @Operation(summary = "批量删除企业信息") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (enterpriseService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/enterprise/entity/Enterprise.java b/src/main/java/com/gxwebsoft/enterprise/entity/Enterprise.java new file mode 100644 index 0000000..9e99efd --- /dev/null +++ b/src/main/java/com/gxwebsoft/enterprise/entity/Enterprise.java @@ -0,0 +1,67 @@ +package com.gxwebsoft.enterprise.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.gxwebsoft.common.core.web.BaseParam; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 企业信息 + * @author GIIT-YC + */ +@Data +@TableName("enterprise") +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Enterprise对象", description = "企业信息表") +public class Enterprise extends BaseParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "企业名称") + private String name; + + @Schema(description = "统一代码") + private String creditCode; + + @Schema(description = "企业性质(国企、行政事业单位、民间非营利组织)") + private String enterpriseType; + + @Schema(description = "所属行业(使用插件)") + private String industry; + + @Schema(description = "知识库ID") + private String kbId; + + @Schema(description = "客户ID") + private Integer userId; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/enterprise/mapper/EnterpriseMapper.java b/src/main/java/com/gxwebsoft/enterprise/mapper/EnterpriseMapper.java new file mode 100644 index 0000000..33ca945 --- /dev/null +++ b/src/main/java/com/gxwebsoft/enterprise/mapper/EnterpriseMapper.java @@ -0,0 +1,13 @@ +package com.gxwebsoft.enterprise.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.enterprise.entity.Enterprise; + +/** + * + * @author GIIT-YC + * + */ +public interface EnterpriseMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gxwebsoft/enterprise/mapper/xml/EnterpriseMapper.xml b/src/main/java/com/gxwebsoft/enterprise/mapper/xml/EnterpriseMapper.xml new file mode 100644 index 0000000..b543162 --- /dev/null +++ b/src/main/java/com/gxwebsoft/enterprise/mapper/xml/EnterpriseMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/java/com/gxwebsoft/enterprise/service/EnterpriseService.java b/src/main/java/com/gxwebsoft/enterprise/service/EnterpriseService.java new file mode 100644 index 0000000..daf3ff8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/enterprise/service/EnterpriseService.java @@ -0,0 +1,13 @@ +package com.gxwebsoft.enterprise.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.enterprise.entity.Enterprise; + +/** + * + * @author GIIT-YC + * + */ +public interface EnterpriseService extends IService { + +} diff --git a/src/main/java/com/gxwebsoft/enterprise/service/impl/EnterpriseServiceImpl.java b/src/main/java/com/gxwebsoft/enterprise/service/impl/EnterpriseServiceImpl.java new file mode 100644 index 0000000..a6eedda --- /dev/null +++ b/src/main/java/com/gxwebsoft/enterprise/service/impl/EnterpriseServiceImpl.java @@ -0,0 +1,17 @@ +package com.gxwebsoft.enterprise.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.enterprise.entity.Enterprise; +import com.gxwebsoft.enterprise.mapper.EnterpriseMapper; +import com.gxwebsoft.enterprise.service.EnterpriseService; +import org.springframework.stereotype.Service; + +/** + * + * @author GIIT-YC + * + */ +@Service +public class EnterpriseServiceImpl extends ServiceImpl implements EnterpriseService { + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmBxLogController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmBxLogController.java new file mode 100644 index 0000000..7456c7a --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmBxLogController.java @@ -0,0 +1,174 @@ +package com.gxwebsoft.hjm.controller; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.utils.FileServerUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.service.FileRecordService; +import com.gxwebsoft.hjm.service.HjmBxLogService; +import com.gxwebsoft.hjm.entity.HjmBxLog; +import com.gxwebsoft.hjm.param.HjmBxLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.List; + +/** + * 黄家明_报险记录控制器 + * + * @author 科技小王子 + * @since 2025-06-06 13:08:29 + */ +@Tag(name = "黄家明_报险记录管理") +@RestController +@RequestMapping("/api/hjm/hjm-bx-log") +public class HjmBxLogController extends BaseController { + @Resource + private ConfigProperties config; + @Resource + private HjmBxLogService hjmBxLogService; + + @Operation(summary = "分页查询黄家明_报险记录") + @GetMapping("/page") + public ApiResult> page(HjmBxLogParam param) { + // 使用关联查询 + return success(hjmBxLogService.pageRel(param)); + } + + @Operation(summary = "查询全部黄家明_报险记录") + @GetMapping() + public ApiResult> list(HjmBxLogParam param) { + // 使用关联查询 + return success(hjmBxLogService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_报险记录") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmBxLogService.getByIdRel(id)); + } + + @Operation(summary = "添加黄家明_报险记录") + @PostMapping() + public ApiResult save(@RequestBody HjmBxLog hjmBxLog) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + hjmBxLog.setUserId(loginUser.getUserId()); + } + if (hjmBxLogService.save(hjmBxLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmBxLog:update')") + @OperationLog + @Operation(summary = "修改黄家明_报险记录") + @PutMapping() + public ApiResult update(@RequestBody HjmBxLog hjmBxLog) { + if (hjmBxLogService.updateById(hjmBxLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmBxLog:remove')") + @OperationLog + @Operation(summary = "删除黄家明_报险记录") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmBxLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmBxLog:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_报险记录") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmBxLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmBxLog:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_报险记录") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmBxLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmBxLog:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_报险记录") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmBxLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "上传文件") + @PostMapping("/upload") + public ApiResult upload(@RequestParam MultipartFile file, HttpServletRequest request) { + FileRecord result = null; + try { + String dir = getUploadDir(); + File upload = FileServerUtil.upload(file, dir, config.getUploadUuidName()); + String path = upload.getAbsolutePath().replace("\\", "/").substring(dir.length() - 1); + // String requestURL = StrUtil.removeSuffix(request.getRequestURL(), "/upload"); + String requestURL = config.getFileServer() + "/api/file"; + String originalName = file.getOriginalFilename(); + result = new FileRecord(); + result.setCreateUserId(getLoginUserId()); + result.setName(StrUtil.isBlank(originalName) ? upload.getName() : originalName); + result.setLength(upload.length()); + result.setPath(path); + result.setUrl(requestURL + path); + String contentType = FileServerUtil.getContentType(upload); + result.setContentType(contentType); + if (FileServerUtil.isImage(contentType)) { + result.setThumbnail(requestURL + "/thumbnail" + path); + } + result.setUrl(path); + System.out.println("result = " + result); + return success(result); + } catch (Exception e) { + e.printStackTrace(); + return fail("上传失败", result).setError(e.toString()); + } + } + + /** + * 文件上传位置(服务器) + */ + private String getUploadDir() { + return config.getUploadPath() + "file/"; + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmCarController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmCarController.java new file mode 100644 index 0000000..835ef14 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmCarController.java @@ -0,0 +1,405 @@ +package com.gxwebsoft.hjm.controller; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; + +import java.util.ArrayList; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.param.DictDataParam; +import com.gxwebsoft.common.system.service.DictDataService; +import com.gxwebsoft.hjm.service.HjmCarService; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.param.HjmCarParam; +import com.gxwebsoft.hjm.param.HjmCarImportParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.hjm.service.GpsDiagnosticService; +import com.gxwebsoft.hjm.service.HjmFenceService; +import com.gxwebsoft.hjm.service.HjmGpsLogService; +import com.gxwebsoft.hjm.service.MqttService; +import com.gxwebsoft.hjm.service.impl.HjmCarServiceImpl; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.apache.poi.ss.usermodel.Workbook; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + + +/** + * 黄家明_车辆管理控制器 + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +@Tag(name = "黄家明_车辆管理管理") +@RestController +@RequestMapping("/api/hjm/hjm-car") +public class HjmCarController extends BaseController { + private static final Logger logger = LoggerFactory.getLogger(PushCallback.class); + @Resource + private HjmCarService hjmCarService; + @Resource + private HjmFenceService hjmFenceService; + @Resource + private DictDataService dictDataService; + @Resource + private HjmGpsLogService hjmGpsLogService; + @Resource + private HjmCarServiceImpl hjmCarServiceImpl; + @Resource + private MqttService mqttService; + @Resource + private GpsDiagnosticService gpsDiagnosticService; + + @Operation(summary = "分页查询黄家明_车辆管理") + @GetMapping("/page") + public ApiResult> page(HjmCarParam param) { + return success(hjmCarService.pageRel(param)); + } + + @Operation(summary = "查询全部黄家明_车辆管理") + @GetMapping() + public ApiResult> list(HjmCarParam param) { + // 使用关联查询 + return success(hjmCarService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_车辆管理") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmCarService.getByIdRel(id)); + } + + @Operation(summary = "根据code查询车辆") + @GetMapping("/getByCode/{code}") + public ApiResult getByCode(@PathVariable("code") String code) { + return success(hjmCarService.getByCode(code)); + } + + @PreAuthorize("hasAuthority('hjm:hjmCar:save')") + @OperationLog + @Operation(summary = "添加黄家明_车辆管理") + @PostMapping() + public ApiResult save(@RequestBody HjmCar hjmCar) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + hjmCar.setUserId(loginUser.getUserId()); + } + if (hjmCarService.save(hjmCar)) { + // 重新生成编号 + hjmCar.setCode(hjmCar.getCode().concat(hjmCar.getId().toString())); + hjmCarService.updateById(hjmCar); + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改黄家明_车辆管理") + @PutMapping() + public ApiResult update(@RequestBody HjmCar hjmCar) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + // 判断GPS设备是否被绑定 + final HjmCar byGpsNo = hjmCarService.getByGpsNo(hjmCar.getGpsNo()); + if(byGpsNo != null && byGpsNo.getInstallerId().equals(1)){ + return fail("该GPS设备已被绑定"); + } + hjmCar.setDriverName(loginUser.getRealName()); + hjmCar.setUserId(loginUser.getUserId()); + if (hjmCarService.updateById(hjmCar)) { + return success("绑定成功"); + } + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCar:remove')") + @OperationLog + @Operation(summary = "删除黄家明_车辆管理") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmCarService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCar:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_车辆管理") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmCarService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCar:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_车辆管理") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmCarService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCar:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_车辆管理") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmCarService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + + /** + * excel批量导入车辆 + * Excel表头格式:车辆名称、车辆图片、类型、管理负责人、车辆编号、司机ID、保险状态、GPS设备编号、电子围栏ID、电子围栏名称、位置、纬度、经度、区域、地址、组织ID、父级组织ID、微信小程序码、用户ID、排序、备注、状态 + */ + @PreAuthorize("hasAuthority('hjm:hjmCar:save')") + @Transactional(rollbackFor = {Exception.class}) + @Operation(summary = "批量导入车辆") + @PostMapping("/import") + public ApiResult> importBatch(MultipartFile file) { + ImportParams importParams = new ImportParams(); + List errorMessages = new ArrayList<>(); + int successCount = 0; + + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), HjmCarImportParam.class, importParams); + + // 获取当前登录用户 + User loginUser = getLoginUser(); + Integer currentUserId = loginUser != null ? loginUser.getUserId() : null; + + for (int i = 0; i < list.size(); i++) { + HjmCarImportParam param = list.get(i); + try { + // 手动转换对象,避免JSON序列化问题 + HjmCar item = convertImportParamToEntity(param); + + // 设置必填字段的默认值 + if (item.getUserId() == null && currentUserId != null) { + item.setUserId(currentUserId); + } + if (item.getStatus() == null) { + item.setStatus(0); // 默认状态为正常 + } + if (item.getDeleted() == null) { + item.setDeleted(0); // 默认未删除 + } + if (item.getType() == null) { + item.setType(0); // 默认类型为汽车 + } + + // 验证必填字段 + if (item.getCode() == null) { + errorMessages.add("第" + (i + 1) + "行:车辆编号不能为空"); + continue; + } + // 保存数据 + if (hjmCarService.save(item)) { + successCount++; + } else { + errorMessages.add("第" + (i + 1) + "行:保存失败"); + } + + } catch (Exception e) { + errorMessages.add("第" + (i + 1) + "行:" + e.getMessage()); + System.err.println("导入第" + (i + 1) + "行数据失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + // 返回结果 + if (errorMessages.isEmpty()) { + return success("成功导入" + successCount + "条数据", null); + } else { + return success("导入完成,成功" + successCount + "条,失败" + errorMessages.size() + "条", errorMessages); + } + + } catch (Exception e) { + System.err.println("批量导入车辆失败: " + e.getMessage()); + e.printStackTrace(); + return fail("导入失败:" + e.getMessage(), null); + } + } + + /** + * 下载车辆导入模板 + */ + @Operation(summary = "下载车辆导入模板") + @GetMapping("/import/template") + public void downloadTemplate(HttpServletResponse response) throws IOException { + // 创建空的导入参数列表作为模板 + List templateList = new ArrayList<>(); + + // 添加一行示例数据 + HjmCarImportParam example = new HjmCarImportParam(); + example.setName("示例车辆"); + example.setType(0); + example.setKuaidiAdmin("管理员"); + example.setCode("CAR"); + example.setInsuranceStatus("正常"); + example.setGpsNo("GPS001"); + example.setDistrict("示例区域"); + example.setStatus(0); + example.setComments("这是示例数据,请删除后填入真实数据"); + templateList.add(example); + + // 设置导出参数 + ExportParams exportParams = new ExportParams("车辆导入模板", "车辆信息"); + + // 生成Excel + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, HjmCarImportParam.class, templateList); + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=car_import_template.xlsx"); + + // 输出到响应流 + workbook.write(response.getOutputStream()); + workbook.close(); + } + + /** + * 将HjmCarImportParam转换为HjmCar实体 + */ + private HjmCar convertImportParamToEntity(HjmCarImportParam param) { + HjmCar entity = new HjmCar(); + + // 基本字段转换 + entity.setName(param.getName()); + entity.setImage(param.getImage()); + entity.setType(param.getType()); + entity.setKuaidiAdmin(param.getKuaidiAdmin()); + entity.setCode(param.getCode()); + entity.setDriverId(param.getDriverId()); + entity.setInsuranceStatus(param.getInsuranceStatus()); + entity.setGpsNo(param.getGpsNo()); + entity.setFenceId(param.getFenceId()); + entity.setFenceName(param.getFenceName()); + entity.setLocation(param.getLocation()); + entity.setLatitude(param.getLatitude()); + entity.setLongitude(param.getLongitude()); + entity.setDistrict(param.getDistrict()); + entity.setAddress(param.getAddress()); + entity.setOrganizationId(param.getOrganizationId()); + entity.setOrganizationParentId(param.getOrganizationParentId()); + entity.setMpCode(param.getMpCode()); + entity.setUserId(param.getUserId()); + entity.setSortNumber(param.getSortNumber()); + entity.setComments(param.getComments()); + entity.setStatus(param.getStatus()); + + return entity; + } + + @Operation(summary = "根据坐标解析地址(腾讯地图)") + @GetMapping("/pageByQQMap") + public ApiResult> pageByQQMap(HjmCarParam param) { + final DictDataParam dictDataParam = new DictDataParam(); + dictDataParam.setDictCode("QQMapKey"); +// final List dictDataList = dictDataService.listRel(dictDataParam); +// if (!CollectionUtils.isEmpty(dictDataList)) { +// final DictData dictData = dictDataList.get(0); + final String API_KEY = "RDABZ-IF7AB-L4AUO-JHMX3-GBSGE-KIF53"; + String url = "https://apis.map.qq.com/ws/geocoder/v1/?location=".concat(param.getLatitude()).concat(",").concat(param.getLongitude()).concat("&key=").concat(API_KEY); + final String string = HttpUtil.get(url); + if (string.contains("\"result\":")) { + JSONObject jsonObject = JSONObject.parseObject(string); + JSONObject result = jsonObject.getJSONObject("result"); + JSONObject address_component = result.getJSONObject("address_component"); + String district = address_component.getString("district"); + System.out.println("district = " + district); + param.setDistrict(district); + return success(hjmCarService.pageRel(param)); + } +// } + param.setLimit(100L); + return success(hjmCarService.pageRel(param)); + } + + @Operation(summary = "获取MQTT连接状态") + @GetMapping("/mqtt/status") + public ApiResult getMqttStatus() { + boolean connected = mqttService.isConnected(); + String clientInfo = mqttService.getClientInfo(); + return success("MQTT状态查询成功", Map.of( + "connected", connected, + "clientInfo", clientInfo, + "status", connected ? "已连接" : "未连接" + )); + } + + @Operation(summary = "重新连接MQTT") + @PostMapping("/mqtt/reconnect") + public ApiResult reconnectMqtt() { + try { + mqttService.reconnect(); + return success("MQTT重连请求已发送"); + } catch (Exception e) { + logger.error("MQTT重连失败", e); + return fail("MQTT重连失败: " + e.getMessage()); + } + } + + @Operation(summary = "GPS数据诊断") + @GetMapping("/gps/diagnose") + public ApiResult diagnoseGpsData() { + try { + Map report = gpsDiagnosticService.diagnoseGpsDataIssues(); + return success("GPS数据诊断完成", report); + } catch (Exception e) { + logger.error("GPS数据诊断失败", e); + return fail("GPS数据诊断失败: " + e.getMessage()); + } + } + + @Operation(summary = "检查特定GPS设备") + @GetMapping("/gps/check/{gpsNo}") + public ApiResult checkSpecificGpsDevice(@PathVariable String gpsNo) { + try { + Map deviceInfo = gpsDiagnosticService.checkSpecificGpsDevice(gpsNo); + return success("GPS设备检查完成", deviceInfo); + } catch (Exception e) { + logger.error("检查GPS设备失败: {}", gpsNo, e); + return fail("检查GPS设备失败: " + e.getMessage()); + } + } + + + + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmChoicesController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmChoicesController.java new file mode 100644 index 0000000..eef4ee8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmChoicesController.java @@ -0,0 +1,122 @@ +package com.gxwebsoft.hjm.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.service.HjmChoicesService; +import com.gxwebsoft.hjm.entity.HjmChoices; +import com.gxwebsoft.hjm.param.HjmChoicesParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 黄家明_选择题选项控制器 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Tag(name = "黄家明_选择题选项管理") +@RestController +@RequestMapping("/api/hjm/hjm-choices") +public class HjmChoicesController extends BaseController { + @Resource + private HjmChoicesService hjmChoicesService; + + @Operation(summary = "分页查询黄家明_选择题选项") + @GetMapping("/page") + public ApiResult> page(HjmChoicesParam param) { + // 使用关联查询 + return success(hjmChoicesService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:list')") + @Operation(summary = "查询全部黄家明_选择题选项") + @GetMapping() + public ApiResult> list(HjmChoicesParam param) { + // 使用关联查询 + return success(hjmChoicesService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_选择题选项") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmChoicesService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:save')") + @OperationLog + @Operation(summary = "添加黄家明_选择题选项") + @PostMapping() + public ApiResult save(@RequestBody HjmChoices hjmChoices) { + if (hjmChoicesService.save(hjmChoices)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:update')") + @OperationLog + @Operation(summary = "修改黄家明_选择题选项") + @PutMapping() + public ApiResult update(@RequestBody HjmChoices hjmChoices) { + if (hjmChoicesService.updateById(hjmChoices)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:remove')") + @OperationLog + @Operation(summary = "删除黄家明_选择题选项") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmChoicesService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_选择题选项") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmChoicesService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_选择题选项") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmChoicesService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmChoices:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_选择题选项") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmChoicesService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmCoursesController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmCoursesController.java new file mode 100644 index 0000000..d3f3beb --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmCoursesController.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.hjm.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.service.HjmCoursesService; +import com.gxwebsoft.hjm.entity.HjmCourses; +import com.gxwebsoft.hjm.param.HjmCoursesParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 黄家明_课程控制器 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Tag(name = "黄家明_课程管理") +@RestController +@RequestMapping("/api/hjm/hjm-courses") +public class HjmCoursesController extends BaseController { + @Resource + private HjmCoursesService hjmCoursesService; + + @Operation(summary = "分页查询黄家明_课程") + @GetMapping("/page") + public ApiResult> page(HjmCoursesParam param) { + // 使用关联查询 + return success(hjmCoursesService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:list')") + @Operation(summary = "查询全部黄家明_课程") + @GetMapping() + public ApiResult> list(HjmCoursesParam param) { + // 使用关联查询 + return success(hjmCoursesService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_课程") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmCoursesService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:save')") + @OperationLog + @Operation(summary = "添加黄家明_课程") + @PostMapping() + public ApiResult save(@RequestBody HjmCourses hjmCourses) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + hjmCourses.setUserId(loginUser.getUserId()); + } + if (hjmCoursesService.save(hjmCourses)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:update')") + @OperationLog + @Operation(summary = "修改黄家明_课程") + @PutMapping() + public ApiResult update(@RequestBody HjmCourses hjmCourses) { + if (hjmCoursesService.updateById(hjmCourses)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:remove')") + @OperationLog + @Operation(summary = "删除黄家明_课程") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmCoursesService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_课程") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmCoursesService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_课程") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmCoursesService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmCourses:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_课程") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmCoursesService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmExamLogController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmExamLogController.java new file mode 100644 index 0000000..7202eec --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmExamLogController.java @@ -0,0 +1,162 @@ +package com.gxwebsoft.hjm.controller; + +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.service.HjmExamLogService; +import com.gxwebsoft.hjm.entity.HjmExamLog; +import com.gxwebsoft.hjm.param.HjmExamLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 黄家明_学习记录控制器 + * + * @author 科技小王子 + * @since 2025-06-05 14:32:03 + */ +@Tag(name = "黄家明_学习记录管理") +@RestController +@RequestMapping("/api/hjm/hjm-exam-log") +public class HjmExamLogController extends BaseController { + @Resource + private HjmExamLogService hjmExamLogService; + + @Operation(summary = "分页查询黄家明_学习记录") + @GetMapping("/page") + public ApiResult> page(HjmExamLogParam param) { + // 使用关联查询 + return success(hjmExamLogService.pageRel(param)); + } + + @Operation(summary = "查询全部黄家明_学习记录") + @GetMapping() + public ApiResult> list(HjmExamLogParam param) { + // 使用关联查询 + return success(hjmExamLogService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_学习记录") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmExamLogService.getByIdRel(id)); + } + + @Operation(summary = "添加黄家明_学习记录") + @PostMapping() + public ApiResult save(@RequestBody HjmExamLog hjmExamLog) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + hjmExamLog.setUserId(loginUser.getUserId()); + } + if (hjmExamLogService.save(hjmExamLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmExamLog:update')") + @OperationLog + @Operation(summary = "修改黄家明_学习记录") + @PutMapping() + public ApiResult update(@RequestBody HjmExamLog hjmExamLog) { + if (hjmExamLogService.updateById(hjmExamLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmExamLog:remove')") + @OperationLog + @Operation(summary = "删除黄家明_学习记录") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmExamLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmExamLog:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_学习记录") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmExamLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmExamLog:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_学习记录") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmExamLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmExamLog:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_学习记录") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmExamLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "查询本月是否已完成学习任务") + @GetMapping("/getMyMonthExamLog") + public ApiResult> getMyMonthExamLog() { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录",null); + } + + // 查询当前月份的记录 + List list = hjmExamLogService.list(new LambdaQueryWrapper() + .eq(HjmExamLog::getStatus, 1) + .eq(HjmExamLog::getUserId, loginUser.getUserId()) + .ge(HjmExamLog::getCreateTime, DateUtil.beginOfMonth(DateUtil.date())) + .le(HjmExamLog::getCreateTime, DateUtil.endOfMonth(DateUtil.date()))); + + return success("查询成功", list); + } + + @Operation(summary = "检查本月是否已完成学习任务") + @GetMapping("/checkMonthTaskCompleted") + public ApiResult checkMonthTaskCompleted() { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录",null); + } + + // 查询本月是否有完成的学习记录 + long count = hjmExamLogService.count(new LambdaQueryWrapper() + .eq(HjmExamLog::getStatus, 1) + .eq(HjmExamLog::getUserId, loginUser.getUserId()) + .ge(HjmExamLog::getCreateTime, DateUtil.beginOfMonth(DateUtil.date())) + .le(HjmExamLog::getCreateTime, DateUtil.endOfMonth(DateUtil.date()))); + + boolean isCompleted = count > 0; + return success(isCompleted ? "本月已完成学习任务" : "本月尚未完成学习任务", isCompleted); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmFenceController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmFenceController.java new file mode 100644 index 0000000..956a7e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmFenceController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.hjm.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.service.HjmFenceService; +import com.gxwebsoft.hjm.entity.HjmFence; +import com.gxwebsoft.hjm.param.HjmFenceParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 黄家明_电子围栏控制器 + * + * @author 科技小王子 + * @since 2025-06-03 02:08:03 + */ +@Tag(name = "黄家明_电子围栏管理") +@RestController +@RequestMapping("/api/hjm/hjm-fence") +public class HjmFenceController extends BaseController { + @Resource + private HjmFenceService hjmFenceService; + + @Operation(summary = "分页查询黄家明_电子围栏") + @GetMapping("/page") + public ApiResult> page(HjmFenceParam param) { + // 使用关联查询 + return success(hjmFenceService.pageRel(param)); + } + + @Operation(summary = "查询全部黄家明_电子围栏") + @GetMapping() + public ApiResult> list(HjmFenceParam param) { + // 使用关联查询 + return success(hjmFenceService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_电子围栏") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmFenceService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('hjm:hjmFence:save')") + @OperationLog + @Operation(summary = "添加黄家明_电子围栏") + @PostMapping() + public ApiResult save(@RequestBody HjmFence hjmFence) { + if (hjmFenceService.save(hjmFence)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmFence:update')") + @OperationLog + @Operation(summary = "修改黄家明_电子围栏") + @PutMapping() + public ApiResult update(@RequestBody HjmFence hjmFence) { + if (hjmFenceService.updateById(hjmFence)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmFence:remove')") + @OperationLog + @Operation(summary = "删除黄家明_电子围栏") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmFenceService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmFence:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_电子围栏") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmFenceService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmFence:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_电子围栏") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmFenceService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmFence:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_电子围栏") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmFenceService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmGpsLogController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmGpsLogController.java new file mode 100644 index 0000000..f176e86 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmGpsLogController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.hjm.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.service.HjmGpsLogService; +import com.gxwebsoft.hjm.entity.HjmGpsLog; +import com.gxwebsoft.hjm.param.HjmGpsLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 黄家明_gps轨迹控制器 + * + * @author 科技小王子 + * @since 2025-06-11 12:03:50 + */ +@Tag(name = "黄家明_gps轨迹管理") +@RestController +@RequestMapping("/api/hjm/hjm-gps-log") +public class HjmGpsLogController extends BaseController { + @Resource + private HjmGpsLogService hjmGpsLogService; + + @Operation(summary = "分页查询黄家明_gps轨迹") + @GetMapping("/page") + public ApiResult> page(HjmGpsLogParam param) { + // 使用关联查询 + return success(hjmGpsLogService.pageRel(param)); + } + + @Operation(summary = "查询全部黄家明_gps轨迹") + @GetMapping() + public ApiResult> list(HjmGpsLogParam param) { + // 使用关联查询 + return success(hjmGpsLogService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_gps轨迹") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmGpsLogService.getByIdRel(id)); + } + + @Operation(summary = "添加黄家明_gps轨迹") + @PostMapping() + public ApiResult save(@RequestBody HjmGpsLog hjmGpsLog) { + if (hjmGpsLogService.save(hjmGpsLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmGpsLog:update')") + @OperationLog + @Operation(summary = "修改黄家明_gps轨迹") + @PutMapping() + public ApiResult update(@RequestBody HjmGpsLog hjmGpsLog) { + if (hjmGpsLogService.updateById(hjmGpsLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmGpsLog:remove')") + @OperationLog + @Operation(summary = "删除黄家明_gps轨迹") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmGpsLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmGpsLog:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_gps轨迹") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmGpsLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmGpsLog:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_gps轨迹") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmGpsLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmGpsLog:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_gps轨迹") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmGpsLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + // 分析车辆是否逆行 + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmQuestionsController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmQuestionsController.java new file mode 100644 index 0000000..0a55323 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmQuestionsController.java @@ -0,0 +1,143 @@ +package com.gxwebsoft.hjm.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.entity.HjmChoices; +import com.gxwebsoft.hjm.service.HjmChoicesService; +import com.gxwebsoft.hjm.service.HjmQuestionsService; +import com.gxwebsoft.hjm.entity.HjmQuestions; +import com.gxwebsoft.hjm.param.HjmQuestionsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 黄家明_题目控制器 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Tag(name = "黄家明_题目管理") +@RestController +@RequestMapping("/api/hjm/hjm-questions") +public class HjmQuestionsController extends BaseController { + @Resource + private HjmQuestionsService hjmQuestionsService; + @Resource + private HjmChoicesService hjmChoicesService; + + @Operation(summary = "分页查询黄家明_题目") + @GetMapping("/page") + public ApiResult> page(HjmQuestionsParam param) { + // 使用关联查询 + return success(hjmQuestionsService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:list')") + @Operation(summary = "查询全部黄家明_题目") + @GetMapping() + public ApiResult> list(HjmQuestionsParam param) { + // 使用关联查询 + return success(hjmQuestionsService.listRel(param)); + } + + @Operation(summary = "根据id查询黄家明_题目") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmQuestionsService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:save')") + @OperationLog + @Operation(summary = "添加黄家明_题目") + @PostMapping() + public ApiResult save(@RequestBody HjmQuestions hjmQuestions) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + hjmQuestions.setUserId(loginUser.getUserId()); + } + if (hjmQuestionsService.save(hjmQuestions)) { + final List choicesList = hjmQuestions.getChoicesList(); + if (!CollectionUtils.isEmpty(choicesList)) { + choicesList.forEach(choice -> { + choice.setQuestionId(hjmQuestions.getId()); + }); + hjmChoicesService.saveBatch(choicesList); + } + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:update')") + @OperationLog + @Operation(summary = "修改黄家明_题目") + @PutMapping() + public ApiResult update(@RequestBody HjmQuestions hjmQuestions) { + if (hjmQuestionsService.updateById(hjmQuestions)) { + if (!CollectionUtils.isEmpty(hjmQuestions.getChoicesList())) { + hjmChoicesService.updateBatchById(hjmQuestions.getChoicesList()); + } + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:remove')") + @OperationLog + @Operation(summary = "删除黄家明_题目") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmQuestionsService.removeById(id)) { + hjmChoicesService.remove(new LambdaQueryWrapper().eq(HjmChoices::getQuestionId, id)); + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_题目") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmQuestionsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_题目") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmQuestionsService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmQuestions:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_题目") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmQuestionsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/HjmViolationController.java b/src/main/java/com/gxwebsoft/hjm/controller/HjmViolationController.java new file mode 100644 index 0000000..0d0520b --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/HjmViolationController.java @@ -0,0 +1,140 @@ +package com.gxwebsoft.hjm.controller; + +import cn.hutool.core.util.ObjUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.service.HjmCarService; +import com.gxwebsoft.hjm.service.HjmViolationService; +import com.gxwebsoft.hjm.entity.HjmViolation; +import com.gxwebsoft.hjm.param.HjmViolationParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 黄家明_违章记录控制器 + * + * @author 科技小王子 + * @since 2025-06-20 13:48:43 + */ +@Tag(name = "黄家明_违章记录管理") +@RestController +@RequestMapping("/api/hjm/hjm-violation") +public class HjmViolationController extends BaseController { + @Resource + private HjmViolationService hjmViolationService; + @Resource + private HjmCarService hjmCarService; + + @PreAuthorize("hasAuthority('hjm:hjmViolation:list')") + @Operation(summary = "分页查询黄家明_违章记录") + @GetMapping("/page") + public ApiResult> page(HjmViolationParam param) { + // 使用关联查询 + return success(hjmViolationService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:list')") + @Operation(summary = "查询全部黄家明_违章记录") + @GetMapping() + public ApiResult> list(HjmViolationParam param) { + // 使用关联查询 + return success(hjmViolationService.listRel(param)); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:list')") + @Operation(summary = "根据id查询黄家明_违章记录") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(hjmViolationService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:save')") + @OperationLog + @Operation(summary = "添加黄家明_违章记录") + @PostMapping() + public ApiResult save(@RequestBody HjmViolation hjmViolation) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + hjmViolation.setUserId(loginUser.getUserId()); + } + final HjmCar car = hjmCarService.getByCode(hjmViolation.getCode()); + if (ObjUtil.isEmpty(car)) { + return fail("车辆编号不存在"); + } + if (hjmViolationService.save(hjmViolation)) { + hjmViolationService.send(hjmViolation); + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:update')") + @OperationLog + @Operation(summary = "修改黄家明_违章记录") + @PutMapping() + public ApiResult update(@RequestBody HjmViolation hjmViolation) { + if (hjmViolationService.updateById(hjmViolation)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:remove')") + @OperationLog + @Operation(summary = "删除黄家明_违章记录") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (hjmViolationService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:save')") + @OperationLog + @Operation(summary = "批量添加黄家明_违章记录") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (hjmViolationService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:update')") + @OperationLog + @Operation(summary = "批量修改黄家明_违章记录") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(hjmViolationService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('hjm:hjmViolation:remove')") + @OperationLog + @Operation(summary = "批量删除黄家明_违章记录") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (hjmViolationService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/MQTTClientDemo.java b/src/main/java/com/gxwebsoft/hjm/controller/MQTTClientDemo.java new file mode 100644 index 0000000..585cafd --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/MQTTClientDemo.java @@ -0,0 +1,150 @@ +package com.gxwebsoft.hjm.controller; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +/** +* @Description: + * maven 依赖: + * + * org.eclipse.paho + * org.eclipse.paho.client.mqttv3 + * 1.2.1 + * + * +* @Author: lx +* @Version: 1.0.0 +* @Date: 2025/7/2 +*/ +public class MQTTClientDemo { + + private static final Log log = LogFactory.getLog(MQTTClientDemo.class); + + private MqttClient client; + + private String HOST = "TCP://127.0.0.1:1883"; + private String userName = ""; + private String passWord = ""; + private String mqttClientId=""; + private MqttCallback callback; + + + public MQTTClientDemo(MqttCallback callback, String host,String clientId,String usrname,String password) throws MqttException { + this.callback = callback; + HOST = host; + mqttClientId = clientId; + userName = usrname; + passWord = password; + + client = new MqttClient(HOST, mqttClientId, new MemoryPersistence()); + connect(); + } + + /** + * 用来连接服务器 + */ + public void connect() { + MqttConnectOptions options = new MqttConnectOptions(); + options.setCleanSession(false); + options.setUserName(userName); + options.setPassword(passWord.toCharArray()); + // 设置超时时间 + options.setConnectionTimeout(10); + // 设置会话心跳时间 + options.setKeepAliveInterval(20); + //设置自动重连 + options.setAutomaticReconnect(true); + try { + System.out.println("DC MQTT Host="+HOST); + System.out.println("DC MQTT ClientId="+mqttClientId); + System.out.println("DC MQTT userName="+userName); + System.out.println("DC MQTT passWord="+passWord); + client.setCallback(this.callback); + client.connect(options); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public MqttTopic getTopic(String topic) { + return client.getTopic(topic); + } + + public void subscribe(String topic) throws MqttException { + client.subscribe(topic); + } + + public void subscribe(String[] topics) throws MqttException { + client.subscribe(topics); + } + + //qos 0:最多一次 、1:最少一次 、2:只有一次 + public void subscribe(String topic,int qos) throws MqttException { + client.subscribe(topic,qos); + } + + public void subscribe(String[] topics,int[] qosArr) throws MqttException { + client.subscribe(topics,qosArr); + } + + /** + * + * @param topic + * @param message + * @throws MqttPersistenceException + * @throws MqttException + */ + public void publish(MqttTopic topic , MqttMessage message) throws MqttPersistenceException, + MqttException { + MqttDeliveryToken token = topic.publish(message); + /* + //原地等待发送结果 + token.waitForCompletion(); + if(token.isComplete()) { + log.info("message is published completely! "); + }else { + log.info("message is published failed! "); + } + */ + } + + public void publish(MqttTopic topic,String payload,int qos,boolean retained) throws MqttPersistenceException, MqttException { + MqttMessage msg = new MqttMessage(); + msg.setQos(qos); + msg.setRetained(retained); + msg.setPayload(payload.getBytes()); + publish(topic,msg); + } + + public boolean isConnected() { + return client.isConnected(); + } + + + public static void main(String[] args) throws MqttException { + MQTTClientDemo mqttClient = new MQTTClientDemo(new MqttCallback() { + @Override + public void connectionLost(Throwable throwable) { + log.error("connectionLost..."); + } + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + if (topic.equals("xxxxx")){ + /*是指定topic*/ + } + log.debug("DC 接收消息主题 : " + topic); + log.debug("DC 接收消息Qos : " + message.getQos()); + log.debug("DC 接收消息内容 : " + new String(message.getPayload())); + } + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + log.error("deliveryComplete..." + token.isComplete()); + } + },"HOST", "SERVER_" + System.currentTimeMillis(), "userName", "passWord"); + /*订阅一个*/ + mqttClient.subscribe("TOPIC"); +// mqttClient.subscribe(new String[] {"TOPIC"}); + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/PushCallback.java b/src/main/java/com/gxwebsoft/hjm/controller/PushCallback.java new file mode 100644 index 0000000..6e59c83 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/PushCallback.java @@ -0,0 +1,31 @@ +package com.gxwebsoft.hjm.controller; + +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 推送消息的回调类 + */ +public class PushCallback implements MqttCallback { + private static final Logger logger = LoggerFactory.getLogger(PushCallback.class); + @Override + public void connectionLost(Throwable throwable) { + logger.info("连接丢失............."); + } + + @Override + public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { + logger.info("接收消息主题 : " + topic); + logger.info("接收消息Qos : " + mqttMessage.getQos()); + logger.info("接收消息内容 : " + new String(mqttMessage.getPayload())); + + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + logger.info("deliveryComplete............."); + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/SendSubscriptionMessages.java b/src/main/java/com/gxwebsoft/hjm/controller/SendSubscriptionMessages.java new file mode 100644 index 0000000..7ca5d11 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/SendSubscriptionMessages.java @@ -0,0 +1,136 @@ +package com.gxwebsoft.hjm.controller; + +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.hjm.dto.BatchTemplateMessageRequest; +import com.gxwebsoft.hjm.dto.SubscribeMessageRequest; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest; +import com.gxwebsoft.hjm.service.WxNotificationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 微信公众号订阅通知控制器 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +@Slf4j +@Tag(name = "微信公众号订阅通知") +@RestController +@RequestMapping("/api/hjm/wx-subscription") +public class SendSubscriptionMessages extends BaseController { + + @Resource + private WxNotificationService wxNotificationService; + + /** + * 发送模板消息 + */ + @Operation(summary = "发送模板消息") + @OperationLog("发送模板消息") + @PostMapping("/send-template") + public ApiResult sendTemplateMessage(@RequestBody TemplateMessageRequest request) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + // 发送模板消息 + boolean success = wxNotificationService.sendTemplateMessage(tenantId, request); + + if (success) { + return success("模板消息发送成功"); + } else { + return fail("模板消息发送失败"); + } + + } catch (Exception e) { + log.error("发送模板消息失败", e); + return fail("发送失败:" + e.getMessage()); + } + } + + + + + + /** + * 发送订阅消息(小程序订阅消息) + */ + @Operation(summary = "发送订阅消息") + @OperationLog("发送订阅消息") + @PostMapping("/send-subscribe") + public ApiResult sendSubscribeMessage(@RequestBody SubscribeMessageRequest request) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + // 发送订阅消息 + boolean success = wxNotificationService.sendSubscribeMessage(tenantId, request); + + if (success) { + return success("订阅消息发送成功"); + } else { + return fail("订阅消息发送失败"); + } + + } catch (Exception e) { + log.error("发送订阅消息失败", e); + return fail("发送失败:" + e.getMessage()); + } + } + + + + /** + * 批量发送模板消息 + */ + @Operation(summary = "批量发送模板消息") + @OperationLog("批量发送模板消息") + @PostMapping("/batch-send-template") + public ApiResult batchSendTemplateMessage(@RequestBody BatchTemplateMessageRequest request) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + WxNotificationService.BatchSendResult result = wxNotificationService.batchSendTemplateMessage(tenantId, request.getMessages()); + + return success("批量发送完成," + result.toString()); + + } catch (Exception e) { + log.error("批量发送模板消息失败", e); + return fail("批量发送失败:" + e.getMessage()); + } + } + + /** + * 获取模板列表 + */ + @Operation(summary = "获取模板列表") + @GetMapping("/templates") + public ApiResult getTemplateList() { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + String response = wxNotificationService.getTemplateList(tenantId); + if (response != null) { + JSONObject result = JSONObject.parseObject(response); + return success("获取成功", result); + } else { + return fail("获取失败"); + } + + } catch (Exception e) { + log.error("获取模板列表失败", e); + return fail("获取失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/controller/WxNotificationTestController.java b/src/main/java/com/gxwebsoft/hjm/controller/WxNotificationTestController.java new file mode 100644 index 0000000..1d7aac4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/controller/WxNotificationTestController.java @@ -0,0 +1,222 @@ +package com.gxwebsoft.hjm.controller; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.hjm.dto.SubscribeMessageRequest; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest; +import com.gxwebsoft.hjm.service.WxNotificationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信通知测试控制器 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +@Slf4j +@Tag(name = "微信通知测试") +@RestController +@RequestMapping("/api/hjm/wx-notification-test") +public class WxNotificationTestController extends BaseController { + + @Resource + private WxNotificationService wxNotificationService; + + /** + * 测试发送订单确认通知 + */ + @Operation(summary = "测试发送订单确认通知") + @PostMapping("/test-order-notification") + public ApiResult testOrderNotification(@RequestParam String openId, + @RequestParam String orderNo, + @RequestParam(required = false) String templateId) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + // 构建模板消息请求 + TemplateMessageRequest request = new TemplateMessageRequest(); + request.setToUser(openId); + request.setTemplateId(templateId != null ? templateId : "your_order_template_id"); + request.setUrl("https://your-domain.com/order/" + orderNo); + request.setTopColor("#173177"); + + // 构建模板数据 + Map data = new HashMap<>(); + data.put("first", new TemplateMessageRequest.TemplateDataItem("您的订单已确认", "#173177")); + data.put("keyword1", new TemplateMessageRequest.TemplateDataItem(orderNo, "#173177")); + data.put("keyword2", new TemplateMessageRequest.TemplateDataItem( + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()), "#173177")); + data.put("remark", new TemplateMessageRequest.TemplateDataItem("感谢您的使用,如有疑问请联系客服!", "#173177")); + + request.setData(data); + + // 发送消息 + boolean success = wxNotificationService.sendTemplateMessage(tenantId, request); + + if (success) { + return success("订单确认通知发送成功"); + } else { + return fail("订单确认通知发送失败"); + } + + } catch (Exception e) { + log.error("测试发送订单确认通知失败", e); + return fail("发送失败:" + e.getMessage()); + } + } + + /** + * 测试发送支付成功通知 + */ + @Operation(summary = "测试发送支付成功通知") + @PostMapping("/test-payment-notification") + public ApiResult testPaymentNotification(@RequestParam String openId, + @RequestParam String orderNo, + @RequestParam String amount, + @RequestParam(required = false) String templateId) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + TemplateMessageRequest request = new TemplateMessageRequest(); + request.setToUser(openId); + request.setTemplateId(templateId != null ? templateId : "your_payment_template_id"); + request.setUrl("https://your-domain.com/order/" + orderNo); + + Map data = new HashMap<>(); + data.put("first", new TemplateMessageRequest.TemplateDataItem("支付成功通知", "#173177")); + data.put("keyword1", new TemplateMessageRequest.TemplateDataItem(orderNo, "#173177")); + data.put("keyword2", new TemplateMessageRequest.TemplateDataItem("¥" + amount, "#FF0000")); + data.put("keyword3", new TemplateMessageRequest.TemplateDataItem( + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()), "#173177")); + data.put("remark", new TemplateMessageRequest.TemplateDataItem("您的订单已支付成功,我们将尽快为您处理!", "#173177")); + + request.setData(data); + + boolean success = wxNotificationService.sendTemplateMessage(tenantId, request); + + if (success) { + return success("支付成功通知发送成功"); + } else { + return fail("支付成功通知发送失败"); + } + + } catch (Exception e) { + log.error("测试发送支付成功通知失败", e); + return fail("发送失败:" + e.getMessage()); + } + } + + /** + * 测试发送订阅消息 + */ + @Operation(summary = "测试发送订阅消息") + @PostMapping("/test-subscribe-notification") + public ApiResult testSubscribeNotification(@RequestParam String openId, + @RequestParam String title, + @RequestParam String content, + @RequestParam(required = false) String templateId) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + SubscribeMessageRequest request = new SubscribeMessageRequest(); + request.setToUser(openId); + request.setTemplateId(templateId != null ? templateId : "your_subscribe_template_id"); + request.setPage("pages/notification/detail"); + request.setMiniprogramState("formal"); + request.setLang("zh_CN"); + + Map data = new HashMap<>(); + data.put("thing1", new SubscribeMessageRequest.SubscribeDataItem(title)); + data.put("thing2", new SubscribeMessageRequest.SubscribeDataItem(content)); + data.put("time3", new SubscribeMessageRequest.SubscribeDataItem( + new SimpleDateFormat("yyyy年MM月dd日 HH:mm").format(LocalDateTime.now()))); + + request.setData(data); + + boolean success = wxNotificationService.sendSubscribeMessage(tenantId, request); + + if (success) { + return success("订阅消息发送成功"); + } else { + return fail("订阅消息发送失败"); + } + + } catch (Exception e) { + log.error("测试发送订阅消息失败", e); + return fail("发送失败:" + e.getMessage()); + } + } + + /** + * 测试发送系统通知 + */ + @Operation(summary = "测试发送系统通知") + @PostMapping("/test-system-notification") + public ApiResult testSystemNotification(@RequestParam String openId, + @RequestParam String message, + @RequestParam(required = false) String templateId) { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + TemplateMessageRequest request = new TemplateMessageRequest(); + request.setToUser(openId); + request.setTemplateId(templateId != null ? templateId : "your_system_template_id"); + request.setTopColor("#173177"); + + Map data = new HashMap<>(); + data.put("first", new TemplateMessageRequest.TemplateDataItem("系统通知", "#173177")); + data.put("keyword1", new TemplateMessageRequest.TemplateDataItem("系统消息", "#173177")); + data.put("keyword2", new TemplateMessageRequest.TemplateDataItem(message, "#173177")); + data.put("keyword3", new TemplateMessageRequest.TemplateDataItem( + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()), "#173177")); + data.put("remark", new TemplateMessageRequest.TemplateDataItem("如有疑问,请联系客服。", "#173177")); + + request.setData(data); + + boolean success = wxNotificationService.sendTemplateMessage(tenantId, request); + + if (success) { + return success("系统通知发送成功"); + } else { + return fail("系统通知发送失败"); + } + + } catch (Exception e) { + log.error("测试发送系统通知失败", e); + return fail("发送失败:" + e.getMessage()); + } + } + + /** + * 获取当前配置的模板列表 + */ + @Operation(summary = "获取当前配置的模板列表") + @GetMapping("/get-templates") + public ApiResult getTemplates() { + try { + User loginUser = getLoginUser(); + Integer tenantId = loginUser != null ? loginUser.getTenantId() : 0; + + String response = wxNotificationService.getTemplateList(tenantId); + return success("获取成功", response); + + } catch (Exception e) { + log.error("获取模板列表失败", e); + return fail("获取失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/dto/BatchTemplateMessageRequest.java b/src/main/java/com/gxwebsoft/hjm/dto/BatchTemplateMessageRequest.java new file mode 100644 index 0000000..dbea9fc --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/dto/BatchTemplateMessageRequest.java @@ -0,0 +1,24 @@ +package com.gxwebsoft.hjm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 批量发送模板消息请求 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +@Data +@Schema(name = "BatchTemplateMessageRequest", description = "批量发送模板消息请求") +public class BatchTemplateMessageRequest { + + @Schema(description = "模板消息列表", required = true) + private List messages; + + @Schema(description = "发送间隔时间(毫秒),默认100ms") + private Long intervalMs = 100L; +} diff --git a/src/main/java/com/gxwebsoft/hjm/dto/SubscribeMessageRequest.java b/src/main/java/com/gxwebsoft/hjm/dto/SubscribeMessageRequest.java new file mode 100644 index 0000000..8625784 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/dto/SubscribeMessageRequest.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.hjm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Map; + +/** + * 微信小程序订阅消息请求 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +@Data +@Schema(name = "SubscribeMessageRequest", description = "微信小程序订阅消息请求") +public class SubscribeMessageRequest { + + @Schema(description = "接收者openid", required = true) + private String toUser; + + @Schema(description = "所需下发的订阅模板id", required = true) + private String templateId; + + @Schema(description = "点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。") + private String page; + + @Schema(description = "跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版") + private String miniprogramState = "formal"; + + @Schema(description = "进入小程序查看的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN") + private String lang = "zh_CN"; + + @Schema(description = "模板内容,格式形如 { \"key1\": { \"value\": any }, \"key2\": { \"value\": any } }", required = true) + private Map data; + + /** + * 订阅消息数据项 + */ + @Data + @Schema(name = "SubscribeDataItem", description = "订阅消息数据项") + public static class SubscribeDataItem { + @Schema(description = "数据值", required = true) + private String value; + + public SubscribeDataItem() {} + + public SubscribeDataItem(String value) { + this.value = value; + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/dto/TemplateMessageRequest.java b/src/main/java/com/gxwebsoft/hjm/dto/TemplateMessageRequest.java new file mode 100644 index 0000000..f15f6e4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/dto/TemplateMessageRequest.java @@ -0,0 +1,73 @@ +package com.gxwebsoft.hjm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Map; + +/** + * 微信公众号模板消息请求 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +@Data +@Schema(name = "TemplateMessageRequest", description = "微信公众号模板消息请求") +public class TemplateMessageRequest { + + @Schema(description = "接收者openid", required = true) + private String toUser; + + @Schema(description = "模板ID", required = true) + private String templateId; + + @Schema(description = "模板跳转链接(海外帐号没有跳转能力)") + private String url; + + @Schema(description = "模板内容字体颜色,不填默认为黑色") + private String topColor = "#173177"; + + @Schema(description = "模板数据", required = true) + private Map data; + + @Schema(description = "跳小程序所需数据,不需跳小程序可不用传该数据") + private MiniProgram miniprogram; + + /** + * 模板数据项 + */ + @Data + @Schema(name = "TemplateDataItem", description = "模板数据项") + public static class TemplateDataItem { + @Schema(description = "数据值", required = true) + private String value; + + @Schema(description = "数据颜色,不填默认为黑色") + private String color = "#173177"; + + public TemplateDataItem() {} + + public TemplateDataItem(String value) { + this.value = value; + } + + public TemplateDataItem(String value, String color) { + this.value = value; + this.color = color; + } + } + + /** + * 小程序跳转信息 + */ + @Data + @Schema(name = "MiniProgram", description = "小程序跳转信息") + public static class MiniProgram { + @Schema(description = "所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)") + private String appid; + + @Schema(description = "所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏") + private String pagepath; + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/Gps.java b/src/main/java/com/gxwebsoft/hjm/entity/Gps.java new file mode 100644 index 0000000..6c9768f --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/Gps.java @@ -0,0 +1,73 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * GPS + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "GPS对象", description = "GPS") +public class Gps implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "物联网卡的imsi号") + @TableField(exist = false) + private String imsi; + + @Schema(description = "设备ID") + private String imei; + + @Schema(description = "移动网络编码") + private String plmn; + + @Schema(description = "4G网络路由区") + private String lac; + + @Schema(description = "4G网络基站小区id") + private String ci; + + @Schema(description = "4G网络信号值") + private String rssi; + + @Schema(description = "设备当前时间戳") + private Integer time; + + @Schema(description = "设备当前时间") + private String ddmmyy; + + @Schema(description = "时分秒") + private String hhmmss; + + @Schema(description = "速度") + private String speed; + + @Schema(description = "miles") + private String miles; + + @Schema(description = "是否定位") + private Boolean fixed; + + @Schema(description = "经度") + private String lat; + + @Schema(description = "纬度") + private String lng; + + @Schema(description = "定位星数") + private String satcnt; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmBxLog.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmBxLog.java new file mode 100644 index 0000000..f17bb6d --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmBxLog.java @@ -0,0 +1,84 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_报险记录 + * + * @author 科技小王子 + * @since 2025-06-06 13:08:29 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmBxLog对象", description = "黄家明_报险记录") +public class HjmBxLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "真实姓名") + @TableField(exist = false) + private String realName; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "事故类型") + private String accidentType; + + @Schema(description = "车辆ID") + private Integer carId; + + @Schema(description = "车辆编号") + @TableField(exist = false) + private String carNo; + + @Schema(description = "车辆图片") + @TableField(exist = false) + private String carAvatar; + + @Schema(description = "保险图片") + private String image; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmCar.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmCar.java new file mode 100644 index 0000000..0cc6983 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmCar.java @@ -0,0 +1,173 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_车辆管理 + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmCar对象", description = "黄家明_车辆管理") +public class HjmCar implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "车辆名称") + private String name; + + @Schema(description = "车辆图片") + private String image; + + @Schema(description = "类型 0汽车 1其他车") + private Integer type; + + @Schema(description = "管理负责人") + @TableField(exist = false) + private String kuaidiAdmin; + + @Schema(description = "车辆编号") + private String code; + + @Schema(description = "司机") + private Integer driverId; + + @Schema(description = "操作员") + @TableField(exist = false) + private String driver; + + @Schema(description = "操作员") + private String driverName; + + @Schema(description = "操作员电话") + @TableField(exist = false) + private String driverPhone; + + @Schema(description = "保险状态") + private String insuranceStatus; + + @Schema(description = "GPS设备编号") + private String gpsNo; + + @Schema(description = "电子围栏ID") + private Integer fenceId; + + @Schema(description = "电子围栏名称") + private String fenceName; + + @Schema(description = "电子围栏名称") + @TableField(exist = false) + private HjmFence fence; + + @Schema(description = "是否在围栏内") + private Boolean inFence; + + @Schema(description = "位置") + private String location; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "速度") + private String speed; + + @Schema(description = "区域") + private String district; + + @Schema(description = "地址") + private String address; + + @Schema(description = "组织ID") + private Integer organizationId; + + @Schema(description = "机构名称") + private String organizationName; + + @Schema(description = "机构名称") + @TableField(exist = false) + private String organization; + + @Schema(description = "父级组织ID") + private Integer organizationParentId; + + @Schema(description = "父级机构名称") + private String organizationParentName; + + @Schema(description = "父级机构名称") + @TableField(exist = false) + private String parentOrganization; + + @Schema(description = "快递公司管理员") + @TableField(exist = false) + private String parentOrganizationAdmin; + + @Schema(description = "微信小程序码") + private String mpCode; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "安装工ID") + private Integer installerId; + + @Schema(description = "安装时间") + private String installTime; + + @Schema(description = "接受推送消息的微信openId") + private String toUser; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "appId") + @TableField(exist = false) + private String appId; + + @Schema(description = "是否认领, 0未认领, 1已认领") + private Integer claim; + + @Schema(description = "认领时间") + private String claimTime; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmChoices.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmChoices.java new file mode 100644 index 0000000..550be7e --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmChoices.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_选择题选项 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmChoices对象", description = "黄家明_选择题选项") +public class HjmChoices implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "题目ID") + private Integer questionId; + + @Schema(description = "题目") + private String content; + + @Schema(description = "是否正确") + private Boolean isCorrect; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmCourses.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmCourses.java new file mode 100644 index 0000000..6871cca --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmCourses.java @@ -0,0 +1,71 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_课程 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmCourses对象", description = "黄家明_课程") +public class HjmCourses implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "课程名称") + private String name; + + @Schema(description = "类型") + private Integer type; + + @Schema(description = "课程编号") + private String code; + + @Schema(description = "课程封面图") + private String image; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmExamLog.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmExamLog.java new file mode 100644 index 0000000..b5d9a45 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmExamLog.java @@ -0,0 +1,78 @@ +package com.gxwebsoft.hjm.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_学习记录 + * + * @author 科技小王子 + * @since 2025-06-05 14:32:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmExamLog对象", description = "黄家明_学习记录") +public class HjmExamLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "真实姓名") + @TableField(exist = false) + private String realName; + + @Schema(description = "手机号码") + @TableField(exist = false) + private String phone; + + @Schema(description = "得分") + private BigDecimal total; + + @Schema(description = "用时") + private String useTime; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmFence.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmFence.java new file mode 100644 index 0000000..548e889 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmFence.java @@ -0,0 +1,71 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_电子围栏 + * + * @author 科技小王子 + * @since 2025-06-03 02:08:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmFence对象", description = "黄家明_电子围栏") +public class HjmFence implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "围栏名称") + private String name; + + @Schema(description = "类型 1多边形") + private Integer type; + + @Schema(description = "位置") + private String location; + + @Schema(description = "经度") + private Double longitude; + + @Schema(description = "纬度") + private Double latitude; + + @Schema(description = "区域") + private String district; + + @Schema(description = "轮廓") + private String points; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmGpsLog.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmGpsLog.java new file mode 100644 index 0000000..86b67b2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmGpsLog.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_gps轨迹 + * + * @author 科技小王子 + * @since 2025-06-11 12:03:50 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmGpsLog对象", description = "黄家明_gps轨迹") +public class HjmGpsLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "车辆ID") + private Integer carId; + + @Schema(description = "gps编号") + private String gpsNo; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "设备当前时间") + private String ddmmyy; + + @Schema(description = "时分秒") + private String hhmmss; + + @Schema(description = "速度") + private String speed; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "操作员电话") + @TableField(exist = false) + private String phone; + + @Schema(description = "车辆编号") + @TableField(exist = false) + private String carNo; + + @Schema(description = "司机ID") + @TableField(exist = false) + private Integer driverId; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmQuestions.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmQuestions.java new file mode 100644 index 0000000..8bb5796 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmQuestions.java @@ -0,0 +1,102 @@ +package com.gxwebsoft.hjm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_题目 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmQuestions对象", description = "黄家明_题目") +public class HjmQuestions implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "课程ID") + private Integer courseId; + + @Schema(description = "课程名称") + @TableField(exist = false) + private String courseName; + + @Schema(description = "类型 0choice 1fill 2essay") + private Integer type; + + @Schema(description = "题目") + private String question; + + @Schema(description = "正确答案") + private String correctAnswer; + + @Schema(description = "难度,0简单 1中等 2难") + private Integer difficulty; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "正确答案") + @TableField(exist = false) + private Integer choices; +// +// @Schema(description = "选项A") +// @TableField(exist = false) +// private String choicesA; +// +// @Schema(description = "选项B") +// @TableField(exist = false) +// private String choicesB; +// +// @Schema(description = "选项C") +// @TableField(exist = false) +// private String choicesC; +// +// @Schema(description = "选项D") +// @TableField(exist = false) +// private String choicesD; + + @Schema(description = "选项列表") + @TableField(exist = false) + private List choicesList; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/entity/HjmViolation.java b/src/main/java/com/gxwebsoft/hjm/entity/HjmViolation.java new file mode 100644 index 0000000..3d059d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/entity/HjmViolation.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.hjm.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_违章记录 + * + * @author 科技小王子 + * @since 2025-06-20 13:48:43 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HjmViolation对象", description = "黄家明_违章记录") +public class HjmViolation implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "标题") + private String title; + + @Schema(description = "车辆编号") + private String code; + + @Schema(description = "文章分类ID") + private Integer categoryId; + + @Schema(description = "处罚金额") + private BigDecimal money; + + @Schema(description = "扣分") + private BigDecimal score; + + @Schema(description = "录入员") + private Integer adminId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0未处理, 1已处理") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmBxLogMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmBxLogMapper.java new file mode 100644 index 0000000..0e525fc --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmBxLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmBxLog; +import com.gxwebsoft.hjm.param.HjmBxLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_报险记录Mapper + * + * @author 科技小王子 + * @since 2025-06-06 13:08:29 + */ +public interface HjmBxLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmBxLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmBxLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmCarMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmCarMapper.java new file mode 100644 index 0000000..0513d21 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmCarMapper.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.param.HjmCarParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_车辆管理Mapper + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +public interface HjmCarMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmCarParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmCarParam param); + + + @InterceptorIgnore(tenantLine = "true") + HjmCar getByGpsNo(@Param("gpsNo") String gpsNo); + + @InterceptorIgnore(tenantLine = "true") + boolean updateByGpsNo(@Param("param") HjmCar param); + + @InterceptorIgnore(tenantLine = "true") + HjmCar getByCode(String code); +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmChoicesMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmChoicesMapper.java new file mode 100644 index 0000000..5ca7ae8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmChoicesMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmChoices; +import com.gxwebsoft.hjm.param.HjmChoicesParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_选择题选项Mapper + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +public interface HjmChoicesMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmChoicesParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmChoicesParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmCoursesMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmCoursesMapper.java new file mode 100644 index 0000000..81127d0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmCoursesMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmCourses; +import com.gxwebsoft.hjm.param.HjmCoursesParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_课程Mapper + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +public interface HjmCoursesMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmCoursesParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmCoursesParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmExamLogMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmExamLogMapper.java new file mode 100644 index 0000000..ab875fa --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmExamLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmExamLog; +import com.gxwebsoft.hjm.param.HjmExamLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_学习记录Mapper + * + * @author 科技小王子 + * @since 2025-06-05 14:32:03 + */ +public interface HjmExamLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmExamLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmExamLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmFenceMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmFenceMapper.java new file mode 100644 index 0000000..ab0c9af --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmFenceMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmFence; +import com.gxwebsoft.hjm.param.HjmFenceParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_电子围栏Mapper + * + * @author 科技小王子 + * @since 2025-06-03 02:08:03 + */ +public interface HjmFenceMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmFenceParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmFenceParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmGpsLogMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmGpsLogMapper.java new file mode 100644 index 0000000..caaae06 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmGpsLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmGpsLog; +import com.gxwebsoft.hjm.param.HjmGpsLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_gps轨迹Mapper + * + * @author 科技小王子 + * @since 2025-06-11 12:03:50 + */ +public interface HjmGpsLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmGpsLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmGpsLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmQuestionsMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmQuestionsMapper.java new file mode 100644 index 0000000..980dd4f --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmQuestionsMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmQuestions; +import com.gxwebsoft.hjm.param.HjmQuestionsParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_题目Mapper + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +public interface HjmQuestionsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmQuestionsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmQuestionsParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/HjmViolationMapper.java b/src/main/java/com/gxwebsoft/hjm/mapper/HjmViolationMapper.java new file mode 100644 index 0000000..539c87e --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/HjmViolationMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.hjm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.hjm.entity.HjmViolation; +import com.gxwebsoft.hjm.param.HjmViolationParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 黄家明_违章记录Mapper + * + * @author 科技小王子 + * @since 2025-06-20 13:48:43 + */ +public interface HjmViolationMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HjmViolationParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HjmViolationParam param); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmBxLogMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmBxLogMapper.xml new file mode 100644 index 0000000..d008f01 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmBxLogMapper.xml @@ -0,0 +1,62 @@ + + + + + + + SELECT a.*, b.name as carName,b.code as carNo, b.image as carAvatar, u.real_name as realName, u.nickname + FROM hjm_bx_log a + LEFT JOIN hjm_car b ON a.car_id = b.id + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.car_id = #{param.carId} + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCarMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCarMapper.xml new file mode 100644 index 0000000..ae01dba --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCarMapper.xml @@ -0,0 +1,153 @@ + + + + + + + SELECT a.*,b.organization_name as organization,e.name as fence, f.organization_name as parentOrganization, + f.comments as + parentOrganizationAdmin, u.real_name as driver,u.phone as driverPhone + FROM hjm_car a + LEFT JOIN gxwebsoft_core.sys_organization b ON a.organization_id = b.organization_id + LEFT JOIN gxwebsoft_core.sys_organization f ON a.organization_parent_id = f.organization_id + LEFT JOIN hjm_fence e ON a.fence_id = e.id + LEFT JOIN gxwebsoft_core.sys_user u ON a.driver_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.type = #{param.type} + + + AND a.organization_id = #{param.organizationId} + + + AND a.organization_parent_id = #{param.organizationParentId} + + + AND a.driver_id = #{param.driverId} + + + AND a.kuaidi LIKE CONCAT('%', #{param.kuaidi}, '%') + + + AND a.kuaidi_admin LIKE CONCAT('%', #{param.kuaidiAdmin}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.driver = #{param.driver} + + + AND a.insurance_status = #{param.insuranceStatus} + + + AND a.gps_no LIKE CONCAT('%', #{param.gpsNo}, '%') + + + AND a.fence LIKE CONCAT('%', #{param.fence}, '%') + + + AND a.district LIKE CONCAT('%', #{param.district}, '%') + + + AND a.claim = #{param.claim} + + + AND a.installer_id = #{param.installerId} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.code LIKE CONCAT('%', #{param.keywords}, '%') + OR a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.gps_no = #{param.keywords} + OR a.driver_name = #{param.keywords} + OR u.phone = #{param.keywords} + ) + + + + + + + + + + + + + + + + UPDATE hjm_car + + + longitude = #{param.longitude}, + + + latitude = #{param.latitude}, + + + speed = #{param.speed}, + + + + gps_no = #{param.gpsNo} + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmChoicesMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmChoicesMapper.xml new file mode 100644 index 0000000..9999614 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmChoicesMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM hjm_choices a + + + AND a.id = #{param.id} + + + AND a.question_id = #{param.questionId} + + + AND a.choice_content LIKE CONCAT('%', #{param.choiceContent}, '%') + + + AND a.is_correct = #{param.isCorrect} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCoursesMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCoursesMapper.xml new file mode 100644 index 0000000..bc58532 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmCoursesMapper.xml @@ -0,0 +1,66 @@ + + + + + + + SELECT a.* + FROM hjm_courses a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.type = #{param.type} + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmExamLogMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmExamLogMapper.xml new file mode 100644 index 0000000..1b869e0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmExamLogMapper.xml @@ -0,0 +1,61 @@ + + + + + + + SELECT a.*, b.nickname, b.phone, b.real_name + FROM hjm_exam_log a + LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.total = #{param.total} + + + AND a.use_time LIKE CONCAT('%', #{param.useTime}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmFenceMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmFenceMapper.xml new file mode 100644 index 0000000..6bff929 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmFenceMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM hjm_fence a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.type = #{param.type} + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.district LIKE CONCAT('%', #{param.district}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmGpsLogMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmGpsLogMapper.xml new file mode 100644 index 0000000..b6cb487 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmGpsLogMapper.xml @@ -0,0 +1,65 @@ + + + + + + + SELECT a.* + FROM hjm_gps_log a + + + AND a.id = #{param.id} + + + AND a.car_id = #{param.carId} + + + AND a.gps_no = #{param.gpsNo} + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.ddmmyy LIKE CONCAT('%', #{param.ddmmyy}, '%') + + + AND a.hhmmss LIKE CONCAT(#{param.hhmmss}, '%') + + + AND a.speed = #{param.speed} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.create_time LIKE CONCAT('%', #{param.dateTime}, '%') + + + AND a.gps_no = #{param.keywords} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmQuestionsMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmQuestionsMapper.xml new file mode 100644 index 0000000..88cb559 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmQuestionsMapper.xml @@ -0,0 +1,70 @@ + + + + + + + SELECT a.*, b.title as courseName + FROM hjm_questions a + LEFT JOIN cms_navigation b ON a.course_id = b.navigation_id + + + AND a.id = #{param.id} + + + AND a.course_id = #{param.courseId} + + + AND a.type = #{param.type} + + + AND a.question LIKE CONCAT('%', #{param.question}, '%') + + + AND a.correct_answer LIKE CONCAT('%', #{param.correctAnswer}, '%') + + + AND a.difficulty LIKE CONCAT('%', #{param.difficulty}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmViolationMapper.xml b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmViolationMapper.xml new file mode 100644 index 0000000..60d8b50 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/mapper/xml/HjmViolationMapper.xml @@ -0,0 +1,75 @@ + + + + + + + SELECT a.* + FROM hjm_violation a + LEFT JOIN hjm_car b ON a.code = b.code + LEFT JOIN gxwebsoft_core.sys_organization c ON b.organization_id = c.organization_id + LEFT JOIN gxwebsoft_core.sys_organization f ON b.organization_parent_id = f.organization_id + + + AND a.id = #{param.id} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.code = #{param.code} + + + AND a.category_id = #{param.categoryId} + + + AND a.money = #{param.money} + + + AND a.score = #{param.score} + + + AND b.organization_id = #{param.organizationId} + + + AND b.organization_parent_id = #{param.organizationParentId} + + + AND a.admin_id = #{param.adminId} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmBxLogParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmBxLogParam.java new file mode 100644 index 0000000..6f777f6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmBxLogParam.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_报险记录查询参数 + * + * @author 科技小王子 + * @since 2025-06-06 13:08:29 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmBxLogParam对象", description = "黄家明_报险记录查询参数") +public class HjmBxLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "车辆ID") + @QueryField(type = QueryType.EQ) + private Integer carId; + + @Schema(description = "保险图片") + private String image; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmCarImportParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmCarImportParam.java new file mode 100644 index 0000000..afea961 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmCarImportParam.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.hjm.param; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +import java.io.Serializable; + +/** + * 车辆导入参数 + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +@Data +public class HjmCarImportParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Excel(name = "车辆名称") + private String name; + + @Excel(name = "车辆图片") + private String image; + + @Excel(name = "类型") + private Integer type; + + @Excel(name = "管理负责人") + private String kuaidiAdmin; + + @Excel(name = "车辆编号") + private String code; + + @Excel(name = "司机ID") + private Integer driverId; + + @Excel(name = "保险状态") + private String insuranceStatus; + + @Excel(name = "GPS设备编号") + private String gpsNo; + + @Excel(name = "电子围栏ID") + private Integer fenceId; + + @Excel(name = "电子围栏名称") + private String fenceName; + + @Excel(name = "位置") + private String location; + + @Excel(name = "纬度") + private String latitude; + + @Excel(name = "经度") + private String longitude; + + @Excel(name = "区域") + private String district; + + @Excel(name = "地址") + private String address; + + @Excel(name = "站点ID") + private Integer organizationId; + + @Excel(name = "所属快递公司ID") + private Integer organizationParentId; + + @Excel(name = "微信小程序码") + private String mpCode; + + @Excel(name = "用户ID") + private Integer userId; + + @Excel(name = "排序") + private Integer sortNumber; + + @Excel(name = "备注") + private String comments; + + @Excel(name = "状态") + private Integer status; +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmCarParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmCarParam.java new file mode 100644 index 0000000..097b92f --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmCarParam.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_车辆管理查询参数 + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmCarParam对象", description = "黄家明_车辆管理查询参数") +public class HjmCarParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "车辆名称") + private String name; + + @Schema(description = "车辆图片") + private String image; + + @Schema(description = "类型 0汽车 1其他车") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "快递公司") + private String kuaidi; + + @Schema(description = "管理负责人") + private String kuaidiAdmin; + + @Schema(description = "车辆编号") + private String code; + + @Schema(description = "司机ID") + @QueryField(type = QueryType.EQ) + private Integer driverId; + + @Schema(description = "司机") + @QueryField(type = QueryType.EQ) + private Integer driver; + + @Schema(description = "保险状态") + @QueryField(type = QueryType.EQ) + private String insuranceStatus; + + @Schema(description = "GPS设备编号") + private String gpsNo; + + @Schema(description = "电子围栏") + private String fence; + + @Schema(description = "所在区域") + @QueryField(type = QueryType.EQ) + private String district; + + @Schema(description = "纬度") + @QueryField(type = QueryType.EQ) + private String latitude; + + @Schema(description = "经度") + @QueryField(type = QueryType.EQ) + private String longitude; + + @Schema(description = "组织ID") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "父级组织ID") + @QueryField(type = QueryType.EQ) + private Integer organizationParentId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "安装人员ID") + @QueryField(type = QueryType.EQ) + private Integer installerId; + + @Schema(description = "安装时间") + private String installTime; + + @Schema(description = "是否认领, 0未认领, 1已认领") + @QueryField(type = QueryType.EQ) + private Integer claim; + + @Schema(description = "认领时间") + private String claimTime; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "所属站点") + @QueryField(type = QueryType.EQ) + private String organizationName; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmChoicesParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmChoicesParam.java new file mode 100644 index 0000000..76398e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmChoicesParam.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_选择题选项查询参数 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmChoicesParam对象", description = "黄家明_选择题选项查询参数") +public class HjmChoicesParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "题目ID") + @QueryField(type = QueryType.EQ) + private Integer questionId; + + @Schema(description = "题目") + private String choiceContent; + + @Schema(description = "是否正确") + @QueryField(type = QueryType.EQ) + private Integer isCorrect; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmCoursesParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmCoursesParam.java new file mode 100644 index 0000000..5992687 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmCoursesParam.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_课程查询参数 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmCoursesParam对象", description = "黄家明_课程查询参数") +public class HjmCoursesParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "课程名称") + private String name; + + @Schema(description = "类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "课程编号") + private String code; + + @Schema(description = "课程封面图") + private String image; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmExamLogParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmExamLogParam.java new file mode 100644 index 0000000..2b37098 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmExamLogParam.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_学习记录查询参数 + * + * @author 科技小王子 + * @since 2025-06-05 14:32:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmExamLogParam对象", description = "黄家明_学习记录查询参数") +public class HjmExamLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "得分") + @QueryField(type = QueryType.EQ) + private BigDecimal total; + + @Schema(description = "用时") + private String useTime; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmFenceParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmFenceParam.java new file mode 100644 index 0000000..a67e93f --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmFenceParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_电子围栏查询参数 + * + * @author 科技小王子 + * @since 2025-06-03 02:08:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmFenceParam对象", description = "黄家明_电子围栏查询参数") +public class HjmFenceParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "围栏名称") + private String name; + + @Schema(description = "类型 0圆形 1方形") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "区域") + private String district; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmGpsLogParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmGpsLogParam.java new file mode 100644 index 0000000..50d42d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmGpsLogParam.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_gps轨迹查询参数 + * + * @author 科技小王子 + * @since 2025-06-11 12:03:50 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmGpsLogParam对象", description = "黄家明_gps轨迹查询参数") +public class HjmGpsLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "车辆ID") + @QueryField(type = QueryType.EQ) + private Integer carId; + + @Schema(description = "gps编号") + @QueryField(type = QueryType.EQ) + private String gpsNo; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "速度") + private String speed; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "按日期查询") + @QueryField(type = QueryType.EQ) + private String dateTime; + + @Schema(description = "设备当前时间") + @QueryField(type = QueryType.EQ) + private String ddmmyy; + + @Schema(description = "时分秒") + @QueryField(type = QueryType.LIKE) + private String hhmmss; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmQuestionsParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmQuestionsParam.java new file mode 100644 index 0000000..27c80d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmQuestionsParam.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_题目查询参数 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmQuestionsParam对象", description = "黄家明_题目查询参数") +public class HjmQuestionsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "课程ID") + @QueryField(type = QueryType.EQ) + private Integer courseId; + + @Schema(description = "类型 0choice 1fill 2essay") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "题目") + private String question; + + @Schema(description = "正确答案") + private String correctAnswer; + + @Schema(description = "难度,'easy', 'medium', 'hard'") + private String difficulty; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/param/HjmViolationParam.java b/src/main/java/com/gxwebsoft/hjm/param/HjmViolationParam.java new file mode 100644 index 0000000..f9f2d7a --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/param/HjmViolationParam.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.hjm.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 黄家明_违章记录查询参数 + * + * @author 科技小王子 + * @since 2025-06-20 13:48:43 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HjmViolationParam对象", description = "黄家明_违章记录查询参数") +public class HjmViolationParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "标题") + private String title; + + @Schema(description = "车牌号") + @QueryField(type = QueryType.EQ) + private String code; + + @Schema(description = "文章分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "处罚金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "扣分") + @QueryField(type = QueryType.EQ) + private BigDecimal score; + + @Schema(description = "录入员") + @QueryField(type = QueryType.EQ) + private Integer adminId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0未处理, 1已处理") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "组织ID") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "父级组织ID") + @QueryField(type = QueryType.EQ) + private Integer organizationParentId; + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/GpsDiagnosticService.java b/src/main/java/com/gxwebsoft/hjm/service/GpsDiagnosticService.java new file mode 100644 index 0000000..28d648a --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/GpsDiagnosticService.java @@ -0,0 +1,289 @@ +package com.gxwebsoft.hjm.service; + +import cn.hutool.core.util.StrUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.entity.HjmGpsLog; +import com.gxwebsoft.hjm.param.HjmCarParam; +import com.gxwebsoft.hjm.param.HjmGpsLogParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * GPS诊断服务 + * + * @author 科技小王子 + * @since 2025-07-02 + */ +@Service +public class GpsDiagnosticService { + + private static final Logger logger = LoggerFactory.getLogger(GpsDiagnosticService.class); + + @Resource + private HjmCarService hjmCarService; + + @Resource + private HjmGpsLogService hjmGpsLogService; + + @Resource + private RedisUtil redisUtil; + + /** + * 诊断GPS数据上送问题 + * + * @return 诊断报告 + */ + public Map diagnoseGpsDataIssues() { + Map report = new HashMap<>(); + + logger.info("开始GPS数据诊断..."); + + // 1. 检查所有车辆的GPS配置 + Map carGpsConfig = checkCarGpsConfiguration(); + report.put("车辆GPS配置检查", carGpsConfig); + + // 2. 检查GPS日志数据 + Map gpsLogAnalysis = analyzeGpsLogData(); + report.put("GPS日志分析", gpsLogAnalysis); + + // 3. 检查Redis缓存状态 + Map redisStatus = checkRedisCache(); + report.put("Redis缓存状态", redisStatus); + + // 4. 检查有数据的GPS设备 + Map activeGpsDevices = checkActiveGpsDevices(); + report.put("活跃GPS设备", activeGpsDevices); + + logger.info("GPS数据诊断完成"); + + return report; + } + + /** + * 检查车辆GPS配置 + */ + private Map checkCarGpsConfiguration() { + Map result = new HashMap<>(); + + try { + // 查询所有车辆 + HjmCarParam param = new HjmCarParam(); + List allCars = hjmCarService.listRel(param); + + int totalCars = allCars.size(); + int carsWithGps = 0; + int carsWithoutGps = 0; + + Map gpsDeviceMapping = new HashMap<>(); + + for (HjmCar car : allCars) { + if (StrUtil.isNotBlank(car.getGpsNo())) { + carsWithGps++; + gpsDeviceMapping.put(car.getGpsNo(), car.getCode()); + } else { + carsWithoutGps++; + logger.warn("车辆 {} (ID: {}) 未配置GPS设备编号", car.getCode(), car.getId()); + } + } + + result.put("总车辆数", totalCars); + result.put("已配置GPS的车辆数", carsWithGps); + result.put("未配置GPS的车辆数", carsWithoutGps); + result.put("GPS设备映射", gpsDeviceMapping); + + logger.info("车辆GPS配置检查完成 - 总数: {}, 已配置GPS: {}, 未配置GPS: {}", + totalCars, carsWithGps, carsWithoutGps); + + } catch (Exception e) { + logger.error("检查车辆GPS配置失败", e); + result.put("错误", e.getMessage()); + } + + return result; + } + + /** + * 分析GPS日志数据 + */ + private Map analyzeGpsLogData() { + Map result = new HashMap<>(); + + try { + // 查询最近的GPS日志 + HjmGpsLogParam param = new HjmGpsLogParam(); + List recentLogs = hjmGpsLogService.listRel(param); + + Map gpsDeviceLogCount = new HashMap<>(); + Map latestLogTime = new HashMap<>(); + + for (HjmGpsLog log : recentLogs) { + String gpsNo = log.getGpsNo(); + if (StrUtil.isNotBlank(gpsNo)) { + gpsDeviceLogCount.put(gpsNo, gpsDeviceLogCount.getOrDefault(gpsNo, 0) + 1); + + if (log.getCreateTime() != null) { + String currentTime = latestLogTime.get(gpsNo); + String logTime = log.getCreateTime().toString(); + if (currentTime == null || logTime.compareTo(currentTime) > 0) { + latestLogTime.put(gpsNo, logTime); + } + } + } + } + + result.put("总日志条数", recentLogs.size()); + result.put("各GPS设备日志数量", gpsDeviceLogCount); + result.put("各GPS设备最新日志时间", latestLogTime); + + // 找出有数据的GPS设备 + if (!gpsDeviceLogCount.isEmpty()) { + String mostActiveGps = gpsDeviceLogCount.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse("无"); + result.put("最活跃的GPS设备", mostActiveGps); + result.put("最活跃设备日志数", gpsDeviceLogCount.getOrDefault(mostActiveGps, 0)); + } + + logger.info("GPS日志分析完成 - 总日志数: {}, 活跃设备数: {}", + recentLogs.size(), gpsDeviceLogCount.size()); + + } catch (Exception e) { + logger.error("分析GPS日志数据失败", e); + result.put("错误", e.getMessage()); + } + + return result; + } + + /** + * 检查Redis缓存状态 + */ + private Map checkRedisCache() { + Map result = new HashMap<>(); + + try { + // 检查围栏缓存 + String testGpsNo = "862317042719778"; + String fenceKey = "inFence:" + testGpsNo; + String fenceCache = redisUtil.get(fenceKey); + + result.put("测试GPS围栏缓存键", fenceKey); + result.put("测试GPS围栏缓存值", fenceCache != null ? fenceCache : "无缓存"); + + // 检查GPS日志缓存 + String gpsLogCache = redisUtil.get(testGpsNo); + result.put("测试GPS日志缓存", gpsLogCache != null ? gpsLogCache : "无缓存"); + + logger.info("Redis缓存状态检查完成"); + + } catch (Exception e) { + logger.error("检查Redis缓存状态失败", e); + result.put("错误", e.getMessage()); + } + + return result; + } + + /** + * 检查活跃的GPS设备 + */ + private Map checkActiveGpsDevices() { + Map result = new HashMap<>(); + + try { + // 检查特定GPS设备的车辆信息 + String activeGpsNo = "862317042719778"; + HjmCar activeCar = hjmCarService.getByGpsNo(activeGpsNo); + + if (activeCar != null) { + Map activeCarInfo = new HashMap<>(); + activeCarInfo.put("车辆编号", activeCar.getCode()); + activeCarInfo.put("车辆名称", activeCar.getName()); + activeCarInfo.put("车辆ID", activeCar.getId()); + activeCarInfo.put("GPS设备编号", activeCar.getGpsNo()); + activeCarInfo.put("最新经度", activeCar.getLongitude()); + activeCarInfo.put("最新纬度", activeCar.getLatitude()); + activeCarInfo.put("最新速度", activeCar.getSpeed()); + activeCarInfo.put("更新时间", activeCar.getUpdateTime()); + activeCarInfo.put("围栏ID", activeCar.getFenceId()); + activeCarInfo.put("是否在围栏内", activeCar.getInFence()); + + result.put("活跃GPS设备信息", activeCarInfo); + } else { + result.put("活跃GPS设备信息", "未找到对应车辆"); + } + + // 检查其他GPS设备 + HjmCarParam param = new HjmCarParam(); + List allCars = hjmCarService.listRel(param); + + Map otherGpsDevices = new HashMap<>(); + for (HjmCar car : allCars) { + if (StrUtil.isNotBlank(car.getGpsNo()) && !activeGpsNo.equals(car.getGpsNo())) { + otherGpsDevices.put(car.getGpsNo(), car.getCode()); + } + } + + result.put("其他GPS设备", otherGpsDevices); + + logger.info("活跃GPS设备检查完成"); + + } catch (Exception e) { + logger.error("检查活跃GPS设备失败", e); + result.put("错误", e.getMessage()); + } + + return result; + } + + /** + * 检查特定GPS设备的详细信息 + * + * @param gpsNo GPS设备编号 + * @return 设备详细信息 + */ + public Map checkSpecificGpsDevice(String gpsNo) { + Map result = new HashMap<>(); + + try { + logger.info("检查GPS设备详细信息: {}", gpsNo); + + // 检查车辆信息 + HjmCar car = hjmCarService.getByGpsNo(gpsNo); + if (car != null) { + result.put("车辆信息", car); + } else { + result.put("车辆信息", "未找到对应车辆"); + } + + // 检查GPS日志 + HjmGpsLogParam logParam = new HjmGpsLogParam(); + logParam.setGpsNo(gpsNo); + List logs = hjmGpsLogService.listRel(logParam); + result.put("GPS日志数量", logs.size()); + if (!logs.isEmpty()) { + result.put("最新GPS日志", logs.get(0)); + } + + // 检查Redis缓存 + String fenceCache = redisUtil.get("inFence:" + gpsNo); + String logCache = redisUtil.get(gpsNo); + result.put("围栏缓存", fenceCache); + result.put("日志缓存", logCache); + + } catch (Exception e) { + logger.error("检查GPS设备详细信息失败: {}", gpsNo, e); + result.put("错误", e.getMessage()); + } + + return result; + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/GpsMessageCallback.java b/src/main/java/com/gxwebsoft/hjm/service/GpsMessageCallback.java new file mode 100644 index 0000000..de2247c --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/GpsMessageCallback.java @@ -0,0 +1,123 @@ +package com.gxwebsoft.hjm.service; + +import org.eclipse.paho.client.mqttv3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * GPS消息回调处理器 + * + * @author 科技小王子 + * @since 2025-07-02 + */ +@Component +public class GpsMessageCallback implements MqttCallback { + + private static final Logger logger = LoggerFactory.getLogger(GpsMessageCallback.class); + + @Resource + private GpsMessageProcessor gpsMessageProcessor; + + @Override + public void connectionLost(Throwable cause) { + logger.error("MQTT连接丢失", cause); + + // 可以在这里添加告警通知逻辑 + // 例如:发送邮件、短信通知管理员 + notifyConnectionLost(cause); + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + try { + String payload = new String(message.getPayload()); + + logger.debug("接收到MQTT消息 - 主题: {}, QoS: {}, 消息长度: {}", + topic, message.getQos(), payload.length()); + + // 记录详细的消息内容(仅在DEBUG级别) + if (logger.isDebugEnabled()) { + logger.debug("消息内容: {}", payload); + } + + // 委托给专门的处理器处理 + gpsMessageProcessor.processGpsMessage(payload); + + } catch (Exception e) { + logger.error("处理MQTT消息失败 - 主题: {}, 错误: {}", topic, e.getMessage(), e); + + // 不要重新抛出异常,避免影响其他消息的处理 + // 可以在这里添加失败消息的重试机制或死信队列 + handleMessageProcessingFailure(topic, message, e); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + logger.debug("MQTT消息发送完成: {}", token.isComplete()); + + try { + if (token.getTopics() != null && token.getTopics().length > 0) { + logger.debug("发送完成的主题: {}", String.join(", ", token.getTopics())); + } + } catch (Exception e) { + logger.warn("获取发送完成的主题信息失败", e); + } + } + + /** + * 处理连接丢失的通知 + * + * @param cause 连接丢失的原因 + */ + private void notifyConnectionLost(Throwable cause) { + try { + // 这里可以实现具体的通知逻辑 + // 例如: + // 1. 发送邮件通知 + // 2. 发送短信通知 + // 3. 写入告警日志 + // 4. 调用监控系统API + + logger.warn("MQTT连接丢失通知已触发,原因: {}", cause.getMessage()); + + // 示例:可以调用告警服务 + // alertService.sendAlert("MQTT连接丢失", cause.getMessage()); + + } catch (Exception e) { + logger.error("发送MQTT连接丢失通知失败", e); + } + } + + /** + * 处理消息处理失败的情况 + * + * @param topic 消息主题 + * @param message 消息内容 + * @param error 错误信息 + */ + private void handleMessageProcessingFailure(String topic, MqttMessage message, Exception error) { + try { + // 这里可以实现失败消息的处理逻辑 + // 例如: + // 1. 将失败的消息保存到数据库 + // 2. 发送到死信队列 + // 3. 记录到失败日志文件 + // 4. 实现重试机制 + + String payload = new String(message.getPayload()); + logger.warn("消息处理失败记录 - 主题: {}, 消息: {}, 错误: {}", + topic, payload.length() > 100 ? payload.substring(0, 100) + "..." : payload, + error.getMessage()); + + // 示例:可以保存失败消息到数据库 + // failedMessageService.saveFailedMessage(topic, payload, error.getMessage()); + + } catch (Exception e) { + logger.error("处理消息失败记录时发生错误", e); + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/GpsMessageProcessor.java b/src/main/java/com/gxwebsoft/hjm/service/GpsMessageProcessor.java new file mode 100644 index 0000000..5bc1fd8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/GpsMessageProcessor.java @@ -0,0 +1,258 @@ +package com.gxwebsoft.hjm.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.support.geo.Point; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.hjm.entity.Gps; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.entity.HjmFence; +import com.gxwebsoft.hjm.entity.HjmGpsLog; +import com.gxwebsoft.hjm.service.impl.HjmCarServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + +/** + * GPS消息处理器 + * + * @author 科技小王子 + * @since 2025-07-02 + */ +@Service +public class GpsMessageProcessor { + + private static final Logger logger = LoggerFactory.getLogger(GpsMessageProcessor.class); + + @Resource + private HjmCarService hjmCarService; + + @Resource + private HjmGpsLogService hjmGpsLogService; + + @Resource + private HjmFenceService hjmFenceService; + + @Resource + private RedisUtil redisUtil; + + @Resource + private HjmCarServiceImpl hjmCarServiceImpl; + + /** + * 处理GPS消息 + * + * @param payload 消息内容 + */ + public void processGpsMessage(String payload) { + try { + logger.debug("开始处理GPS消息: {}", payload); + + Gps gps = JSONUtil.parseObject(payload, Gps.class); + if (ObjectUtil.isEmpty(gps)) { + logger.warn("GPS数据为空,跳过处理"); + return; + } + + processGpsData(gps); + + } catch (Exception e) { + logger.error("处理GPS数据失败: {}", payload, e); + } + } + + /** + * 处理GPS数据 + * + * @param gps GPS数据 + */ + private void processGpsData(Gps gps) { + try { + String gpsNo = gps.getImei(); + if (StrUtil.isBlank(gpsNo)) { + logger.warn("GPS设备编号为空,跳过处理"); + return; + } + + // 详细记录GPS数据处理过程 + logger.info("处理GPS数据 - 设备编号: {}, Fixed: {}, 经度: {}, 纬度: {}, 速度: {}", + gpsNo, gps.getFixed(), gps.getLng(), gps.getLat(), gps.getSpeed()); + + HjmCar car = hjmCarService.getByGpsNo(gpsNo); + + if (car == null) { + logger.warn("GPS设备编号: {} 在数据库中未找到对应车辆,请检查车辆配置", gpsNo); + return; + } + + logger.info("GPS设备编号: {} 对应车辆: {} (ID: {})", gpsNo, car.getCode(), car.getId()); + + if (!gps.getFixed()) { + logger.warn("GPS设备编号: {} 定位未固定(Fixed=false),跳过处理", gpsNo); + return; + } + + // 更新车辆GPS定位信息 + updateCarLocation(car, gps); + + // 保存GPS轨迹日志 + saveGpsLog(car, gps); + + // 检查电子围栏 + checkFence(car, gps); + + } catch (Exception e) { + logger.error("处理GPS数据时发生错误", e); + } + } + + /** + * 更新车辆位置信息 + */ + private void updateCarLocation(HjmCar car, Gps gps) { + try { + car.setLongitude(gps.getLng()); + car.setLatitude(gps.getLat()); + car.setSpeed(gps.getSpeed()); + car.setUpdateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(gps.getTime() * 1000), ZoneId.systemDefault())); + car.setGpsNo(gps.getImei()); + + if (hjmCarService.updateByGpsNo(car)) { + logger.debug("更新车辆GPS定位信息成功: {}", car.getCode()); + + // 检查是否需要保存GPS日志(避免重复保存) + String keyByGpsNo = redisUtil.get(gps.getImei()); + if (StrUtil.isBlank(keyByGpsNo)) { + saveGpsLogRecord(car, gps); + } + } else { + logger.warn("更新车辆GPS定位信息失败: {}", car.getCode()); + } + + } catch (Exception e) { + logger.error("更新车辆位置信息失败", e); + } + } + + /** + * 保存GPS轨迹日志记录 + */ + private void saveGpsLogRecord(HjmCar car, Gps gps) { + try { + HjmGpsLog log = new HjmGpsLog(); + log.setTenantId(10519); + log.setCarId(car.getId()); + log.setGpsNo(gps.getImei()); + log.setLatitude(gps.getLat()); + log.setLongitude(gps.getLng()); + log.setDdmmyy(gps.getDdmmyy()); + log.setHhmmss(gps.getHhmmss()); + log.setSpeed(gps.getSpeed()); + + if (!log.getSpeed().equals("0.000")) { + hjmGpsLogService.save(log); + logger.debug("保存GPS轨迹日志成功: {}", gps.getImei()); + } + + } catch (Exception e) { + logger.error("保存GPS轨迹日志失败", e); + } + } + + /** + * 保存GPS日志(兼容原有方法) + */ + private void saveGpsLog(HjmCar car, Gps gps) { + // 这里可以添加其他GPS日志相关的处理逻辑 + logger.debug("处理GPS日志: 车辆={}, GPS={}", car.getCode(), gps.getImei()); + } + + /** + * 检查电子围栏 + */ + private void checkFence(HjmCar car, Gps gps) { + try { + String gpsNo = gps.getImei(); + + // 检查围栏缓存,避免频繁计算 + String inFenceKey = redisUtil.get("inFence:" + gpsNo); + if (StrUtil.isNotBlank(inFenceKey)) { + logger.debug("围栏检查缓存命中,跳过计算: {}", gpsNo); + return; + } + + // 设置围栏检查缓存(1天) + redisUtil.set("inFence:" + gpsNo, "1", 1L, TimeUnit.DAYS); + + // 检查是否配置了围栏 + if (car.getFenceId() == null) { + logger.debug("车辆未配置围栏: {}", car.getCode()); + return; + } + + HjmFence fence = hjmFenceService.getById(car.getFenceId()); + if (fence == null) { + logger.warn("围栏不存在: {}", car.getFenceId()); + return; + } + + // 检查坐标有效性 + if (!isValidCoordinate(car.getLongitude(), car.getLatitude())) { + logger.warn("车辆坐标无效: 经度={}, 纬度={}", car.getLongitude(), car.getLatitude()); + return; + } + + // 执行围栏判断 + performFenceCheck(car, fence); + + } catch (Exception e) { + logger.error("检查电子围栏时发生错误", e); + } + } + + /** + * 检查坐标有效性 + */ + private boolean isValidCoordinate(String longitude, String latitude) { + return longitude != null && latitude != null && + !longitude.trim().isEmpty() && !latitude.trim().isEmpty(); + } + + /** + * 执行围栏判断 + */ + private void performFenceCheck(HjmCar car, HjmFence fence) { + try { + double lng = Double.parseDouble(car.getLongitude()); + double lat = Double.parseDouble(car.getLatitude()); + + Point carPoint = new Point(); + carPoint.setLongitude(lng); + carPoint.setLatitude(lat); + + // 使用多边形围栏判断 + boolean isInFence = hjmCarServiceImpl.checkPolygonFence(carPoint, fence); + + // 更新围栏状态 + car.setInFence(isInFence); + car.setUpdateTime(LocalDateTime.now()); + hjmCarService.updateById(car); + + logger.info("车辆围栏检查完成: 车辆={}, 围栏={}, 是否在围栏内={}", + car.getCode(), fence.getId(), isInFence); + + } catch (NumberFormatException e) { + logger.error("车辆坐标格式错误: 经度={}, 纬度={}", car.getLongitude(), car.getLatitude(), e); + } catch (Exception e) { + logger.error("执行围栏判断时发生错误", e); + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmBxLogService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmBxLogService.java new file mode 100644 index 0000000..4dd762d --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmBxLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmBxLog; +import com.gxwebsoft.hjm.param.HjmBxLogParam; + +import java.util.List; + +/** + * 黄家明_报险记录Service + * + * @author 科技小王子 + * @since 2025-06-06 13:08:29 + */ +public interface HjmBxLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmBxLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmBxLogParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmBxLog + */ + HjmBxLog getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmCarService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmCarService.java new file mode 100644 index 0000000..6332030 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmCarService.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.param.HjmCarParam; + +import java.util.List; + +/** + * 黄家明_车辆管理Service + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +public interface HjmCarService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmCarParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmCarParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmCar + */ + HjmCar getByIdRel(Integer id); + + HjmCar getByGpsNo(String gpsNo); + + boolean updateByGpsNo(HjmCar byGpsNo); + + HjmCar getByCode(String code); +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmChoicesService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmChoicesService.java new file mode 100644 index 0000000..1fb113d --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmChoicesService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmChoices; +import com.gxwebsoft.hjm.param.HjmChoicesParam; + +import java.util.List; + +/** + * 黄家明_选择题选项Service + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +public interface HjmChoicesService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmChoicesParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmChoicesParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmChoices + */ + HjmChoices getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmCoursesService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmCoursesService.java new file mode 100644 index 0000000..25b8984 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmCoursesService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmCourses; +import com.gxwebsoft.hjm.param.HjmCoursesParam; + +import java.util.List; + +/** + * 黄家明_课程Service + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +public interface HjmCoursesService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmCoursesParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmCoursesParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmCourses + */ + HjmCourses getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmExamLogService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmExamLogService.java new file mode 100644 index 0000000..6340bdb --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmExamLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmExamLog; +import com.gxwebsoft.hjm.param.HjmExamLogParam; + +import java.util.List; + +/** + * 黄家明_学习记录Service + * + * @author 科技小王子 + * @since 2025-06-05 14:32:03 + */ +public interface HjmExamLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmExamLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmExamLogParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmExamLog + */ + HjmExamLog getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmFenceService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmFenceService.java new file mode 100644 index 0000000..6b353ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmFenceService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmFence; +import com.gxwebsoft.hjm.param.HjmFenceParam; + +import java.util.List; + +/** + * 黄家明_电子围栏Service + * + * @author 科技小王子 + * @since 2025-06-03 02:08:03 + */ +public interface HjmFenceService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmFenceParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmFenceParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmFence + */ + HjmFence getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmGpsLogService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmGpsLogService.java new file mode 100644 index 0000000..04537d8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmGpsLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmGpsLog; +import com.gxwebsoft.hjm.param.HjmGpsLogParam; + +import java.util.List; + +/** + * 黄家明_gps轨迹Service + * + * @author 科技小王子 + * @since 2025-06-11 12:03:50 + */ +public interface HjmGpsLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmGpsLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmGpsLogParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmGpsLog + */ + HjmGpsLog getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmQuestionsService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmQuestionsService.java new file mode 100644 index 0000000..442a252 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmQuestionsService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmQuestions; +import com.gxwebsoft.hjm.param.HjmQuestionsParam; + +import java.util.List; + +/** + * 黄家明_题目Service + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +public interface HjmQuestionsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmQuestionsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmQuestionsParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmQuestions + */ + HjmQuestions getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/HjmViolationService.java b/src/main/java/com/gxwebsoft/hjm/service/HjmViolationService.java new file mode 100644 index 0000000..731f8e8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/HjmViolationService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.hjm.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.entity.HjmViolation; +import com.gxwebsoft.hjm.param.HjmViolationParam; + +import java.util.List; + +/** + * 黄家明_违章记录Service + * + * @author 科技小王子 + * @since 2025-06-20 13:48:43 + */ +public interface HjmViolationService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HjmViolationParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HjmViolationParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return HjmViolation + */ + HjmViolation getByIdRel(Integer id); + + void send(HjmViolation hjmViolation); +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/MqttService.java b/src/main/java/com/gxwebsoft/hjm/service/MqttService.java new file mode 100644 index 0000000..7dba245 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/MqttService.java @@ -0,0 +1,330 @@ +package com.gxwebsoft.hjm.service; + +import com.gxwebsoft.common.core.config.MqttProperties; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * MQTT服务类 + * + * @author 科技小王子 + * @since 2025-07-02 + */ +@Service +public class MqttService { + + private static final Logger logger = LoggerFactory.getLogger(MqttService.class); + + @Resource + private MqttProperties mqttProperties; + + @Resource + private GpsMessageCallback gpsMessageCallback; + + private MqttClient client; + private String clientId; + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + @PostConstruct + public void init() { + try { + logger.info("开始初始化MQTT服务..."); + + // 检查是否启用MQTT服务 + if (!mqttProperties.isEnabled()) { + logger.info("MQTT服务已禁用,跳过初始化"); + return; + } + + // 验证配置属性 + validateMqttProperties(); + + // 生成唯一的客户端ID + clientId = mqttProperties.getClientIdPrefix() + System.currentTimeMillis(); + logger.info("生成客户端ID: {}", clientId); + + // 连接MQTT服务器 + connect(); + + // 订阅主题 + subscribe(); + + // 启动连接状态监控 + startConnectionMonitor(); + + logger.info("MQTT服务初始化完成"); + + } catch (Exception e) { + logger.error("MQTT服务初始化失败", e); + // 不要抛出异常,避免影响应用启动 + // 可以在后台定期重试连接 + scheduleReconnect(); + } + } + + /** + * 验证MQTT配置属性 + */ + private void validateMqttProperties() { + if (mqttProperties == null) { + throw new IllegalArgumentException("MQTT配置属性为null,请检查配置文件和@EnableConfigurationProperties注解"); + } + + if (gpsMessageCallback == null) { + throw new IllegalArgumentException("GPS消息回调处理器为null,请检查@Component注解"); + } + + logger.info("MQTT配置验证:"); + logger.info(" Host: {}", mqttProperties.getHost()); + logger.info(" Username: {}", mqttProperties.getUsername()); + logger.info(" Password: {}", mqttProperties.getPassword() != null ? "***" : "null"); + logger.info(" ClientIdPrefix: {}", mqttProperties.getClientIdPrefix()); + logger.info(" Topic: {}", mqttProperties.getTopic()); + logger.info(" QoS: {}", mqttProperties.getQos()); + logger.info(" ConnectionTimeout: {}", mqttProperties.getConnectionTimeout()); + logger.info(" KeepAliveInterval: {}", mqttProperties.getKeepAliveInterval()); + logger.info(" AutoReconnect: {}", mqttProperties.isAutoReconnect()); + logger.info(" CleanSession: {}", mqttProperties.isCleanSession()); + + if (mqttProperties.getHost() == null || mqttProperties.getHost().trim().isEmpty()) { + throw new IllegalArgumentException("MQTT服务器地址不能为空"); + } + + if (mqttProperties.getClientIdPrefix() == null) { + throw new IllegalArgumentException("MQTT客户端ID前缀不能为空"); + } + + if (mqttProperties.getTopic() == null || mqttProperties.getTopic().trim().isEmpty()) { + throw new IllegalArgumentException("MQTT订阅主题不能为空"); + } + + logger.info("MQTT配置验证通过"); + } + + /** + * 连接MQTT服务器 + */ + private void connect() throws MqttException { + if (client != null && client.isConnected()) { + logger.debug("MQTT客户端已连接,跳过连接操作"); + return; + } + + logger.info("正在连接MQTT服务器: {}", mqttProperties.getHost()); + + // 创建MQTT客户端 + client = new MqttClient(mqttProperties.getHost(), clientId, new MemoryPersistence()); + + // 设置连接选项 + MqttConnectOptions options = createConnectOptions(); + + // 设置回调 + client.setCallback(gpsMessageCallback); + + // 连接服务器 + client.connect(options); + + logger.info("MQTT连接成功 - 服务器: {}, 客户端ID: {}", mqttProperties.getHost(), clientId); + } + + /** + * 创建连接选项 + */ + private MqttConnectOptions createConnectOptions() { + MqttConnectOptions options = new MqttConnectOptions(); + + // 基本连接参数 + options.setCleanSession(mqttProperties.isCleanSession()); + options.setUserName(mqttProperties.getUsername()); + options.setPassword(mqttProperties.getPassword().toCharArray()); + + // 超时和心跳设置 + options.setConnectionTimeout(mqttProperties.getConnectionTimeout()); + options.setKeepAliveInterval(mqttProperties.getKeepAliveInterval()); + + // 自动重连设置 + options.setAutomaticReconnect(mqttProperties.isAutoReconnect()); + + // 遗嘱消息设置(可选) + // options.setWill("client/disconnect", "Client disconnected".getBytes(), 1, false); + + logger.debug("MQTT连接选项配置完成 - 自动重连: {}, 清除会话: {}", + options.isAutomaticReconnect(), options.isCleanSession()); + + return options; + } + + /** + * 订阅主题 + */ + public void subscribe() throws MqttException { + if (client == null || !client.isConnected()) { + logger.warn("MQTT客户端未连接,无法订阅主题"); + return; + } + + String topic = mqttProperties.getTopic(); + int qos = mqttProperties.getQos(); + + client.subscribe(topic, qos); + logger.info("MQTT主题订阅成功 - 主题: {}, QoS: {}", topic, qos); + } + + /** + * 发布消息 + */ + public void publish(String topic, String payload) throws MqttException { + publish(topic, payload, mqttProperties.getQos(), false); + } + + /** + * 发布消息(指定QoS和保留标志) + */ + public void publish(String topic, String payload, int qos, boolean retained) throws MqttException { + if (client == null || !client.isConnected()) { + throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED); + } + + MqttMessage message = new MqttMessage(); + message.setPayload(payload.getBytes()); + message.setQos(qos); + message.setRetained(retained); + + client.publish(topic, message); + + logger.debug("MQTT消息发布 - 主题: {}, QoS: {}, 保留: {}, 消息长度: {}", + topic, qos, retained, payload.length()); + + // 可选:等待发布完成 + // token.waitForCompletion(); + } + + /** + * 检查连接状态 + */ + public boolean isConnected() { + return client != null && client.isConnected(); + } + + /** + * 获取客户端信息 + */ + public String getClientInfo() { + if (client == null) { + return "MQTT客户端未初始化"; + } + + return String.format("客户端ID: %s, 连接状态: %s, 服务器: %s", + clientId, + client.isConnected() ? "已连接" : "未连接", + mqttProperties.getHost()); + } + + /** + * 启动连接状态监控 + */ + private void startConnectionMonitor() { + scheduler.scheduleWithFixedDelay(() -> { + try { + if (!isConnected()) { + logger.warn("检测到MQTT连接断开,尝试重新连接..."); + reconnect(); + } + } catch (Exception e) { + logger.error("MQTT连接监控异常", e); + } + }, 30, 30, TimeUnit.SECONDS); // 每30秒检查一次连接状态 + + logger.debug("MQTT连接状态监控已启动"); + } + + /** + * 重新连接 + */ + public void reconnect() { + try { + if (client != null && client.isConnected()) { + return; + } + + logger.info("正在重新连接MQTT服务器..."); + + // 先断开现有连接 + disconnect(); + + // 重新连接 + connect(); + subscribe(); + + logger.info("MQTT重新连接成功"); + + } catch (Exception e) { + logger.error("MQTT重新连接失败", e); + // 安排下次重试 + scheduleReconnect(); + } + } + + /** + * 安排重新连接 + */ + private void scheduleReconnect() { + scheduler.schedule(() -> { + logger.info("执行定时重连任务..."); + reconnect(); + }, 60, TimeUnit.SECONDS); // 60秒后重试 + } + + /** + * 断开连接 + */ + public void disconnect() { + try { + if (client != null && client.isConnected()) { + client.disconnect(); + logger.info("MQTT连接已断开"); + } + } catch (MqttException e) { + logger.error("断开MQTT连接失败", e); + } + } + + /** + * 应用关闭时清理资源 + */ + @PreDestroy + public void destroy() { + logger.info("正在关闭MQTT服务..."); + + try { + // 关闭定时任务 + scheduler.shutdown(); + if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + + // 断开MQTT连接 + if (client != null) { + if (client.isConnected()) { + client.disconnect(); + } + client.close(); + } + + logger.info("MQTT服务已关闭"); + + } catch (Exception e) { + logger.error("关闭MQTT服务时发生错误", e); + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/WxNotificationService.java b/src/main/java/com/gxwebsoft/hjm/service/WxNotificationService.java new file mode 100644 index 0000000..42873a7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/WxNotificationService.java @@ -0,0 +1,82 @@ +package com.gxwebsoft.hjm.service; + +import com.gxwebsoft.hjm.dto.SubscribeMessageRequest; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest; + +import java.util.List; + +/** + * 微信通知服务接口 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +public interface WxNotificationService { + + /** + * 发送微信公众号模板消息 + * + * @param tenantId 租户ID + * @param request 模板消息请求 + * @return 发送结果 + */ + boolean sendTemplateMessage(Integer tenantId, TemplateMessageRequest request); + + /** + * 发送微信小程序订阅消息 + * + * @param tenantId 租户ID + * @param request 订阅消息请求 + * @return 发送结果 + */ + boolean sendSubscribeMessage(Integer tenantId, SubscribeMessageRequest request); + + /** + * 批量发送模板消息 + * + * @param tenantId 租户ID + * @param requests 模板消息请求列表 + * @return 发送结果统计 + */ + BatchSendResult batchSendTemplateMessage(Integer tenantId, List requests); + + /** + * 获取微信公众号模板列表 + * + * @param tenantId 租户ID + * @return 模板列表 + */ + String getTemplateList(Integer tenantId); + + /** + * 批量发送结果 + */ + class BatchSendResult { + private int successCount; + private int failCount; + private int totalCount; + + public BatchSendResult(int successCount, int failCount) { + this.successCount = successCount; + this.failCount = failCount; + this.totalCount = successCount + failCount; + } + + public int getSuccessCount() { + return successCount; + } + + public int getFailCount() { + return failCount; + } + + public int getTotalCount() { + return totalCount; + } + + @Override + public String toString() { + return String.format("总计:%d,成功:%d,失败:%d", totalCount, successCount, failCount); + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmBxLogServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmBxLogServiceImpl.java new file mode 100644 index 0000000..edae174 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmBxLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.mapper.HjmBxLogMapper; +import com.gxwebsoft.hjm.service.HjmBxLogService; +import com.gxwebsoft.hjm.entity.HjmBxLog; +import com.gxwebsoft.hjm.param.HjmBxLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 黄家明_报险记录Service实现 + * + * @author 科技小王子 + * @since 2025-06-06 13:08:29 + */ +@Service +public class HjmBxLogServiceImpl extends ServiceImpl implements HjmBxLogService { + + @Override + public PageResult pageRel(HjmBxLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmBxLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmBxLog getByIdRel(Integer id) { + HjmBxLogParam param = new HjmBxLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmCarServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmCarServiceImpl.java new file mode 100644 index 0000000..50cd5a3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmCarServiceImpl.java @@ -0,0 +1,403 @@ +package com.gxwebsoft.hjm.service.impl; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.support.geo.Point; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.entity.HjmFence; +import com.gxwebsoft.hjm.mapper.HjmCarMapper; +import com.gxwebsoft.hjm.service.HjmCarService; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.param.HjmCarParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.hjm.service.HjmFenceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 黄家明_车辆管理Service实现 + * + * @author 科技小王子 + * @since 2025-04-14 16:43:26 + */ +@Service +public class HjmCarServiceImpl extends ServiceImpl implements HjmCarService { + @Resource + private HjmFenceService hjmFenceService; + @Autowired + private HjmCarService hjmCarService; + + + @Override + public PageResult pageRel(HjmCarParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmCarParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmCar getByIdRel(Integer id) { + HjmCarParam param = new HjmCarParam(); + param.setId(id); + final HjmCar hjmCar = param.getOne(baseMapper.selectListRel(param)); + hjmCar.setFence(hjmFenceService.getById(hjmCar.getFenceId())); + return hjmCar; + } + + @Override + public HjmCar getByGpsNo(String gpsNo) { + return baseMapper.getByGpsNo(gpsNo); + } + + @Override + public boolean updateByGpsNo(HjmCar byGpsNo) { + return baseMapper.updateByGpsNo(byGpsNo); + } + + @Override + public HjmCar getByCode(String code) { + final HjmCar byCode = baseMapper.getByCode(code); + + // 检查车辆是否存在 + if (byCode == null) { + return null; + } + + // 检查是否有围栏ID + if (byCode.getFenceId() == null) { + return byCode; + } + + final HjmFence fence = hjmFenceService.getById(byCode.getFenceId()); + byCode.setFence(fence); + + // 检查围栏是否存在 + if (fence == null) { + return byCode; + } + + // 检查车辆坐标是否有效 + if (byCode.getLongitude() == null || byCode.getLatitude() == null || + byCode.getLongitude().trim().isEmpty() || byCode.getLatitude().trim().isEmpty()) { + return byCode; + } + + try { + // 字符串转为浮点 + final double lng = Double.parseDouble(byCode.getLongitude()); + final double lat = Double.parseDouble(byCode.getLatitude()); + Point carPoint = new Point(); + carPoint.setLongitude(lng); + carPoint.setLatitude(lat); + + boolean isInFence = false; + + // 使用多边形围栏判断 + isInFence = checkPolygonFence(carPoint, fence); + + // 将围栏判断结果保存到车辆对象中 + byCode.setInFence(isInFence); + System.out.println("车辆 " + code + " 是否在围栏内: " + isInFence); + byCode.setUpdateTime(LocalDateTime.now()); + hjmCarService.updateById(byCode); + return byCode; + + } catch (NumberFormatException e) { + System.err.println("车辆坐标格式错误: " + e.getMessage()); + return byCode; + } catch (Exception e) { + System.err.println("判断围栏时发生错误: " + e.getMessage()); + e.printStackTrace(); + return byCode; + } + } + + + + /** + * 判断点是否在多边形内 + * @param point 测试点 + * @param pts 多边形的点 + * @return boolean true:在多边形内, false:在多边形外 + * @throws + * @Title: IsPointInPoly + */ + public static boolean isInPolygon(Point point, List pts) { + + int N = pts.size(); + boolean boundOrVertex = true; + //交叉点数量 + int intersectCount = 0; + //浮点类型计算时候与0比较时候的容差 + double precision = 2e-10; + //临近顶点 + Point p1, p2; + //当前点 + Point p = point; + + p1 = pts.get(0); + for (int i = 1; i <= N; ++i) { + if (p.equals(p1)) { + return boundOrVertex; + } + + p2 = pts.get(i % N); + if (p.getLongitude() < Math.min(p1.getLongitude(), p2.getLongitude()) || p.getLongitude() > Math.max(p1.getLongitude(), p2.getLongitude())) { + p1 = p2; + continue; + } + + //射线穿过算法 + if (p.getLongitude() > Math.min(p1.getLongitude(), p2.getLongitude()) && p.getLongitude() < Math.max(p1.getLongitude(), p2.getLongitude())) { + if (p.getLatitude() <= Math.max(p1.getLatitude(), p2.getLatitude())) { + if (p1.getLongitude() == p2.getLongitude() && p.getLatitude() >= Math.min(p1.getLatitude(), p2.getLatitude())) { + return boundOrVertex; + } + + if (p1.getLatitude() == p2.getLatitude()) { + if (p1.getLatitude() == p.getLatitude()) { + return boundOrVertex; + } else { + ++intersectCount; + } + } else { + double xinters = (p.getLongitude() - p1.getLongitude()) * (p2.getLatitude() - p1.getLatitude()) / (p2.getLongitude() - p1.getLongitude()) + p1.getLatitude(); + if (Math.abs(p.getLatitude() - xinters) < precision) { + return boundOrVertex; + } + + if (p.getLatitude() < xinters) { + ++intersectCount; + } + } + } + } else { + if (p.getLongitude() == p2.getLongitude() && p.getLatitude() <= p2.getLatitude()) { + Point p3 = pts.get((i + 1) % N); + if (p.getLongitude() >= Math.min(p1.getLongitude(), p3.getLongitude()) && p.getLongitude() <= Math.max(p1.getLongitude(), p3.getLongitude())) { + ++intersectCount; + } else { + intersectCount += 2; + } + } + } + p1 = p2; + } + return intersectCount % 2 != 0; + } + + + + /** + * 检查点是否在多边形围栏内 + * @param carPoint 车辆位置点 + * @param fence 围栏信息 + * @return boolean true:在围栏内, false:在围栏外 + */ + public boolean checkPolygonFence(Point carPoint, HjmFence fence) { + if (fence.getPoints() == null || fence.getPoints().trim().isEmpty()) { + System.err.println("多边形围栏点数据为空"); + return false; + } + + try { + final String points = fence.getPoints(); + + // 支持多种分隔符格式:逗号、分号、空格等 + String[] coordinates = parseCoordinates(points); + + // 检查点数据是否为偶数(经度,纬度 成对出现) + if (coordinates.length % 2 != 0) { + System.err.println("多边形围栏点数据格式错误,应为偶数个数值。当前数据: " + points); + return false; + } + + List pts = new ArrayList<>(); + for (int i = 0; i < coordinates.length; i += 2) { + try { + Point point = new Point(); + double lng = Double.parseDouble(coordinates[i].trim()); + double lat = Double.parseDouble(coordinates[i + 1].trim()); + + // 验证坐标范围 + if (lng < -180 || lng > 180) { + System.err.println("经度超出有效范围[-180,180]: " + lng); + return false; + } + if (lat < -90 || lat > 90) { + System.err.println("纬度超出有效范围[-90,90]: " + lat); + return false; + } + + point.setLongitude(lng); + point.setLatitude(lat); + pts.add(point); + + System.out.println("解析坐标点 " + (i/2 + 1) + ": (" + lng + ", " + lat + ")"); + + } catch (NumberFormatException e) { + System.err.println("坐标解析失败,位置 " + (i/2 + 1) + ": [" + coordinates[i] + ", " + coordinates[i + 1] + "]"); + throw e; + } + } + + // 至少需要3个点才能构成多边形 + if (pts.size() < 3) { + System.err.println("多边形围栏至少需要3个点"); + return false; + } + + return isInPolygon(carPoint, pts); + + } catch (NumberFormatException e) { + System.err.println("多边形围栏点坐标格式错误: " + e.getMessage()); + return false; + } catch (Exception e) { + System.err.println("检查多边形围栏时发生错误: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * 解析坐标字符串,支持多种分隔符格式 + * @param coordinatesStr 坐标字符串 + * @return 坐标数组 + */ + private String[] parseCoordinates(String coordinatesStr) { + if (coordinatesStr == null || coordinatesStr.trim().isEmpty()) { + return new String[0]; + } + + // 移除首尾空格 + String cleanStr = coordinatesStr.trim(); + System.out.println("原始坐标数据: " + cleanStr); + + // 检查是否是混合分隔符格式:纬度,经度;纬度,经度;... + if (cleanStr.contains(";") && cleanStr.contains(",")) { + System.out.println("检测到混合分隔符格式(分号分隔点,逗号分隔经纬度)"); + return parseMixedFormat(cleanStr); + } + + // 支持的单一分隔符格式 + String[] coordinates; + + if (cleanStr.contains(";")) { + // 使用分号分隔 + coordinates = cleanStr.split(";"); + System.out.println("使用分号分隔符解析坐标"); + } else if (cleanStr.contains(",")) { + // 使用逗号分隔 + coordinates = cleanStr.split(","); + System.out.println("使用逗号分隔符解析坐标"); + } else { + // 使用空格或制表符分隔 + coordinates = cleanStr.split("\\s+"); + System.out.println("使用空格分隔符解析坐标"); + } + + // 清理每个坐标值的空格 + for (int i = 0; i < coordinates.length; i++) { + coordinates[i] = coordinates[i].trim(); + } + + return coordinates; + } + + /** + * 解析混合分隔符格式:纬度,经度;纬度,经度;... + * @param coordinatesStr 坐标字符串 + * @return 坐标数组(按经度,纬度顺序) + */ + private String[] parseMixedFormat(String coordinatesStr) { + // 先按分号分隔得到各个坐标点 + String[] points = coordinatesStr.split(";"); + System.out.println("分解出 " + points.length + " 个坐标点"); + + // 创建结果数组,每个点有2个坐标值 + String[] coordinates = new String[points.length * 2]; + + for (int i = 0; i < points.length; i++) { + String point = points[i].trim(); + String[] latLng = point.split(","); + + if (latLng.length != 2) { + throw new IllegalArgumentException("坐标点格式错误: " + point + ",应为 '纬度,经度' 格式"); + } + + String lat = latLng[0].trim(); + String lng = latLng[1].trim(); + + // 注意:输入格式是纬度,经度,但我们需要按经度,纬度的顺序存储 + coordinates[i * 2] = lng; // 经度 + coordinates[i * 2 + 1] = lat; // 纬度 + + System.out.println("坐标点 " + (i + 1) + ": 纬度=" + lat + ", 经度=" + lng + " -> 存储为 (" + lng + ", " + lat + ")"); + } + + return coordinates; + } + + /** + * 通用的围栏判断方法(只支持多边形围栏) + * @param carPoint 车辆位置点 + * @param fence 围栏信息 + * @return boolean true:在围栏内, false:在围栏外 + */ + public boolean isPointInFence(Point carPoint, HjmFence fence) { + if (carPoint == null || fence == null) { + return false; + } + + // 只使用多边形围栏判断 + return checkPolygonFence(carPoint, fence); + } + + /** + * 测试坐标解析功能 + * @param coordinatesStr 坐标字符串 + */ + public void testCoordinateParsing(String coordinatesStr) { + System.out.println("=== 测试坐标解析 ==="); + System.out.println("输入: " + coordinatesStr); + + try { + String[] coordinates = parseCoordinates(coordinatesStr); + System.out.println("解析结果: " + java.util.Arrays.toString(coordinates)); + System.out.println("坐标点数量: " + coordinates.length / 2); + + if (coordinates.length % 2 == 0 && coordinates.length >= 6) { + System.out.println("格式验证: ✓ 通过"); + for (int i = 0; i < coordinates.length; i += 2) { + double lng = Double.parseDouble(coordinates[i].trim()); + double lat = Double.parseDouble(coordinates[i + 1].trim()); + System.out.println("最终坐标点 " + (i/2 + 1) + ": 经度=" + lng + ", 纬度=" + lat); + } + } else { + System.out.println("格式验证: ✗ 失败 - 需要至少3个点(6个数值),当前有 " + coordinates.length + " 个数值"); + } + } catch (Exception e) { + System.err.println("解析失败: " + e.getMessage()); + e.printStackTrace(); + } + System.out.println("=================="); + } + + + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmChoicesServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmChoicesServiceImpl.java new file mode 100644 index 0000000..b777b12 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmChoicesServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.mapper.HjmChoicesMapper; +import com.gxwebsoft.hjm.service.HjmChoicesService; +import com.gxwebsoft.hjm.entity.HjmChoices; +import com.gxwebsoft.hjm.param.HjmChoicesParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 黄家明_选择题选项Service实现 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Service +public class HjmChoicesServiceImpl extends ServiceImpl implements HjmChoicesService { + + @Override + public PageResult pageRel(HjmChoicesParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmChoicesParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmChoices getByIdRel(Integer id) { + HjmChoicesParam param = new HjmChoicesParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmCoursesServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmCoursesServiceImpl.java new file mode 100644 index 0000000..78ff215 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmCoursesServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.mapper.HjmCoursesMapper; +import com.gxwebsoft.hjm.service.HjmCoursesService; +import com.gxwebsoft.hjm.entity.HjmCourses; +import com.gxwebsoft.hjm.param.HjmCoursesParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 黄家明_课程Service实现 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Service +public class HjmCoursesServiceImpl extends ServiceImpl implements HjmCoursesService { + + @Override + public PageResult pageRel(HjmCoursesParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmCoursesParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmCourses getByIdRel(Integer id) { + HjmCoursesParam param = new HjmCoursesParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmExamLogServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmExamLogServiceImpl.java new file mode 100644 index 0000000..3ae9dff --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmExamLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.mapper.HjmExamLogMapper; +import com.gxwebsoft.hjm.service.HjmExamLogService; +import com.gxwebsoft.hjm.entity.HjmExamLog; +import com.gxwebsoft.hjm.param.HjmExamLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 黄家明_学习记录Service实现 + * + * @author 科技小王子 + * @since 2025-06-05 14:32:03 + */ +@Service +public class HjmExamLogServiceImpl extends ServiceImpl implements HjmExamLogService { + + @Override + public PageResult pageRel(HjmExamLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmExamLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmExamLog getByIdRel(Integer id) { + HjmExamLogParam param = new HjmExamLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmFenceServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmFenceServiceImpl.java new file mode 100644 index 0000000..4d71a3c --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmFenceServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.mapper.HjmFenceMapper; +import com.gxwebsoft.hjm.service.HjmFenceService; +import com.gxwebsoft.hjm.entity.HjmFence; +import com.gxwebsoft.hjm.param.HjmFenceParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 黄家明_电子围栏Service实现 + * + * @author 科技小王子 + * @since 2025-06-03 02:08:03 + */ +@Service +public class HjmFenceServiceImpl extends ServiceImpl implements HjmFenceService { + + @Override + public PageResult pageRel(HjmFenceParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmFenceParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmFence getByIdRel(Integer id) { + HjmFenceParam param = new HjmFenceParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmGpsLogServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmGpsLogServiceImpl.java new file mode 100644 index 0000000..641afb6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmGpsLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.mapper.HjmGpsLogMapper; +import com.gxwebsoft.hjm.service.HjmGpsLogService; +import com.gxwebsoft.hjm.entity.HjmGpsLog; +import com.gxwebsoft.hjm.param.HjmGpsLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 黄家明_gps轨迹Service实现 + * + * @author 科技小王子 + * @since 2025-06-11 12:03:50 + */ +@Service +public class HjmGpsLogServiceImpl extends ServiceImpl implements HjmGpsLogService { + + @Override + public PageResult pageRel(HjmGpsLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmGpsLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmGpsLog getByIdRel(Integer id) { + HjmGpsLogParam param = new HjmGpsLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmQuestionsServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmQuestionsServiceImpl.java new file mode 100644 index 0000000..50903af --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmQuestionsServiceImpl.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.hjm.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.hjm.entity.HjmChoices; +import com.gxwebsoft.hjm.mapper.HjmQuestionsMapper; +import com.gxwebsoft.hjm.service.HjmChoicesService; +import com.gxwebsoft.hjm.service.HjmQuestionsService; +import com.gxwebsoft.hjm.entity.HjmQuestions; +import com.gxwebsoft.hjm.param.HjmQuestionsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 黄家明_题目Service实现 + * + * @author 科技小王子 + * @since 2025-06-02 12:59:49 + */ +@Service +public class HjmQuestionsServiceImpl extends ServiceImpl implements HjmQuestionsService { + @Resource + private HjmChoicesService hjmChoicesService; + + @Override + public PageResult pageRel(HjmQuestionsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + final Set collectByIds = list.stream().map(HjmQuestions::getId).collect(Collectors.toSet()); + final List choices = hjmChoicesService.list(new LambdaQueryWrapper().in(HjmChoices::getQuestionId, collectByIds)); + final Map> collectByQuestionId = choices.stream().collect(Collectors.groupingBy(HjmChoices::getQuestionId)); + list.forEach(item -> { + final List choicesList = collectByQuestionId.get(item.getId()); + item.setChoicesList(choicesList); + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmQuestionsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmQuestions getByIdRel(Integer id) { + HjmQuestionsParam param = new HjmQuestionsParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/HjmViolationServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmViolationServiceImpl.java new file mode 100644 index 0000000..6e2880c --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/HjmViolationServiceImpl.java @@ -0,0 +1,106 @@ +package com.gxwebsoft.hjm.service.impl; + +import cn.hutool.core.util.ObjUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.impl.UserServiceImpl; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.entity.HjmViolation; +import com.gxwebsoft.hjm.mapper.HjmViolationMapper; +import com.gxwebsoft.hjm.param.HjmViolationParam; +import com.gxwebsoft.hjm.service.HjmCarService; +import com.gxwebsoft.hjm.service.HjmViolationService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; + +/** + * 黄家明_违章记录Service实现 + * + * @author 科技小王子 + * @since 2025-06-20 13:48:43 + */ +@Service +public class HjmViolationServiceImpl extends ServiceImpl implements HjmViolationService { + + @Resource + private HjmCarService hjmCarService; + @Resource + private WxNotificationServiceImpl wxNotificationService; + @Resource + private UserServiceImpl userService; + + @Override + public PageResult pageRel(HjmViolationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HjmViolationParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HjmViolation getByIdRel(Integer id) { + HjmViolationParam param = new HjmViolationParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void send(HjmViolation hjmViolation) { + final HjmCar item = hjmCarService.getByCode(hjmViolation.getCode()); + + // 获取所有的邮政协会/管局工作人员 + final List users = userService.listByAlert(); + if (ObjUtil.isEmpty(item)) { + return; + } + users.forEach(d -> { + item.setToUser(d.getOfficeOpenid()); + item.setAppId("wxd2723d1afd9c4553"); + sendTemplateMessage(item, hjmViolation); + }); + + } + + public void sendTemplateMessage(HjmCar item, HjmViolation violation) { + // 发送模板消息 + final TemplateMessageRequest templateMessageRequest = new TemplateMessageRequest(); + templateMessageRequest.setToUser(item.getToUser()); + templateMessageRequest.setTemplateId("gZshS5yJs47BhIFodo9yenZcmsVwJOCKkL-SYaZTioU"); + final TemplateMessageRequest.MiniProgram miniProgram = new TemplateMessageRequest.MiniProgram(); + miniProgram.setAppid(item.getAppId()); + miniProgram.setPagepath("hjm/violation/detail?id=".concat(item.getCode())); +// miniProgram.setPagepath("hjm/query?id=".concat(item.getCode())); + templateMessageRequest.setMiniprogram(miniProgram); + HashMap map = new HashMap<>(); + map.put("thing7", new TemplateMessageRequest.TemplateDataItem(item.getDriverName())); + map.put("phone_number8", new TemplateMessageRequest.TemplateDataItem(item.getDriverPhone())); + map.put("const4", new TemplateMessageRequest.TemplateDataItem("违章")); + map.put("car_number1", new TemplateMessageRequest.TemplateDataItem(item.getCode())); + // 获取当前时间,格式2024年1月1号 10:20 + map.put("time2", new TemplateMessageRequest.TemplateDataItem( + new SimpleDateFormat("yyyy年M月d日 HH:mm").format(LocalDateTime.now()), "#173177") + ); + System.out.println("map = " + map); + templateMessageRequest.setData(map); + boolean success = wxNotificationService.sendTemplateMessage(10519, templateMessageRequest); + System.out.println("2向 = " + item.getDriverName() + "发送消息成功:" + success); + } + +} diff --git a/src/main/java/com/gxwebsoft/hjm/service/impl/WxNotificationServiceImpl.java b/src/main/java/com/gxwebsoft/hjm/service/impl/WxNotificationServiceImpl.java new file mode 100644 index 0000000..161f412 --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/service/impl/WxNotificationServiceImpl.java @@ -0,0 +1,258 @@ +package com.gxwebsoft.hjm.service.impl; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.common.core.utils.WxOfficialUtil; +import com.gxwebsoft.common.system.entity.Setting; +import com.gxwebsoft.common.system.service.SettingService; +import com.gxwebsoft.hjm.dto.SubscribeMessageRequest; +import com.gxwebsoft.hjm.dto.SubscribeMessageRequest.SubscribeDataItem; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest.TemplateDataItem; +import com.gxwebsoft.hjm.service.WxNotificationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 微信通知服务实现 + * + * @author 科技小王子 + * @since 2025-06-15 + */ +@Slf4j +@Service +public class WxNotificationServiceImpl implements WxNotificationService { + + @Resource + private SettingService settingService; + + @Resource + private WxOfficialUtil wxOfficialUtil; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Override + public boolean sendTemplateMessage(Integer tenantId, TemplateMessageRequest request) { + try { + String accessToken = getWxAccessToken(tenantId); + return sendWxTemplateMessage(accessToken, request); + } catch (Exception e) { + log.error("发送模板消息失败", e); + return false; + } + } + + @Override + public boolean sendSubscribeMessage(Integer tenantId, SubscribeMessageRequest request) { + try { + String accessToken = getWxAccessToken(tenantId); + return sendWxSubscribeMessage(accessToken, request); + } catch (Exception e) { + log.error("发送订阅消息失败", e); + return false; + } + } + + @Override + public BatchSendResult batchSendTemplateMessage(Integer tenantId, List requests) { + int successCount = 0; + int failCount = 0; + + try { + String accessToken = getWxAccessToken(tenantId); + + for (TemplateMessageRequest request : requests) { + boolean success = sendWxTemplateMessage(accessToken, request); + if (success) { + successCount++; + } else { + failCount++; + } + + // 避免频率限制,每次发送间隔100ms + Thread.sleep(100); + } + } catch (Exception e) { + log.error("批量发送模板消息失败", e); + failCount += (requests.size() - successCount - failCount); + } + + return new BatchSendResult(successCount, failCount); + } + + @Override + public String getTemplateList(Integer tenantId) { + try { + String accessToken = getWxAccessToken(tenantId); + String url = "https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=" + accessToken; + + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + return response; + } catch (Exception e) { + log.error("获取模板列表失败", e); + return null; + } + } + + /** + * 获取微信公众号Access Token + */ + private String getWxAccessToken(Integer tenantId) { + String cacheKey = "wx_official_access_token:" + tenantId; + + // 先从缓存获取 + String cachedToken = stringRedisTemplate.opsForValue().get(cacheKey); + if (cachedToken != null) { + return cachedToken; + } + + // 缓存中没有,重新获取 + try { + // 获取微信公众号配置 + String appId = "wx100365d412078b8c"; + String appSecret = "bba73c9fc8f5f7d0edc4de50786a8c62"; + + if (appId == null || appSecret == null) { + throw new RuntimeException("微信公众号配置不完整"); + } + + + + // 调用微信API获取access_token + String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + + appId + "&secret=" + appSecret; + + String response = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); + System.out.println("response = " + response); + JSONObject jsonObject = JSONObject.parseObject(response); + + String accessToken = jsonObject.getString("access_token"); + Integer expiresIn = jsonObject.getInteger("expires_in"); + + if (accessToken == null) { + String errorMsg = jsonObject.getString("errmsg"); + throw new RuntimeException("获取access_token失败: " + errorMsg); + } + + // 缓存access_token,提前5分钟过期 + int cacheSeconds = expiresIn != null ? expiresIn - 300 : 7200 - 300; + stringRedisTemplate.opsForValue().set(cacheKey, accessToken, cacheSeconds, TimeUnit.SECONDS); + + return accessToken; + + } catch (Exception e) { + log.error("获取微信公众号access_token失败", e); + throw new RuntimeException("获取access_token失败: " + e.getMessage()); + } + } + + /** + * 发送微信模板消息 + */ + private boolean sendWxTemplateMessage(String accessToken, TemplateMessageRequest request) { + try { + String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken; + + // 构建请求数据 + JSONObject data = new JSONObject(); + data.put("touser", request.getToUser()); + data.put("template_id", request.getTemplateId()); + data.put("url", request.getUrl()); + data.put("topcolor", request.getTopColor()); + + // 构建模板数据 + JSONObject templateData = new JSONObject(); + if (request.getData() != null) { + for (Map.Entry entry : request.getData().entrySet()) { + JSONObject item = new JSONObject(); + item.put("value", entry.getValue().getValue()); + item.put("color", entry.getValue().getColor()); + templateData.put(entry.getKey(), item); + } + } + data.put("data", templateData); + + // 小程序跳转 + if (request.getMiniprogram() != null) { + JSONObject miniprogram = new JSONObject(); + miniprogram.put("appid", request.getMiniprogram().getAppid()); + miniprogram.put("pagepath", request.getMiniprogram().getPagepath()); + data.put("miniprogram", miniprogram); + } + + // 发送请求 + String response = HttpUtil.post(url, data.toJSONString()); + JSONObject result = JSONObject.parseObject(response); + + Integer errcode = result.getInteger("errcode"); + String errmsg = result.getString("errmsg"); + + if (errcode != null && errcode == 0) { + log.info("模板消息发送成功: {}", result.getString("msgid")); + return true; + } else { + log.error("模板消息发送失败: errcode={}, errmsg={}", errcode, errmsg); + return false; + } + + } catch (Exception e) { + log.error("发送微信模板消息异常", e); + return false; + } + } + + /** + * 发送微信订阅消息 + */ + private boolean sendWxSubscribeMessage(String accessToken, SubscribeMessageRequest request) { + try { + String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken; + + // 构建请求数据 + JSONObject data = new JSONObject(); + data.put("touser", request.getToUser()); + data.put("template_id", request.getTemplateId()); + data.put("page", request.getPage()); + data.put("miniprogram_state", request.getMiniprogramState()); + data.put("lang", request.getLang()); + + // 构建订阅消息数据 + JSONObject subscribeData = new JSONObject(); + if (request.getData() != null) { + for (Map.Entry entry : request.getData().entrySet()) { + JSONObject item = new JSONObject(); + item.put("value", entry.getValue().getValue()); + subscribeData.put(entry.getKey(), item); + } + } + data.put("data", subscribeData); + + // 发送请求 + String response = HttpUtil.post(url, data.toJSONString()); + JSONObject result = JSONObject.parseObject(response); + + Integer errcode = result.getInteger("errcode"); + String errmsg = result.getString("errmsg"); + + if (errcode != null && errcode == 0) { + log.info("订阅消息发送成功"); + return true; + } else { + log.error("订阅消息发送失败: errcode={}, errmsg={}", errcode, errmsg); + return false; + } + + } catch (Exception e) { + log.error("发送微信订阅消息异常", e); + return false; + } + } +} diff --git a/src/main/java/com/gxwebsoft/hjm/task/PushHjmFenceOutController.java b/src/main/java/com/gxwebsoft/hjm/task/PushHjmFenceOutController.java new file mode 100644 index 0000000..b31737e --- /dev/null +++ b/src/main/java/com/gxwebsoft/hjm/task/PushHjmFenceOutController.java @@ -0,0 +1,97 @@ +package com.gxwebsoft.hjm.task; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.hjm.dto.TemplateMessageRequest; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.service.HjmCarService; +import com.gxwebsoft.hjm.service.WxNotificationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.List; + +/** + * 定时任务 + * + * @author 科技小王子 + * @since 2022-12-15 19:11:07 + */ +@Tag(name = "定时任务") +@RestController +@RequestMapping("/api/hjm/scheduling") +public class PushHjmFenceOutController extends BaseController { + @Resource + private HjmCarService hjmCarService; + @Resource + private UserService userService; + @Resource + private WxNotificationService wxNotificationService; + @Value("${spring.profiles.active}") + String active; + + /** + * 定时推送订阅消息 + * @Scheduled(fixedDelay = 2000, initialDelay = 2000) + * @Scheduled(cron = "0 0 9 * * ?") + */ +// @Scheduled(cron = "0 0/10 * * * ?") + public void index() { + final List list = hjmCarService.list(new LambdaQueryWrapper() + .eq(HjmCar::getStatus, 1) + .eq(HjmCar::getDeleted, 0) + .eq(HjmCar::getInFence, false) + ); + + // 开发环境 + if (active.equals("dev")){ + return; + } + + // 获取所有的邮政协会/管局工作人员 + final List users = userService.listByAlert(); + + list.forEach(item -> { + // 执行推送 + users.forEach(d -> { + if(StrUtil.isNotBlank(d.getOfficeOpenid())){ + item.setToUser(d.getOfficeOpenid()); + item.setAppId("wxd2723d1afd9c4553"); + sendTemplateMessage(item); + } + }); + }); + } + + public void sendTemplateMessage(HjmCar item) { + // 发送模板消息 + final TemplateMessageRequest templateMessageRequest = new TemplateMessageRequest(); + templateMessageRequest.setToUser(item.getToUser()); + templateMessageRequest.setTemplateId("oMckHaNgNT-ivInYF5DtCcqyd9O-i1hP_G0jQALsx54"); +// templateMessageRequest.setUrl("https://mp.websoft.top"); + final TemplateMessageRequest.MiniProgram miniProgram = new TemplateMessageRequest.MiniProgram(); + miniProgram.setAppid(item.getAppId()); + miniProgram.setPagepath("hjm/query?id=".concat(item.getCode())); + templateMessageRequest.setMiniprogram(miniProgram); + HashMap map = new HashMap<>(); + map.put("phrase6", new TemplateMessageRequest.TemplateDataItem(item.getDriverName())); + map.put("time4", new TemplateMessageRequest.TemplateDataItem( + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item.getCreateTime()), "#173177")); + map.put("phrase7", new TemplateMessageRequest.TemplateDataItem("离开围栏")); + map.put("thing11", new TemplateMessageRequest.TemplateDataItem(item.getFenceName())); + map.put("car_number12", new TemplateMessageRequest.TemplateDataItem(item.getCode())); + templateMessageRequest.setData(map); + boolean success = wxNotificationService.sendTemplateMessage(10519, templateMessageRequest); + System.out.println("向 = " + item.getDriverName() + "发送消息成功:" + success); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/controller/HouseInfoController.java b/src/main/java/com/gxwebsoft/house/controller/HouseInfoController.java new file mode 100644 index 0000000..05a4758 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/controller/HouseInfoController.java @@ -0,0 +1,155 @@ +package com.gxwebsoft.house.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.bszx.entity.BszxBm; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.house.entity.HouseLikeLog; +import com.gxwebsoft.house.entity.HouseViewsLog; +import com.gxwebsoft.house.service.HouseInfoService; +import com.gxwebsoft.house.entity.HouseInfo; +import com.gxwebsoft.house.param.HouseInfoParam; +import com.gxwebsoft.house.util.SortSceneUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.house.service.HouseLikeLogService; +import com.gxwebsoft.house.service.HouseViewsLogService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 房源信息表控制器 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Tag(name = "房源信息表管理") +@RestController +@RequestMapping("/api/house/house-info") +public class HouseInfoController extends BaseController { + @Resource + private HouseInfoService houseInfoService; + @Resource + private HouseLikeLogService houseLikeLogService; + @Resource + private HouseViewsLogService houseViewsLogService; + + @Operation(summary = "分页查询房源信息表") + @GetMapping("/page") + public ApiResult> page(HouseInfoParam param) { + // 标准化排序参数,解决URL编码问题 + if (param.getSortScene() != null) { + String normalizedSortScene = SortSceneUtil.normalizeSortScene(param.getSortScene()); + param.setSortScene(normalizedSortScene); + } + + // 使用关联查询 + return success(houseInfoService.pageRel(param)); + } + + + @PreAuthorize("hasAuthority('house:houseInfo:list')") + @Operation(summary = "查询全部房源信息表") + @GetMapping() + public ApiResult> list(HouseInfoParam param) { + // 使用关联查询 + return success(houseInfoService.listRel(param)); + } + + @Operation(summary = "根据id查询房源信息表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + HouseInfo byIdRel = houseInfoService.getByIdRel(id); + Integer loginUserId = getLoginUserId(); + if(loginUserId != null) { + // 是否喜欢 + HouseLikeLog log = houseLikeLogService.getByIdRel(id, loginUserId); + byIdRel.setLiked(log != null); + // 添加浏览记录 + houseViewsLogService.add(byIdRel, loginUserId); + } + // 使用关联查询 + return success(byIdRel); + } + + @PreAuthorize("hasAuthority('house:houseInfo:save')") + @Operation(summary = "添加房源信息表") + @PostMapping() + public ApiResult save(@RequestBody HouseInfo houseInfo) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + houseInfo.setUserId(loginUser.getUserId()); + } + if (houseInfoService.save(houseInfo)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('house:houseInfo:update')") + @Operation(summary = "修改房源信息表") + @PutMapping() + public ApiResult update(@RequestBody HouseInfo houseInfo) { + if (houseInfoService.updateById(houseInfo)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseInfo:remove')") + @Operation(summary = "删除房源信息表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (houseInfoService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('house:houseInfo:save')") + @Operation(summary = "批量添加房源信息表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (houseInfoService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('house:houseInfo:update')") + @Operation(summary = "批量修改房源信息表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(houseInfoService, "house_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseInfo:remove')") + @Operation(summary = "批量删除房源信息表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (houseInfoService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "获取海报地址") + @GetMapping("/generatePoster/{id}") + public ApiResult generatePoster(@PathVariable("id") Integer id) throws Exception { + final HouseInfo houseInfo = houseInfoService.getOne(new LambdaQueryWrapper().eq(HouseInfo::getHouseId, id).last("limit 1")); + return success("生成房源海报",houseInfoService.generatePoster(houseInfo)); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/controller/HouseLikeLogController.java b/src/main/java/com/gxwebsoft/house/controller/HouseLikeLogController.java new file mode 100644 index 0000000..44b045d --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/controller/HouseLikeLogController.java @@ -0,0 +1,119 @@ +package com.gxwebsoft.house.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.house.service.HouseLikeLogService; +import com.gxwebsoft.house.entity.HouseLikeLog; +import com.gxwebsoft.house.param.HouseLikeLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 房源点赞表控制器 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Tag(name = "房源点赞表管理") +@RestController +@RequestMapping("/api/house/house-like-log") +public class HouseLikeLogController extends BaseController { + @Resource + private HouseLikeLogService houseLikeLogService; + + @Operation(summary = "分页查询房源点赞表") + @GetMapping("/page") + public ApiResult> page(HouseLikeLogParam param) { + // 使用关联查询 + return success(houseLikeLogService.pageRel(param)); + } + + @Operation(summary = "查询全部房源点赞表") + @GetMapping() + public ApiResult> list(HouseLikeLogParam param) { + // 使用关联查询 + return success(houseLikeLogService.listRel(param)); + } + + @PreAuthorize("hasAuthority('house:houseLikeLog:list')") + @Operation(summary = "根据id查询房源点赞表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(houseLikeLogService.getById(id)); + } + + @Operation(summary = "添加房源点赞表") + @PostMapping() + public ApiResult save(@RequestBody HouseLikeLog houseLikeLog) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + houseLikeLog.setUserId(loginUser.getUserId()); + } + if (houseLikeLogService.save(houseLikeLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + @PreAuthorize("hasAuthority('house:houseLikeLog:update')") + @Operation(summary = "修改房源点赞表") + @PutMapping() + public ApiResult update(@RequestBody HouseLikeLog houseLikeLog) { + if (houseLikeLogService.updateById(houseLikeLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseLikeLog:remove')") + @Operation(summary = "删除房源点赞表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (houseLikeLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('house:houseLikeLog:save')") + @Operation(summary = "批量添加房源点赞表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (houseLikeLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('house:houseLikeLog:update')") + @Operation(summary = "批量修改房源点赞表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(houseLikeLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseLikeLog:remove')") + @Operation(summary = "批量删除房源点赞表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (houseLikeLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/controller/HouseReservationController.java b/src/main/java/com/gxwebsoft/house/controller/HouseReservationController.java new file mode 100644 index 0000000..941dae7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/controller/HouseReservationController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.house.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.house.service.HouseReservationService; +import com.gxwebsoft.house.entity.HouseReservation; +import com.gxwebsoft.house.param.HouseReservationParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 预约记录表控制器 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Tag(name = "预约记录表管理") +@RestController +@RequestMapping("/api/house/house-reservation") +public class HouseReservationController extends BaseController { + @Resource + private HouseReservationService houseReservationService; + + @Operation(summary = "分页查询预约记录表") + @GetMapping("/page") + public ApiResult> page(HouseReservationParam param) { + // 使用关联查询 + return success(houseReservationService.pageRel(param)); + } + + @Operation(summary = "查询全部预约记录表") + @GetMapping() + public ApiResult> list(HouseReservationParam param) { + // 使用关联查询 + return success(houseReservationService.listRel(param)); + } + + @PreAuthorize("hasAuthority('house:houseReservation:list')") + @Operation(summary = "根据id查询预约记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(houseReservationService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('house:houseReservation:save')") + @Operation(summary = "添加预约记录表") + @PostMapping() + public ApiResult save(@RequestBody HouseReservation houseReservation) { +// 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + houseReservation.setUserId(loginUser.getUserId()); + } + if (houseReservationService.count(new LambdaQueryWrapper().eq(HouseReservation::getHouseId,houseReservation.getHouseId()).eq(HouseReservation::getUserId,loginUser.getUserId()).eq(HouseReservation::getStatus,0)) > 0){ + return fail("请勿重复提交"); + } + if (houseReservationService.save(houseReservation)) { + return success("提交成功"); + } + return fail("提交失败"); + } + + @PreAuthorize("hasAuthority('house:houseReservation:update')") + @Operation(summary = "修改预约记录表") + @PutMapping() + public ApiResult update(@RequestBody HouseReservation houseReservation) { + if (houseReservationService.updateById(houseReservation)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseReservation:remove')") + @Operation(summary = "删除预约记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (houseReservationService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('house:houseReservation:save')") + @Operation(summary = "批量添加预约记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (houseReservationService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('house:houseReservation:update')") + @Operation(summary = "批量修改预约记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(houseReservationService, "log_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseReservation:remove')") + @Operation(summary = "批量删除预约记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (houseReservationService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/controller/HouseUserController.java b/src/main/java/com/gxwebsoft/house/controller/HouseUserController.java new file mode 100644 index 0000000..9a4efa1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/controller/HouseUserController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.house.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.house.service.HouseUserService; +import com.gxwebsoft.house.entity.HouseUser; +import com.gxwebsoft.house.param.HouseUserParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用户表控制器 + * + * @author 科技小王子 + * @since 2025-03-05 15:13:05 + */ +@Tag(name = "用户表管理") +@RestController +@RequestMapping("/api/house/house-user") +public class HouseUserController extends BaseController { + @Resource + private HouseUserService houseUserService; + + @Operation(summary = "分页查询用户表") + @GetMapping("/page") + public ApiResult> page(HouseUserParam param) { + // 使用关联查询 + return success(houseUserService.pageRel(param)); + } + + @Operation(summary = "查询全部用户表") + @GetMapping() + public ApiResult> list(HouseUserParam param) { + // 使用关联查询 + return success(houseUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('house:houseUser:list')") + @Operation(summary = "根据id查询用户表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(houseUserService.getByIdRel(id)); + } + + @Operation(summary = "添加用户表") + @PostMapping() + public ApiResult save(@RequestBody HouseUser houseUser) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + houseUser.setUserId(loginUser.getUserId()); + } + if (houseUserService.save(houseUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改用户表") + @PutMapping() + public ApiResult update(@RequestBody HouseUser houseUser) { + if (houseUserService.updateById(houseUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除用户表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (houseUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加用户表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (houseUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改用户表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(houseUserService, "user_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除用户表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (houseUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/controller/HouseViewsLogController.java b/src/main/java/com/gxwebsoft/house/controller/HouseViewsLogController.java new file mode 100644 index 0000000..dc3b3a4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/controller/HouseViewsLogController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.house.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.house.service.HouseViewsLogService; +import com.gxwebsoft.house.entity.HouseViewsLog; +import com.gxwebsoft.house.param.HouseViewsLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 看房记录表控制器 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Tag(name = "看房记录表管理") +@RestController +@RequestMapping("/api/house/house-views-log") +public class HouseViewsLogController extends BaseController { + @Resource + private HouseViewsLogService houseViewsLogService; + + @Operation(summary = "分页查询看房记录表") + @GetMapping("/page") + public ApiResult> page(HouseViewsLogParam param) { + // 使用关联查询 + return success(houseViewsLogService.pageRel(param)); + } + + @Operation(summary = "查询全部看房记录表") + @GetMapping() + public ApiResult> list(HouseViewsLogParam param) { + // 使用关联查询 + return success(houseViewsLogService.listRel(param)); + } + + @Operation(summary = "根据id查询看房记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(houseViewsLogService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('house:houseViewsLog:save')") + @Operation(summary = "添加看房记录表") + @PostMapping() + public ApiResult save(@RequestBody HouseViewsLog houseViewsLog) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + houseViewsLog.setUserId(loginUser.getUserId()); + } + if (houseViewsLogService.save(houseViewsLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('house:houseViewsLog:update')") + @Operation(summary = "修改看房记录表") + @PutMapping() + public ApiResult update(@RequestBody HouseViewsLog houseViewsLog) { + if (houseViewsLogService.updateById(houseViewsLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseViewsLog:remove')") + @Operation(summary = "删除看房记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (houseViewsLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('house:houseViewsLog:save')") + @Operation(summary = "批量添加看房记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (houseViewsLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('house:houseViewsLog:update')") + @Operation(summary = "批量修改看房记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(houseViewsLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('house:houseViewsLog:remove')") + @Operation(summary = "批量删除看房记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (houseViewsLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseFile.java b/src/main/java/com/gxwebsoft/house/entity/HouseFile.java new file mode 100644 index 0000000..e277c19 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseFile.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.house.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 看房记录表 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseFiles对象", description = "房源图片") +public class HouseFile implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "文件大小") + private Integer size; + + @Schema(description = "图片类型") + private String type; + + @Schema(description = "图片链接") + private String url; + + @Schema(description = "状态") + private String status; + + @Schema(description = "描述") + private String message; +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseFiles.java b/src/main/java/com/gxwebsoft/house/entity/HouseFiles.java new file mode 100644 index 0000000..9a67e0e --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseFiles.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.house.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 看房记录表 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseFiles对象", description = "房源图片") +public class HouseFiles implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "文件列表") + private List files; +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseInfo.java b/src/main/java/com/gxwebsoft/house/entity/HouseInfo.java new file mode 100644 index 0000000..1989562 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseInfo.java @@ -0,0 +1,186 @@ +package com.gxwebsoft.house.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 房源信息表 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseInfo对象", description = "房源信息表") +public class HouseInfo implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "house_id", type = IdType.AUTO) + private Integer houseId; + + @Schema(description = "房源标题") + private String houseTitle; + + @Schema(description = "房产所在的城市") + private String cityByHouse; + + @Schema(description = "户型") + private String houseType; + + @Schema(description = "租赁方式") + private String leaseMethod; + + @Schema(description = "租金") + private BigDecimal rent; + + @Schema(description = "月租金") + private BigDecimal monthlyRent; + + @Schema(description = "佣金") + private BigDecimal commission; + + @Schema(description = "物业费") + private BigDecimal propertyFees; + + @Schema(description = "面积") + private String extent; + + @Schema(description = "楼层") + private String floor; + + @Schema(description = "卖价") + private String salePrice; + + @Schema(description = "总价") + private String totalPrice; + + @Schema(description = "房号") + private String roomNumber; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "进入房屋的密码") + private String password; + + @Schema(description = "房屋朝向") + private String toward; + + @Schema(description = "房屋标签") + private String houseLabel; + + @Schema(description = "办公室配套") + private String supporting; + + @Schema(description = "房源视频") + private String videoUrl; + + @Schema(description = "图片附件") + private String files; + + @Schema(description = "房源介绍") + private String content; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "所在地区") + private String area; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "是否必看") + private Integer mustSee; + + @Schema(description = "是否可溢价") + private String premium; + + @Schema(description = "租期") + private String tenancy; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否实名认证") + private Integer authentication; + + @Schema(description = "状态 10待审核 20驳回 30通过") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "用户等级") + @TableField(exist = false) + private String gradeName; + + @Schema(description = "是否选中") + @TableField(exist = false) + private Boolean selected; + + @Schema(description = "是否喜欢") + @TableField(exist = false) + private Boolean liked; + +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseLikeLog.java b/src/main/java/com/gxwebsoft/house/entity/HouseLikeLog.java new file mode 100644 index 0000000..d73397f --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseLikeLog.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.house.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 房源点赞表 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseLikeLog对象", description = "房源点赞表") +public class HouseLikeLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "房源ID") + private Integer houseId; + + @Schema(description = "房主ID") + private Integer houseUserId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "删除") + @TableLogic + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseReservation.java b/src/main/java/com/gxwebsoft/house/entity/HouseReservation.java new file mode 100644 index 0000000..1eb0f42 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseReservation.java @@ -0,0 +1,116 @@ +package com.gxwebsoft.house.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 预约记录表 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseReservation对象", description = "预约记录表") +public class HouseReservation implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "log_id", type = IdType.AUTO) + private Integer logId; + + @Schema(description = "订单号") + private String logNo; + + @Schema(description = "类型") + private Integer type; + + @Schema(description = "付款金额") + private BigDecimal money; + + @Schema(description = "房源ID") + private Integer houseId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "付款时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime payTime; + + @Schema(description = "付款状态(10未付款 20已付款)") + private Integer payStatus; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "所在地区") + private String area; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "订单是否已结算(0未结算 1已结算)") + private Integer isSettled; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseUser.java b/src/main/java/com/gxwebsoft/house/entity/HouseUser.java new file mode 100644 index 0000000..2b0969f --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseUser.java @@ -0,0 +1,206 @@ +package com.gxwebsoft.house.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户表 + * + * @author 科技小王子 + * @since 2025-03-05 15:13:05 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseUser对象", description = "用户表") +public class HouseUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "用户id") + @TableId(value = "user_id", type = IdType.AUTO) + private Integer userId; + + @Schema(description = "用户类型,0个人用户 6开发者 10企业") + private Integer type; + + @Schema(description = "账号") + private String username; + + @Schema(description = "密码") + private String password; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "性别 1男 2女") + private Integer sex; + + @Schema(description = "职务") + private String position; + + @Schema(description = "注册来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "邮箱是否验证, 0否, 1是") + private Integer emailVerified; + + @Schema(description = "别名") + private String alias; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "单位姓名") + private String companyName; + + @Schema(description = "证件号码") + private String idCard; + + @Schema(description = "出生日期") + private LocalDate birthday; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "用户可用余额") + private BigDecimal balance; + + @Schema(description = "用户可用积分") + private Integer points; + + @Schema(description = "用户总支付的金额") + private BigDecimal payMoney; + + @Schema(description = "实际消费的金额(不含退款)") + private BigDecimal expendMoney; + + @Schema(description = "会员等级ID") + private Integer gradeId; + + @Schema(description = "个人简介") + private String introduction; + + @Schema(description = "机构id") + private Integer organizationId; + + @Schema(description = "头像") + private String avatar; + + @Schema(description = "背景图") + private String bgImage; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "客户ID") + private Integer customerId; + + @Schema(description = "用户编码") + private String userCode; + + @Schema(description = "是否已实名认证") + private Integer certification; + + @Schema(description = "兴趣爱好") + private String interest; + + @Schema(description = "身高") + private String height; + + @Schema(description = "体重") + private String weight; + + @Schema(description = "月薪") + private String monthlyPay; + + @Schema(description = "学历") + private String education; + + @Schema(description = "职业") + private String vocation; + + @Schema(description = "年龄") + private Integer age; + + @Schema(description = "是否线下会员") + private Boolean offline; + + @Schema(description = "关注数") + private Integer followers; + + @Schema(description = "粉丝数") + private Integer fans; + + @Schema(description = "点赞数") + private Integer likes; + + @Schema(description = "评论数") + private Integer commentNumbers; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0在线, 1离线") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "最后结算时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime settlementTime; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/house/entity/HouseViewsLog.java b/src/main/java/com/gxwebsoft/house/entity/HouseViewsLog.java new file mode 100644 index 0000000..3ff9eb2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/entity/HouseViewsLog.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.house.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 看房记录表 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "HouseViewsLog对象", description = "看房记录表") +public class HouseViewsLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "房源ID") + private Integer houseId; + + @Schema(description = "房主ID") + private Integer houseUserId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "删除") + @TableLogic + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/house/mapper/HouseInfoMapper.java b/src/main/java/com/gxwebsoft/house/mapper/HouseInfoMapper.java new file mode 100644 index 0000000..3a15558 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/HouseInfoMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.house.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.house.entity.HouseInfo; +import com.gxwebsoft.house.param.HouseInfoParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 房源信息表Mapper + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +public interface HouseInfoMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HouseInfoParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HouseInfoParam param); + +} diff --git a/src/main/java/com/gxwebsoft/house/mapper/HouseLikeLogMapper.java b/src/main/java/com/gxwebsoft/house/mapper/HouseLikeLogMapper.java new file mode 100644 index 0000000..ea43c42 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/HouseLikeLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.house.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.yulichang.base.MPJBaseMapper; +import com.gxwebsoft.house.entity.HouseLikeLog; +import com.gxwebsoft.house.param.HouseLikeLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 房源点赞表Mapper + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +public interface HouseLikeLogMapper extends MPJBaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HouseLikeLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HouseLikeLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/house/mapper/HouseReservationMapper.java b/src/main/java/com/gxwebsoft/house/mapper/HouseReservationMapper.java new file mode 100644 index 0000000..aa3b3fe --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/HouseReservationMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.house.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.house.entity.HouseReservation; +import com.gxwebsoft.house.param.HouseReservationParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 预约记录表Mapper + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +public interface HouseReservationMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HouseReservationParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HouseReservationParam param); + +} diff --git a/src/main/java/com/gxwebsoft/house/mapper/HouseUserMapper.java b/src/main/java/com/gxwebsoft/house/mapper/HouseUserMapper.java new file mode 100644 index 0000000..901c5ed --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/HouseUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.house.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.house.entity.HouseUser; +import com.gxwebsoft.house.param.HouseUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户表Mapper + * + * @author 科技小王子 + * @since 2025-03-05 15:13:05 + */ +public interface HouseUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HouseUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HouseUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/house/mapper/HouseViewsLogMapper.java b/src/main/java/com/gxwebsoft/house/mapper/HouseViewsLogMapper.java new file mode 100644 index 0000000..32f7df7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/HouseViewsLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.house.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.yulichang.base.MPJBaseMapper; +import com.gxwebsoft.house.entity.HouseViewsLog; +import com.gxwebsoft.house.param.HouseViewsLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 看房记录表Mapper + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +public interface HouseViewsLogMapper extends MPJBaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") HouseViewsLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") HouseViewsLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/house/mapper/xml/HouseInfoMapper.xml b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseInfoMapper.xml new file mode 100644 index 0000000..bbc00b5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseInfoMapper.xml @@ -0,0 +1,172 @@ + + + + + + + SELECT a.*, + b.nickname,b.avatar,b.grade_id + FROM house_info a + LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id + + + AND a.house_id = #{param.houseId} + + + AND a.house_title LIKE CONCAT('%', #{param.houseTitle}, '%') + + + AND a.city_by_house LIKE CONCAT('%', #{param.cityByHouse}, '%') + + + AND a.house_type LIKE CONCAT('%', #{param.houseType}, '%') + + + AND a.lease_method LIKE CONCAT('%', #{param.leaseMethod}, '%') + + + AND a.rent = #{param.rent} + + + AND a.monthly_rent = #{param.monthlyRent} + + + AND a.extent >= #{param.extentStart} + + + AND a.extent <= #{param.extentEnd} + + + AND a.floor LIKE CONCAT('%', #{param.floor}, '%') + + + AND a.room_number LIKE CONCAT('%', #{param.roomNumber}, '%') + + + AND a.sale_price = #{param.salePrice} + + + AND a.total_Price = #{param.totalPrice} + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.toward LIKE CONCAT('%', #{param.toward}, '%') + + + AND a.house_label LIKE CONCAT('%', #{param.houseLabel}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.area LIKE CONCAT('%', #{param.area}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.authentication = #{param.authentication} + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.recommend = #{param.recommend} + + + AND a.must_see = #{param.mustSee} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND ( + a.house_title LIKE CONCAT('%', #{param.keywords}, '%') + OR a.house_id = #{param.keywords} + OR b.nickname = #{param.keywords} + OR a.room_number LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + a.sort_number asc, + + + a.create_time desc, + + + a.monthly_rent asc, + + + a.monthly_rent desc, + + + a.extent asc, + + + a.extent desc, + + + ABS(a.monthly_rent - #{param.priceScene}), + + + + ABS(a.extent - #{param.extentScene}), + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/house/mapper/xml/HouseLikeLogMapper.xml b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseLikeLogMapper.xml new file mode 100644 index 0000000..5d7f6fb --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseLikeLogMapper.xml @@ -0,0 +1,51 @@ + + + + + + + SELECT a.* + FROM house_like_log a + + + AND a.id = #{param.id} + + + AND a.house_id = #{param.houseId} + + + AND a.house_user_id = #{param.houseUserId} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/house/mapper/xml/HouseReservationMapper.xml b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseReservationMapper.xml new file mode 100644 index 0000000..093c28b --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseReservationMapper.xml @@ -0,0 +1,99 @@ + + + + + + + SELECT a.* + FROM house_reservation a + + + AND a.log_id = #{param.logId} + + + AND a.log_no LIKE CONCAT('%', #{param.logNo}, '%') + + + AND a.type = #{param.type} + + + AND a.money = #{param.money} + + + AND a.house_id = #{param.houseId} + + + AND a.user_id = #{param.userId} + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.pay_time LIKE CONCAT('%', #{param.payTime}, '%') + + + AND a.pay_status = #{param.payStatus} + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.area LIKE CONCAT('%', #{param.area}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.is_settled = #{param.isSettled} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/house/mapper/xml/HouseUserMapper.xml b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseUserMapper.xml new file mode 100644 index 0000000..0af9ccd --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseUserMapper.xml @@ -0,0 +1,202 @@ + + + + + + + SELECT a.* + FROM house_user a + + + AND a.user_id = #{param.userId} + + + AND a.type = #{param.type} + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.sex = #{param.sex} + + + AND a.position LIKE CONCAT('%', #{param.position}, '%') + + + AND a.platform LIKE CONCAT('%', #{param.platform}, '%') + + + AND a.email LIKE CONCAT('%', #{param.email}, '%') + + + AND a.email_verified = #{param.emailVerified} + + + AND a.alias LIKE CONCAT('%', #{param.alias}, '%') + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.company_name LIKE CONCAT('%', #{param.companyName}, '%') + + + AND a.id_card LIKE CONCAT('%', #{param.idCard}, '%') + + + AND a.birthday LIKE CONCAT('%', #{param.birthday}, '%') + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.balance = #{param.balance} + + + AND a.points = #{param.points} + + + AND a.pay_money = #{param.payMoney} + + + AND a.expend_money = #{param.expendMoney} + + + AND a.grade_id = #{param.gradeId} + + + AND a.introduction LIKE CONCAT('%', #{param.introduction}, '%') + + + AND a.organization_id = #{param.organizationId} + + + AND a.avatar LIKE CONCAT('%', #{param.avatar}, '%') + + + AND a.bg_image LIKE CONCAT('%', #{param.bgImage}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.customer_id = #{param.customerId} + + + AND a.user_code LIKE CONCAT('%', #{param.userCode}, '%') + + + AND a.certification = #{param.certification} + + + AND a.interest LIKE CONCAT('%', #{param.interest}, '%') + + + AND a.height LIKE CONCAT('%', #{param.height}, '%') + + + AND a.weight LIKE CONCAT('%', #{param.weight}, '%') + + + AND a.monthly_pay LIKE CONCAT('%', #{param.monthlyPay}, '%') + + + AND a.education LIKE CONCAT('%', #{param.education}, '%') + + + AND a.vocation LIKE CONCAT('%', #{param.vocation}, '%') + + + AND a.age = #{param.age} + + + AND a.offline = #{param.offline} + + + AND a.followers = #{param.followers} + + + AND a.fans = #{param.fans} + + + AND a.likes = #{param.likes} + + + AND a.comment_numbers = #{param.commentNumbers} + + + AND a.recommend = #{param.recommend} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.merchant_code LIKE CONCAT('%', #{param.merchantCode}, '%') + + + AND a.settlement_time LIKE CONCAT('%', #{param.settlementTime}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND ( + a.nickname LIKE CONCAT('%', #{param.keywords}, '%') + OR a.user_id = #{param.keywords} + OR b.nickname = #{param.keywords} + OR a.phone = #{param.keywords} + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/house/mapper/xml/HouseViewsLogMapper.xml b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseViewsLogMapper.xml new file mode 100644 index 0000000..53fcf8a --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/mapper/xml/HouseViewsLogMapper.xml @@ -0,0 +1,51 @@ + + + + + + + SELECT a.* + FROM house_views_log a + + + AND a.id = #{param.id} + + + AND a.house_id = #{param.houseId} + + + AND a.house_user_id = #{param.houseUserId} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/house/param/HouseInfoParam.java b/src/main/java/com/gxwebsoft/house/param/HouseInfoParam.java new file mode 100644 index 0000000..af7ab49 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/param/HouseInfoParam.java @@ -0,0 +1,178 @@ +package com.gxwebsoft.house.param; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 房源信息表查询参数 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HouseInfoParam对象", description = "房源信息表查询参数") +public class HouseInfoParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer houseId; + + @Schema(description = "房源标题") + private String houseTitle; + + @Schema(description = "房产所在的城市") + private String cityByHouse; + + @Schema(description = "户型") + private String houseType; + + @Schema(description = "租赁方式") + private String leaseMethod; + + @Schema(description = "租金") + @QueryField(type = QueryType.EQ) + private BigDecimal rent; + + @Schema(description = "面积起始值") + @QueryField(type = QueryType.GE) + private Integer extentStart; + + @Schema(description = "面积结束值") + @QueryField(type = QueryType.LE) + private Integer extentEnd; + + @Schema(description = "月租金") + @QueryField(type = QueryType.EQ) + private BigDecimal monthlyRent; + + @Schema(description = "物业费") + @QueryField(type = QueryType.EQ) + private BigDecimal propertyFees; + + @Schema(description = "面积") + private String extent; + + @Schema(description = "楼层") + private String floor; + + @Schema(description = "卖价") + private String salePrice; + + @Schema(description = "总价") + private String totalPrice; + + @Schema(description = "房号") + private String roomNumber; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "进入房屋的密码") + private String password; + + @Schema(description = "房屋朝向") + private String toward; + + @Schema(description = "房屋标签") + private String houseLabel; + + @Schema(description = "图片附件") + private String files; + + @Schema(description = "房源介绍") + private String content; + + @Schema(description = "到期时间") + private String expirationTime; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "所在地区") + private String area; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否实名认证") + @QueryField(type = QueryType.EQ) + private Integer authentication; + + @Schema(description = "状态 10待审核 20驳回 30通过") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "价格起始值") + @TableField(exist = false) + private String priceScene; + + @Schema(description = "面积筛选") + @TableField(exist = false) + private String extentScene; + + @Schema(description = "排序") + @TableField(exist = false) + private String sortScene; + + @Schema(description = "是否推荐") + @TableField(exist = false) + private Integer recommend; + + @Schema(description = "是否必看") + @TableField(exist = false) + private Integer mustSee; + + @Schema(description = "是否可溢价") + @TableField(exist = false) + private String premium; + +} diff --git a/src/main/java/com/gxwebsoft/house/param/HouseLikeLogParam.java b/src/main/java/com/gxwebsoft/house/param/HouseLikeLogParam.java new file mode 100644 index 0000000..3ff4a55 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/param/HouseLikeLogParam.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.house.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 房源点赞表查询参数 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HouseLikeLogParam对象", description = "房源点赞表查询参数") +public class HouseLikeLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "房源ID") + @QueryField(type = QueryType.EQ) + private Integer houseId; + + @Schema(description = "房主ID") + @QueryField(type = QueryType.EQ) + private Integer houseUserId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "删除") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/house/param/HouseReservationParam.java b/src/main/java/com/gxwebsoft/house/param/HouseReservationParam.java new file mode 100644 index 0000000..43d0487 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/param/HouseReservationParam.java @@ -0,0 +1,109 @@ +package com.gxwebsoft.house.param; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 预约记录表查询参数 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HouseReservationParam对象", description = "预约记录表查询参数") +public class HouseReservationParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer logId; + + @Schema(description = "订单号") + private String logNo; + + @Schema(description = "类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "付款金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "房源ID") + @QueryField(type = QueryType.EQ) + private Integer houseId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "付款时间") + private String payTime; + + @Schema(description = "付款状态(10未付款 20已付款)") + @QueryField(type = QueryType.EQ) + private Integer payStatus; + + @Schema(description = "到期时间") + private String expirationTime; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "所在地区") + private String area; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "订单是否已结算(0未结算 1已结算)") + @QueryField(type = QueryType.EQ) + private Integer isSettled; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + +} diff --git a/src/main/java/com/gxwebsoft/house/param/HouseUserParam.java b/src/main/java/com/gxwebsoft/house/param/HouseUserParam.java new file mode 100644 index 0000000..25ce014 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/param/HouseUserParam.java @@ -0,0 +1,210 @@ +package com.gxwebsoft.house.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户表查询参数 + * + * @author 科技小王子 + * @since 2025-03-05 15:13:05 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HouseUserParam对象", description = "用户表查询参数") +public class HouseUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "用户类型,0个人用户 6开发者 10企业") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "账号") + private String username; + + @Schema(description = "密码") + private String password; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "性别 1男 2女") + @QueryField(type = QueryType.EQ) + private Integer sex; + + @Schema(description = "职务") + private String position; + + @Schema(description = "注册来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "邮箱是否验证, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer emailVerified; + + @Schema(description = "别名") + private String alias; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "单位姓名") + private String companyName; + + @Schema(description = "证件号码") + private String idCard; + + @Schema(description = "出生日期") + private String birthday; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "用户可用余额") + @QueryField(type = QueryType.EQ) + private BigDecimal balance; + + @Schema(description = "用户可用积分") + @QueryField(type = QueryType.EQ) + private Integer points; + + @Schema(description = "用户总支付的金额") + @QueryField(type = QueryType.EQ) + private BigDecimal payMoney; + + @Schema(description = "实际消费的金额(不含退款)") + @QueryField(type = QueryType.EQ) + private BigDecimal expendMoney; + + @Schema(description = "会员等级ID") + @QueryField(type = QueryType.EQ) + private Integer gradeId; + + @Schema(description = "个人简介") + private String introduction; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "头像") + private String avatar; + + @Schema(description = "背景图") + private String bgImage; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer customerId; + + @Schema(description = "用户编码") + private String userCode; + + @Schema(description = "是否已实名认证") + @QueryField(type = QueryType.EQ) + private Integer certification; + + @Schema(description = "兴趣爱好") + private String interest; + + @Schema(description = "身高") + private String height; + + @Schema(description = "体重") + private String weight; + + @Schema(description = "月薪") + private String monthlyPay; + + @Schema(description = "学历") + private String education; + + @Schema(description = "职业") + private String vocation; + + @Schema(description = "年龄") + @QueryField(type = QueryType.EQ) + private Integer age; + + @Schema(description = "是否线下会员") + @QueryField(type = QueryType.EQ) + private Boolean offline; + + @Schema(description = "关注数") + @QueryField(type = QueryType.EQ) + private Integer followers; + + @Schema(description = "粉丝数") + @QueryField(type = QueryType.EQ) + private Integer fans; + + @Schema(description = "点赞数") + @QueryField(type = QueryType.EQ) + private Integer likes; + + @Schema(description = "评论数") + @QueryField(type = QueryType.EQ) + private Integer commentNumbers; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0在线, 1离线") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "最后结算时间") + private String settlementTime; + +} diff --git a/src/main/java/com/gxwebsoft/house/param/HouseViewsLogParam.java b/src/main/java/com/gxwebsoft/house/param/HouseViewsLogParam.java new file mode 100644 index 0000000..b1c2590 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/param/HouseViewsLogParam.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.house.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 看房记录表查询参数 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "HouseViewsLogParam对象", description = "看房记录表查询参数") +public class HouseViewsLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "房源ID") + @QueryField(type = QueryType.EQ) + private Integer houseId; + + @Schema(description = "房主ID") + @QueryField(type = QueryType.EQ) + private Integer houseUserId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "删除") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/house/service/HouseInfoService.java b/src/main/java/com/gxwebsoft/house/service/HouseInfoService.java new file mode 100644 index 0000000..4f1f2a5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/HouseInfoService.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.house.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.yulichang.base.MPJBaseService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.house.entity.HouseInfo; +import com.gxwebsoft.house.param.HouseInfoParam; + +import java.util.List; + +/** + * 房源信息表Service + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +public interface HouseInfoService extends MPJBaseService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HouseInfoParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HouseInfoParam param); + + /** + * 根据id查询 + * + * @param houseId 自增ID + * @return HouseInfo + */ + HouseInfo getByIdRel(Integer houseId); + + String generatePoster(HouseInfo houseInfo) throws Exception; +} diff --git a/src/main/java/com/gxwebsoft/house/service/HouseLikeLogService.java b/src/main/java/com/gxwebsoft/house/service/HouseLikeLogService.java new file mode 100644 index 0000000..d04bc0c --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/HouseLikeLogService.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.house.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.github.yulichang.base.MPJBaseService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.house.entity.HouseLikeLog; +import com.gxwebsoft.house.param.HouseLikeLogParam; + +import java.util.List; + +/** + * 房源点赞表Service + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +public interface HouseLikeLogService extends MPJBaseService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HouseLikeLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HouseLikeLogParam param); + + /** + * 根据id查询 + * + * @param houseId ID + * @return LikeLog + */ + HouseLikeLog getByIdRel(Integer houseId, Integer userId); + + boolean add(HouseLikeLog likeLog); + +} diff --git a/src/main/java/com/gxwebsoft/house/service/HouseReservationService.java b/src/main/java/com/gxwebsoft/house/service/HouseReservationService.java new file mode 100644 index 0000000..e7f9e5a --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/HouseReservationService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.house.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.house.entity.HouseReservation; +import com.gxwebsoft.house.param.HouseReservationParam; + +import java.util.List; + +/** + * 预约记录表Service + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +public interface HouseReservationService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HouseReservationParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HouseReservationParam param); + + /** + * 根据id查询 + * + * @param logId ID + * @return HouseReservation + */ + HouseReservation getByIdRel(Integer logId); + +} diff --git a/src/main/java/com/gxwebsoft/house/service/HouseUserService.java b/src/main/java/com/gxwebsoft/house/service/HouseUserService.java new file mode 100644 index 0000000..40b8559 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/HouseUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.house.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.house.entity.HouseUser; +import com.gxwebsoft.house.param.HouseUserParam; + +import java.util.List; + +/** + * 用户表Service + * + * @author 科技小王子 + * @since 2025-03-05 15:13:05 + */ +public interface HouseUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HouseUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HouseUserParam param); + + /** + * 根据id查询 + * + * @param userId 用户id + * @return HouseUser + */ + HouseUser getByIdRel(Integer userId); + +} diff --git a/src/main/java/com/gxwebsoft/house/service/HouseViewsLogService.java b/src/main/java/com/gxwebsoft/house/service/HouseViewsLogService.java new file mode 100644 index 0000000..0424c03 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/HouseViewsLogService.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.house.service; + +import com.github.yulichang.base.MPJBaseService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.house.entity.HouseInfo; +import com.gxwebsoft.house.entity.HouseViewsLog; +import com.gxwebsoft.house.param.HouseViewsLogParam; + +import java.util.List; + +/** + * 看房记录表Service + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +public interface HouseViewsLogService extends MPJBaseService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(HouseViewsLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(HouseViewsLogParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return HouseViewsLog + */ + HouseViewsLog getByIdRel(Integer id); + + void add(HouseInfo houseInfo, Integer loginUserId); +} diff --git a/src/main/java/com/gxwebsoft/house/service/impl/HouseInfoServiceImpl.java b/src/main/java/com/gxwebsoft/house/service/impl/HouseInfoServiceImpl.java new file mode 100644 index 0000000..9167777 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/impl/HouseInfoServiceImpl.java @@ -0,0 +1,324 @@ +package com.gxwebsoft.house.service.impl; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.freewayso.image.combiner.ImageCombiner; +import com.freewayso.image.combiner.enums.OutputFormat; +import com.freewayso.image.combiner.enums.ZoomMode; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.ImageUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.system.controller.WxLoginController; +import com.gxwebsoft.common.system.service.SettingService; +import com.gxwebsoft.house.mapper.HouseInfoMapper; +import com.gxwebsoft.house.service.HouseInfoService; +import com.gxwebsoft.house.entity.HouseInfo; +import com.gxwebsoft.house.param.HouseInfoParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.beans.factory.annotation.Value; + +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.awt.*; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 房源信息表Service实现 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Service +public class HouseInfoServiceImpl extends ServiceImpl implements HouseInfoService { + + @Value("${config.upload-path}") + private String uploadPath; + + @Value("${config.file-server}") + private String fileServer; + + @Resource + private ConfigProperties config; + + @Resource + private SettingService settingService; + + @Resource + private RedisUtil redisUtil; + @Resource + private WxLoginController wxLoginController; + + private static final String ACCESS_TOKEN_KEY = "cache:wx:access_token"; + + @Override + public PageResult pageRel(HouseInfoParam param) { + PageParam page = new PageParam<>(param); + // 只有在没有指定排序场景时才设置默认排序 + if (param.getSortScene() == null || param.getSortScene().isEmpty()) { + page.setDefaultOrder("create_time desc"); + } + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HouseInfoParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + // 只有在没有指定排序场景时才使用默认排序 + if (param.getSortScene() == null || param.getSortScene().isEmpty()) { + page.setDefaultOrder("create_time desc"); + } + return page.sortRecords(list); + } + + @Override + public HouseInfo getByIdRel(Integer houseId) { + HouseInfoParam param = new HouseInfoParam(); + param.setHouseId(houseId); + return param.getOne(baseMapper.selectListRel(param)); + } + + /** + * 生成房产海报 ... + * + * @param houseInfo 房源信息 + * @return 海报图片URL + * @throws Exception 异常 + */ + @Override + public String generatePoster(HouseInfo houseInfo) throws Exception { + if (ObjectUtil.isEmpty(houseInfo)) { + return null; + } + + // 解析房源图片文件 + final String files = houseInfo.getFiles(); + if (StrUtil.isBlank(files)) { + return null; + } + + System.out.println("房源图片文件: " + files); + + try { + // 解析JSON数组格式的图片文件 + JSONArray fileArray = JSONArray.parseArray(files); + if (fileArray == null || fileArray.isEmpty()) { + return null; + } + + // 获取第一张图片作为主图 + JSONObject firstFile = fileArray.getJSONObject(0); + if (firstFile == null) { + return null; + } + + String mainImageUrl = firstFile.getString("url"); + if (StrUtil.isBlank(mainImageUrl)) { + return null; + } + + ImageCombiner combiner = new ImageCombiner(mainImageUrl, OutputFormat.JPG); + // 房源信息区域开始Y坐标 + int infoStartY = 330; + int lineHeight = 40; // 增加行高 + int currentY = infoStartY; + + // 添加房源标题(黑色文字,居中) + if (StrUtil.isNotBlank(houseInfo.getHouseTitle())) { + combiner.addTextElement(houseInfo.getHouseTitle(), 32, 50, currentY) + .setColor(Color.YELLOW) + .setCenter(true); + currentY += lineHeight + 10; // 标题后多留空间 + } + + // 添加房源价格信息(红色突出显示,居中) + if (houseInfo.getRent() != null) { + String priceText = "租金: ¥" + houseInfo.getRent(); + if (houseInfo.getMonthlyRent() != null) { + priceText += "/月"; + } + combiner.addTextElement(priceText, 26, 50, currentY) + .setColor(Color.RED) + .setCenter(true); + currentY += lineHeight; + } + + // 添加房源基本信息 + StringBuilder infoText = new StringBuilder(); + if (StrUtil.isNotBlank(houseInfo.getHouseType())) { + infoText.append(houseInfo.getHouseType()); + } + if (StrUtil.isNotBlank(houseInfo.getExtent())) { + if (infoText.length() > 0) infoText.append(" | "); + infoText.append(houseInfo.getExtent()); + } + if (StrUtil.isNotBlank(houseInfo.getFloor())) { + if (infoText.length() > 0) infoText.append(" | "); + infoText.append(houseInfo.getFloor()); + } + +// if (infoText.length() > 0) { +// combiner.addTextElement(infoText.toString(), 22, 50, currentY) +// .setColor(Color.LIGHT_GRAY) +// .setCenter(true); +// } + + // 生成并添加小程序码(左上角) + String qrCodeUrl = generateMiniProgramQRCode(houseInfo.getHouseId()); + if (StrUtil.isNotBlank(qrCodeUrl)) { + // 小程序码放在左上角,尺寸占宽度20%(600*0.2=120px) +// combiner.addImageElement(qrCodeUrl, 30, 30) +// .setX(5); + combiner.addImageElement(qrCodeUrl,40,40,150,150, ZoomMode.Width); + } + + // 执行图片合并 + combiner.combine(); + + // 创建保存目录 + String posterDir = uploadPath + "/file/poster/" + houseInfo.getTenantId() + "/house"; + if (!FileUtil.exist(posterDir)) { + FileUtil.mkdir(posterDir); + } + + // 生成文件路径 + String basePath = "/poster/" + houseInfo.getTenantId() + "/house/big-" + houseInfo.getHouseId() + ".jpg"; + String smallPath = "/poster/" + houseInfo.getTenantId() + "/house/" + houseInfo.getHouseId() + ".jpg"; + String filename = uploadPath + "/file" + basePath; + String smallFileName = uploadPath + "/file" + smallPath; + + // 保存原图 + combiner.save(filename); + + // 压缩图片 + File input = new File(filename); + File output = new File(smallFileName); + ImageUtil.adjustQuality(input, output, 0.8f); + + // 删除原图,保留压缩后的图片 + if (input.exists()) { + input.delete(); + } + + // 返回图片访问URL + return fileServer + smallPath + "?r=" + RandomUtil.randomNumbers(4); + + } catch (Exception e) { + System.err.println("生成房产海报失败: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } + + /** + * 生成房源详情页小程序码 + * + * @param houseId 房源ID + * @return 小程序码图片URL + */ + private String generateMiniProgramQRCode(Integer houseId) { + try { + String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + wxLoginController.getAccessToken(); + final HashMap map = new HashMap<>(); + // 设置小程序页面路径:sub_pages/house/detail/ + houseId + map.put("path", "sub_pages/house/detail/?houseId=" + houseId); + // 可以设置环境版本,如果需要的话 +// map.put("env_version", "trial"); + + // 获取图片 Buffer + byte[] qrCode = HttpRequest.post(apiUrl) + .body(JSON.toJSONString(map)) + .execute().bodyBytes(); + + // 保存的文件名称 + final String fileName = CommonUtil.randomUUID8().concat(".png"); + // 保存路径 + String filePath = getUploadDir().concat("qrcode/house/") + fileName; + + // 确保目录存在 + String qrCodeDir = getUploadDir().concat("qrcode/house/"); + if (!FileUtil.exist(qrCodeDir)) { + FileUtil.mkdir(qrCodeDir); + } + + File file = FileUtil.writeBytes(qrCode, filePath); + if (file != null) { + return config.getFileServer().concat("/qrcode/house/").concat(fileName); + } + } catch (Exception e) { + System.err.println("生成房源小程序码失败: " + e.getMessage()); + e.printStackTrace(); + } + return null; + } + + /** + * 获取微信小程序Access Token + * + * @return access_token + */ +// private String getAccessToken() { +// String key = ACCESS_TOKEN_KEY.concat(":").concat("1"); // 这里可以根据实际情况获取tenantId +// System.out.println("key = " + key); +// // 获取微信小程序配置信息 +// JSONObject setting = settingService.getBySettingKey("mp-weixin"); +// if (setting == null) { +// throw new RuntimeException("请先配置小程序"); +// } +// +// // 从缓存获取access_token +// String value = redisUtil.get(key); +// System.out.println("redisTemplate-value = " + value); +// if (value != null) { +// JSONObject response = JSON.parseObject(value); +// String accessToken = response.getString("access_token"); +// if (StrUtil.isNotBlank(accessToken)) { +// return accessToken; +// } +// } +// +// // 微信获取凭证接口 +// String apiUrl = "https://api.weixin.qq.com/cgi-bin/token"; +// // 组装url参数 +// String url = apiUrl.concat("?grant_type=client_credential") +// .concat("&appid=").concat(setting.getString("appId")) +// .concat("&secret=").concat(setting.getString("appSecret")); +// +// // 执行get请求 +// String result = cn.hutool.http.HttpUtil.get(url); +// System.out.println("获取access_token结果: " + result); +// +// // 解析access_token +// JSONObject response = JSON.parseObject(result); +// if (response.getString("access_token") != null) { +// // 存入缓存 +// redisUtil.set(key, result, 7000L, TimeUnit.SECONDS); +// return response.getString("access_token"); +// } +// +// throw new RuntimeException("小程序配置不正确"); +// } + + /** + * 文件上传位置(服务器) + */ + private String getUploadDir() { + return config.getUploadPath() + "file/"; + } + +} diff --git a/src/main/java/com/gxwebsoft/house/service/impl/HouseLikeLogServiceImpl.java b/src/main/java/com/gxwebsoft/house/service/impl/HouseLikeLogServiceImpl.java new file mode 100644 index 0000000..6670d85 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/impl/HouseLikeLogServiceImpl.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.house.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.github.yulichang.base.MPJBaseServiceImpl; +import com.gxwebsoft.house.mapper.HouseLikeLogMapper; +import com.gxwebsoft.house.service.HouseLikeLogService; +import com.gxwebsoft.house.entity.HouseLikeLog; +import com.gxwebsoft.house.param.HouseLikeLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 房源点赞表Service实现 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:14 + */ +@Service +public class HouseLikeLogServiceImpl extends MPJBaseServiceImpl implements HouseLikeLogService { + + @Override + public PageResult pageRel(HouseLikeLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HouseLikeLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HouseLikeLog getByIdRel(Integer houseId, Integer userId) { + LambdaQueryWrapper wr = Wrappers.lambdaQuery(HouseLikeLog.class).eq(HouseLikeLog::getHouseId, houseId).eq(HouseLikeLog::getUserId, userId); + HouseLikeLog likeLogs = getBaseMapper().selectOne(wr); + return likeLogs; + } + + @Override + public boolean add( HouseLikeLog likeLog) { + LambdaQueryWrapper wr = Wrappers.lambdaQuery(HouseLikeLog.class).eq(HouseLikeLog::getHouseId, likeLog.getHouseId()).eq(HouseLikeLog::getUserId, likeLog.getUserId()); + + List likeLogs = getBaseMapper().selectList(wr); + if(CollectionUtils.isNotEmpty(likeLogs)) { + baseMapper.delete(wr); + return false; + }else { + int insert = baseMapper.insert(likeLog); + return true; + } + } + +} diff --git a/src/main/java/com/gxwebsoft/house/service/impl/HouseReservationServiceImpl.java b/src/main/java/com/gxwebsoft/house/service/impl/HouseReservationServiceImpl.java new file mode 100644 index 0000000..343b3d6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/impl/HouseReservationServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.house.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.house.mapper.HouseReservationMapper; +import com.gxwebsoft.house.service.HouseReservationService; +import com.gxwebsoft.house.entity.HouseReservation; +import com.gxwebsoft.house.param.HouseReservationParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 预约记录表Service实现 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Service +public class HouseReservationServiceImpl extends ServiceImpl implements HouseReservationService { + + @Override + public PageResult pageRel(HouseReservationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HouseReservationParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HouseReservation getByIdRel(Integer logId) { + HouseReservationParam param = new HouseReservationParam(); + param.setLogId(logId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/service/impl/HouseUserServiceImpl.java b/src/main/java/com/gxwebsoft/house/service/impl/HouseUserServiceImpl.java new file mode 100644 index 0000000..93f0f56 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/impl/HouseUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.house.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.house.mapper.HouseUserMapper; +import com.gxwebsoft.house.service.HouseUserService; +import com.gxwebsoft.house.entity.HouseUser; +import com.gxwebsoft.house.param.HouseUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户表Service实现 + * + * @author 科技小王子 + * @since 2025-03-05 15:13:05 + */ +@Service +public class HouseUserServiceImpl extends ServiceImpl implements HouseUserService { + + @Override + public PageResult pageRel(HouseUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HouseUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HouseUser getByIdRel(Integer userId) { + HouseUserParam param = new HouseUserParam(); + param.setUserId(userId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/service/impl/HouseViewsLogServiceImpl.java b/src/main/java/com/gxwebsoft/house/service/impl/HouseViewsLogServiceImpl.java new file mode 100644 index 0000000..8ae54b1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/service/impl/HouseViewsLogServiceImpl.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.house.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.gxwebsoft.house.entity.HouseInfo; +import com.gxwebsoft.house.mapper.HouseViewsLogMapper; +import com.gxwebsoft.house.service.HouseViewsLogService; +import com.github.yulichang.base.MPJBaseServiceImpl; +import com.gxwebsoft.house.entity.HouseViewsLog; +import com.gxwebsoft.house.param.HouseViewsLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 看房记录表Service实现 + * + * @author 科技小王子 + * @since 2025-03-05 13:47:15 + */ +@Service +public class HouseViewsLogServiceImpl extends MPJBaseServiceImpl implements HouseViewsLogService { + + @Override + public PageResult pageRel(HouseViewsLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(HouseViewsLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public HouseViewsLog getByIdRel(Integer id) { + HouseViewsLogParam param = new HouseViewsLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void add(HouseInfo houseInfo, Integer loginUserId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(HouseViewsLog.class).eq(HouseViewsLog::getUserId, loginUserId).eq(HouseViewsLog::getHouseId, houseInfo.getHouseId()); + HouseViewsLog viewsLog = baseMapper.selectOne(wrapper); + if(viewsLog == null){ + viewsLog = new HouseViewsLog(); + viewsLog.setUserId(loginUserId); + viewsLog.setHouseId(houseInfo.getHouseId()); + viewsLog.setHouseUserId(houseInfo.getUserId()); + } + viewsLog.setUpdateTime(LocalDateTime.now()); + saveOrUpdate(viewsLog); + } + +} diff --git a/src/main/java/com/gxwebsoft/house/util/SortSceneUtil.java b/src/main/java/com/gxwebsoft/house/util/SortSceneUtil.java new file mode 100644 index 0000000..b9873a9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/house/util/SortSceneUtil.java @@ -0,0 +1,128 @@ +package com.gxwebsoft.house.util; + +import cn.hutool.core.util.StrUtil; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +/** + * 排序场景工具类 + * 用于处理前端传递的排序参数,解决URL编码和字符串匹配问题 + * + * @author 科技小王子 + * @since 2025-08-04 + */ +public class SortSceneUtil { + + /** + * 标准化排序场景参数 + * @param sortScene 原始排序场景参数 + * @return 标准化后的排序场景参数 + */ + public static String normalizeSortScene(String sortScene) { + if (StrUtil.isBlank(sortScene)) { + return null; + } + + // 尝试URL解码 + String decoded = sortScene; + try { + // 如果包含%,尝试URL解码 + if (sortScene.contains("%")) { + decoded = URLDecoder.decode(sortScene, "UTF-8"); + } + } catch (UnsupportedEncodingException e) { + // 解码失败,使用原始值 + decoded = sortScene; + } + + // 去除首尾空格 + decoded = decoded.trim(); + + // 标准化常见的排序场景 + if (decoded.contains("价格") && decoded.contains("低") && decoded.contains("高")) { + if (decoded.contains("低-高") || decoded.contains("低到高") || decoded.contains("升序")) { + return "价格(低-高)"; + } else if (decoded.contains("高-低") || decoded.contains("高到低") || decoded.contains("降序")) { + return "价格(高-低)"; + } + } + + if (decoded.contains("面积") && decoded.contains("小") && decoded.contains("大")) { + if (decoded.contains("小-大") || decoded.contains("小到大") || decoded.contains("升序")) { + return "面积(小-大)"; + } else if (decoded.contains("大-小") || decoded.contains("大到小") || decoded.contains("降序")) { + return "面积(大-小)"; + } + } + + if (decoded.contains("最新") || decoded.contains("时间") || decoded.contains("发布")) { + return "最新发布"; + } + + if (decoded.contains("综合") || decoded.contains("默认")) { + return "综合排序"; + } + + return decoded; + } + + /** + * 判断是否为价格升序排序 + * @param sortScene 排序场景参数 + * @return true表示价格升序 + */ + public static boolean isPriceAsc(String sortScene) { + String normalized = normalizeSortScene(sortScene); + return "价格(低-高)".equals(normalized); + } + + /** + * 判断是否为价格降序排序 + * @param sortScene 排序场景参数 + * @return true表示价格降序 + */ + public static boolean isPriceDesc(String sortScene) { + String normalized = normalizeSortScene(sortScene); + return "价格(高-低)".equals(normalized); + } + + /** + * 判断是否为面积升序排序 + * @param sortScene 排序场景参数 + * @return true表示面积升序 + */ + public static boolean isAreaAsc(String sortScene) { + String normalized = normalizeSortScene(sortScene); + return "面积(小-大)".equals(normalized); + } + + /** + * 判断是否为面积降序排序 + * @param sortScene 排序场景参数 + * @return true表示面积降序 + */ + public static boolean isAreaDesc(String sortScene) { + String normalized = normalizeSortScene(sortScene); + return "面积(大-小)".equals(normalized); + } + + /** + * 判断是否为最新发布排序 + * @param sortScene 排序场景参数 + * @return true表示最新发布排序 + */ + public static boolean isLatest(String sortScene) { + String normalized = normalizeSortScene(sortScene); + return "最新发布".equals(normalized); + } + + /** + * 判断是否为综合排序 + * @param sortScene 排序场景参数 + * @return true表示综合排序 + */ + public static boolean isComprehensive(String sortScene) { + String normalized = normalizeSortScene(sortScene); + return "综合排序".equals(normalized); + } +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAppController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAppController.java new file mode 100644 index 0000000..893921d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAppController.java @@ -0,0 +1,330 @@ +package com.gxwebsoft.oa.controller; + +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.net.url.UrlQuery; +import cn.hutool.core.util.DesensitizedUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.security.JwtUtil; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.entity.OaAppUrl; +import com.gxwebsoft.oa.entity.OaAppUser; +import com.gxwebsoft.oa.service.OaAppService; +import com.gxwebsoft.oa.entity.OaApp; +import com.gxwebsoft.oa.param.OaAppParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.oa.service.OaAppUrlService; +import com.gxwebsoft.oa.service.OaAppUserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.gxwebsoft.common.core.constants.AppUserConstants.ADMINISTRATOR; + +/** + * 应用控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "应用管理") +@RestController +@RequestMapping("/api/oa/oa-app") +public class OaAppController extends BaseController { + @Resource + private OaAppService oaAppService; + @Resource + private OaAppUrlService oaAppUrlService; + @Resource + private OaAppUserService oaAppUserService; + @Resource + private RedisUtil redisUtil; + @Resource + private ConfigProperties configProperties; + + @PreAuthorize("hasAuthority('oa:app:list')") + @Operation(summary = "分页查询应用") + @GetMapping("/page") + public ApiResult> page(OaAppParam param, HttpServletRequest request) { + final User loginUser = getLoginUser(); + // 未登录情况 + if(loginUser == null){ + String access_token = JwtUtil.getAccessToken(request); + param.setToken(access_token); + return success("案例列表",caseList(param)); + } + final Integer userId = loginUser.getUserId(); + loginUser.getRoles().forEach(d -> { + if(!StrUtil.equals(d.getRoleCode(),"superAdmin") && !StrUtil.equals(d.getRoleCode(),"admin")){ + // 非管理员按项目成员权限显示 + final List list = oaAppUserService.list(new LambdaQueryWrapper().eq(OaAppUser::getUserId, userId)); + final Set collect = list.stream().map(OaAppUser::getAppId).collect(Collectors.toSet()); + param.setAppIds(collect); + } + }); + // 过滤续费提醒参数 + if (param.getAppStatus() != null && param.getAppStatus().equals("续费中")) { + param.setAppStatus(null); + } + // 使用关联查询 + return success(oaAppService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:app:list')") + @Operation(summary = "查询全部应用") + @GetMapping() + public ApiResult> list(OaAppParam param) { + // 使用关联查询 + return success(oaAppService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:app:list')") + @Operation(summary = "根据id查询应用") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + final User loginUser = getLoginUser(); + // 未登录情况 + if(loginUser == null){ + return success("案例详情",getDesensitizedApp(id)); + } + if (!CommonUtil.hasRole(loginUser.getRoles(),"superAdmin") && !CommonUtil.hasRole(loginUser.getRoles(),"admin")) { + // 查询权限 + if (oaAppUserService.count(new LambdaQueryWrapper().eq(OaAppUser::getUserId, getLoginUserId()).eq(OaAppUser::getAppId,id)) == 0) { + return fail("无查看权限",null); + } + } + // 使用关联查询 + return success(oaAppService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:app:save')") + @Operation(summary = "添加应用") + @PostMapping() + public ApiResult save(@RequestBody OaApp app) { + // 记录当前登录用户id、租户id、商户编号 + User loginUser = getLoginUser(); + OaAppUser appUser = new OaAppUser(); + if (loginUser != null) { + app.setUserId(loginUser.getUserId()); + app.setTenantId(loginUser.getTenantId()); + } + if (oaAppService.count(new LambdaQueryWrapper() + .eq(OaApp::getAppCode, app.getAppCode())) > 0) { + return fail("应用标识已存在"); + } + if (oaAppService.save(app)) { + // 添加应用管理员 + if (loginUser != null) { + appUser.setUserId(loginUser.getUserId()); + appUser.setNickname(loginUser.getNickname()); + appUser.setAppId(app.getAppId()); + appUser.setRole(ADMINISTRATOR); + oaAppUserService.save(appUser); + } + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:app:update')") + @Operation(summary = "修改应用") + @PutMapping() + public ApiResult update(@RequestBody OaApp oaApp) { + if (oaAppService.updateById(oaApp)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:app:remove')") + @Operation(summary = "删除应用") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAppService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:app:save')") + @Operation(summary = "批量添加应用") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAppService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:app:update')") + @Operation(summary = "批量修改应用") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAppService, "app_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:app:remove')") + @Operation(summary = "批量删除应用") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAppService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "统计信息") + @GetMapping("/data") + public ApiResult> data() { + Map data = new HashMap<>(); + long totalNum = oaAppService.count( + new LambdaQueryWrapper<>() + ); + long totalNum2 = oaAppService.count( + new LambdaQueryWrapper() + .eq(OaApp::getAppStatus, "开发中") + ); + long totalNum3 = oaAppService.count( + new LambdaQueryWrapper() + .eq(OaApp::getAppStatus, "已上架") + ); + long totalNum4 = oaAppService.count( + new LambdaQueryWrapper() + .eq(OaApp::getAppStatus, "已上架") + .eq(OaApp::getShowExpiration,false) + ); + long totalNum5 = oaAppService.count( + new LambdaQueryWrapper() + .eq(OaApp::getAppStatus, "已下架") + ); + long totalNum6 = oaAppService.count( + new LambdaQueryWrapper() + .eq(OaApp::getShowCase,true) + ); + long totalNum7 = oaAppService.count( + new LambdaQueryWrapper() + .eq(OaApp::getShowIndex, true) + ); + + data.put("totalNum", Math.toIntExact(totalNum)); + data.put("totalNum2", Math.toIntExact(totalNum2)); + data.put("totalNum3", Math.toIntExact(totalNum3)); + data.put("totalNum4", Math.toIntExact(totalNum4)); + data.put("totalNum5", Math.toIntExact(totalNum5)); + data.put("totalNum6", Math.toIntExact(totalNum6)); + data.put("totalNum7", Math.toIntExact(totalNum7)); + return success(data); + } + + + // 读取案例列表 + private PageResult caseList(OaAppParam param){ + param.setShowCase(true); + final PageResult appPageResult = oaAppService.pageRel(param); + appPageResult.getList().forEach(d -> { + d.setContent(null); + // 读取账号列表 + d.setAppUrlList(oaAppUrlService.list(new LambdaQueryWrapper().eq(OaAppUrl::getAppId,d.getAppId()))); + // 读取项目附件(链式构建GET请求) + HashMap map = new HashMap<>(); + map.put("appId", d.getAppId()); + final String build = UrlBuilder.of(configProperties.getServerUrl() + "/file/page").setQuery(new UrlQuery(map)).build(); + String response = HttpRequest.get(build) + .header("Authorization", param.getToken()) + .header("Tenantid", d.getTenantId().toString()) + .body(JSONObject.toJSONString(map))//表单内容 + .timeout(20000)//超时,毫秒 + .execute().body(); + + final ApiResult> userResult = JSONObject.parseObject(response, new TypeReference>>() { + }); + d.setAppFiles(userResult.getData().getList()); + }); + return appPageResult; + } + + private OaApp getDesensitizedApp(Integer id) { + final OaApp app = oaAppService.getByIdRel(id); + app.setPhone(DesensitizedUtil.mobilePhone(app.getPhone())); + app.setCompanyName( + StrUtil.hide(app.getCompanyName(), 2, app.getCompanyName().length() - 2)); + app.setRequirement(DesensitizedUtil.chineseName(app.getCompanyName())); + return app; + } + + @PreAuthorize("hasAuthority('oa:app:update')") + @Operation(summary = "重置秘钥") + @PostMapping("/updateAppSecret") + public ApiResult updateAppSecret(@RequestBody OaApp app) { + String key = "code:" + app.getPhone(); + final String code = redisUtil.get(key); + if (app.getAppId() == null) { + return fail("appId不合法"); + } + if(app.getAppSecret() == null){ + return fail("appSecret不合法"); + } + if(app.getAppCode() == null){ + return fail("短信验证码不正确"); + } +// && !app.getAppCode().equals("170083") + if(!app.getAppCode().equals(code)){ + return fail("短信验证码不正确"); + } + oaAppService.updateById(app); + // 保存到redis + redisUtil.set("AppSecret:" + app.getAppId(),app.getAppSecret()); + redisUtil.delete(key); + return success("重置成功",app.getAppSecret()); + } + + @PreAuthorize("hasAuthority('oa:app:list')") + @Operation(summary = "APP应用授权身份效验") + @GetMapping("/authentication/{appid}") + public ApiResult authentication(@PathVariable("appid") String appid) { + final OaApp appInfo = oaAppService.getById(appid); + if(appInfo == null){ + return fail("应用不存在:".concat(appid)); + } + return success("应用信息",appInfo); + } + + @Operation(summary = "查询我的项目信息") + @GetMapping("/getMyApp") + public ApiResult getMyApp() { + final User loginUser = getLoginUser(); + // 未登录情况 + if(loginUser == null){ + return fail("请先登录",null); + } + oaAppService.list(); + + // 使用关联查询 + return success("查询成功",null); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAppFieldController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAppFieldController.java new file mode 100644 index 0000000..7805e3c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAppFieldController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAppFieldService; +import com.gxwebsoft.oa.entity.OaAppField; +import com.gxwebsoft.oa.param.OaAppFieldParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用参数控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "应用参数管理") +@RestController +@RequestMapping("/api/oa/oa-app-field") +public class OaAppFieldController extends BaseController { + @Resource + private OaAppFieldService oaAppFieldService; + + @Operation(summary = "分页查询应用参数") + @GetMapping("/page") + public ApiResult> page(OaAppFieldParam param) { + // 使用关联查询 + return success(oaAppFieldService.pageRel(param)); + } + + @Operation(summary = "查询全部应用参数") + @GetMapping() + public ApiResult> list(OaAppFieldParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaAppFieldService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaAppFieldService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAppField:list')") + @OperationLog + @Operation(summary = "根据id查询应用参数") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaAppFieldService.getById(id)); + // 使用关联查询 + //return success(oaAppFieldService.getByIdRel(id)); + } + + @Operation(summary = "添加应用参数") + @PostMapping() + public ApiResult save(@RequestBody OaAppField oaAppField) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAppField.setUserId(loginUser.getUserId()); + } + if (oaAppFieldService.save(oaAppField)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改应用参数") + @PutMapping() + public ApiResult update(@RequestBody OaAppField oaAppField) { + if (oaAppFieldService.updateById(oaAppField)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除应用参数") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAppFieldService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加应用参数") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAppFieldService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改应用参数") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAppFieldService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除应用参数") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAppFieldService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAppRenewController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAppRenewController.java new file mode 100644 index 0000000..cc98044 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAppRenewController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAppRenewService; +import com.gxwebsoft.oa.entity.OaAppRenew; +import com.gxwebsoft.oa.param.OaAppRenewParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 续费管理控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "续费管理管理") +@RestController +@RequestMapping("/api/oa/oa-app-renew") +public class OaAppRenewController extends BaseController { + @Resource + private OaAppRenewService oaAppRenewService; + + @Operation(summary = "分页查询续费管理") + @GetMapping("/page") + public ApiResult> page(OaAppRenewParam param) { + // 使用关联查询 + return success(oaAppRenewService.pageRel(param)); + } + + @Operation(summary = "查询全部续费管理") + @GetMapping() + public ApiResult> list(OaAppRenewParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaAppRenewService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaAppRenewService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAppRenew:list')") + @OperationLog + @Operation(summary = "根据id查询续费管理") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaAppRenewService.getById(id)); + // 使用关联查询 + //return success(oaAppRenewService.getByIdRel(id)); + } + + @Operation(summary = "添加续费管理") + @PostMapping() + public ApiResult save(@RequestBody OaAppRenew oaAppRenew) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAppRenew.setUserId(loginUser.getUserId()); + } + if (oaAppRenewService.save(oaAppRenew)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改续费管理") + @PutMapping() + public ApiResult update(@RequestBody OaAppRenew oaAppRenew) { + if (oaAppRenewService.updateById(oaAppRenew)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除续费管理") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAppRenewService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加续费管理") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAppRenewService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改续费管理") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAppRenewService, "app_renew_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除续费管理") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAppRenewService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAppUrlController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAppUrlController.java new file mode 100644 index 0000000..49bf27b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAppUrlController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAppUrlService; +import com.gxwebsoft.oa.entity.OaAppUrl; +import com.gxwebsoft.oa.param.OaAppUrlParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 项目域名控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "项目域名管理") +@RestController +@RequestMapping("/api/oa/oa-app-url") +public class OaAppUrlController extends BaseController { + @Resource + private OaAppUrlService oaAppUrlService; + + @Operation(summary = "分页查询项目域名") + @GetMapping("/page") + public ApiResult> page(OaAppUrlParam param) { + // 使用关联查询 + return success(oaAppUrlService.pageRel(param)); + } + + @Operation(summary = "查询全部项目域名") + @GetMapping() + public ApiResult> list(OaAppUrlParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaAppUrlService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaAppUrlService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAppUrl:list')") + @OperationLog + @Operation(summary = "根据id查询项目域名") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaAppUrlService.getById(id)); + // 使用关联查询 + //return success(oaAppUrlService.getByIdRel(id)); + } + + @Operation(summary = "添加项目域名") + @PostMapping() + public ApiResult save(@RequestBody OaAppUrl oaAppUrl) { + if (oaAppUrlService.save(oaAppUrl)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改项目域名") + @PutMapping() + public ApiResult update(@RequestBody OaAppUrl oaAppUrl) { + if (oaAppUrlService.updateById(oaAppUrl)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除项目域名") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAppUrlService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加项目域名") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAppUrlService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改项目域名") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAppUrlService, "app_url_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除项目域名") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAppUrlService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAppUserController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAppUserController.java new file mode 100644 index 0000000..d544035 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAppUserController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAppUserService; +import com.gxwebsoft.oa.entity.OaAppUser; +import com.gxwebsoft.oa.param.OaAppUserParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用成员控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "应用成员管理") +@RestController +@RequestMapping("/api/oa/oa-app-user") +public class OaAppUserController extends BaseController { + @Resource + private OaAppUserService oaAppUserService; + + @Operation(summary = "分页查询应用成员") + @GetMapping("/page") + public ApiResult> page(OaAppUserParam param) { + // 使用关联查询 + return success(oaAppUserService.pageRel(param)); + } + + @Operation(summary = "查询全部应用成员") + @GetMapping() + public ApiResult> list(OaAppUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaAppUserService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaAppUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAppUser:list')") + @OperationLog + @Operation(summary = "根据id查询应用成员") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaAppUserService.getById(id)); + // 使用关联查询 + //return success(oaAppUserService.getByIdRel(id)); + } + + @Operation(summary = "添加应用成员") + @PostMapping() + public ApiResult save(@RequestBody OaAppUser oaAppUser) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAppUser.setUserId(loginUser.getUserId()); + } + if (oaAppUserService.save(oaAppUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改应用成员") + @PutMapping() + public ApiResult update(@RequestBody OaAppUser oaAppUser) { + if (oaAppUserService.updateById(oaAppUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除应用成员") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAppUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加应用成员") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAppUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改应用成员") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAppUserService, "app_user_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除应用成员") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAppUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsCodeController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsCodeController.java new file mode 100644 index 0000000..bee4518 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsCodeController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsCodeService; +import com.gxwebsoft.oa.entity.OaAssetsCode; +import com.gxwebsoft.oa.param.OaAssetsCodeParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 代码仓库控制器 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +@Tag(name = "代码仓库管理") +@RestController +@RequestMapping("/api/oa/oa-assets-code") +public class OaAssetsCodeController extends BaseController { + @Resource + private OaAssetsCodeService oaAssetsCodeService; + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:list')") + @Operation(summary = "分页查询代码仓库") + @GetMapping("/page") + public ApiResult> page(OaAssetsCodeParam param) { + // 使用关联查询 + return success(oaAssetsCodeService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:list')") + @Operation(summary = "查询全部代码仓库") + @GetMapping() + public ApiResult> list(OaAssetsCodeParam param) { + // 使用关联查询 + return success(oaAssetsCodeService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:list')") + @Operation(summary = "根据id查询代码仓库") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsCodeService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:save')") + @OperationLog + @Operation(summary = "添加代码仓库") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsCode oaAssetsCode) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsCode.setUserId(loginUser.getUserId()); + } + if (oaAssetsCodeService.save(oaAssetsCode)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:update')") + @Operation(summary = "修改代码仓库") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsCode oaAssetsCode) { + if (oaAssetsCodeService.updateById(oaAssetsCode)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:remove')") + @Operation(summary = "删除代码仓库") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsCodeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:save')") + @Operation(summary = "批量添加代码仓库") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsCodeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:update')") + @Operation(summary = "批量修改代码仓库") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsCodeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsCode:remove')") + @Operation(summary = "批量删除代码仓库") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsCodeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsController.java new file mode 100644 index 0000000..6f66706 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsController.java @@ -0,0 +1,123 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsService; +import com.gxwebsoft.oa.entity.OaAssets; +import com.gxwebsoft.oa.param.OaAssetsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 云服务器控制器 + * + * @author 科技小王子 + * @since 2024-10-18 18:34:15 + */ +@Tag(name = "云服务器管理") +@RestController +@RequestMapping("/api/oa/oa-assets") +public class OaAssetsController extends BaseController { + @Resource + private OaAssetsService oaAssetsService; + + @PreAuthorize("hasAuthority('oa:oaAssets:list')") + @Operation(summary = "分页查询云服务器") + @GetMapping("/page") + public ApiResult> page(OaAssetsParam param) { + // 使用关联查询 + return success(oaAssetsService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:list')") + @Operation(summary = "查询全部云服务器") + @GetMapping() + public ApiResult> list(OaAssetsParam param) { + // 使用关联查询 + return success(oaAssetsService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:list')") + @Operation(summary = "根据id查询云服务器") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:save')") + @OperationLog + @Operation(summary = "添加云服务器") + @PostMapping() + public ApiResult save(@RequestBody OaAssets oaAssets) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssets.setUserId(loginUser.getUserId()); + } + if (oaAssetsService.save(oaAssets)) { + return success("添加成功"); + } + return fail("添加失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssets:update')") + @Operation(summary = "修改云服务器") + @PutMapping() + public ApiResult update(@RequestBody OaAssets oaAssets) { + if (oaAssetsService.updateById(oaAssets)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:remvoe')") + @Operation(summary = "删除云服务器") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:save')") + @Operation(summary = "批量添加云服务器") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:update')") + @Operation(summary = "批量修改云服务器") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsService, "assets_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssets:remove')") + @Operation(summary = "批量删除云服务器") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsDomainController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsDomainController.java new file mode 100644 index 0000000..bacd540 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsDomainController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsDomainService; +import com.gxwebsoft.oa.entity.OaAssetsDomain; +import com.gxwebsoft.oa.param.OaAssetsDomainParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 域名控制器 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Tag(name = "域名管理") +@RestController +@RequestMapping("/api/oa/oa-assets-domain") +public class OaAssetsDomainController extends BaseController { + @Resource + private OaAssetsDomainService oaAssetsDomainService; + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:list')") + @Operation(summary = "分页查询域名") + @GetMapping("/page") + public ApiResult> page(OaAssetsDomainParam param) { + // 使用关联查询 + return success(oaAssetsDomainService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:list')") + @Operation(summary = "查询全部域名") + @GetMapping() + public ApiResult> list(OaAssetsDomainParam param) { + // 使用关联查询 + return success(oaAssetsDomainService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:list')") + @Operation(summary = "根据id查询域名") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsDomainService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:save')") + @OperationLog + @Operation(summary = "添加域名") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsDomain oaAssetsDomain) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsDomain.setUserId(loginUser.getUserId()); + } + if (oaAssetsDomainService.save(oaAssetsDomain)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:update')") + @Operation(summary = "修改域名") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsDomain oaAssetsDomain) { + if (oaAssetsDomainService.updateById(oaAssetsDomain)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:remove')") + @Operation(summary = "删除域名") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsDomainService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:save')") + @Operation(summary = "批量添加域名") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsDomainService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:update')") + @Operation(summary = "批量修改域名") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsDomainService, "domain_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsDomain:remove')") + @Operation(summary = "批量删除域名") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsDomainService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsEmailController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsEmailController.java new file mode 100644 index 0000000..551905c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsEmailController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsEmailService; +import com.gxwebsoft.oa.entity.OaAssetsEmail; +import com.gxwebsoft.oa.param.OaAssetsEmailParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 企业邮箱记录表控制器 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Tag(name = "企业邮箱记录表管理") +@RestController +@RequestMapping("/api/oa/oa-assets-email") +public class OaAssetsEmailController extends BaseController { + @Resource + private OaAssetsEmailService oaAssetsEmailService; + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:list')") + @Operation(summary = "分页查询企业邮箱记录表") + @GetMapping("/page") + public ApiResult> page(OaAssetsEmailParam param) { + // 使用关联查询 + return success(oaAssetsEmailService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:list')") + @Operation(summary = "查询全部企业邮箱记录表") + @GetMapping() + public ApiResult> list(OaAssetsEmailParam param) { + // 使用关联查询 + return success(oaAssetsEmailService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:list')") + @OperationLog + @Operation(summary = "根据id查询企业邮箱记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsEmailService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:save')") + @OperationLog + @Operation(summary = "添加企业邮箱记录表") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsEmail oaAssetsEmail) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsEmail.setUserId(loginUser.getUserId()); + } + if (oaAssetsEmailService.save(oaAssetsEmail)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:update')") + @Operation(summary = "修改企业邮箱记录表") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsEmail oaAssetsEmail) { + if (oaAssetsEmailService.updateById(oaAssetsEmail)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:remove')") + @Operation(summary = "删除企业邮箱记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsEmailService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:save')") + @Operation(summary = "批量添加企业邮箱记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsEmailService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:update')") + @Operation(summary = "批量修改企业邮箱记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsEmailService, "email_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsEmail:remove')") + @Operation(summary = "批量删除企业邮箱记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsEmailService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsMysqlController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsMysqlController.java new file mode 100644 index 0000000..db6dc1c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsMysqlController.java @@ -0,0 +1,122 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsMysqlService; +import com.gxwebsoft.oa.entity.OaAssetsMysql; +import com.gxwebsoft.oa.param.OaAssetsMysqlParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 云数据库控制器 + * + * @author 科技小王子 + * @since 2024-10-18 19:00:20 + */ +@Tag(name = "云数据库管理") +@RestController +@RequestMapping("/api/oa/oa-assets-mysql") +public class OaAssetsMysqlController extends BaseController { + @Resource + private OaAssetsMysqlService oaAssetsMysqlService; + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:list')") + @Operation(summary = "分页查询云数据库") + @GetMapping("/page") + public ApiResult> page(OaAssetsMysqlParam param) { + // 使用关联查询 + return success(oaAssetsMysqlService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:list')") + @Operation(summary = "查询全部云数据库") + @GetMapping() + public ApiResult> list(OaAssetsMysqlParam param) { + // 使用关联查询 + return success(oaAssetsMysqlService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:list')") + @Operation(summary = "根据id查询云数据库") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsMysqlService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:save')") + @Operation(summary = "添加云数据库") + @OperationLog + @PostMapping() + public ApiResult save(@RequestBody OaAssetsMysql oaAssetsMysql) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsMysql.setUserId(loginUser.getUserId()); + } + if (oaAssetsMysqlService.save(oaAssetsMysql)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:update')") + @Operation(summary = "修改云数据库") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsMysql oaAssetsMysql) { + if (oaAssetsMysqlService.updateById(oaAssetsMysql)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:remove')") + @Operation(summary = "删除云数据库") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsMysqlService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:save')") + @Operation(summary = "批量添加云数据库") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsMysqlService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:update')") + @Operation(summary = "批量修改云数据库") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsMysqlService, "mysql_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssetsMysql:remove')") + @Operation(summary = "批量删除云数据库") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsMysqlService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsServerController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsServerController.java new file mode 100644 index 0000000..8865482 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsServerController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsServerService; +import com.gxwebsoft.oa.entity.OaAssetsServer; +import com.gxwebsoft.oa.param.OaAssetsServerParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 服务器资产记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "服务器资产记录表管理") +@RestController +@RequestMapping("/api/oa/oa-assets-server") +public class OaAssetsServerController extends BaseController { + @Resource + private OaAssetsServerService oaAssetsServerService; + + @Operation(summary = "分页查询服务器资产记录表") + @GetMapping("/page") + public ApiResult> page(OaAssetsServerParam param) { + // 使用关联查询 + return success(oaAssetsServerService.pageRel(param)); + } + + @Operation(summary = "查询全部服务器资产记录表") + @GetMapping() + public ApiResult> list(OaAssetsServerParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaAssetsServerService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaAssetsServerService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsServer:list')") + @OperationLog + @Operation(summary = "根据id查询服务器资产记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaAssetsServerService.getById(id)); + // 使用关联查询 + //return success(oaAssetsServerService.getByIdRel(id)); + } + + @Operation(summary = "添加服务器资产记录表") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsServer oaAssetsServer) { + // 记录当前登录用户id + + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsServer.setUserId(loginUser.getUserId()); + } + if (oaAssetsServerService.save(oaAssetsServer)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改服务器资产记录表") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsServer oaAssetsServer) { + if (oaAssetsServerService.updateById(oaAssetsServer)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除服务器资产记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsServerService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加服务器资产记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsServerService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改服务器资产记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsServerService, "server_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除服务器资产记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsServerService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSiteController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSiteController.java new file mode 100644 index 0000000..a1f8286 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSiteController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsSiteService; +import com.gxwebsoft.oa.entity.OaAssetsSite; +import com.gxwebsoft.oa.param.OaAssetsSiteParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 网站信息记录表控制器 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Tag(name = "网站信息记录表管理") +@RestController +@RequestMapping("/api/oa/oa-assets-site") +public class OaAssetsSiteController extends BaseController { + @Resource + private OaAssetsSiteService oaAssetsSiteService; + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:list')") + @Operation(summary = "分页查询网站信息记录表") + @GetMapping("/page") + public ApiResult> page(OaAssetsSiteParam param) { + // 使用关联查询 + return success(oaAssetsSiteService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:list')") + @Operation(summary = "查询全部网站信息记录表") + @GetMapping() + public ApiResult> list(OaAssetsSiteParam param) { + // 使用关联查询 + return success(oaAssetsSiteService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:list')") + @Operation(summary = "根据id查询网站信息记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsSiteService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:save')") + @OperationLog + @Operation(summary = "添加网站信息记录表") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsSite oaAssetsSite) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsSite.setUserId(loginUser.getUserId()); + } + if (oaAssetsSiteService.save(oaAssetsSite)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:update')") + @Operation(summary = "修改网站信息记录表") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsSite oaAssetsSite) { + if (oaAssetsSiteService.updateById(oaAssetsSite)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:remove')") + @Operation(summary = "删除网站信息记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsSiteService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:save')") + @Operation(summary = "批量添加网站信息记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsSiteService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:update')") + @Operation(summary = "批量修改网站信息记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsSiteService, "website_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSite:remove')") + @Operation(summary = "批量删除网站信息记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsSiteService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSoftwareCertController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSoftwareCertController.java new file mode 100644 index 0000000..4cd4b82 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSoftwareCertController.java @@ -0,0 +1,123 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsSoftwareCertService; +import com.gxwebsoft.oa.entity.OaAssetsSoftwareCert; +import com.gxwebsoft.oa.param.OaAssetsSoftwareCertParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 计算机软件著作权登记控制器 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Tag(name = "计算机软件著作权登记管理") +@RestController +@RequestMapping("/api/oa/oa-assets-software-cert") +public class OaAssetsSoftwareCertController extends BaseController { + @Resource + private OaAssetsSoftwareCertService oaAssetsSoftwareCertService; + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:list')") + @Operation(summary = "分页查询计算机软件著作权登记") + @GetMapping("/page") + public ApiResult> page(OaAssetsSoftwareCertParam param) { + // 使用关联查询 + return success(oaAssetsSoftwareCertService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:list')") + @Operation(summary = "查询全部计算机软件著作权登记") + @GetMapping() + public ApiResult> list(OaAssetsSoftwareCertParam param) { + // 使用关联查询 + return success(oaAssetsSoftwareCertService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:list')") + @Operation(summary = "根据id查询计算机软件著作权登记") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsSoftwareCertService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:save')") + @OperationLog + @Operation(summary = "添加计算机软件著作权登记") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsSoftwareCert oaAssetsSoftwareCert) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsSoftwareCert.setUserId(loginUser.getUserId()); + } + if (oaAssetsSoftwareCertService.save(oaAssetsSoftwareCert)) { + return success("添加成功"); + } + return fail("添加失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:update')") + @Operation(summary = "修改计算机软件著作权登记") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsSoftwareCert oaAssetsSoftwareCert) { + if (oaAssetsSoftwareCertService.updateById(oaAssetsSoftwareCert)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:remove')") + @Operation(summary = "删除计算机软件著作权登记") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsSoftwareCertService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:save')") + @Operation(summary = "批量添加计算机软件著作权登记") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsSoftwareCertService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:update')") + @Operation(summary = "批量修改计算机软件著作权登记") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsSoftwareCertService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSoftwareCert:remove')") + @Operation(summary = "批量删除计算机软件著作权登记") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsSoftwareCertService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSslController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSslController.java new file mode 100644 index 0000000..0d27eb2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsSslController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsSslService; +import com.gxwebsoft.oa.entity.OaAssetsSsl; +import com.gxwebsoft.oa.param.OaAssetsSslParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * ssl证书控制器 + * + * @author 科技小王子 + * @since 2024-10-18 19:25:40 + */ +@Tag(name = "ssl证书管理") +@RestController +@RequestMapping("/api/oa/oa-assets-ssl") +public class OaAssetsSslController extends BaseController { + @Resource + private OaAssetsSslService oaAssetsSslService; + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:list')") + @Operation(summary = "分页查询ssl证书") + @GetMapping("/page") + public ApiResult> page(OaAssetsSslParam param) { + // 使用关联查询 + return success(oaAssetsSslService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:list')") + @Operation(summary = "查询全部ssl证书") + @GetMapping() + public ApiResult> list(OaAssetsSslParam param) { + // 使用关联查询 + return success(oaAssetsSslService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:list')") + @OperationLog + @Operation(summary = "根据id查询ssl证书") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsSslService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:save')") + @Operation(summary = "添加ssl证书") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsSsl oaAssetsSsl) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsSsl.setUserId(loginUser.getUserId()); + oaAssetsSsl.setTenantId(loginUser.getTenantId()); + } + if (oaAssetsSslService.save(oaAssetsSsl)) { + return success("添加成功"); + } + return fail("添加失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:update')") + @Operation(summary = "修改ssl证书") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsSsl oaAssetsSsl) { + if (oaAssetsSslService.updateById(oaAssetsSsl)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:remove')") + @Operation(summary = "删除ssl证书") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsSslService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:save')") + @Operation(summary = "批量添加ssl证书") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsSslService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:update')") + @Operation(summary = "批量修改ssl证书") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsSslService, "ssl_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsSsl:remove')") + @Operation(summary = "批量删除ssl证书") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsSslService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsTrademarkController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsTrademarkController.java new file mode 100644 index 0000000..18a5bf6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsTrademarkController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsTrademarkService; +import com.gxwebsoft.oa.entity.OaAssetsTrademark; +import com.gxwebsoft.oa.param.OaAssetsTrademarkParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商标注册控制器 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Tag(name = "商标注册管理") +@RestController +@RequestMapping("/api/oa/oa-assets-trademark") +public class OaAssetsTrademarkController extends BaseController { + @Resource + private OaAssetsTrademarkService oaAssetsTrademarkService; + + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:list')") + @Operation(summary = "分页查询商标注册") + @GetMapping("/page") + public ApiResult> page(OaAssetsTrademarkParam param) { + // 使用关联查询 + return success(oaAssetsTrademarkService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:list')") + @Operation(summary = "查询全部商标注册") + @GetMapping() + public ApiResult> list(OaAssetsTrademarkParam param) { + // 使用关联查询 + return success(oaAssetsTrademarkService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:list')") + @Operation(summary = "根据id查询商标注册") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsTrademarkService.getByIdRel(id)); + } + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:save')") + @Operation(summary = "添加商标注册") + @OperationLog + @PostMapping() + public ApiResult save(@RequestBody OaAssetsTrademark oaAssetsTrademark) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsTrademark.setUserId(loginUser.getUserId()); + } + if (oaAssetsTrademarkService.save(oaAssetsTrademark)) { + return success("添加成功"); + } + return fail("添加失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:update')") + @Operation(summary = "修改商标注册") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsTrademark oaAssetsTrademark) { + if (oaAssetsTrademarkService.updateById(oaAssetsTrademark)) { + return success("修改成功"); + } + return fail("修改失败"); + } + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:remove')") + @Operation(summary = "删除商标注册") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsTrademarkService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:save')") + @Operation(summary = "批量添加商标注册") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsTrademarkService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:update')") + @Operation(summary = "批量修改商标注册") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsTrademarkService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsTrademark:remove')") + @Operation(summary = "批量删除商标注册") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsTrademarkService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsUserController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsUserController.java new file mode 100644 index 0000000..30f47db --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsUserController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsUserService; +import com.gxwebsoft.oa.entity.OaAssetsUser; +import com.gxwebsoft.oa.param.OaAssetsUserParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 服务器成员管理控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Tag(name = "服务器成员管理管理") +@RestController +@RequestMapping("/api/oa/oa-assets-user") +public class OaAssetsUserController extends BaseController { + @Resource + private OaAssetsUserService oaAssetsUserService; + + @Operation(summary = "分页查询服务器成员管理") + @GetMapping("/page") + public ApiResult> page(OaAssetsUserParam param) { + // 使用关联查询 + return success(oaAssetsUserService.pageRel(param)); + } + + @Operation(summary = "查询全部服务器成员管理") + @GetMapping() + public ApiResult> list(OaAssetsUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaAssetsUserService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaAssetsUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsUser:list')") + @OperationLog + @Operation(summary = "根据id查询服务器成员管理") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaAssetsUserService.getById(id)); + // 使用关联查询 + //return success(oaAssetsUserService.getByIdRel(id)); + } + + @Operation(summary = "添加服务器成员管理") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsUser oaAssetsUser) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsUser.setUserId(loginUser.getUserId()); + } + if (oaAssetsUserService.save(oaAssetsUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改服务器成员管理") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsUser oaAssetsUser) { + if (oaAssetsUserService.updateById(oaAssetsUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除服务器成员管理") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加服务器成员管理") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改服务器成员管理") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsUserService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除服务器成员管理") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaAssetsVhostController.java b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsVhostController.java new file mode 100644 index 0000000..17b5875 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaAssetsVhostController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaAssetsVhostService; +import com.gxwebsoft.oa.entity.OaAssetsVhost; +import com.gxwebsoft.oa.param.OaAssetsVhostParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 虚拟主机记录表控制器 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Tag(name = "虚拟主机记录表管理") +@RestController +@RequestMapping("/api/oa/oa-assets-vhost") +public class OaAssetsVhostController extends BaseController { + @Resource + private OaAssetsVhostService oaAssetsVhostService; + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:list')") + @Operation(summary = "分页查询虚拟主机记录表") + @GetMapping("/page") + public ApiResult> page(OaAssetsVhostParam param) { + // 使用关联查询 + return success(oaAssetsVhostService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:list')") + @Operation(summary = "查询全部虚拟主机记录表") + @GetMapping() + public ApiResult> list(OaAssetsVhostParam param) { + // 使用关联查询 + return success(oaAssetsVhostService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:list')") + @Operation(summary = "根据id查询虚拟主机记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(oaAssetsVhostService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:save')") + @OperationLog + @Operation(summary = "添加虚拟主机记录表") + @PostMapping() + public ApiResult save(@RequestBody OaAssetsVhost oaAssetsVhost) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaAssetsVhost.setUserId(loginUser.getUserId()); + } + if (oaAssetsVhostService.save(oaAssetsVhost)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:update')") + @Operation(summary = "修改虚拟主机记录表") + @PutMapping() + public ApiResult update(@RequestBody OaAssetsVhost oaAssetsVhost) { + if (oaAssetsVhostService.updateById(oaAssetsVhost)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:remove')") + @Operation(summary = "删除虚拟主机记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaAssetsVhostService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:save')") + @Operation(summary = "批量添加虚拟主机记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaAssetsVhostService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:update')") + @Operation(summary = "批量修改虚拟主机记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaAssetsVhostService, "vhost_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('oa:oaAssetsVhost:remove')") + @Operation(summary = "批量删除虚拟主机记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaAssetsVhostService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java new file mode 100644 index 0000000..9110cba --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java @@ -0,0 +1,152 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.oa.service.OaCompanyService; + +import cn.hutool.core.util.StrUtil; + +import com.gxwebsoft.oa.entity.OaCompany; +import com.gxwebsoft.oa.param.OaCompanyParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.common.core.annotation.OperationLog; +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.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 企业信息控制器 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Tag(name = "企业信息管理") +@RestController +@RequestMapping("/api/oa/oa-company") +public class OaCompanyController extends BaseController { + @Resource + private OaCompanyService oaCompanyService; + + @Autowired + private KnowledgeBaseService knowledgeBaseService; + + @Operation(summary = "分页查询企业信息") + @GetMapping("/page") + public ApiResult> page(OaCompanyParam param) { + // 使用关联查询 + return success(oaCompanyService.pageRel(param)); + } + + @Operation(summary = "查询全部企业信息") + @GetMapping() + public ApiResult> list(OaCompanyParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaCompanyService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaCompanyService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaCompany:list')") + @OperationLog + @Operation(summary = "根据id查询企业信息") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaCompanyService.getById(id)); + // 使用关联查询 + //return success(oaCompanyService.getByIdRel(id)); + } + + @Operation(summary = "添加企业信息") + @PostMapping() + public ApiResult save(@RequestBody OaCompany oaCompany) { + if(StrUtil.isEmpty(oaCompany.getCompanyCode())) { + return fail("单位唯一标识不能为空"); + } + if(oaCompanyService.count(new LambdaQueryWrapper().eq(OaCompany::getCompanyCode, oaCompany.getCompanyCode()))>0) { + return fail("单位唯一标识已存在"); + } + if (oaCompanyService.save(oaCompany)) { + try { + String kbId = knowledgeBaseService.createKnowledgeBase(oaCompany.getCompanyName(), oaCompany.getCompanyCode()); + // 绑定知识库 + oaCompany.setKbId(kbId); + oaCompanyService.updateById(oaCompany); + // 初始化11个目录 - 调用Service层方法 + oaCompanyService.initCompanyDocDirectories(oaCompany, getLoginUserId()); + } catch (Exception e) { + return fail("添加失败:"+e.getMessage()); + } + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改企业信息") + @PutMapping() + public ApiResult update(@RequestBody OaCompany oaCompany) { + if(StrUtil.isEmpty(oaCompany.getCompanyCode())) { + return fail("单位唯一标识不能为空"); + } + if(StrUtil.isEmpty(oaCompany.getKbId())) { + String kbId = knowledgeBaseService.createKnowledgeBase(oaCompany.getCompanyName(), oaCompany.getCompanyCode()); + //绑定知识库 + oaCompany.setKbId(kbId); + } + if (oaCompanyService.updateById(oaCompany)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除企业信息") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + //删云目录 + oaCompanyService.removeCompanyDocDirectories(id); + //删云知识库 + oaCompanyService.removeCompanyKnowledgeBase(id); + if (oaCompanyService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加企业信息") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaCompanyService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改企业信息") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaCompanyService, "company_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除企业信息") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaCompanyService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaCompanyFieldController.java b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyFieldController.java new file mode 100644 index 0000000..f24b320 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyFieldController.java @@ -0,0 +1,114 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.oa.service.OaCompanyFieldService; +import com.gxwebsoft.oa.entity.OaCompanyField; +import com.gxwebsoft.oa.param.OaCompanyFieldParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 企业参数控制器 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Tag(name = "企业参数管理") +@RestController +@RequestMapping("/api/oa/oa-company-field") +public class OaCompanyFieldController extends BaseController { + @Resource + private OaCompanyFieldService oaCompanyFieldService; + + @Operation(summary = "分页查询企业参数") + @GetMapping("/page") + public ApiResult> page(OaCompanyFieldParam param) { + // 使用关联查询 + return success(oaCompanyFieldService.pageRel(param)); + } + + @Operation(summary = "查询全部企业参数") + @GetMapping() + public ApiResult> list(OaCompanyFieldParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaCompanyFieldService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaCompanyFieldService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaCompanyField:list')") + @OperationLog + @Operation(summary = "根据id查询企业参数") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaCompanyFieldService.getById(id)); + // 使用关联查询 + //return success(oaCompanyFieldService.getByIdRel(id)); + } + + @Operation(summary = "添加企业参数") + @PostMapping() + public ApiResult save(@RequestBody OaCompanyField oaCompanyField) { + if (oaCompanyFieldService.save(oaCompanyField)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改企业参数") + @PutMapping() + public ApiResult update(@RequestBody OaCompanyField oaCompanyField) { + if (oaCompanyFieldService.updateById(oaCompanyField)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除企业参数") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaCompanyFieldService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加企业参数") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaCompanyFieldService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改企业参数") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaCompanyFieldService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除企业参数") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaCompanyFieldService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaCompanyUserController.java b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyUserController.java new file mode 100644 index 0000000..dcb2653 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyUserController.java @@ -0,0 +1,114 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.oa.service.OaCompanyUserService; +import com.gxwebsoft.oa.entity.OaCompanyUser; +import com.gxwebsoft.oa.param.OaCompanyUserParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 成员管理控制器 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Tag(name = "成员管理管理") +@RestController +@RequestMapping("/api/oa/oa-company-user") +public class OaCompanyUserController extends BaseController { + @Resource + private OaCompanyUserService oaCompanyUserService; + + @Operation(summary = "分页查询成员管理") + @GetMapping("/page") + public ApiResult> page(OaCompanyUserParam param) { + // 使用关联查询 + return success(oaCompanyUserService.pageRel(param)); + } + + @Operation(summary = "查询全部成员管理") + @GetMapping() + public ApiResult> list(OaCompanyUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaCompanyUserService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaCompanyUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaCompanyUser:list')") + @OperationLog + @Operation(summary = "根据id查询成员管理") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaCompanyUserService.getById(id)); + // 使用关联查询 + //return success(oaCompanyUserService.getByIdRel(id)); + } + + @Operation(summary = "添加成员管理") + @PostMapping() + public ApiResult save(@RequestBody OaCompanyUser oaCompanyUser) { + if (oaCompanyUserService.save(oaCompanyUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改成员管理") + @PutMapping() + public ApiResult update(@RequestBody OaCompanyUser oaCompanyUser) { + if (oaCompanyUserService.updateById(oaCompanyUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除成员管理") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaCompanyUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加成员管理") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaCompanyUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改成员管理") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaCompanyUserService, "company_user_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除成员管理") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaCompanyUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaLinkController.java b/src/main/java/com/gxwebsoft/oa/controller/OaLinkController.java new file mode 100644 index 0000000..9a7286e --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaLinkController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaLinkService; +import com.gxwebsoft.oa.entity.OaLink; +import com.gxwebsoft.oa.param.OaLinkParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 常用链接控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Tag(name = "常用链接管理") +@RestController +@RequestMapping("/api/oa/oa-link") +public class OaLinkController extends BaseController { + @Resource + private OaLinkService oaLinkService; + + @Operation(summary = "分页查询常用链接") + @GetMapping("/page") + public ApiResult> page(OaLinkParam param) { + // 使用关联查询 + return success(oaLinkService.pageRel(param)); + } + + @Operation(summary = "查询全部常用链接") + @GetMapping() + public ApiResult> list(OaLinkParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaLinkService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaLinkService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaLink:list')") + @OperationLog + @Operation(summary = "根据id查询常用链接") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaLinkService.getById(id)); + // 使用关联查询 + //return success(oaLinkService.getByIdRel(id)); + } + + @Operation(summary = "添加常用链接") + @PostMapping() + public ApiResult save(@RequestBody OaLink oaLink) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaLink.setUserId(loginUser.getUserId()); + } + if (oaLinkService.save(oaLink)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改常用链接") + @PutMapping() + public ApiResult update(@RequestBody OaLink oaLink) { + if (oaLinkService.updateById(oaLink)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除常用链接") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaLinkService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加常用链接") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaLinkService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改常用链接") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaLinkService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除常用链接") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaLinkService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaProductController.java b/src/main/java/com/gxwebsoft/oa/controller/OaProductController.java new file mode 100644 index 0000000..0996cb6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaProductController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaProductService; +import com.gxwebsoft.oa.entity.OaProduct; +import com.gxwebsoft.oa.param.OaProductParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 产品记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Tag(name = "产品记录表管理") +@RestController +@RequestMapping("/api/oa/oa-product") +public class OaProductController extends BaseController { + @Resource + private OaProductService oaProductService; + + @Operation(summary = "分页查询产品记录表") + @GetMapping("/page") + public ApiResult> page(OaProductParam param) { + // 使用关联查询 + return success(oaProductService.pageRel(param)); + } + + @Operation(summary = "查询全部产品记录表") + @GetMapping() + public ApiResult> list(OaProductParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaProductService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaProductService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaProduct:list')") + @OperationLog + @Operation(summary = "根据id查询产品记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaProductService.getById(id)); + // 使用关联查询 + //return success(oaProductService.getByIdRel(id)); + } + + @Operation(summary = "添加产品记录表") + @PostMapping() + public ApiResult save(@RequestBody OaProduct oaProduct) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaProduct.setUserId(loginUser.getUserId()); + } + if (oaProductService.save(oaProduct)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改产品记录表") + @PutMapping() + public ApiResult update(@RequestBody OaProduct oaProduct) { + if (oaProductService.updateById(oaProduct)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除产品记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaProductService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加产品记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaProductService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改产品记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaProductService, "product_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除产品记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaProductService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaProductTabsController.java b/src/main/java/com/gxwebsoft/oa/controller/OaProductTabsController.java new file mode 100644 index 0000000..ac7836e --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaProductTabsController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaProductTabsService; +import com.gxwebsoft.oa.entity.OaProductTabs; +import com.gxwebsoft.oa.param.OaProductTabsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 产品标签记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Tag(name = "产品标签记录表管理") +@RestController +@RequestMapping("/api/oa/oa-product-tabs") +public class OaProductTabsController extends BaseController { + @Resource + private OaProductTabsService oaProductTabsService; + + @Operation(summary = "分页查询产品标签记录表") + @GetMapping("/page") + public ApiResult> page(OaProductTabsParam param) { + // 使用关联查询 + return success(oaProductTabsService.pageRel(param)); + } + + @Operation(summary = "查询全部产品标签记录表") + @GetMapping() + public ApiResult> list(OaProductTabsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaProductTabsService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaProductTabsService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaProductTabs:list')") + @OperationLog + @Operation(summary = "根据id查询产品标签记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaProductTabsService.getById(id)); + // 使用关联查询 + //return success(oaProductTabsService.getByIdRel(id)); + } + + @Operation(summary = "添加产品标签记录表") + @PostMapping() + public ApiResult save(@RequestBody OaProductTabs oaProductTabs) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaProductTabs.setUserId(loginUser.getUserId()); + } + if (oaProductTabsService.save(oaProductTabs)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改产品标签记录表") + @PutMapping() + public ApiResult update(@RequestBody OaProductTabs oaProductTabs) { + if (oaProductTabsService.updateById(oaProductTabs)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除产品标签记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaProductTabsService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加产品标签记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaProductTabsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改产品标签记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaProductTabsService, "tab_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除产品标签记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaProductTabsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaTaskController.java b/src/main/java/com/gxwebsoft/oa/controller/OaTaskController.java new file mode 100644 index 0000000..fab3f41 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaTaskController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaTaskService; +import com.gxwebsoft.oa.entity.OaTask; +import com.gxwebsoft.oa.param.OaTaskParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 任务记录表控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Tag(name = "任务记录表管理") +@RestController +@RequestMapping("/api/oa/oa-task") +public class OaTaskController extends BaseController { + @Resource + private OaTaskService oaTaskService; + + @Operation(summary = "分页查询任务记录表") + @GetMapping("/page") + public ApiResult> page(OaTaskParam param) { + // 使用关联查询 + return success(oaTaskService.pageRel(param)); + } + + @Operation(summary = "查询全部任务记录表") + @GetMapping() + public ApiResult> list(OaTaskParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaTaskService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaTaskService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaTask:list')") + @OperationLog + @Operation(summary = "根据id查询任务记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaTaskService.getById(id)); + // 使用关联查询 + //return success(oaTaskService.getByIdRel(id)); + } + + @Operation(summary = "添加任务记录表") + @PostMapping() + public ApiResult save(@RequestBody OaTask oaTask) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaTask.setUserId(loginUser.getUserId()); + } + if (oaTaskService.save(oaTask)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改任务记录表") + @PutMapping() + public ApiResult update(@RequestBody OaTask oaTask) { + if (oaTaskService.updateById(oaTask)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除任务记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaTaskService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加任务记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaTaskService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改任务记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaTaskService, "task_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除任务记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaTaskService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaTaskCountController.java b/src/main/java/com/gxwebsoft/oa/controller/OaTaskCountController.java new file mode 100644 index 0000000..d4ba413 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaTaskCountController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaTaskCountService; +import com.gxwebsoft.oa.entity.OaTaskCount; +import com.gxwebsoft.oa.param.OaTaskCountParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 数据统计控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Tag(name = "数据统计管理") +@RestController +@RequestMapping("/api/oa/oa-task-count") +public class OaTaskCountController extends BaseController { + @Resource + private OaTaskCountService oaTaskCountService; + + @Operation(summary = "分页查询数据统计") + @GetMapping("/page") + public ApiResult> page(OaTaskCountParam param) { + // 使用关联查询 + return success(oaTaskCountService.pageRel(param)); + } + + @Operation(summary = "查询全部数据统计") + @GetMapping() + public ApiResult> list(OaTaskCountParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaTaskCountService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaTaskCountService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaTaskCount:list')") + @OperationLog + @Operation(summary = "根据id查询数据统计") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaTaskCountService.getById(id)); + // 使用关联查询 + //return success(oaTaskCountService.getByIdRel(id)); + } + + @Operation(summary = "添加数据统计") + @PostMapping() + public ApiResult save(@RequestBody OaTaskCount oaTaskCount) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaTaskCount.setUserId(loginUser.getUserId()); + } + if (oaTaskCountService.save(oaTaskCount)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改数据统计") + @PutMapping() + public ApiResult update(@RequestBody OaTaskCount oaTaskCount) { + if (oaTaskCountService.updateById(oaTaskCount)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除数据统计") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaTaskCountService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加数据统计") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaTaskCountService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改数据统计") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaTaskCountService, "task_count_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除数据统计") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaTaskCountService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaTaskUserController.java b/src/main/java/com/gxwebsoft/oa/controller/OaTaskUserController.java new file mode 100644 index 0000000..59423c9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/controller/OaTaskUserController.java @@ -0,0 +1,120 @@ +package com.gxwebsoft.oa.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.oa.service.OaTaskUserService; +import com.gxwebsoft.oa.entity.OaTaskUser; +import com.gxwebsoft.oa.param.OaTaskUserParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 工单成员控制器 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Tag(name = "工单成员管理") +@RestController +@RequestMapping("/api/oa/oa-task-user") +public class OaTaskUserController extends BaseController { + @Resource + private OaTaskUserService oaTaskUserService; + + @Operation(summary = "分页查询工单成员") + @GetMapping("/page") + public ApiResult> page(OaTaskUserParam param) { + // 使用关联查询 + return success(oaTaskUserService.pageRel(param)); + } + + @Operation(summary = "查询全部工单成员") + @GetMapping() + public ApiResult> list(OaTaskUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + return success(oaTaskUserService.list(page.getOrderWrapper())); + // 使用关联查询 + //return success(oaTaskUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('oa:oaTaskUser:list')") + @OperationLog + @Operation(summary = "根据id查询工单成员") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(oaTaskUserService.getById(id)); + // 使用关联查询 + //return success(oaTaskUserService.getByIdRel(id)); + } + + @Operation(summary = "添加工单成员") + @PostMapping() + public ApiResult save(@RequestBody OaTaskUser oaTaskUser) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + oaTaskUser.setUserId(loginUser.getUserId()); + } + if (oaTaskUserService.save(oaTaskUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改工单成员") + @PutMapping() + public ApiResult update(@RequestBody OaTaskUser oaTaskUser) { + if (oaTaskUserService.updateById(oaTaskUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除工单成员") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (oaTaskUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加工单成员") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (oaTaskUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改工单成员") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(oaTaskUserService, "task_user_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除工单成员") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (oaTaskUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaApp.java b/src/main/java/com/gxwebsoft/oa/entity/OaApp.java new file mode 100644 index 0000000..bc301cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaApp.java @@ -0,0 +1,269 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.util.List; + +import com.gxwebsoft.common.system.entity.FileRecord; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaApp对象", description = "应用") +public class OaApp implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "应用ID") + @TableId(value = "app_id", type = IdType.AUTO) + private Integer appId; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "应用标识") + private String appCode; + + @Schema(description = "应用秘钥") + private String appSecret; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "应用类型") + private String appTypeMultiple; + + @Schema(description = "类型, 0菜单, 1按钮") + private Integer menuType; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "企业名称") + private String companyName; + + @Schema(description = "应用图标") + private String appIcon; + + @Schema(description = "二维码") + private String appQrcode; + + @Schema(description = "链接地址") + private String appUrl; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "下载地址") + private String downUrl; + + @Schema(description = "链接地址") + private String serverUrl; + + @Schema(description = "文件服务器") + private String fileUrl; + + @Schema(description = "回调地址") + private String callbackUrl; + + @Schema(description = "腾讯文档地址") + private String docsUrl; + + @Schema(description = "代码仓库地址") + private String gitUrl; + + @Schema(description = "原型图地址") + private String prototypeUrl; + + @Schema(description = "IP白名单") + private String ipAddress; + + @Schema(description = "应用截图") + private String images; + + @Schema(description = "应用包名") + private String packageName; + + @Schema(description = "下载次数") + private Integer clicks; + + @Schema(description = "安装次数") + private Integer installs; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "应用介绍") + private String content; + + @Schema(description = "项目需求") + private String requirement; + + @Schema(description = "开发者(个人或公司)") + private String developer; + + @Schema(description = "项目负责人") + private String director; + + @Schema(description = "项目经理") + private String projectDirector; + + @Schema(description = "业务员") + private String salesman; + + @Schema(description = "软件定价") + private BigDecimal price; + + @Schema(description = "划线价格") + private BigDecimal linePrice; + + @Schema(description = "评分") + private String score; + + @Schema(description = "星级") + private String star; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "禁止搜索,1禁止 0 允许") + private Integer search; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "版本,0正式版 1体验版 2开发版") + private String edition; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "是否已安装") + private Integer isUse; + + @Schema(description = "附近1") + private String file1; + + @Schema(description = "附件2") + private String file2; + + @Schema(description = "附件3") + private String file3; + + @Schema(description = "是否显示续费提醒") + private Boolean showExpiration; + + @Schema(description = "是否作为案例展示") + private Integer showCase; + + @Schema(description = "是否显示在首页") + private Integer showIndex; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "续费金额") + private BigDecimal renewMoney; + + @Schema(description = "应用状态") + private String appStatus; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "机构id") + private Integer organizationId; + + @Schema(description = "租户编号") + private String tenantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "成员管理") + @TableField(exist = false) + private List users; + + @Schema(description = "链接列表") + @TableField(exist = false) + private List appUrlList; + + @Schema(description = "项目附件") + @TableField(exist = false) + private List appFiles; + + @Schema(description = "主体名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "开发者名称") + @TableField(exist = false) + private String realName; + + @Schema(description = "开发者名称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "开发者头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "手机号码") + @TableField(exist = false) + private String phone; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAppField.java b/src/main/java/com/gxwebsoft/oa/entity/OaAppField.java new file mode 100644 index 0000000..2e6b0dc --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAppField.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAppField对象", description = "应用参数") +public class OaAppField implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "状态, 0正常, 1删除") + private Integer status; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAppRenew.java b/src/main/java/com/gxwebsoft/oa/entity/OaAppRenew.java new file mode 100644 index 0000000..67da767 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAppRenew.java @@ -0,0 +1,69 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 续费管理 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAppRenew对象", description = "续费管理") +public class OaAppRenew implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "app_renew_id", type = IdType.AUTO) + private Integer appRenewId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "续费金额") + private BigDecimal money; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "付款凭证") + private String images; + + @Schema(description = "用户姓名") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAppUrl.java b/src/main/java/com/gxwebsoft/oa/entity/OaAppUrl.java new file mode 100644 index 0000000..5ba08de --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAppUrl.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 项目域名 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAppUrl对象", description = "项目域名") +public class OaAppUrl implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "app_url_id", type = IdType.AUTO) + private Integer appUrlId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "域名类型") + private String name; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAppUser.java b/src/main/java/com/gxwebsoft/oa/entity/OaAppUser.java new file mode 100644 index 0000000..2a29374 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAppUser.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用成员 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAppUser对象", description = "应用成员") +public class OaAppUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "app_user_id", type = IdType.AUTO) + private Integer appUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + private Integer role; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssets.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssets.java new file mode 100644 index 0000000..647b4bf --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssets.java @@ -0,0 +1,161 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 云服务器 + * + * @author 科技小王子 + * @since 2024-10-18 18:34:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssets对象", description = "云服务器") +public class OaAssets implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "资产ID") + @TableId(value = "assets_id", type = IdType.AUTO) + private Integer assetsId; + + @Schema(description = "资产名称") + private String name; + + @Schema(description = "资产标识") + private String code; + + @Schema(description = "资产类型") + private String type; + + @Schema(description = "服务器厂商") + private String brand; + + @Schema(description = "服务器配置") + private String configuration; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "(阿里云/腾讯云)登录账号") + private String brandAccount; + + @Schema(description = "(阿里云/腾讯云)登录密码") + private String brandPassword; + + @Schema(description = "宝塔面板") + private String panel; + + @Schema(description = "宝塔面板账号") + private String panelAccount; + + @Schema(description = "宝塔面板密码") + private String panelPassword; + + @Schema(description = "财务信息-合同金额") + private BigDecimal financeAmount; + + @Schema(description = "购买年限") + private Integer financeYears; + + @Schema(description = "续费金额") + private BigDecimal financeRenew; + + @Schema(description = "客户名称") + private String financeCustomerName; + + @Schema(description = "客户联系人") + private String financeCustomerContact; + + @Schema(description = "客户联系电话") + private String financeCustomerPhone; + + @Schema(description = "客户ID") + private Integer customerId; + + @Schema(description = "客户名称") + private String customerName; + + @Schema(description = "开放端口") + private String openPort; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "可见性(public,private,protected)") + private String visibility; + + @Schema(description = "宝塔接口秘钥") + private String btSign; + + @Schema(description = "文章排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "客户ID") + private Integer companyId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "机构id") + private Integer organizationId; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsCode.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsCode.java new file mode 100644 index 0000000..40d6cbb --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsCode.java @@ -0,0 +1,92 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 代码仓库 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsCode对象", description = "代码仓库") +public class OaAssetsCode implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "服务器ID") + private Integer assetsId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "英文标识") + private String code; + + @Schema(description = "仓库地址") + private String gitUrl; + + @Schema(description = "仓库品牌") + private String brand; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsDomain.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsDomain.java new file mode 100644 index 0000000..4a617ac --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsDomain.java @@ -0,0 +1,104 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 域名 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsDomain对象", description = "域名") +public class OaAssetsDomain implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "domain_id", type = IdType.AUTO) + private Integer domainId; + + @Schema(description = "服务器ID") + private Integer assetsId; + + @Schema(description = "域名") + private String name; + + @Schema(description = "域名标识") + private String code; + + @Schema(description = "注册厂商") + private String brand; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsEmail.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsEmail.java new file mode 100644 index 0000000..c72eb5c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsEmail.java @@ -0,0 +1,104 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 企业邮箱记录表 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsEmail对象", description = "企业邮箱记录表") +public class OaAssetsEmail implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "email_id", type = IdType.AUTO) + private Integer emailId; + + @Schema(description = "邮箱名称") + private String name; + + @Schema(description = "域名标识") + private String code; + + @Schema(description = "邮箱型号") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "初始账号") + private String system; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsMysql.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsMysql.java new file mode 100644 index 0000000..86009d9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsMysql.java @@ -0,0 +1,109 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 云数据库 + * + * @author 科技小王子 + * @since 2024-10-18 19:00:20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsMysql对象", description = "云数据库") +public class OaAssetsMysql implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "mysql_id", type = IdType.AUTO) + private Integer mysqlId; + + @Schema(description = "服务器ID") + private Integer assetsId; + + @Schema(description = "数据库名") + private String name; + + @Schema(description = "数据库标识") + private String code; + + @Schema(description = "注册厂商") + private String brand; + + @Schema(description = "ip地址") + private String ip; + + @Schema(description = "端口") + private String port; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsServer.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsServer.java new file mode 100644 index 0000000..3c856d3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsServer.java @@ -0,0 +1,147 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 服务器资产记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsServer对象", description = "服务器资产记录表") +public class OaAssetsServer implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "资产ID") + @TableId(value = "server_id", type = IdType.AUTO) + private Integer serverId; + + @Schema(description = "资产名称") + private String name; + + @Schema(description = "资产标识") + private String code; + + @Schema(description = "资产类型") + private String type; + + @Schema(description = "服务器厂商") + private String brand; + + @Schema(description = "服务器配置") + private String configuration; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "(阿里云/腾讯云)登录账号") + private String brandAccount; + + @Schema(description = "(阿里云/腾讯云)登录密码") + private String brandPassword; + + @Schema(description = "宝塔面板") + private String panel; + + @Schema(description = "宝塔面板账号") + private String panelAccount; + + @Schema(description = "宝塔面板密码") + private String panelPassword; + + @Schema(description = "财务信息-合同金额") + private BigDecimal financeAmount; + + @Schema(description = "购买年限") + private Integer financeYears; + + @Schema(description = "续费金额") + private BigDecimal financeRenew; + + @Schema(description = "客户名称") + private String financeCustomerName; + + @Schema(description = "客户联系人") + private String financeCustomerContact; + + @Schema(description = "客户联系电话") + private String financeCustomerPhone; + + @Schema(description = "客户ID") + private Integer customerId; + + @Schema(description = "客户名称") + private String customerName; + + @Schema(description = "开放端口") + private String openPort; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "可见性(public,private,protected)") + private String visibility; + + @Schema(description = "宝塔接口秘钥") + private String btSign; + + @Schema(description = "文章排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "客户ID") + private Integer companyId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "机构id") + private Integer organizationId; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSite.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSite.java new file mode 100644 index 0000000..cfaad45 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSite.java @@ -0,0 +1,170 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站信息记录表 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsSite对象", description = "网站信息记录表") +public class OaAssetsSite implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "站点ID") + @TableId(value = "website_id", type = IdType.AUTO) + private Integer websiteId; + + @Schema(description = "网站名称") + private String websiteName; + + @Schema(description = "网站标识") + private String websiteCode; + + @Schema(description = "网站LOGO") + private String websiteIcon; + + @Schema(description = "网站LOGO") + private String websiteLogo; + + @Schema(description = "网站LOGO(深色模式)") + private String websiteDarkLogo; + + @Schema(description = "网站类型") + private String websiteType; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "域名前缀") + private String prefix; + + @Schema(description = "绑定域名") + private String domain; + + @Schema(description = "全局样式") + private String style; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + private Integer version; + + @Schema(description = "服务到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "模版ID") + private Integer templateId; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "电子邮箱") + private String email; + + @Schema(description = "ICP备案号") + private String icpNo; + + @Schema(description = "公安备案") + private String policeNo; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停") + private Integer status; + + @Schema(description = "维护说明") + private String statusText; + + @Schema(description = "关闭说明") + private String statusClose; + + @Schema(description = "全局样式") + private String styles; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "服务器ID") + private Integer assetsId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSoftwareCert.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSoftwareCert.java new file mode 100644 index 0000000..c2c7b2d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSoftwareCert.java @@ -0,0 +1,103 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 计算机软件著作权登记 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsSoftwareCert对象", description = "计算机软件著作权登记") +public class OaAssetsSoftwareCert implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "名称") + private String name; + + @Schema(description = "软件著作权标识") + private String code; + + @Schema(description = "证书类型") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "证书下载地址") + private String certUrl; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSsl.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSsl.java new file mode 100644 index 0000000..7db42bb --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsSsl.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * ssl证书 + * + * @author 科技小王子 + * @since 2024-10-18 19:25:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsSsl对象", description = "ssl证书") +public class OaAssetsSsl implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "ssl_id", type = IdType.AUTO) + private Integer sslId; + + @Schema(description = "证书名称") + private String name; + + @Schema(description = "证书标识") + private String code; + + @Schema(description = "证书类型") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "证书key") + private String certKey; + + @Schema(description = "证书pem") + private String certPem; + + @Schema(description = "证书下载地址") + private String certUrl; + + @Schema(description = "证书crt") + private String certCrt; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "即将过期") + private Integer soon; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsTrademark.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsTrademark.java new file mode 100644 index 0000000..ef1a052 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsTrademark.java @@ -0,0 +1,103 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商标注册 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsTrademark对象", description = "商标注册") +public class OaAssetsTrademark implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "商标名称") + private String name; + + @Schema(description = "商标标识") + private String code; + + @Schema(description = "商标类型") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "证书下载") + private String certUrl; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsUser.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsUser.java new file mode 100644 index 0000000..f940415 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsUser.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 服务器成员管理 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsUser对象", description = "服务器成员管理") +public class OaAssetsUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + private Integer role; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "应用ID") + private Integer assetsId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaAssetsVhost.java b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsVhost.java new file mode 100644 index 0000000..c6cfe72 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaAssetsVhost.java @@ -0,0 +1,112 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 虚拟主机记录表 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaAssetsVhost对象", description = "虚拟主机记录表") +public class OaAssetsVhost implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "vhost_id", type = IdType.AUTO) + private Integer vhostId; + + @Schema(description = "域名") + private String name; + + @Schema(description = "域名标识") + private String code; + + @Schema(description = "主机型号") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "价格") + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "ssl证书") + private String ssl; + + @Schema(description = "购买时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "服务器ID") + private Integer assetsId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaCompany.java b/src/main/java/com/gxwebsoft/oa/entity/OaCompany.java new file mode 100644 index 0000000..699a220 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaCompany.java @@ -0,0 +1,203 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 企业信息 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaCompany对象", description = "企业信息") +public class OaCompany implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "企业id") + @TableId(value = "company_id", type = IdType.AUTO) + private Integer companyId; + + @Schema(description = "企业简称") + private String shortName; + + @Schema(description = "企业全称") + private String companyName; + + @Schema(description = "企业标识") + private String companyCode; + + @Schema(description = "类型 10企业 20政府单位") + private String companyType; + + @Schema(description = "企业类型多选") + private String companyTypeMultiple; + + @Schema(description = "应用标识") + private String companyLogo; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "绑定域名") + private String domain; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "座机电话") + private String tel; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "发票抬头") + @TableField("Invoice_header") + private String invoiceHeader; + + @Schema(description = "企业法人") + private String businessEntity; + + @Schema(description = "服务开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "服务到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "应用版本 10体验版 20授权版 30旗舰版") + private Integer version; + + @Schema(description = "成员数量(人数上限)") + private Integer members; + + @Schema(description = "成员数量(当前)") + private Integer users; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "部门数量") + private Integer departments; + + @Schema(description = "存储空间") + private Long storage; + + @Schema(description = "存储空间(上限)") + private Long storageMax; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否实名认证") + private Integer authentication; + + @Schema(description = "企业默认主体") + private Integer authoritative; + + @Schema(description = "request合法域名") + private String requestUrl; + + @Schema(description = "socket合法域名") + private String socketUrl; + + @Schema(description = "主控端域名") + private String serverUrl; + + @Schema(description = "业务域名") + private String modulesUrl; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "点赞数量") + private Integer likes; + + @Schema(description = "点击数量") + private Integer clicks; + + @Schema(description = "购买数量") + private Integer buys; + + @Schema(description = "是否含税, 0不含, 1含") + private Integer isTax; + + @Schema(description = "当前克隆的租户ID") + private Integer planId; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "知识库ID") + private String kbId; + + @Schema(description = "资料库库IDs") + private String libraryIds; //英文逗号分割 + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaCompanyField.java b/src/main/java/com/gxwebsoft/oa/entity/OaCompanyField.java new file mode 100644 index 0000000..fd28376 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaCompanyField.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 企业参数 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaCompanyField对象", description = "企业参数") +public class OaCompanyField implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "状态, 0正常, 1删除") + private Integer status; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaCompanyUser.java b/src/main/java/com/gxwebsoft/oa/entity/OaCompanyUser.java new file mode 100644 index 0000000..0c78ab0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaCompanyUser.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 成员管理 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaCompanyUser对象", description = "成员管理") +public class OaCompanyUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "company_user_id", type = IdType.AUTO) + private Integer companyUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + private Integer role; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaLink.java b/src/main/java/com/gxwebsoft/oa/entity/OaLink.java new file mode 100644 index 0000000..423d120 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaLink.java @@ -0,0 +1,74 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 常用链接 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaLink对象", description = "常用链接") +public class OaLink implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "链接名称") + private String name; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "链接分类") + private String linkType; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "所属栏目") + private Integer categoryId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaProduct.java b/src/main/java/com/gxwebsoft/oa/entity/OaProduct.java new file mode 100644 index 0000000..2df86dc --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaProduct.java @@ -0,0 +1,106 @@ +package com.gxwebsoft.oa.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 产品记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaProduct对象", description = "产品记录表") +public class OaProduct implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "产品ID") + @TableId(value = "product_id", type = IdType.AUTO) + private Integer productId; + + @Schema(description = "产品名称") + private String name; + + @Schema(description = "产品标识") + private String code; + + @Schema(description = "产品详情") + private String content; + + @Schema(description = "产品类型") + private String type; + + @Schema(description = "产品图标") + private String logo; + + @Schema(description = "产品金额") + private BigDecimal money; + + @Schema(description = "初始销量") + private Integer salesInitial; + + @Schema(description = "实际销量") + private Integer salesActual; + + @Schema(description = "库存总量(包含所有sku)") + private Integer stockTotal; + + @Schema(description = "背景颜色") + private String backgroundColor; + + @Schema(description = "背景图片") + private String backgroundImage; + + @Schema(description = "背景图片(gif)") + private String backgroundGif; + + @Schema(description = "购买链接") + private String buyUrl; + + @Schema(description = "控制台链接") + private String adminUrl; + + @Schema(description = "附件") + private String files; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已上架, 1已下架") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaProductTabs.java b/src/main/java/com/gxwebsoft/oa/entity/OaProductTabs.java new file mode 100644 index 0000000..d2b6c64 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaProductTabs.java @@ -0,0 +1,81 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 产品标签记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaProductTabs对象", description = "产品标签记录表") +public class OaProductTabs implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "产品标签ID") + @TableId(value = "tab_id", type = IdType.AUTO) + private Integer tabId; + + @Schema(description = "产品ID") + private Integer productId; + + @Schema(description = "标签名称") + private String name; + + @Schema(description = "标签类型") + private String type; + + @Schema(description = "产品标签详情") + private String content; + + @Schema(description = "背景颜色") + private String backgroundColor; + + @Schema(description = "背景图片") + private String backgroundImage; + + @Schema(description = "附件") + private String files; + + @Schema(description = "企业ID") + private Integer companyId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已上架, 1已下架") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaTask.java b/src/main/java/com/gxwebsoft/oa/entity/OaTask.java new file mode 100644 index 0000000..10c76d1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaTask.java @@ -0,0 +1,136 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 任务记录表 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaTask对象", description = "任务记录表") +public class OaTask implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "工单ID") + @TableId(value = "task_id", type = IdType.AUTO) + private Integer taskId; + + @Schema(description = "工单类型") + private String taskType; + + @Schema(description = "任务内容") + private String name; + + @Schema(description = "问题描述") + private String content; + + @Schema(description = "工单附件") + private String files; + + @Schema(description = "工单发起人") + private Integer promoter; + + @Schema(description = "受理人") + private Integer commander; + + @Schema(description = "工单状态, 0未开始 1已指派 ") + private Integer progress; + + @Schema(description = "优先级") + private String priority; + + @Schema(description = "品质要求") + private String quality; + + @Schema(description = "时限(天)") + private Integer day; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; + + @Schema(description = "逾期天数") + private Integer overdueDays; + + @Schema(description = "项目ID") + private Integer appId; + + @Schema(description = "机构id") + private Integer organizationId; + + @Schema(description = "项目ID") + private Integer projectId; + + @Schema(description = "客户ID") + private Integer customerId; + + @Schema(description = "资产ID") + private Integer assetsId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否已查阅") + private Integer isRead; + + @Schema(description = "最后回复人") + private Integer lastReadUser; + + @Schema(description = "发起人昵称") + private String nickname; + + @Schema(description = "发起人头像") + private String avatar; + + @Schema(description = "最后回复人头像") + private String lastAvatar; + + @Schema(description = "最后回复人昵称") + private String lastNickname; + + @Schema(description = "订单是否已结算(0未结算 1已结算)") + private Integer isSettled; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0待处理, 1已完成") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaTaskCount.java b/src/main/java/com/gxwebsoft/oa/entity/OaTaskCount.java new file mode 100644 index 0000000..6ff5bd3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaTaskCount.java @@ -0,0 +1,73 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据统计 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaTaskCount对象", description = "数据统计") +public class OaTaskCount implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "task_count_id", type = IdType.AUTO) + private Integer taskCountId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "待处理数") + private Integer pending; + + @Schema(description = "闲置的工单(废弃)") + private Integer unused; + + @Schema(description = "已完成数(废弃)") + private Integer completed; + + @Schema(description = "今天处理数") + private Integer today; + + @Schema(description = "本月处理数") + private Integer month; + + @Schema(description = "今年处理数") + private Integer year; + + @Schema(description = "总工单数") + private Integer total; + + @Schema(description = "部门ID") + private Integer organizationId; + + @Schema(description = "角色ID") + private Integer roleId; + + @Schema(description = "角色标识") + private String roleCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/entity/OaTaskUser.java b/src/main/java/com/gxwebsoft/oa/entity/OaTaskUser.java new file mode 100644 index 0000000..362f388 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/entity/OaTaskUser.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.oa.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工单成员 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "OaTaskUser对象", description = "工单成员") +public class OaTaskUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "task_user_id", type = IdType.AUTO) + private Integer taskUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + private Integer role; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "工单ID") + private Integer taskId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0待处理, 1已完成") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "加入时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAppFieldMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAppFieldMapper.java new file mode 100644 index 0000000..810bc09 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAppFieldMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAppField; +import com.gxwebsoft.oa.param.OaAppFieldParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用参数Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppFieldMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAppFieldParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAppFieldParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAppMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAppMapper.java new file mode 100644 index 0000000..6447c90 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAppMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaApp; +import com.gxwebsoft.oa.param.OaAppParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAppParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAppParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAppRenewMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAppRenewMapper.java new file mode 100644 index 0000000..e04c9dc --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAppRenewMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAppRenew; +import com.gxwebsoft.oa.param.OaAppRenewParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 续费管理Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppRenewMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAppRenewParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAppRenewParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAppUrlMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAppUrlMapper.java new file mode 100644 index 0000000..af8f851 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAppUrlMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAppUrl; +import com.gxwebsoft.oa.param.OaAppUrlParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 项目域名Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppUrlMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAppUrlParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAppUrlParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAppUserMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAppUserMapper.java new file mode 100644 index 0000000..8d416bf --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAppUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAppUser; +import com.gxwebsoft.oa.param.OaAppUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用成员Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAppUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAppUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsCodeMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsCodeMapper.java new file mode 100644 index 0000000..8108c1c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsCodeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsCode; +import com.gxwebsoft.oa.param.OaAssetsCodeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 代码仓库Mapper + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +public interface OaAssetsCodeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsCodeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsCodeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsDomainMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsDomainMapper.java new file mode 100644 index 0000000..6f9c571 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsDomainMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsDomain; +import com.gxwebsoft.oa.param.OaAssetsDomainParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 域名Mapper + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsDomainMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsDomainParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsDomainParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsEmailMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsEmailMapper.java new file mode 100644 index 0000000..6457bb8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsEmailMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsEmail; +import com.gxwebsoft.oa.param.OaAssetsEmailParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 企业邮箱记录表Mapper + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsEmailMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsEmailParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsEmailParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMapper.java new file mode 100644 index 0000000..b1b2879 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssets; +import com.gxwebsoft.oa.param.OaAssetsParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 云服务器Mapper + * + * @author 科技小王子 + * @since 2024-10-18 18:34:15 + */ +public interface OaAssetsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMysqlMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMysqlMapper.java new file mode 100644 index 0000000..168f28b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsMysqlMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsMysql; +import com.gxwebsoft.oa.param.OaAssetsMysqlParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 云数据库Mapper + * + * @author 科技小王子 + * @since 2024-10-18 19:00:20 + */ +public interface OaAssetsMysqlMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsMysqlParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsMysqlParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsServerMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsServerMapper.java new file mode 100644 index 0000000..0c163af --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsServerMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsServer; +import com.gxwebsoft.oa.param.OaAssetsServerParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 服务器资产记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAssetsServerMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsServerParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsServerParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSiteMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSiteMapper.java new file mode 100644 index 0000000..641dad9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSiteMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsSite; +import com.gxwebsoft.oa.param.OaAssetsSiteParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 网站信息记录表Mapper + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsSiteMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsSiteParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsSiteParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSoftwareCertMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSoftwareCertMapper.java new file mode 100644 index 0000000..a861aec --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSoftwareCertMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsSoftwareCert; +import com.gxwebsoft.oa.param.OaAssetsSoftwareCertParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 计算机软件著作权登记Mapper + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +public interface OaAssetsSoftwareCertMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsSoftwareCertParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsSoftwareCertParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSslMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSslMapper.java new file mode 100644 index 0000000..7912f86 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsSslMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsSsl; +import com.gxwebsoft.oa.param.OaAssetsSslParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * ssl证书Mapper + * + * @author 科技小王子 + * @since 2024-10-18 19:25:40 + */ +public interface OaAssetsSslMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsSslParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsSslParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsTrademarkMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsTrademarkMapper.java new file mode 100644 index 0000000..371ab97 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsTrademarkMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsTrademark; +import com.gxwebsoft.oa.param.OaAssetsTrademarkParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商标注册Mapper + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +public interface OaAssetsTrademarkMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsTrademarkParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsTrademarkParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsUserMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsUserMapper.java new file mode 100644 index 0000000..56cd088 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsUser; +import com.gxwebsoft.oa.param.OaAssetsUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 服务器成员管理Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAssetsUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsVhostMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsVhostMapper.java new file mode 100644 index 0000000..c45a874 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaAssetsVhostMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaAssetsVhost; +import com.gxwebsoft.oa.param.OaAssetsVhostParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 虚拟主机记录表Mapper + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsVhostMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaAssetsVhostParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaAssetsVhostParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyFieldMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyFieldMapper.java new file mode 100644 index 0000000..6471da9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyFieldMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaCompanyField; +import com.gxwebsoft.oa.param.OaCompanyFieldParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 企业参数Mapper + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +public interface OaCompanyFieldMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaCompanyFieldParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaCompanyFieldParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyMapper.java new file mode 100644 index 0000000..59d6d85 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaCompany; +import com.gxwebsoft.oa.param.OaCompanyParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 企业信息Mapper + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +public interface OaCompanyMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaCompanyParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaCompanyParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyUserMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyUserMapper.java new file mode 100644 index 0000000..65e6a8e --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaCompanyUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaCompanyUser; +import com.gxwebsoft.oa.param.OaCompanyUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 成员管理Mapper + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +public interface OaCompanyUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaCompanyUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaCompanyUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaLinkMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaLinkMapper.java new file mode 100644 index 0000000..57497a8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaLinkMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaLink; +import com.gxwebsoft.oa.param.OaLinkParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 常用链接Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaLinkMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaLinkParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaLinkParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaProductMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaProductMapper.java new file mode 100644 index 0000000..9d1d9af --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaProductMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaProduct; +import com.gxwebsoft.oa.param.OaProductParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 产品记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaProductMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaProductParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaProductParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaProductTabsMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaProductTabsMapper.java new file mode 100644 index 0000000..39e03c7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaProductTabsMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaProductTabs; +import com.gxwebsoft.oa.param.OaProductTabsParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 产品标签记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaProductTabsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaProductTabsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaProductTabsParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaTaskCountMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaTaskCountMapper.java new file mode 100644 index 0000000..0e139df --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaTaskCountMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaTaskCount; +import com.gxwebsoft.oa.param.OaTaskCountParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 数据统计Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaTaskCountMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaTaskCountParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaTaskCountParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaTaskMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaTaskMapper.java new file mode 100644 index 0000000..963f5f8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaTaskMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaTask; +import com.gxwebsoft.oa.param.OaTaskParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 任务记录表Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaTaskMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaTaskParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaTaskParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/OaTaskUserMapper.java b/src/main/java/com/gxwebsoft/oa/mapper/OaTaskUserMapper.java new file mode 100644 index 0000000..d894bb6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/OaTaskUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.oa.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.oa.entity.OaTaskUser; +import com.gxwebsoft.oa.param.OaTaskUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 工单成员Mapper + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaTaskUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") OaTaskUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") OaTaskUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppFieldMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppFieldMapper.xml new file mode 100644 index 0000000..c7ded82 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppFieldMapper.xml @@ -0,0 +1,50 @@ + + + + + + + SELECT a.* + FROM oa_app_field a + + + AND a.id = #{param.id} + + + AND a.app_id = #{param.appId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppMapper.xml new file mode 100644 index 0000000..af8176b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppMapper.xml @@ -0,0 +1,229 @@ + + + + + + + SELECT a.*, b.company_name,b.short_name,b.company_logo + FROM oa_app a + LEFT JOIN oa_company b ON a.company_id = b.company_id + + + AND a.app_id = #{param.appId} + + + AND a.app_name LIKE CONCAT('%', #{param.appName}, '%') + + + AND a.app_code LIKE CONCAT('%', #{param.appCode}, '%') + + + AND a.show_case = #{param.showCase} + + + AND a.recommend = #{param.recommend} + + + AND a.show_index = #{param.showIndex} + + + AND a.parent_id = #{param.parentId} + + + AND a.app_type LIKE CONCAT('%', #{param.appType}, '%') + + + AND a.app_type_multiple LIKE CONCAT('%', #{param.appTypeMultiple}, '%') + + + AND a.menu_type = #{param.menuType} + + + AND a.company_id = #{param.companyId} + + + AND a.company_name LIKE CONCAT('%', #{param.companyName}, '%') + + + AND a.app_icon LIKE CONCAT('%', #{param.appIcon}, '%') + + + AND a.app_qrcode LIKE CONCAT('%', #{param.appQrcode}, '%') + + + AND a.app_url LIKE CONCAT('%', #{param.appUrl}, '%') + + + AND a.admin_url LIKE CONCAT('%', #{param.adminUrl}, '%') + + + AND a.down_url LIKE CONCAT('%', #{param.downUrl}, '%') + + + AND a.server_url LIKE CONCAT('%', #{param.serverUrl}, '%') + + + AND a.file_url LIKE CONCAT('%', #{param.fileUrl}, '%') + + + AND a.callback_url LIKE CONCAT('%', #{param.callbackUrl}, '%') + + + AND a.docs_url LIKE CONCAT('%', #{param.docsUrl}, '%') + + + AND a.git_url LIKE CONCAT('%', #{param.gitUrl}, '%') + + + AND a.prototype_url LIKE CONCAT('%', #{param.prototypeUrl}, '%') + + + AND a.ip_address LIKE CONCAT('%', #{param.ipAddress}, '%') + + + AND a.images LIKE CONCAT('%', #{param.images}, '%') + + + AND a.package_name LIKE CONCAT('%', #{param.packageName}, '%') + + + AND a.clicks = #{param.clicks} + + + AND a.installs = #{param.installs} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.requirement LIKE CONCAT('%', #{param.requirement}, '%') + + + AND a.developer LIKE CONCAT('%', #{param.developer}, '%') + + + AND a.director LIKE CONCAT('%', #{param.director}, '%') + + + AND a.project_director LIKE CONCAT('%', #{param.projectDirector}, '%') + + + AND a.salesman LIKE CONCAT('%', #{param.salesman}, '%') + + + AND a.price = #{param.price} + + + AND a.line_price = #{param.linePrice} + + + AND a.score LIKE CONCAT('%', #{param.score}, '%') + + + AND a.star LIKE CONCAT('%', #{param.star}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.authority LIKE CONCAT('%', #{param.authority}, '%') + + + AND a.target LIKE CONCAT('%', #{param.target}, '%') + + + AND a.hide = #{param.hide} + + + AND a.search = #{param.search} + + + AND a.active LIKE CONCAT('%', #{param.active}, '%') + + + AND a.meta LIKE CONCAT('%', #{param.meta}, '%') + + + AND a.edition LIKE CONCAT('%', #{param.edition}, '%') + + + AND a.version LIKE CONCAT('%', #{param.version}, '%') + + + AND a.is_use = #{param.isUse} + + + AND a.file1 LIKE CONCAT('%', #{param.file1}, '%') + + + AND a.file2 LIKE CONCAT('%', #{param.file2}, '%') + + + AND a.file3 LIKE CONCAT('%', #{param.file3}, '%') + + + AND a.app_status LIKE CONCAT('%', #{param.appStatus}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.app_id IN + + #{item} + + + + AND a.organization_id = #{param.organizationId} + + + AND a.show_expiration = #{param.showExpiration} + + + AND a.tenant_code LIKE CONCAT('%', #{param.tenantCode}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.app_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.app_code LIKE CONCAT('%', #{param.keywords}, '%') + OR a.company_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.app_url LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUrlMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUrlMapper.xml new file mode 100644 index 0000000..24865f4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUrlMapper.xml @@ -0,0 +1,54 @@ + + + + + + + SELECT a.* + FROM oa_app_url a + + + AND a.app_url_id = #{param.appUrlId} + + + AND a.app_id = #{param.appId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUserMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUserMapper.xml new file mode 100644 index 0000000..83cf18a --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAppUserMapper.xml @@ -0,0 +1,51 @@ + + + + + + + SELECT a.* + FROM oa_app_user a + + + AND a.app_user_id = #{param.appUserId} + + + AND a.role = #{param.role} + + + AND a.user_id = #{param.userId} + + + AND a.app_id = #{param.appId} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (b.nickname LIKE CONCAT('%', #{param.keywords}, '%') + OR b.email LIKE CONCAT('%', #{param.keywords}, '%') + OR b.username LIKE CONCAT('%', #{param.keywords}, '%') + OR b.phone LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsCodeMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsCodeMapper.xml new file mode 100644 index 0000000..482f9cf --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsCodeMapper.xml @@ -0,0 +1,75 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_code a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.id = #{param.id} + + + AND a.assets_id = #{param.assetsId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.git_url LIKE CONCAT('%', #{param.gitUrl}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsDomainMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsDomainMapper.xml new file mode 100644 index 0000000..af8d0c4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsDomainMapper.xml @@ -0,0 +1,84 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_domain a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.domain_id = #{param.domainId} + + + AND a.assets_id = #{param.assetsId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.price = #{param.price} + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsEmailMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsEmailMapper.xml new file mode 100644 index 0000000..da6012b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsEmailMapper.xml @@ -0,0 +1,84 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_email a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.email_id = #{param.emailId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.system LIKE CONCAT('%', #{param.system}, '%') + + + AND a.price = #{param.price} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMapper.xml new file mode 100644 index 0000000..2ae8353 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMapper.xml @@ -0,0 +1,146 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.assets_id = #{param.assetsId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.configuration LIKE CONCAT('%', #{param.configuration}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.brand_account LIKE CONCAT('%', #{param.brandAccount}, '%') + + + AND a.brand_password LIKE CONCAT('%', #{param.brandPassword}, '%') + + + AND a.panel LIKE CONCAT('%', #{param.panel}, '%') + + + AND a.panel_account LIKE CONCAT('%', #{param.panelAccount}, '%') + + + AND a.panel_password LIKE CONCAT('%', #{param.panelPassword}, '%') + + + AND a.finance_amount = #{param.financeAmount} + + + AND a.finance_years = #{param.financeYears} + + + AND a.finance_renew = #{param.financeRenew} + + + AND a.finance_customer_name LIKE CONCAT('%', #{param.financeCustomerName}, '%') + + + AND a.finance_customer_contact LIKE CONCAT('%', #{param.financeCustomerContact}, '%') + + + AND a.finance_customer_phone LIKE CONCAT('%', #{param.financeCustomerPhone}, '%') + + + AND a.customer_id = #{param.customerId} + + + AND a.customer_name LIKE CONCAT('%', #{param.customerName}, '%') + + + AND a.open_port LIKE CONCAT('%', #{param.openPort}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.visibility LIKE CONCAT('%', #{param.visibility}, '%') + + + AND a.bt_sign LIKE CONCAT('%', #{param.btSign}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.organization_id = #{param.organizationId} + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.tenant_id = #{param.keywords} + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMysqlMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMysqlMapper.xml new file mode 100644 index 0000000..98fbbe4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsMysqlMapper.xml @@ -0,0 +1,90 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_mysql a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.mysql_id = #{param.mysqlId} + + + AND a.assets_id = #{param.assetsId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.ip LIKE CONCAT('%', #{param.ip}, '%') + + + AND a.port LIKE CONCAT('%', #{param.port}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.price = #{param.price} + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsServerMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsServerMapper.xml new file mode 100644 index 0000000..edd376e --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsServerMapper.xml @@ -0,0 +1,137 @@ + + + + + + + SELECT a.* + FROM oa_assets_server a + + + AND a.server_id = #{param.serverId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.configuration LIKE CONCAT('%', #{param.configuration}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.brand_account LIKE CONCAT('%', #{param.brandAccount}, '%') + + + AND a.brand_password LIKE CONCAT('%', #{param.brandPassword}, '%') + + + AND a.panel LIKE CONCAT('%', #{param.panel}, '%') + + + AND a.panel_account LIKE CONCAT('%', #{param.panelAccount}, '%') + + + AND a.panel_password LIKE CONCAT('%', #{param.panelPassword}, '%') + + + AND a.finance_amount = #{param.financeAmount} + + + AND a.finance_years = #{param.financeYears} + + + AND a.finance_renew = #{param.financeRenew} + + + AND a.finance_customer_name LIKE CONCAT('%', #{param.financeCustomerName}, '%') + + + AND a.finance_customer_contact LIKE CONCAT('%', #{param.financeCustomerContact}, '%') + + + AND a.finance_customer_phone LIKE CONCAT('%', #{param.financeCustomerPhone}, '%') + + + AND a.customer_id = #{param.customerId} + + + AND a.customer_name LIKE CONCAT('%', #{param.customerName}, '%') + + + AND a.open_port LIKE CONCAT('%', #{param.openPort}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.visibility LIKE CONCAT('%', #{param.visibility}, '%') + + + AND a.bt_sign LIKE CONCAT('%', #{param.btSign}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.user_id = #{param.userId} + + + AND a.organization_id = #{param.organizationId} + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSiteMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSiteMapper.xml new file mode 100644 index 0000000..4ac42c5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSiteMapper.xml @@ -0,0 +1,153 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_site a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.website_id = #{param.websiteId} + + + AND a.website_name LIKE CONCAT('%', #{param.websiteName}, '%') + + + AND a.website_code LIKE CONCAT('%', #{param.websiteCode}, '%') + + + AND a.website_icon LIKE CONCAT('%', #{param.websiteIcon}, '%') + + + AND a.website_logo LIKE CONCAT('%', #{param.websiteLogo}, '%') + + + AND a.website_dark_logo LIKE CONCAT('%', #{param.websiteDarkLogo}, '%') + + + AND a.website_type LIKE CONCAT('%', #{param.websiteType}, '%') + + + AND a.keywords LIKE CONCAT('%', #{param.keywords}, '%') + + + AND a.prefix LIKE CONCAT('%', #{param.prefix}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.style LIKE CONCAT('%', #{param.style}, '%') + + + AND a.admin_url LIKE CONCAT('%', #{param.adminUrl}, '%') + + + AND a.version = #{param.version} + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.template_id = #{param.templateId} + + + AND a.industry_parent LIKE CONCAT('%', #{param.industryParent}, '%') + + + AND a.industry_child LIKE CONCAT('%', #{param.industryChild}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.email LIKE CONCAT('%', #{param.email}, '%') + + + AND a.icp_no LIKE CONCAT('%', #{param.icpNo}, '%') + + + AND a.police_no LIKE CONCAT('%', #{param.policeNo}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.status = #{param.status} + + + AND a.status_text LIKE CONCAT('%', #{param.statusText}, '%') + + + AND a.status_close LIKE CONCAT('%', #{param.statusClose}, '%') + + + AND a.styles LIKE CONCAT('%', #{param.styles}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.assets_id = #{param.assetsId} + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSoftwareCertMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSoftwareCertMapper.xml new file mode 100644 index 0000000..2ac29a1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSoftwareCertMapper.xml @@ -0,0 +1,84 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_software_cert a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.price = #{param.price} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.cert_url LIKE CONCAT('%', #{param.certUrl}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSslMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSslMapper.xml new file mode 100644 index 0000000..e6563a7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsSslMapper.xml @@ -0,0 +1,98 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_ssl a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.ssl_id = #{param.sslId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.price = #{param.price} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.cert_key LIKE CONCAT('%', #{param.certKey}, '%') + + + AND a.cert_pem LIKE CONCAT('%', #{param.certPem}, '%') + + + AND a.cert_url LIKE CONCAT('%', #{param.certUrl}, '%') + + + AND a.cert_crt LIKE CONCAT('%', #{param.certCrt}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.tenant_id = #{param.keywords} + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsTrademarkMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsTrademarkMapper.xml new file mode 100644 index 0000000..5028926 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsTrademarkMapper.xml @@ -0,0 +1,84 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_trademark a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.price = #{param.price} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.cert_url LIKE CONCAT('%', #{param.certUrl}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsUserMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsUserMapper.xml new file mode 100644 index 0000000..0306c98 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsUserMapper.xml @@ -0,0 +1,44 @@ + + + + + + + SELECT a.* + FROM oa_assets_user a + + + AND a.id = #{param.id} + + + AND a.role = #{param.role} + + + AND a.user_id = #{param.userId} + + + AND a.assets_id = #{param.assetsId} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsVhostMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsVhostMapper.xml new file mode 100644 index 0000000..682f29d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaAssetsVhostMapper.xml @@ -0,0 +1,93 @@ + + + + + + + SELECT a.*, b.short_name AS tenantName,b.company_logo as logo + FROM oa_assets_vhost a + LEFT JOIN gxwebsoft_core.sys_company b ON a.tenant_id = b.tenant_id + + + AND a.vhost_id = #{param.vhostId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.brand LIKE CONCAT('%', #{param.brand}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.price = #{param.price} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.ssl LIKE CONCAT('%', #{param.ssl}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.is_top LIKE CONCAT('%', #{param.isTop}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.assets_id = #{param.assetsId} + + + AND a.user_id = #{param.userId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.status LIKE CONCAT('%', #{param.status}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyFieldMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyFieldMapper.xml new file mode 100644 index 0000000..4cdb978 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyFieldMapper.xml @@ -0,0 +1,50 @@ + + + + + + + SELECT a.* + FROM oa_company_field a + + + AND a.id = #{param.id} + + + AND a.company_id = #{param.companyId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyMapper.xml new file mode 100644 index 0000000..1ed818b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyMapper.xml @@ -0,0 +1,179 @@ + + + + + + + SELECT a.* + FROM oa_company a + + + AND a.company_id = #{param.companyId} + + + AND a.short_name LIKE CONCAT('%', #{param.shortName}, '%') + + + AND a.company_name LIKE CONCAT('%', #{param.companyName}, '%') + + + AND a.company_code LIKE CONCAT('%', #{param.companyCode}, '%') + + + AND a.company_type LIKE CONCAT('%', #{param.companyType}, '%') + + + AND a.company_type_multiple LIKE CONCAT('%', #{param.companyTypeMultiple}, '%') + + + AND a.company_logo LIKE CONCAT('%', #{param.companyLogo}, '%') + + + AND a.app_type LIKE CONCAT('%', #{param.appType}, '%') + + + AND a.kb_id LIKE CONCAT('%', #{param.kbId}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.tel LIKE CONCAT('%', #{param.tel}, '%') + + + AND a.email LIKE CONCAT('%', #{param.email}, '%') + + + AND a.Invoice_header LIKE CONCAT('%', #{param.invoiceHeader}, '%') + + + AND a.business_entity LIKE CONCAT('%', #{param.businessEntity}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.version = #{param.version} + + + AND a.members = #{param.members} + + + AND a.users = #{param.users} + + + AND a.industry_parent LIKE CONCAT('%', #{param.industryParent}, '%') + + + AND a.industry_child LIKE CONCAT('%', #{param.industryChild}, '%') + + + AND a.departments = #{param.departments} + + + AND a.storage LIKE CONCAT('%', #{param.storage}, '%') + + + AND a.storage_max LIKE CONCAT('%', #{param.storageMax}, '%') + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.authentication = #{param.authentication} + + + AND a.authoritative = #{param.authoritative} + + + AND a.request_url LIKE CONCAT('%', #{param.requestUrl}, '%') + + + AND a.socket_url LIKE CONCAT('%', #{param.socketUrl}, '%') + + + AND a.server_url LIKE CONCAT('%', #{param.serverUrl}, '%') + + + AND a.modules_url LIKE CONCAT('%', #{param.modulesUrl}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.likes = #{param.likes} + + + AND a.clicks = #{param.clicks} + + + AND a.buys = #{param.buys} + + + AND a.is_tax = #{param.isTax} + + + AND a.plan_id = #{param.planId} + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyUserMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyUserMapper.xml new file mode 100644 index 0000000..e4aeb35 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaCompanyUserMapper.xml @@ -0,0 +1,47 @@ + + + + + + + SELECT a.* + FROM oa_company_user a + + + AND a.company_user_id = #{param.companyUserId} + + + AND a.role = #{param.role} + + + AND a.user_id = #{param.userId} + + + AND a.company_id = #{param.companyId} + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaLinkMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaLinkMapper.xml new file mode 100644 index 0000000..55a4a1e --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaLinkMapper.xml @@ -0,0 +1,71 @@ + + + + + + + SELECT a.* + FROM oa_link a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.icon LIKE CONCAT('%', #{param.icon}, '%') + + + AND a.url LIKE CONCAT('%', #{param.url}, '%') + + + AND a.link_type LIKE CONCAT('%', #{param.linkType}, '%') + + + AND a.app_id = #{param.appId} + + + AND a.category_id = #{param.categoryId} + + + AND a.user_id = #{param.userId} + + + AND a.recommend = #{param.recommend} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductMapper.xml new file mode 100644 index 0000000..0bca9c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductMapper.xml @@ -0,0 +1,98 @@ + + + + + + + SELECT a.* + FROM oa_product a + + + AND a.product_id = #{param.productId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.logo LIKE CONCAT('%', #{param.logo}, '%') + + + AND a.money = #{param.money} + + + AND a.sales_initial = #{param.salesInitial} + + + AND a.sales_actual = #{param.salesActual} + + + AND a.stock_total = #{param.stockTotal} + + + AND a.background_color LIKE CONCAT('%', #{param.backgroundColor}, '%') + + + AND a.background_image LIKE CONCAT('%', #{param.backgroundImage}, '%') + + + AND a.background_gif LIKE CONCAT('%', #{param.backgroundGif}, '%') + + + AND a.buy_url LIKE CONCAT('%', #{param.buyUrl}, '%') + + + AND a.admin_url LIKE CONCAT('%', #{param.adminUrl}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductTabsMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductTabsMapper.xml new file mode 100644 index 0000000..075457c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaProductTabsMapper.xml @@ -0,0 +1,74 @@ + + + + + + + SELECT a.* + FROM oa_product_tabs a + + + AND a.tab_id = #{param.tabId} + + + AND a.product_id = #{param.productId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.background_color LIKE CONCAT('%', #{param.backgroundColor}, '%') + + + AND a.background_image LIKE CONCAT('%', #{param.backgroundImage}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.company_id = #{param.companyId} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskCountMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskCountMapper.xml new file mode 100644 index 0000000..5e5cf81 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskCountMapper.xml @@ -0,0 +1,65 @@ + + + + + + + SELECT a.* + FROM oa_task_count a + + + AND a.task_count_id = #{param.taskCountId} + + + AND a.user_id = #{param.userId} + + + AND a.pending = #{param.pending} + + + AND a.unused = #{param.unused} + + + AND a.completed = #{param.completed} + + + AND a.today = #{param.today} + + + AND a.month = #{param.month} + + + AND a.year = #{param.year} + + + AND a.total = #{param.total} + + + AND a.organization_id = #{param.organizationId} + + + AND a.role_id = #{param.roleId} + + + AND a.role_code LIKE CONCAT('%', #{param.roleCode}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskMapper.xml new file mode 100644 index 0000000..e068988 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskMapper.xml @@ -0,0 +1,128 @@ + + + + + + + SELECT a.* + FROM oa_task a + + + AND a.task_id = #{param.taskId} + + + AND a.task_type LIKE CONCAT('%', #{param.taskType}, '%') + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.promoter = #{param.promoter} + + + AND a.commander = #{param.commander} + + + AND a.progress = #{param.progress} + + + AND a.priority LIKE CONCAT('%', #{param.priority}, '%') + + + AND a.quality LIKE CONCAT('%', #{param.quality}, '%') + + + AND a.day = #{param.day} + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.overdue_days = #{param.overdueDays} + + + AND a.app_id = #{param.appId} + + + AND a.organization_id = #{param.organizationId} + + + AND a.project_id = #{param.projectId} + + + AND a.customer_id = #{param.customerId} + + + AND a.assets_id = #{param.assetsId} + + + AND a.user_id = #{param.userId} + + + AND a.is_read = #{param.isRead} + + + AND a.last_read_user = #{param.lastReadUser} + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.avatar LIKE CONCAT('%', #{param.avatar}, '%') + + + AND a.last_avatar LIKE CONCAT('%', #{param.lastAvatar}, '%') + + + AND a.last_nickname LIKE CONCAT('%', #{param.lastNickname}, '%') + + + AND a.is_settled = #{param.isSettled} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskUserMapper.xml b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskUserMapper.xml new file mode 100644 index 0000000..c620f5c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/mapper/xml/OaTaskUserMapper.xml @@ -0,0 +1,47 @@ + + + + + + + SELECT a.* + FROM oa_task_user a + + + AND a.task_user_id = #{param.taskUserId} + + + AND a.role = #{param.role} + + + AND a.user_id = #{param.userId} + + + AND a.task_id = #{param.taskId} + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAppFieldParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAppFieldParam.java new file mode 100644 index 0000000..17eb253 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAppFieldParam.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAppFieldParam对象", description = "应用参数查询参数") +public class OaAppFieldParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "状态, 0正常, 1删除") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAppParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAppParam.java new file mode 100644 index 0000000..2a09e83 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAppParam.java @@ -0,0 +1,246 @@ +package com.gxwebsoft.oa.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Set; + +/** + * 应用查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAppParam对象", description = "应用查询参数") +public class OaAppParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "应用标识") + private String appCode; + + @Schema(description = "应用秘钥") + private String appSecret; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "应用类型") + private String appTypeMultiple; + + @Schema(description = "类型, 0菜单, 1按钮") + @QueryField(type = QueryType.EQ) + private Integer menuType; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "企业名称") + private String companyName; + + @Schema(description = "应用图标") + private String appIcon; + + @Schema(description = "二维码") + private String appQrcode; + + @Schema(description = "链接地址") + private String appUrl; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "下载地址") + private String downUrl; + + @Schema(description = "链接地址") + private String serverUrl; + + @Schema(description = "文件服务器") + private String fileUrl; + + @Schema(description = "回调地址") + private String callbackUrl; + + @Schema(description = "腾讯文档地址") + private String docsUrl; + + @Schema(description = "代码仓库地址") + private String gitUrl; + + @Schema(description = "原型图地址") + private String prototypeUrl; + + @Schema(description = "IP白名单") + private String ipAddress; + + @Schema(description = "应用截图") + private String images; + + @Schema(description = "应用包名") + private String packageName; + + @Schema(description = "下载次数") + @QueryField(type = QueryType.EQ) + private Integer clicks; + + @Schema(description = "安装次数") + @QueryField(type = QueryType.EQ) + private Integer installs; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "应用介绍") + private String content; + + @Schema(description = "项目需求") + private String requirement; + + @Schema(description = "开发者(个人或公司)") + private String developer; + + @Schema(description = "项目负责人") + private String director; + + @Schema(description = "项目经理") + private String projectDirector; + + @Schema(description = "业务员") + private String salesman; + + @Schema(description = "软件定价") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "划线价格") + @QueryField(type = QueryType.EQ) + private BigDecimal linePrice; + + @Schema(description = "评分") + private String score; + + @Schema(description = "星级") + private String star; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "禁止搜索,1禁止 0 允许") + @QueryField(type = QueryType.EQ) + private Integer search; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "版本,0正式版 1体验版 2开发版") + private String edition; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "是否已安装") + @QueryField(type = QueryType.EQ) + private Integer isUse; + + @Schema(description = "附近1") + private String file1; + + @Schema(description = "附件2") + private String file2; + + @Schema(description = "附件3") + private String file3; + + @Schema(description = "是否显示续费提醒") + @QueryField(type = QueryType.EQ) + private Boolean showExpiration; + + @Schema(description = "是否作为案例展示") + @QueryField(type = QueryType.EQ) + private Boolean showCase; + + @Schema(description = "是否显示在首页") + @QueryField(type = QueryType.EQ) + private Integer showIndex; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "到期时间") + private String expirationTime; + + @Schema(description = "续费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal renewMoney; + + @Schema(description = "应用状态") + private String appStatus; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "租户编号") + private String tenantCode; + + @Schema(description = "按APPID集搜索") + @TableField(exist = false) + private Set appIds; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAppRenewParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAppRenewParam.java new file mode 100644 index 0000000..7a2d2b0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAppRenewParam.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 续费管理查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAppRenewParam对象", description = "续费管理查询参数") +public class OaAppRenewParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer appRenewId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "续费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "开始时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "付款凭证") + private String images; + + @Schema(description = "用户姓名") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAppUrlParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAppUrlParam.java new file mode 100644 index 0000000..1fd4639 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAppUrlParam.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 项目域名查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAppUrlParam对象", description = "项目域名查询参数") +public class OaAppUrlParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer appUrlId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "域名类型") + private String name; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAppUserParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAppUserParam.java new file mode 100644 index 0000000..c4ee26d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAppUserParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用成员查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAppUserParam对象", description = "应用成员查询参数") +public class OaAppUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer appUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + @QueryField(type = QueryType.EQ) + private Integer role; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsCodeParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsCodeParam.java new file mode 100644 index 0000000..2723b78 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsCodeParam.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 代码仓库查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsCodeParam对象", description = "代码仓库查询参数") +public class OaAssetsCodeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "服务器ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "英文标识") + private String code; + + @Schema(description = "仓库地址") + private String gitUrl; + + @Schema(description = "仓库品牌") + private String brand; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsDomainParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsDomainParam.java new file mode 100644 index 0000000..a33902c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsDomainParam.java @@ -0,0 +1,84 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 域名查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsDomainParam对象", description = "域名查询参数") +public class OaAssetsDomainParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer domainId; + + @Schema(description = "服务器ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "域名") + private String name; + + @Schema(description = "域名标识") + private String code; + + @Schema(description = "注册厂商") + private String brand; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsEmailParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsEmailParam.java new file mode 100644 index 0000000..2477a23 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsEmailParam.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 企业邮箱记录表查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsEmailParam对象", description = "企业邮箱记录表查询参数") +public class OaAssetsEmailParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer emailId; + + @Schema(description = "邮箱名称") + private String name; + + @Schema(description = "域名标识") + private String code; + + @Schema(description = "邮箱型号") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "初始账号") + private String system; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsMysqlParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsMysqlParam.java new file mode 100644 index 0000000..ccbc789 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsMysqlParam.java @@ -0,0 +1,90 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 云数据库查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 19:00:20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsMysqlParam对象", description = "云数据库查询参数") +public class OaAssetsMysqlParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer mysqlId; + + @Schema(description = "服务器ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "数据库名") + private String name; + + @Schema(description = "数据库标识") + private String code; + + @Schema(description = "注册厂商") + private String brand; + + @Schema(description = "ip地址") + private String ip; + + @Schema(description = "端口") + private String port; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsParam.java new file mode 100644 index 0000000..66456d1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsParam.java @@ -0,0 +1,145 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 云服务器查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 18:34:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsParam对象", description = "云服务器查询参数") +public class OaAssetsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "资产ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "资产名称") + private String name; + + @Schema(description = "资产标识") + private String code; + + @Schema(description = "资产类型") + private String type; + + @Schema(description = "服务器厂商") + private String brand; + + @Schema(description = "服务器配置") + private String configuration; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "(阿里云/腾讯云)登录账号") + private String brandAccount; + + @Schema(description = "(阿里云/腾讯云)登录密码") + private String brandPassword; + + @Schema(description = "宝塔面板") + private String panel; + + @Schema(description = "宝塔面板账号") + private String panelAccount; + + @Schema(description = "宝塔面板密码") + private String panelPassword; + + @Schema(description = "财务信息-合同金额") + @QueryField(type = QueryType.EQ) + private BigDecimal financeAmount; + + @Schema(description = "购买年限") + @QueryField(type = QueryType.EQ) + private Integer financeYears; + + @Schema(description = "续费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal financeRenew; + + @Schema(description = "客户名称") + private String financeCustomerName; + + @Schema(description = "客户联系人") + private String financeCustomerContact; + + @Schema(description = "客户联系电话") + private String financeCustomerPhone; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer customerId; + + @Schema(description = "客户名称") + private String customerName; + + @Schema(description = "开放端口") + private String openPort; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "可见性(public,private,protected)") + private String visibility; + + @Schema(description = "宝塔接口秘钥") + private String btSign; + + @Schema(description = "文章排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsServerParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsServerParam.java new file mode 100644 index 0000000..9633536 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsServerParam.java @@ -0,0 +1,142 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 服务器资产记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsServerParam对象", description = "服务器资产记录表查询参数") +public class OaAssetsServerParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "资产ID") + @QueryField(type = QueryType.EQ) + private Integer serverId; + + @Schema(description = "资产名称") + private String name; + + @Schema(description = "资产标识") + private String code; + + @Schema(description = "资产类型") + private String type; + + @Schema(description = "服务器厂商") + private String brand; + + @Schema(description = "服务器配置") + private String configuration; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "(阿里云/腾讯云)登录账号") + private String brandAccount; + + @Schema(description = "(阿里云/腾讯云)登录密码") + private String brandPassword; + + @Schema(description = "宝塔面板") + private String panel; + + @Schema(description = "宝塔面板账号") + private String panelAccount; + + @Schema(description = "宝塔面板密码") + private String panelPassword; + + @Schema(description = "财务信息-合同金额") + @QueryField(type = QueryType.EQ) + private BigDecimal financeAmount; + + @Schema(description = "购买年限") + @QueryField(type = QueryType.EQ) + private Integer financeYears; + + @Schema(description = "续费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal financeRenew; + + @Schema(description = "客户名称") + private String financeCustomerName; + + @Schema(description = "客户联系人") + private String financeCustomerContact; + + @Schema(description = "客户联系电话") + private String financeCustomerPhone; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer customerId; + + @Schema(description = "客户名称") + private String customerName; + + @Schema(description = "开放端口") + private String openPort; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "可见性(public,private,protected)") + private String visibility; + + @Schema(description = "宝塔接口秘钥") + private String btSign; + + @Schema(description = "文章排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsSiteParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsSiteParam.java new file mode 100644 index 0000000..2b28bea --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsSiteParam.java @@ -0,0 +1,155 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网站信息记录表查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsSiteParam对象", description = "网站信息记录表查询参数") +public class OaAssetsSiteParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "站点ID") + @QueryField(type = QueryType.EQ) + private Integer websiteId; + + @Schema(description = "网站名称") + private String websiteName; + + @Schema(description = "网站标识") + private String websiteCode; + + @Schema(description = "网站LOGO") + private String websiteIcon; + + @Schema(description = "网站LOGO") + private String websiteLogo; + + @Schema(description = "网站LOGO(深色模式)") + private String websiteDarkLogo; + + @Schema(description = "网站类型") + private String websiteType; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "域名前缀") + private String prefix; + + @Schema(description = "绑定域名") + private String domain; + + @Schema(description = "全局样式") + private String style; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "服务到期时间") + private String expirationTime; + + @Schema(description = "模版ID") + @QueryField(type = QueryType.EQ) + private Integer templateId; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "电子邮箱") + private String email; + + @Schema(description = "ICP备案号") + private String icpNo; + + @Schema(description = "公安备案") + private String policeNo; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "维护说明") + private String statusText; + + @Schema(description = "关闭说明") + private String statusClose; + + @Schema(description = "全局样式") + private String styles; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "服务器ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsSoftwareCertParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsSoftwareCertParam.java new file mode 100644 index 0000000..22c7937 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsSoftwareCertParam.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 计算机软件著作权登记查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsSoftwareCertParam对象", description = "计算机软件著作权登记查询参数") +public class OaAssetsSoftwareCertParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "名称") + private String name; + + @Schema(description = "软件著作权标识") + private String code; + + @Schema(description = "证书类型") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "证书下载地址") + private String certUrl; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsSslParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsSslParam.java new file mode 100644 index 0000000..a11a568 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsSslParam.java @@ -0,0 +1,92 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * ssl证书查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 19:25:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsSslParam对象", description = "ssl证书查询参数") +public class OaAssetsSslParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer sslId; + + @Schema(description = "证书名称") + private String name; + + @Schema(description = "证书标识") + private String code; + + @Schema(description = "证书类型") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "证书key") + private String certKey; + + @Schema(description = "证书pem") + private String certPem; + + @Schema(description = "证书下载地址") + private String certUrl; + + @Schema(description = "证书crt") + private String certCrt; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsTrademarkParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsTrademarkParam.java new file mode 100644 index 0000000..fa3b93a --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsTrademarkParam.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 商标注册查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsTrademarkParam对象", description = "商标注册查询参数") +public class OaAssetsTrademarkParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "商标名称") + private String name; + + @Schema(description = "商标标识") + private String code; + + @Schema(description = "商标类型") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "证书下载") + private String certUrl; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsUserParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsUserParam.java new file mode 100644 index 0000000..26b3ec7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsUserParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 服务器成员管理查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsUserParam对象", description = "服务器成员管理查询参数") +public class OaAssetsUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + @QueryField(type = QueryType.EQ) + private Integer role; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaAssetsVhostParam.java b/src/main/java/com/gxwebsoft/oa/param/OaAssetsVhostParam.java new file mode 100644 index 0000000..2e90dbf --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaAssetsVhostParam.java @@ -0,0 +1,93 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 虚拟主机记录表查询参数 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaAssetsVhostParam对象", description = "虚拟主机记录表查询参数") +public class OaAssetsVhostParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer vhostId; + + @Schema(description = "域名") + private String name; + + @Schema(description = "域名标识") + private String code; + + @Schema(description = "主机型号") + private String type; + + @Schema(description = "品牌厂商") + private String brand; + + @Schema(description = "初始账号") + private String account; + + @Schema(description = "初始密码") + private String password; + + @Schema(description = "价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "详情内容") + private String content; + + @Schema(description = "ssl证书") + private String ssl; + + @Schema(description = "购买时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "置顶状态") + private String isTop; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "描述") + private String comments; + + @Schema(description = "服务器ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "可见用户") + private String userIds; + + @Schema(description = "状态, 0正常, 1冻结") + private String status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaCompanyFieldParam.java b/src/main/java/com/gxwebsoft/oa/param/OaCompanyFieldParam.java new file mode 100644 index 0000000..c81f21b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaCompanyFieldParam.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 企业参数查询参数 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaCompanyFieldParam对象", description = "企业参数查询参数") +public class OaCompanyFieldParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "状态, 0正常, 1删除") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaCompanyParam.java b/src/main/java/com/gxwebsoft/oa/param/OaCompanyParam.java new file mode 100644 index 0000000..5a8534e --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaCompanyParam.java @@ -0,0 +1,191 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 企业信息查询参数 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaCompanyParam对象", description = "企业信息查询参数") +public class OaCompanyParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "企业id") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "企业简称") + private String shortName; + + @Schema(description = "企业全称") + private String companyName; + + @Schema(description = "企业标识") + private String companyCode; + + @Schema(description = "类型 10企业 20政府单位") + private String companyType; + + @Schema(description = "企业类型多选") + private String companyTypeMultiple; + + @Schema(description = "应用标识") + private String companyLogo; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "绑定域名") + private String domain; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "座机电话") + private String tel; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "发票抬头") + private String invoiceHeader; + + @Schema(description = "企业法人") + private String businessEntity; + + @Schema(description = "服务开始时间") + private String startTime; + + @Schema(description = "服务到期时间") + private String expirationTime; + + @Schema(description = "应用版本 10体验版 20授权版 30旗舰版") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "成员数量(人数上限)") + @QueryField(type = QueryType.EQ) + private Integer members; + + @Schema(description = "成员数量(当前)") + @QueryField(type = QueryType.EQ) + private Integer users; + + @Schema(description = "行业类型(父级)") + private String industryParent; + + @Schema(description = "行业类型(子级)") + private String industryChild; + + @Schema(description = "部门数量") + @QueryField(type = QueryType.EQ) + private Integer departments; + + @Schema(description = "存储空间") + private Long storage; + + @Schema(description = "存储空间(上限)") + private Long storageMax; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否实名认证") + @QueryField(type = QueryType.EQ) + private Integer authentication; + + @Schema(description = "企业默认主体") + @QueryField(type = QueryType.EQ) + private Integer authoritative; + + @Schema(description = "request合法域名") + private String requestUrl; + + @Schema(description = "socket合法域名") + private String socketUrl; + + @Schema(description = "主控端域名") + private String serverUrl; + + @Schema(description = "业务域名") + private String modulesUrl; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "点赞数量") + @QueryField(type = QueryType.EQ) + private Integer likes; + + @Schema(description = "点击数量") + @QueryField(type = QueryType.EQ) + private Integer clicks; + + @Schema(description = "购买数量") + @QueryField(type = QueryType.EQ) + private Integer buys; + + @Schema(description = "是否含税, 0不含, 1含") + @QueryField(type = QueryType.EQ) + private Integer isTax; + + @Schema(description = "当前克隆的租户ID") + @QueryField(type = QueryType.EQ) + private Integer planId; + + @Schema(description = "知识库ID") + @QueryField(type = QueryType.EQ) + private String kbId; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaCompanyUserParam.java b/src/main/java/com/gxwebsoft/oa/param/OaCompanyUserParam.java new file mode 100644 index 0000000..bcba114 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaCompanyUserParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 成员管理查询参数 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaCompanyUserParam对象", description = "成员管理查询参数") +public class OaCompanyUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer companyUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + @QueryField(type = QueryType.EQ) + private Integer role; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaLinkParam.java b/src/main/java/com/gxwebsoft/oa/param/OaLinkParam.java new file mode 100644 index 0000000..08a965b --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaLinkParam.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 常用链接查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaLinkParam对象", description = "常用链接查询参数") +public class OaLinkParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "链接名称") + private String name; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "链接分类") + private String linkType; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "所属栏目") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaProductParam.java b/src/main/java/com/gxwebsoft/oa/param/OaProductParam.java new file mode 100644 index 0000000..f200fe1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaProductParam.java @@ -0,0 +1,103 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 产品记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaProductParam对象", description = "产品记录表查询参数") +public class OaProductParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "产品ID") + @QueryField(type = QueryType.EQ) + private Integer productId; + + @Schema(description = "产品名称") + private String name; + + @Schema(description = "产品标识") + private String code; + + @Schema(description = "产品详情") + private String content; + + @Schema(description = "产品类型") + private String type; + + @Schema(description = "产品图标") + private String logo; + + @Schema(description = "产品金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "初始销量") + @QueryField(type = QueryType.EQ) + private Integer salesInitial; + + @Schema(description = "实际销量") + @QueryField(type = QueryType.EQ) + private Integer salesActual; + + @Schema(description = "库存总量(包含所有sku)") + @QueryField(type = QueryType.EQ) + private Integer stockTotal; + + @Schema(description = "背景颜色") + private String backgroundColor; + + @Schema(description = "背景图片") + private String backgroundImage; + + @Schema(description = "背景图片(gif)") + private String backgroundGif; + + @Schema(description = "购买链接") + private String buyUrl; + + @Schema(description = "控制台链接") + private String adminUrl; + + @Schema(description = "附件") + private String files; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已上架, 1已下架") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaProductTabsParam.java b/src/main/java/com/gxwebsoft/oa/param/OaProductTabsParam.java new file mode 100644 index 0000000..bf42fa9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaProductTabsParam.java @@ -0,0 +1,74 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 产品标签记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaProductTabsParam对象", description = "产品标签记录表查询参数") +public class OaProductTabsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "产品标签ID") + @QueryField(type = QueryType.EQ) + private Integer tabId; + + @Schema(description = "产品ID") + @QueryField(type = QueryType.EQ) + private Integer productId; + + @Schema(description = "标签名称") + private String name; + + @Schema(description = "标签类型") + private String type; + + @Schema(description = "产品标签详情") + private String content; + + @Schema(description = "背景颜色") + private String backgroundColor; + + @Schema(description = "背景图片") + private String backgroundImage; + + @Schema(description = "附件") + private String files; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已上架, 1已下架") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaTaskCountParam.java b/src/main/java/com/gxwebsoft/oa/param/OaTaskCountParam.java new file mode 100644 index 0000000..d60bcb4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaTaskCountParam.java @@ -0,0 +1,72 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据统计查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaTaskCountParam对象", description = "数据统计查询参数") +public class OaTaskCountParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer taskCountId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "待处理数") + @QueryField(type = QueryType.EQ) + private Integer pending; + + @Schema(description = "闲置的工单(废弃)") + @QueryField(type = QueryType.EQ) + private Integer unused; + + @Schema(description = "已完成数(废弃)") + @QueryField(type = QueryType.EQ) + private Integer completed; + + @Schema(description = "今天处理数") + @QueryField(type = QueryType.EQ) + private Integer today; + + @Schema(description = "本月处理数") + @QueryField(type = QueryType.EQ) + private Integer month; + + @Schema(description = "今年处理数") + @QueryField(type = QueryType.EQ) + private Integer year; + + @Schema(description = "总工单数") + @QueryField(type = QueryType.EQ) + private Integer total; + + @Schema(description = "部门ID") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "角色ID") + @QueryField(type = QueryType.EQ) + private Integer roleId; + + @Schema(description = "角色标识") + private String roleCode; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaTaskParam.java b/src/main/java/com/gxwebsoft/oa/param/OaTaskParam.java new file mode 100644 index 0000000..85129f7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaTaskParam.java @@ -0,0 +1,139 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 任务记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaTaskParam对象", description = "任务记录表查询参数") +public class OaTaskParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "工单ID") + @QueryField(type = QueryType.EQ) + private Integer taskId; + + @Schema(description = "工单类型") + private String taskType; + + @Schema(description = "任务内容") + private String name; + + @Schema(description = "问题描述") + private String content; + + @Schema(description = "工单附件") + private String files; + + @Schema(description = "工单发起人") + @QueryField(type = QueryType.EQ) + private Integer promoter; + + @Schema(description = "受理人") + @QueryField(type = QueryType.EQ) + private Integer commander; + + @Schema(description = "工单状态, 0未开始 1已指派 ") + @QueryField(type = QueryType.EQ) + private Integer progress; + + @Schema(description = "优先级") + private String priority; + + @Schema(description = "品质要求") + private String quality; + + @Schema(description = "时限(天)") + @QueryField(type = QueryType.EQ) + private Integer day; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "开始时间") + private String startTime; + + @Schema(description = "结束时间") + private String endTime; + + @Schema(description = "逾期天数") + @QueryField(type = QueryType.EQ) + private Integer overdueDays; + + @Schema(description = "项目ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "项目ID") + @QueryField(type = QueryType.EQ) + private Integer projectId; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer customerId; + + @Schema(description = "资产ID") + @QueryField(type = QueryType.EQ) + private Integer assetsId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否已查阅") + @QueryField(type = QueryType.EQ) + private Integer isRead; + + @Schema(description = "最后回复人") + @QueryField(type = QueryType.EQ) + private Integer lastReadUser; + + @Schema(description = "发起人昵称") + private String nickname; + + @Schema(description = "发起人头像") + private String avatar; + + @Schema(description = "最后回复人头像") + private String lastAvatar; + + @Schema(description = "最后回复人昵称") + private String lastNickname; + + @Schema(description = "订单是否已结算(0未结算 1已结算)") + @QueryField(type = QueryType.EQ) + private Integer isSettled; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0待处理, 1已完成") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaTaskRecordParam.java b/src/main/java/com/gxwebsoft/oa/param/OaTaskRecordParam.java new file mode 100644 index 0000000..8d5f275 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaTaskRecordParam.java @@ -0,0 +1,68 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工单回复记录表查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaTaskRecordParam对象", description = "工单回复记录表查询参数") +public class OaTaskRecordParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "回复ID") + @QueryField(type = QueryType.EQ) + private Integer taskRecordId; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "工单ID") + @QueryField(type = QueryType.EQ) + private Integer taskId; + + @Schema(description = "内容") + private String content; + + @Schema(description = "机密信息") + private String confidential; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "工单附件") + private String files; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0待处理, 1已完成") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/oa/param/OaTaskUserParam.java b/src/main/java/com/gxwebsoft/oa/param/OaTaskUserParam.java new file mode 100644 index 0000000..3fdfbeb --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/param/OaTaskUserParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.oa.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工单成员查询参数 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "OaTaskUserParam对象", description = "工单成员查询参数") +public class OaTaskUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer taskUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + @QueryField(type = QueryType.EQ) + private Integer role; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "工单ID") + @QueryField(type = QueryType.EQ) + private Integer taskId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0待处理, 1已完成") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAppFieldService.java b/src/main/java/com/gxwebsoft/oa/service/OaAppFieldService.java new file mode 100644 index 0000000..94d905f --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAppFieldService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAppField; +import com.gxwebsoft.oa.param.OaAppFieldParam; + +import java.util.List; + +/** + * 应用参数Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppFieldService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAppFieldParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAppFieldParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return OaAppField + */ + OaAppField getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAppRenewService.java b/src/main/java/com/gxwebsoft/oa/service/OaAppRenewService.java new file mode 100644 index 0000000..2cb38cf --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAppRenewService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAppRenew; +import com.gxwebsoft.oa.param.OaAppRenewParam; + +import java.util.List; + +/** + * 续费管理Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppRenewService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAppRenewParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAppRenewParam param); + + /** + * 根据id查询 + * + * @param appRenewId 自增ID + * @return OaAppRenew + */ + OaAppRenew getByIdRel(Integer appRenewId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAppService.java b/src/main/java/com/gxwebsoft/oa/service/OaAppService.java new file mode 100644 index 0000000..98bb4c4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAppService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaApp; +import com.gxwebsoft.oa.param.OaAppParam; + +import java.util.List; + +/** + * 应用Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAppParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAppParam param); + + /** + * 根据id查询 + * + * @param appId 应用ID + * @return OaApp + */ + OaApp getByIdRel(Integer appId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAppUrlService.java b/src/main/java/com/gxwebsoft/oa/service/OaAppUrlService.java new file mode 100644 index 0000000..eef2650 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAppUrlService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAppUrl; +import com.gxwebsoft.oa.param.OaAppUrlParam; + +import java.util.List; + +/** + * 项目域名Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppUrlService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAppUrlParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAppUrlParam param); + + /** + * 根据id查询 + * + * @param appUrlId 自增ID + * @return OaAppUrl + */ + OaAppUrl getByIdRel(Integer appUrlId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAppUserService.java b/src/main/java/com/gxwebsoft/oa/service/OaAppUserService.java new file mode 100644 index 0000000..1dd7f82 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAppUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAppUser; +import com.gxwebsoft.oa.param.OaAppUserParam; + +import java.util.List; + +/** + * 应用成员Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAppUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAppUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAppUserParam param); + + /** + * 根据id查询 + * + * @param appUserId 自增ID + * @return OaAppUser + */ + OaAppUser getByIdRel(Integer appUserId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsCodeService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsCodeService.java new file mode 100644 index 0000000..681ef81 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsCodeService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsCode; +import com.gxwebsoft.oa.param.OaAssetsCodeParam; + +import java.util.List; + +/** + * 代码仓库Service + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +public interface OaAssetsCodeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsCodeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsCodeParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return OaAssetsCode + */ + OaAssetsCode getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsDomainService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsDomainService.java new file mode 100644 index 0000000..735df28 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsDomainService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsDomain; +import com.gxwebsoft.oa.param.OaAssetsDomainParam; + +import java.util.List; + +/** + * 域名Service + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsDomainService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsDomainParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsDomainParam param); + + /** + * 根据id查询 + * + * @param domainId ID + * @return OaAssetsDomain + */ + OaAssetsDomain getByIdRel(Integer domainId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsEmailService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsEmailService.java new file mode 100644 index 0000000..84c1ab7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsEmailService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsEmail; +import com.gxwebsoft.oa.param.OaAssetsEmailParam; + +import java.util.List; + +/** + * 企业邮箱记录表Service + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsEmailService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsEmailParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsEmailParam param); + + /** + * 根据id查询 + * + * @param emailId ID + * @return OaAssetsEmail + */ + OaAssetsEmail getByIdRel(Integer emailId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsMysqlService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsMysqlService.java new file mode 100644 index 0000000..7263afd --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsMysqlService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsMysql; +import com.gxwebsoft.oa.param.OaAssetsMysqlParam; + +import java.util.List; + +/** + * 云数据库Service + * + * @author 科技小王子 + * @since 2024-10-18 19:00:20 + */ +public interface OaAssetsMysqlService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsMysqlParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsMysqlParam param); + + /** + * 根据id查询 + * + * @param mysqlId ID + * @return OaAssetsMysql + */ + OaAssetsMysql getByIdRel(Integer mysqlId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsServerService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsServerService.java new file mode 100644 index 0000000..0b91d35 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsServerService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsServer; +import com.gxwebsoft.oa.param.OaAssetsServerParam; + +import java.util.List; + +/** + * 服务器资产记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAssetsServerService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsServerParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsServerParam param); + + /** + * 根据id查询 + * + * @param serverId 资产ID + * @return OaAssetsServer + */ + OaAssetsServer getByIdRel(Integer serverId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsService.java new file mode 100644 index 0000000..4142d5f --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssets; +import com.gxwebsoft.oa.param.OaAssetsParam; + +import java.util.List; + +/** + * 云服务器Service + * + * @author 科技小王子 + * @since 2024-10-18 18:34:15 + */ +public interface OaAssetsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsParam param); + + /** + * 根据id查询 + * + * @param assetsId 资产ID + * @return OaAssets + */ + OaAssets getByIdRel(Integer assetsId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsSiteService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsSiteService.java new file mode 100644 index 0000000..c8ec714 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsSiteService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsSite; +import com.gxwebsoft.oa.param.OaAssetsSiteParam; + +import java.util.List; + +/** + * 网站信息记录表Service + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsSiteService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsSiteParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsSiteParam param); + + /** + * 根据id查询 + * + * @param websiteId 站点ID + * @return OaAssetsSite + */ + OaAssetsSite getByIdRel(Integer websiteId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsSoftwareCertService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsSoftwareCertService.java new file mode 100644 index 0000000..039f97d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsSoftwareCertService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsSoftwareCert; +import com.gxwebsoft.oa.param.OaAssetsSoftwareCertParam; + +import java.util.List; + +/** + * 计算机软件著作权登记Service + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +public interface OaAssetsSoftwareCertService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsSoftwareCertParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsSoftwareCertParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return OaAssetsSoftwareCert + */ + OaAssetsSoftwareCert getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsSslService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsSslService.java new file mode 100644 index 0000000..3d150e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsSslService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsSsl; +import com.gxwebsoft.oa.param.OaAssetsSslParam; + +import java.util.List; + +/** + * ssl证书Service + * + * @author 科技小王子 + * @since 2024-10-18 19:25:40 + */ +public interface OaAssetsSslService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsSslParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsSslParam param); + + /** + * 根据id查询 + * + * @param sslId ID + * @return OaAssetsSsl + */ + OaAssetsSsl getByIdRel(Integer sslId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsTrademarkService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsTrademarkService.java new file mode 100644 index 0000000..838ed04 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsTrademarkService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsTrademark; +import com.gxwebsoft.oa.param.OaAssetsTrademarkParam; + +import java.util.List; + +/** + * 商标注册Service + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +public interface OaAssetsTrademarkService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsTrademarkParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsTrademarkParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return OaAssetsTrademark + */ + OaAssetsTrademark getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsUserService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsUserService.java new file mode 100644 index 0000000..f976095 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsUser; +import com.gxwebsoft.oa.param.OaAssetsUserParam; + +import java.util.List; + +/** + * 服务器成员管理Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +public interface OaAssetsUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsUserParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return OaAssetsUser + */ + OaAssetsUser getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaAssetsVhostService.java b/src/main/java/com/gxwebsoft/oa/service/OaAssetsVhostService.java new file mode 100644 index 0000000..4b54c43 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaAssetsVhostService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaAssetsVhost; +import com.gxwebsoft.oa.param.OaAssetsVhostParam; + +import java.util.List; + +/** + * 虚拟主机记录表Service + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +public interface OaAssetsVhostService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaAssetsVhostParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaAssetsVhostParam param); + + /** + * 根据id查询 + * + * @param vhostId ID + * @return OaAssetsVhost + */ + OaAssetsVhost getByIdRel(Integer vhostId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaCompanyFieldService.java b/src/main/java/com/gxwebsoft/oa/service/OaCompanyFieldService.java new file mode 100644 index 0000000..66ac876 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaCompanyFieldService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaCompanyField; +import com.gxwebsoft.oa.param.OaCompanyFieldParam; + +import java.util.List; + +/** + * 企业参数Service + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +public interface OaCompanyFieldService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaCompanyFieldParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaCompanyFieldParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return OaCompanyField + */ + OaCompanyField getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java b/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java new file mode 100644 index 0000000..dd91ee7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaCompany; +import com.gxwebsoft.oa.param.OaCompanyParam; + +import java.util.List; + +/** + * 企业信息Service + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +public interface OaCompanyService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaCompanyParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaCompanyParam param); + + /** + * 根据id查询 + * + * @param companyId 企业id + * @return OaCompany + */ + OaCompany getByIdRel(Integer companyId); + + /** + * 初始化企业目录 + * + * @param oaCompany 企业信息 + * @param userId 创建用户ID + */ + void initCompanyDocDirectories(OaCompany oaCompany, Integer userId); + + /** + * 清空企业目录 + * + * @param oaCompany 企业Id + */ + boolean removeCompanyDocDirectories(Integer companyId); + + /** + * 清空企业知识库 + * + * @param oaCompany 企业Id + */ + boolean removeCompanyKnowledgeBase(Integer companyId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaCompanyUserService.java b/src/main/java/com/gxwebsoft/oa/service/OaCompanyUserService.java new file mode 100644 index 0000000..ff2c147 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaCompanyUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaCompanyUser; +import com.gxwebsoft.oa.param.OaCompanyUserParam; + +import java.util.List; + +/** + * 成员管理Service + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +public interface OaCompanyUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaCompanyUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaCompanyUserParam param); + + /** + * 根据id查询 + * + * @param companyUserId 自增ID + * @return OaCompanyUser + */ + OaCompanyUser getByIdRel(Integer companyUserId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaLinkService.java b/src/main/java/com/gxwebsoft/oa/service/OaLinkService.java new file mode 100644 index 0000000..6d8738f --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaLinkService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaLink; +import com.gxwebsoft.oa.param.OaLinkParam; + +import java.util.List; + +/** + * 常用链接Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaLinkService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaLinkParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaLinkParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return OaLink + */ + OaLink getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaProductService.java b/src/main/java/com/gxwebsoft/oa/service/OaProductService.java new file mode 100644 index 0000000..305cbd2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaProductService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaProduct; +import com.gxwebsoft.oa.param.OaProductParam; + +import java.util.List; + +/** + * 产品记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaProductService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaProductParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaProductParam param); + + /** + * 根据id查询 + * + * @param productId 产品ID + * @return OaProduct + */ + OaProduct getByIdRel(Integer productId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaProductTabsService.java b/src/main/java/com/gxwebsoft/oa/service/OaProductTabsService.java new file mode 100644 index 0000000..e1725ff --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaProductTabsService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaProductTabs; +import com.gxwebsoft.oa.param.OaProductTabsParam; + +import java.util.List; + +/** + * 产品标签记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaProductTabsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaProductTabsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaProductTabsParam param); + + /** + * 根据id查询 + * + * @param tabId 产品标签ID + * @return OaProductTabs + */ + OaProductTabs getByIdRel(Integer tabId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaTaskCountService.java b/src/main/java/com/gxwebsoft/oa/service/OaTaskCountService.java new file mode 100644 index 0000000..903f7cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaTaskCountService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaTaskCount; +import com.gxwebsoft.oa.param.OaTaskCountParam; + +import java.util.List; + +/** + * 数据统计Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaTaskCountService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaTaskCountParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaTaskCountParam param); + + /** + * 根据id查询 + * + * @param taskCountId 自增ID + * @return OaTaskCount + */ + OaTaskCount getByIdRel(Integer taskCountId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaTaskService.java b/src/main/java/com/gxwebsoft/oa/service/OaTaskService.java new file mode 100644 index 0000000..4f06f34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaTaskService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaTask; +import com.gxwebsoft.oa.param.OaTaskParam; + +import java.util.List; + +/** + * 任务记录表Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaTaskService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaTaskParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaTaskParam param); + + /** + * 根据id查询 + * + * @param taskId 工单ID + * @return OaTask + */ + OaTask getByIdRel(Integer taskId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/OaTaskUserService.java b/src/main/java/com/gxwebsoft/oa/service/OaTaskUserService.java new file mode 100644 index 0000000..9c33890 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/OaTaskUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.oa.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaTaskUser; +import com.gxwebsoft.oa.param.OaTaskUserParam; + +import java.util.List; + +/** + * 工单成员Service + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +public interface OaTaskUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(OaTaskUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(OaTaskUserParam param); + + /** + * 根据id查询 + * + * @param taskUserId 自增ID + * @return OaTaskUser + */ + OaTaskUser getByIdRel(Integer taskUserId); + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAppFieldServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppFieldServiceImpl.java new file mode 100644 index 0000000..d70ecf7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppFieldServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAppFieldMapper; +import com.gxwebsoft.oa.service.OaAppFieldService; +import com.gxwebsoft.oa.entity.OaAppField; +import com.gxwebsoft.oa.param.OaAppFieldParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用参数Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAppFieldServiceImpl extends ServiceImpl implements OaAppFieldService { + + @Override + public PageResult pageRel(OaAppFieldParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAppFieldParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAppField getByIdRel(Integer id) { + OaAppFieldParam param = new OaAppFieldParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAppRenewServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppRenewServiceImpl.java new file mode 100644 index 0000000..9e8bd8d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppRenewServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAppRenewMapper; +import com.gxwebsoft.oa.service.OaAppRenewService; +import com.gxwebsoft.oa.entity.OaAppRenew; +import com.gxwebsoft.oa.param.OaAppRenewParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 续费管理Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAppRenewServiceImpl extends ServiceImpl implements OaAppRenewService { + + @Override + public PageResult pageRel(OaAppRenewParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAppRenewParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAppRenew getByIdRel(Integer appRenewId) { + OaAppRenewParam param = new OaAppRenewParam(); + param.setAppRenewId(appRenewId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAppServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppServiceImpl.java new file mode 100644 index 0000000..81f8f01 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAppMapper; +import com.gxwebsoft.oa.service.OaAppService; +import com.gxwebsoft.oa.entity.OaApp; +import com.gxwebsoft.oa.param.OaAppParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAppServiceImpl extends ServiceImpl implements OaAppService { + + @Override + public PageResult pageRel(OaAppParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAppParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaApp getByIdRel(Integer appId) { + OaAppParam param = new OaAppParam(); + param.setAppId(appId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAppUrlServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppUrlServiceImpl.java new file mode 100644 index 0000000..1adab2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppUrlServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAppUrlMapper; +import com.gxwebsoft.oa.service.OaAppUrlService; +import com.gxwebsoft.oa.entity.OaAppUrl; +import com.gxwebsoft.oa.param.OaAppUrlParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 项目域名Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAppUrlServiceImpl extends ServiceImpl implements OaAppUrlService { + + @Override + public PageResult pageRel(OaAppUrlParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAppUrlParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAppUrl getByIdRel(Integer appUrlId) { + OaAppUrlParam param = new OaAppUrlParam(); + param.setAppUrlId(appUrlId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAppUserServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppUserServiceImpl.java new file mode 100644 index 0000000..ae43f9a --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAppUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAppUserMapper; +import com.gxwebsoft.oa.service.OaAppUserService; +import com.gxwebsoft.oa.entity.OaAppUser; +import com.gxwebsoft.oa.param.OaAppUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用成员Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAppUserServiceImpl extends ServiceImpl implements OaAppUserService { + + @Override + public PageResult pageRel(OaAppUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAppUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAppUser getByIdRel(Integer appUserId) { + OaAppUserParam param = new OaAppUserParam(); + param.setAppUserId(appUserId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsCodeServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsCodeServiceImpl.java new file mode 100644 index 0000000..5d37730 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsCodeServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsCodeMapper; +import com.gxwebsoft.oa.service.OaAssetsCodeService; +import com.gxwebsoft.oa.entity.OaAssetsCode; +import com.gxwebsoft.oa.param.OaAssetsCodeParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 代码仓库Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:01 + */ +@Service +public class OaAssetsCodeServiceImpl extends ServiceImpl implements OaAssetsCodeService { + + @Override + public PageResult pageRel(OaAssetsCodeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsCodeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsCode getByIdRel(Integer id) { + OaAssetsCodeParam param = new OaAssetsCodeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsDomainServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsDomainServiceImpl.java new file mode 100644 index 0000000..ac207c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsDomainServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsDomainMapper; +import com.gxwebsoft.oa.service.OaAssetsDomainService; +import com.gxwebsoft.oa.entity.OaAssetsDomain; +import com.gxwebsoft.oa.param.OaAssetsDomainParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 域名Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Service +public class OaAssetsDomainServiceImpl extends ServiceImpl implements OaAssetsDomainService { + + @Override + public PageResult pageRel(OaAssetsDomainParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsDomainParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsDomain getByIdRel(Integer domainId) { + OaAssetsDomainParam param = new OaAssetsDomainParam(); + param.setDomainId(domainId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsEmailServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsEmailServiceImpl.java new file mode 100644 index 0000000..7c3f4bc --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsEmailServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsEmailMapper; +import com.gxwebsoft.oa.service.OaAssetsEmailService; +import com.gxwebsoft.oa.entity.OaAssetsEmail; +import com.gxwebsoft.oa.param.OaAssetsEmailParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 企业邮箱记录表Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Service +public class OaAssetsEmailServiceImpl extends ServiceImpl implements OaAssetsEmailService { + + @Override + public PageResult pageRel(OaAssetsEmailParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsEmailParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsEmail getByIdRel(Integer emailId) { + OaAssetsEmailParam param = new OaAssetsEmailParam(); + param.setEmailId(emailId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsMysqlServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsMysqlServiceImpl.java new file mode 100644 index 0000000..fc17f9a --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsMysqlServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsMysqlMapper; +import com.gxwebsoft.oa.service.OaAssetsMysqlService; +import com.gxwebsoft.oa.entity.OaAssetsMysql; +import com.gxwebsoft.oa.param.OaAssetsMysqlParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 云数据库Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 19:00:20 + */ +@Service +public class OaAssetsMysqlServiceImpl extends ServiceImpl implements OaAssetsMysqlService { + + @Override + public PageResult pageRel(OaAssetsMysqlParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsMysqlParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsMysql getByIdRel(Integer mysqlId) { + OaAssetsMysqlParam param = new OaAssetsMysqlParam(); + param.setMysqlId(mysqlId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServerServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServerServiceImpl.java new file mode 100644 index 0000000..7788484 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServerServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsServerMapper; +import com.gxwebsoft.oa.service.OaAssetsServerService; +import com.gxwebsoft.oa.entity.OaAssetsServer; +import com.gxwebsoft.oa.param.OaAssetsServerParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 服务器资产记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAssetsServerServiceImpl extends ServiceImpl implements OaAssetsServerService { + + @Override + public PageResult pageRel(OaAssetsServerParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsServerParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsServer getByIdRel(Integer serverId) { + OaAssetsServerParam param = new OaAssetsServerParam(); + param.setServerId(serverId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServiceImpl.java new file mode 100644 index 0000000..3c64144 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsMapper; +import com.gxwebsoft.oa.service.OaAssetsService; +import com.gxwebsoft.oa.entity.OaAssets; +import com.gxwebsoft.oa.param.OaAssetsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 云服务器Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 18:34:15 + */ +@Service +public class OaAssetsServiceImpl extends ServiceImpl implements OaAssetsService { + + @Override + public PageResult pageRel(OaAssetsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssets getByIdRel(Integer assetsId) { + OaAssetsParam param = new OaAssetsParam(); + param.setAssetsId(assetsId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSiteServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSiteServiceImpl.java new file mode 100644 index 0000000..b94667d --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSiteServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsSiteMapper; +import com.gxwebsoft.oa.service.OaAssetsSiteService; +import com.gxwebsoft.oa.entity.OaAssetsSite; +import com.gxwebsoft.oa.param.OaAssetsSiteParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 网站信息记录表Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Service +public class OaAssetsSiteServiceImpl extends ServiceImpl implements OaAssetsSiteService { + + @Override + public PageResult pageRel(OaAssetsSiteParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsSiteParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsSite getByIdRel(Integer websiteId) { + OaAssetsSiteParam param = new OaAssetsSiteParam(); + param.setWebsiteId(websiteId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSoftwareCertServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSoftwareCertServiceImpl.java new file mode 100644 index 0000000..2ac941c --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSoftwareCertServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsSoftwareCertMapper; +import com.gxwebsoft.oa.service.OaAssetsSoftwareCertService; +import com.gxwebsoft.oa.entity.OaAssetsSoftwareCert; +import com.gxwebsoft.oa.param.OaAssetsSoftwareCertParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 计算机软件著作权登记Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Service +public class OaAssetsSoftwareCertServiceImpl extends ServiceImpl implements OaAssetsSoftwareCertService { + + @Override + public PageResult pageRel(OaAssetsSoftwareCertParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsSoftwareCertParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsSoftwareCert getByIdRel(Integer id) { + OaAssetsSoftwareCertParam param = new OaAssetsSoftwareCertParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSslServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSslServiceImpl.java new file mode 100644 index 0000000..39df8ec --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsSslServiceImpl.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsSslMapper; +import com.gxwebsoft.oa.service.OaAssetsSslService; +import com.gxwebsoft.oa.entity.OaAssetsSsl; +import com.gxwebsoft.oa.param.OaAssetsSslParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * ssl证书Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 19:25:40 + */ +@Service +public class OaAssetsSslServiceImpl extends ServiceImpl implements OaAssetsSslService { + + @Override + public PageResult pageRel(OaAssetsSslParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + list.forEach(d -> { + LocalDateTime now = LocalDateTime.now(); + // 即将过期(一周内过期的) + d.setSoon(d.getEndTime().minusDays(7).compareTo(now)); + // 是否过期 -1已过期 大于0 未过期 + d.setStatus(d.getEndTime().compareTo(now)); + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsSslParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsSsl getByIdRel(Integer sslId) { + OaAssetsSslParam param = new OaAssetsSslParam(); + param.setSslId(sslId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsTrademarkServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsTrademarkServiceImpl.java new file mode 100644 index 0000000..7baede1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsTrademarkServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsTrademarkMapper; +import com.gxwebsoft.oa.service.OaAssetsTrademarkService; +import com.gxwebsoft.oa.entity.OaAssetsTrademark; +import com.gxwebsoft.oa.param.OaAssetsTrademarkParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商标注册Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 19:46:21 + */ +@Service +public class OaAssetsTrademarkServiceImpl extends ServiceImpl implements OaAssetsTrademarkService { + + @Override + public PageResult pageRel(OaAssetsTrademarkParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsTrademarkParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsTrademark getByIdRel(Integer id) { + OaAssetsTrademarkParam param = new OaAssetsTrademarkParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsUserServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsUserServiceImpl.java new file mode 100644 index 0000000..4869455 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsUserMapper; +import com.gxwebsoft.oa.service.OaAssetsUserService; +import com.gxwebsoft.oa.entity.OaAssetsUser; +import com.gxwebsoft.oa.param.OaAssetsUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 服务器成员管理Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:41 + */ +@Service +public class OaAssetsUserServiceImpl extends ServiceImpl implements OaAssetsUserService { + + @Override + public PageResult pageRel(OaAssetsUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsUser getByIdRel(Integer id) { + OaAssetsUserParam param = new OaAssetsUserParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsVhostServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsVhostServiceImpl.java new file mode 100644 index 0000000..87b1a08 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaAssetsVhostServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaAssetsVhostMapper; +import com.gxwebsoft.oa.service.OaAssetsVhostService; +import com.gxwebsoft.oa.entity.OaAssetsVhost; +import com.gxwebsoft.oa.param.OaAssetsVhostParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 虚拟主机记录表Service实现 + * + * @author 科技小王子 + * @since 2024-10-18 18:27:02 + */ +@Service +public class OaAssetsVhostServiceImpl extends ServiceImpl implements OaAssetsVhostService { + + @Override + public PageResult pageRel(OaAssetsVhostParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaAssetsVhostParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaAssetsVhost getByIdRel(Integer vhostId) { + OaAssetsVhostParam param = new OaAssetsVhostParam(); + param.setVhostId(vhostId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyFieldServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyFieldServiceImpl.java new file mode 100644 index 0000000..e7b76d6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyFieldServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaCompanyFieldMapper; +import com.gxwebsoft.oa.service.OaCompanyFieldService; +import com.gxwebsoft.oa.entity.OaCompanyField; +import com.gxwebsoft.oa.param.OaCompanyFieldParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 企业参数Service实现 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Service +public class OaCompanyFieldServiceImpl extends ServiceImpl implements OaCompanyFieldService { + + @Override + public PageResult pageRel(OaCompanyFieldParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaCompanyFieldParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaCompanyField getByIdRel(Integer id) { + OaCompanyFieldParam param = new OaCompanyFieldParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java new file mode 100644 index 0000000..f1b8937 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java @@ -0,0 +1,192 @@ +package com.gxwebsoft.oa.service.impl; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.DeleteCategoryResponseBody; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaCompanyMapper; +import com.gxwebsoft.oa.service.OaCompanyService; + +import cn.hutool.core.util.StrUtil; + +import com.gxwebsoft.oa.entity.OaCompany; +import com.gxwebsoft.oa.param.OaCompanyParam; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.util.AiCloudDataCenterUtil; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 企业信息Service实现 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Service +public class OaCompanyServiceImpl extends ServiceImpl implements OaCompanyService { + + @Autowired + private AiCloudDocService aiCloudDocService; + + @Autowired + private AiCloudFileService aiCloudFileService; + + @Autowired + private KnowledgeBaseConfig config; + + @Autowired + private KnowledgeBaseClientFactory clientFactory; + + @Override + public PageResult pageRel(OaCompanyParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaCompanyParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaCompany getByIdRel(Integer companyId) { + OaCompanyParam param = new OaCompanyParam(); + param.setCompanyId(companyId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void initCompanyDocDirectories(OaCompany oaCompany, Integer userId) { + String workspaceId = config.getWorkspaceId(); + String topCategoryId = config.getTopCategoryId(); + String categoryId = ""; + try { + Client client = clientFactory.createClient(); + categoryId = AiCloudDataCenterUtil.addCategory(client, workspaceId, topCategoryId, oaCompany.getCompanyName()).getBody().getData().getCategoryId(); + } catch (Exception e) { + e.printStackTrace(); + return; // 创建分类失败时直接返回 + } + + // 1. 先创建顶级目录(公司名称) + AiCloudDoc topDirectory = new AiCloudDoc(); + topDirectory.setCategoryId(categoryId); + topDirectory.setCompanyId(oaCompany.getCompanyId()); + topDirectory.setParentId(0); // 顶级目录的父ID为0 + topDirectory.setName(oaCompany.getCompanyName()); + topDirectory.setSortNumber(0); + topDirectory.setStatus(0); + topDirectory.setDeleted(0); + topDirectory.setUserId(userId); + topDirectory.setTenantId(oaCompany.getTenantId()); + topDirectory.setCreateTime(LocalDateTime.now()); + topDirectory.setUpdateTime(LocalDateTime.now()); + + // 保存顶级目录并获取其ID + aiCloudDocService.save(topDirectory); + Integer topDirId = topDirectory.getId(); // 假设实体类有getDocId()方法 + + // 2. 创建子目录列表 + List directoryNames = Arrays.asList( + "1.基本情况", "2.企业领导人员任职情况", "3.任期内年度总结报告", "4.公司章程及议事规则", + "5.领导班子分工", "6.任期内会议纪要与会议记录", "7.内部控制制度", "8.企业发展规划及战略", + "9.目标责任", "10-11.重大经济事项决策及执行(项目)", "12.固定资产、资产处置资料", + "13.财务数据", "14.以前年度审计" + ); + + List directories = new ArrayList<>(); + for (int i = 0; i < directoryNames.size(); i++) { + AiCloudDoc doc = new AiCloudDoc(); + doc.setCategoryId(categoryId); + doc.setCompanyId(oaCompany.getCompanyId()); + doc.setParentId(topDirId); // 关键修改:父目录ID设为顶级目录的ID + doc.setName(directoryNames.get(i)); + doc.setSortNumber(i + 1); + doc.setStatus(0); + doc.setDeleted(0); + doc.setUserId(userId); + doc.setTenantId(oaCompany.getTenantId()); + doc.setCreateTime(LocalDateTime.now()); + doc.setUpdateTime(LocalDateTime.now()); + directories.add(doc); + } + + // 批量保存子目录 + aiCloudDocService.saveBatch(directories); + } + + @Override + public boolean removeCompanyDocDirectories(Integer companyId) { + boolean ret = true; + String workspaceId = config.getWorkspaceId(); + if(companyId==null) { + return ret; + } + //目录 + List docList = aiCloudDocService.list(new LambdaQueryWrapper().eq(AiCloudDoc::getCompanyId, companyId)); + List docIds = docList.stream().map(AiCloudDoc::getId).collect(Collectors.toList()); + //先删目阿里云录下所有文件 + List fileList = aiCloudFileService.list(new LambdaQueryWrapper().in(AiCloudFile::getDocId, docIds)); + List fileIds = fileList.stream().map(AiCloudFile::getId).collect(Collectors.toList()); + for(AiCloudFile file : fileList) { + try { + Client client = clientFactory.createClient(); + AiCloudDataCenterUtil.deleteFile(client, workspaceId, file.getFileId()); + } catch (Exception e) { + e.printStackTrace(); + } + } + //再删阿里云目录 + List categoryIds = docList.stream().map(AiCloudDoc::getCategoryId).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList()); + for(String categoryId : categoryIds) { + try { + Client client = clientFactory.createClient(); + ret = AiCloudDataCenterUtil.deleteCategory(client, workspaceId, categoryId).getBody().getSuccess(); + } catch (Exception e) { + e.printStackTrace(); + } + } + aiCloudDocService.removeByIds(docIds); + aiCloudFileService.removeByIds(fileIds); + return ret; + } + + @Override + public boolean removeCompanyKnowledgeBase(Integer companyId) { + boolean ret = true; + String workspaceId = config.getWorkspaceId(); + if(companyId==null) { + return ret; + } + //删阿里云知识库 + OaCompany oaCompany = baseMapper.selectById(companyId); + try { + Client client = clientFactory.createClient(); + ret = AiCloudKnowledgeBaseUtil.deleteIndex(client, workspaceId, oaCompany.getKbId()).getBody().getSuccess(); + } catch (Exception e) { + e.printStackTrace(); + } + return ret; + } +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyUserServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyUserServiceImpl.java new file mode 100644 index 0000000..43b4230 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaCompanyUserMapper; +import com.gxwebsoft.oa.service.OaCompanyUserService; +import com.gxwebsoft.oa.entity.OaCompanyUser; +import com.gxwebsoft.oa.param.OaCompanyUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 成员管理Service实现 + * + * @author 科技小王子 + * @since 2024-09-20 12:33:12 + */ +@Service +public class OaCompanyUserServiceImpl extends ServiceImpl implements OaCompanyUserService { + + @Override + public PageResult pageRel(OaCompanyUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaCompanyUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaCompanyUser getByIdRel(Integer companyUserId) { + OaCompanyUserParam param = new OaCompanyUserParam(); + param.setCompanyUserId(companyUserId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaLinkServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaLinkServiceImpl.java new file mode 100644 index 0000000..4050f49 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaLinkServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaLinkMapper; +import com.gxwebsoft.oa.service.OaLinkService; +import com.gxwebsoft.oa.entity.OaLink; +import com.gxwebsoft.oa.param.OaLinkParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 常用链接Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Service +public class OaLinkServiceImpl extends ServiceImpl implements OaLinkService { + + @Override + public PageResult pageRel(OaLinkParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaLinkParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaLink getByIdRel(Integer id) { + OaLinkParam param = new OaLinkParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaProductServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaProductServiceImpl.java new file mode 100644 index 0000000..09506a3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaProductServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaProductMapper; +import com.gxwebsoft.oa.service.OaProductService; +import com.gxwebsoft.oa.entity.OaProduct; +import com.gxwebsoft.oa.param.OaProductParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 产品记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Service +public class OaProductServiceImpl extends ServiceImpl implements OaProductService { + + @Override + public PageResult pageRel(OaProductParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaProductParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaProduct getByIdRel(Integer productId) { + OaProductParam param = new OaProductParam(); + param.setProductId(productId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaProductTabsServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaProductTabsServiceImpl.java new file mode 100644 index 0000000..98a4203 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaProductTabsServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaProductTabsMapper; +import com.gxwebsoft.oa.service.OaProductTabsService; +import com.gxwebsoft.oa.entity.OaProductTabs; +import com.gxwebsoft.oa.param.OaProductTabsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 产品标签记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Service +public class OaProductTabsServiceImpl extends ServiceImpl implements OaProductTabsService { + + @Override + public PageResult pageRel(OaProductTabsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaProductTabsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaProductTabs getByIdRel(Integer tabId) { + OaProductTabsParam param = new OaProductTabsParam(); + param.setTabId(tabId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskCountServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskCountServiceImpl.java new file mode 100644 index 0000000..8da15ca --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskCountServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaTaskCountMapper; +import com.gxwebsoft.oa.service.OaTaskCountService; +import com.gxwebsoft.oa.entity.OaTaskCount; +import com.gxwebsoft.oa.param.OaTaskCountParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 数据统计Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Service +public class OaTaskCountServiceImpl extends ServiceImpl implements OaTaskCountService { + + @Override + public PageResult pageRel(OaTaskCountParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaTaskCountParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaTaskCount getByIdRel(Integer taskCountId) { + OaTaskCountParam param = new OaTaskCountParam(); + param.setTaskCountId(taskCountId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskServiceImpl.java new file mode 100644 index 0000000..1ea3691 --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaTaskMapper; +import com.gxwebsoft.oa.service.OaTaskService; +import com.gxwebsoft.oa.entity.OaTask; +import com.gxwebsoft.oa.param.OaTaskParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 任务记录表Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Service +public class OaTaskServiceImpl extends ServiceImpl implements OaTaskService { + + @Override + public PageResult pageRel(OaTaskParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaTaskParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaTask getByIdRel(Integer taskId) { + OaTaskParam param = new OaTaskParam(); + param.setTaskId(taskId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskUserServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskUserServiceImpl.java new file mode 100644 index 0000000..c7a15ef --- /dev/null +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaTaskUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.oa.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.oa.mapper.OaTaskUserMapper; +import com.gxwebsoft.oa.service.OaTaskUserService; +import com.gxwebsoft.oa.entity.OaTaskUser; +import com.gxwebsoft.oa.param.OaTaskUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 工单成员Service实现 + * + * @author 科技小王子 + * @since 2024-09-10 20:57:42 + */ +@Service +public class OaTaskUserServiceImpl extends ServiceImpl implements OaTaskUserService { + + @Override + public PageResult pageRel(OaTaskUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(OaTaskUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public OaTaskUser getByIdRel(Integer taskUserId) { + OaTaskUserParam param = new OaTaskUserParam(); + param.setTaskUserId(taskUserId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/payment/constants/PaymentConstants.java b/src/main/java/com/gxwebsoft/payment/constants/PaymentConstants.java new file mode 100644 index 0000000..80f0354 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/constants/PaymentConstants.java @@ -0,0 +1,244 @@ +package com.gxwebsoft.payment.constants; + +/** + * 支付模块常量类 + * 统一管理支付相关的常量配置 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public class PaymentConstants { + + /** + * 支付状态常量 + */ + public static class Status { + /** 待支付 */ + public static final String PENDING = "PENDING"; + /** 支付成功 */ + public static final String SUCCESS = "SUCCESS"; + /** 支付失败 */ + public static final String FAILED = "FAILED"; + /** 支付取消 */ + public static final String CANCELLED = "CANCELLED"; + /** 支付超时 */ + public static final String TIMEOUT = "TIMEOUT"; + /** 退款成功 */ + public static final String REFUNDED = "REFUNDED"; + } + + /** + * 微信支付相关常量 + */ + public static class Wechat { + /** 货币类型 */ + public static final String CURRENCY = "CNY"; + /** 金额转换倍数(元转分) */ + public static final int AMOUNT_MULTIPLIER = 100; + + /** 支付状态 */ + public static final String PAY_SUCCESS = "SUCCESS"; + public static final String PAY_REFUND = "REFUND"; + public static final String PAY_NOTPAY = "NOTPAY"; + public static final String PAY_CLOSED = "CLOSED"; + public static final String PAY_REVOKED = "REVOKED"; + public static final String PAY_USERPAYING = "USERPAYING"; + public static final String PAY_PAYERROR = "PAYERROR"; + + /** 回调响应 */ + public static final String NOTIFY_SUCCESS = "SUCCESS"; + public static final String NOTIFY_FAIL = "FAIL"; + + /** 通知类型 */ + public static final String EVENT_PAYMENT = "TRANSACTION.SUCCESS"; + public static final String EVENT_REFUND = "REFUND.SUCCESS"; + + /** HTTP头部 */ + public static final String HEADER_SIGNATURE = "Wechatpay-Signature"; + public static final String HEADER_TIMESTAMP = "Wechatpay-Timestamp"; + public static final String HEADER_NONCE = "Wechatpay-Nonce"; + public static final String HEADER_SERIAL = "Wechatpay-Serial"; + public static final String HEADER_REQUEST_ID = "Request-ID"; + } + + /** + * 支付宝相关常量 + */ + public static class Alipay { + /** 货币类型 */ + public static final String CURRENCY = "CNY"; + + /** 支付状态 */ + public static final String PAY_SUCCESS = "TRADE_SUCCESS"; + public static final String PAY_FINISHED = "TRADE_FINISHED"; + public static final String PAY_CLOSED = "TRADE_CLOSED"; + + /** 回调响应 */ + public static final String NOTIFY_SUCCESS = "success"; + public static final String NOTIFY_FAIL = "failure"; + + /** 产品码 */ + public static final String PRODUCT_CODE_WEB = "FAST_INSTANT_TRADE_PAY"; + public static final String PRODUCT_CODE_WAP = "QUICK_WAP_WAY"; + public static final String PRODUCT_CODE_APP = "QUICK_MSECURITY_PAY"; + } + + /** + * 银联支付相关常量 + */ + public static class UnionPay { + /** 货币类型 */ + public static final String CURRENCY = "156"; // 人民币代码 + + /** 支付状态 */ + public static final String PAY_SUCCESS = "00"; + public static final String PAY_FAILED = "01"; + + /** 交易类型 */ + public static final String TXN_TYPE_CONSUME = "01"; // 消费 + public static final String TXN_TYPE_REFUND = "04"; // 退货 + } + + /** + * 缓存键常量 + */ + public static class CacheKey { + /** 支付配置缓存前缀 */ + public static final String PAYMENT_CONFIG = "payment:config:"; + /** 支付订单缓存前缀 */ + public static final String PAYMENT_ORDER = "payment:order:"; + /** 支付锁前缀 */ + public static final String PAYMENT_LOCK = "payment:lock:"; + /** 回调处理锁前缀 */ + public static final String NOTIFY_LOCK = "payment:notify:lock:"; + } + + /** + * 配置相关常量 + */ + public static class Config { + /** 订单超时时间(分钟) */ + public static final int ORDER_TIMEOUT_MINUTES = 30; + /** 订单描述最大长度 */ + public static final int DESCRIPTION_MAX_LENGTH = 127; + /** 最大重试次数 */ + public static final int MAX_RETRY_COUNT = 3; + /** 重试间隔(毫秒) */ + public static final long RETRY_INTERVAL_MS = 1000; + /** 签名有效期(秒) */ + public static final long SIGNATURE_VALID_SECONDS = 300; + } + + /** + * 错误信息常量 + */ + public static class ErrorMessage { + /** 参数错误 */ + public static final String PARAM_ERROR = "参数错误"; + /** 配置未找到 */ + public static final String CONFIG_NOT_FOUND = "支付配置未找到"; + /** 支付方式不支持 */ + public static final String PAYMENT_TYPE_NOT_SUPPORTED = "支付方式不支持"; + /** 金额错误 */ + public static final String AMOUNT_ERROR = "金额错误"; + /** 订单不存在 */ + public static final String ORDER_NOT_FOUND = "订单不存在"; + /** 订单状态错误 */ + public static final String ORDER_STATUS_ERROR = "订单状态错误"; + /** 签名验证失败 */ + public static final String SIGNATURE_ERROR = "签名验证失败"; + /** 网络请求失败 */ + public static final String NETWORK_ERROR = "网络请求失败"; + /** 系统内部错误 */ + public static final String SYSTEM_ERROR = "系统内部错误"; + /** 余额不足 */ + public static final String INSUFFICIENT_BALANCE = "余额不足"; + /** 支付超时 */ + public static final String PAYMENT_TIMEOUT = "支付超时"; + /** 重复支付 */ + public static final String DUPLICATE_PAYMENT = "重复支付"; + } + + /** + * 日志消息常量 + */ + public static class LogMessage { + /** 支付请求开始 */ + public static final String PAYMENT_START = "开始处理支付请求"; + /** 支付请求成功 */ + public static final String PAYMENT_SUCCESS = "支付请求处理成功"; + /** 支付请求失败 */ + public static final String PAYMENT_FAILED = "支付请求处理失败"; + + /** 回调处理开始 */ + public static final String NOTIFY_START = "开始处理支付回调"; + /** 回调处理成功 */ + public static final String NOTIFY_SUCCESS = "支付回调处理成功"; + /** 回调处理失败 */ + public static final String NOTIFY_FAILED = "支付回调处理失败"; + + /** 退款请求开始 */ + public static final String REFUND_START = "开始处理退款请求"; + /** 退款请求成功 */ + public static final String REFUND_SUCCESS = "退款请求处理成功"; + /** 退款请求失败 */ + public static final String REFUND_FAILED = "退款请求处理失败"; + } + + /** + * 正则表达式常量 + */ + public static class Regex { + /** 订单号格式 */ + public static final String ORDER_NO = "^[a-zA-Z0-9_-]{1,32}$"; + /** 金额格式(分) */ + public static final String AMOUNT = "^[1-9]\\d*$"; + /** 手机号格式 */ + public static final String MOBILE = "^1[3-9]\\d{9}$"; + /** 邮箱格式 */ + public static final String EMAIL = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"; + } + + /** + * 时间相关常量 + */ + public static class Time { + /** 配置缓存有效期(秒) */ + public static final long CONFIG_CACHE_SECONDS = 3600; + /** 订单缓存有效期(秒) */ + public static final long ORDER_CACHE_SECONDS = 1800; + /** 支付锁有效期(秒) */ + public static final long PAYMENT_LOCK_SECONDS = 60; + /** 回调锁有效期(秒) */ + public static final long NOTIFY_LOCK_SECONDS = 30; + } + + /** + * 文件相关常量 + */ + public static class File { + /** 证书文件扩展名 */ + public static final String CERT_EXTENSION = ".pem"; + /** 私钥文件后缀 */ + public static final String PRIVATE_KEY_SUFFIX = "_key.pem"; + /** 公钥文件后缀 */ + public static final String PUBLIC_KEY_SUFFIX = "_cert.pem"; + } + + /** + * 环境相关常量 + */ + public static class Environment { + /** 开发环境 */ + public static final String DEV = "dev"; + /** 测试环境 */ + public static final String TEST = "test"; + /** 生产环境 */ + public static final String PROD = "prod"; + } + + // 私有构造函数,防止实例化 + private PaymentConstants() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/constants/WechatPayType.java b/src/main/java/com/gxwebsoft/payment/constants/WechatPayType.java new file mode 100644 index 0000000..68b2d84 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/constants/WechatPayType.java @@ -0,0 +1,81 @@ +package com.gxwebsoft.payment.constants; + +/** + * 微信支付类型常量 + * 定义微信支付的具体实现方式 + * + * @author 科技小王子 + * @since 2025-08-30 + */ +public class WechatPayType { + + /** + * JSAPI支付 - 小程序/公众号内支付 + * 需要用户的openid + */ + public static final String JSAPI = "JSAPI"; + + /** + * Native支付 - 扫码支付 + * 生成二维码供用户扫描支付 + */ + public static final String NATIVE = "NATIVE"; + + /** + * H5支付 - 手机网页支付 + * 在手机浏览器中调起微信支付 + */ + public static final String H5 = "H5"; + + /** + * APP支付 - 移动应用支付 + * 在APP中调起微信支付 + */ + public static final String APP = "APP"; + + /** + * 根据openid自动选择微信支付类型 + * + * @param openid 用户openid + * @return JSAPI 或 NATIVE + */ + public static String getAutoType(String openid) { + return (openid != null && !openid.trim().isEmpty()) ? JSAPI : NATIVE; + } + + /** + * 检查是否为有效的微信支付类型 + * + * @param payType 支付类型 + * @return true表示有效 + */ + public static boolean isValidType(String payType) { + return JSAPI.equals(payType) || NATIVE.equals(payType) || + H5.equals(payType) || APP.equals(payType); + } + + /** + * 获取支付类型描述 + * + * @param payType 支付类型 + * @return 描述文本 + */ + public static String getDescription(String payType) { + if (payType == null) { + return "未知支付类型"; + } + + switch (payType) { + case JSAPI: + return "小程序/公众号支付"; + case NATIVE: + return "扫码支付"; + case H5: + return "手机网页支付"; + case APP: + return "移动应用支付"; + default: + return "未知支付类型: " + payType; + } + } +} diff --git a/src/main/java/com/gxwebsoft/payment/controller/PaymentController.java b/src/main/java/com/gxwebsoft/payment/controller/PaymentController.java new file mode 100644 index 0000000..0d51551 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/controller/PaymentController.java @@ -0,0 +1,360 @@ +package com.gxwebsoft.payment.controller; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.payment.constants.PaymentConstants; +import com.gxwebsoft.payment.dto.PaymentRequest; +import com.gxwebsoft.payment.dto.PaymentResponse; +import com.gxwebsoft.payment.dto.PaymentStatusUpdateRequest; +import com.gxwebsoft.payment.dto.PaymentWithOrderRequest; +import com.gxwebsoft.payment.enums.PaymentType; +import com.gxwebsoft.payment.exception.PaymentException; +import com.gxwebsoft.payment.service.PaymentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 统一支付控制器 + * 提供所有支付方式的统一入口 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Validated +@Tag(name = "统一支付接口", description = "支持所有支付方式的统一支付接口") +@RestController("unifiedPaymentController") +@RequestMapping("/api/payment") +public class PaymentController extends BaseController { + + @Resource(name = "unifiedPaymentServiceImpl") + private PaymentService paymentService; + + @Operation(summary = "创建支付订单", description = "支持微信、支付宝、银联等多种支付方式") + @PostMapping("/create") + public ApiResult createPayment(@Valid @RequestBody PaymentRequest request) { + log.info("收到支付请求: {}", request); + final User loginUser = getLoginUser(); + + if(loginUser == null){ + return fail("请先登录"); + } + + request.setUserId(loginUser.getUserId()); + if(request.getTenantId() == null){ + request.setTenantId(loginUser.getTenantId()); + } + try { + PaymentResponse response = paymentService.createPayment(request); + return this.success("支付订单创建成功", response); + + } catch (PaymentException e) { + log.error("支付订单创建失败: {}", e.getMessage()); + return fail(e.getMessage()); + } catch (Exception e) { + log.error("支付订单创建系统错误: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "创建支付订单(包含订单信息)", description = "统一支付模块:创建订单并发起支付") + @PostMapping("/create-with-order") + public ApiResult createPaymentWithOrder(@Valid @RequestBody PaymentWithOrderRequest request) { + log.info("收到支付与订单创建请求: {}", request); + final User loginUser = getLoginUser(); + + if(loginUser == null){ + return fail("请先登录"); + } + + // 设置用户信息 + if(request.getTenantId() == null){ + request.setTenantId(loginUser.getTenantId()); + } + + try { + PaymentResponse response = paymentService.createPaymentWithOrder(request, loginUser); + return this.success("订单创建并发起支付成功", response); + } catch (PaymentException e) { + log.error("创建支付订单失败: {}", e.getMessage()); + return fail(e.getMessage()); + } catch (Exception e) { + log.error("创建支付订单系统错误: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "查询支付状态", description = "查询指定订单的支付状态") + @GetMapping("/query") + public ApiResult queryPayment( + @Parameter(description = "订单号", required = true) + @RequestParam @NotBlank(message = "订单号不能为空") String orderNo, + + @Parameter(description = "支付类型", required = true) + @RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType, + + @Parameter(description = "租户ID", required = true) + @RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) { + + log.info("查询支付状态: orderNo={}, paymentType={}, tenantId={}", orderNo, paymentType, tenantId); + + // 参数验证 + if (orderNo == null || orderNo.trim().isEmpty()) { + return fail("订单号不能为空"); + } + if (paymentType == null) { + return fail("支付类型不能为空"); + } + if (tenantId == null || tenantId <= 0) { + return fail("租户ID不能为空且必须为正数"); + } + + try { + PaymentResponse response = paymentService.queryPayment(orderNo, paymentType, tenantId); + return this.success("支付状态查询成功", response); + + } catch (PaymentException e) { + log.error("支付状态查询失败: {}", e.getMessage()); + return fail(e.getMessage()); + } catch (Exception e) { + log.error("支付状态查询系统错误: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "申请退款", description = "申请订单退款") + @PostMapping("/refund") + public ApiResult refund( + @Parameter(description = "订单号", required = true) + @RequestParam @NotBlank(message = "订单号不能为空") String orderNo, + + @Parameter(description = "退款单号", required = true) + @RequestParam @NotBlank(message = "退款单号不能为空") String refundNo, + + @Parameter(description = "支付类型", required = true) + @RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType, + + @Parameter(description = "订单总金额", required = true) + @RequestParam @NotNull(message = "订单总金额不能为空") @Positive(message = "订单总金额必须大于0") BigDecimal totalAmount, + + @Parameter(description = "退款金额", required = true) + @RequestParam @NotNull(message = "退款金额不能为空") @Positive(message = "退款金额必须大于0") BigDecimal refundAmount, + + @Parameter(description = "退款原因") + @RequestParam(required = false) String reason, + + @Parameter(description = "租户ID", required = true) + @RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) { + + log.info("申请退款: orderNo={}, refundNo={}, paymentType={}, totalAmount={}, refundAmount={}, tenantId={}", + orderNo, refundNo, paymentType, totalAmount, refundAmount, tenantId); + + try { + PaymentResponse response = paymentService.refund(orderNo, refundNo, paymentType, + totalAmount, refundAmount, reason, tenantId); + return this.success("退款申请成功", response); + + } catch (PaymentException e) { + log.error("退款申请失败: {}", e.getMessage()); + return fail(e.getMessage()); + } catch (Exception e) { + log.error("退款申请系统错误: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "查询退款状态", description = "查询指定退款单的状态") + @GetMapping("/refund/query") + public ApiResult queryRefund( + @Parameter(description = "退款单号", required = true) + @RequestParam @NotBlank(message = "退款单号不能为空") String refundNo, + + @Parameter(description = "支付类型", required = true) + @RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType, + + @Parameter(description = "租户ID", required = true) + @RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) { + + log.info("查询退款状态: refundNo={}, paymentType={}, tenantId={}", refundNo, paymentType, tenantId); + + try { + PaymentResponse response = paymentService.queryRefund(refundNo, paymentType, tenantId); + return this.success("退款状态查询成功", response); + + } catch (PaymentException e) { + log.error("退款状态查询失败: {}", e.getMessage()); + return fail(e.getMessage()); + } catch (Exception e) { + log.error("退款状态查询系统错误: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "关闭订单", description = "关闭未支付的订单") + @PostMapping("/close") + public ApiResult closeOrder( + @Parameter(description = "订单号", required = true) + @RequestParam @NotBlank(message = "订单号不能为空") String orderNo, + + @Parameter(description = "支付类型", required = true) + @RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType, + + @Parameter(description = "租户ID", required = true) + @RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) { + + log.info("关闭订单: orderNo={}, paymentType={}, tenantId={}", orderNo, paymentType, tenantId); + + try { + boolean result = paymentService.closeOrder(orderNo, paymentType, tenantId); + return success(result ? "订单关闭成功" : "订单关闭失败", result); + + } catch (PaymentException e) { + log.error("订单关闭失败: {}", e.getMessage()); + return fail(e.getMessage()); + } catch (Exception e) { + log.error("订单关闭系统错误: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "获取支持的支付类型", description = "获取系统支持的所有支付类型列表") + @GetMapping("/types") + public ApiResult getSupportedPaymentTypes() { + try { + List paymentTypes = paymentService.getSupportedPaymentTypes(); + return this.>success("获取支付类型成功", paymentTypes); + } catch (Exception e) { + log.error("获取支付类型失败: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "获取支付策略信息", description = "获取指定支付类型的策略信息") + @GetMapping("/strategy/{paymentType}") + public ApiResult getPaymentStrategyInfo( + @Parameter(description = "支付类型", required = true) + @PathVariable @NotNull(message = "支付类型不能为空") PaymentType paymentType) { + + try { + Map strategyInfo = paymentService.getPaymentStrategyInfo(paymentType); + if (strategyInfo == null) { + return fail("不支持的支付类型: " + paymentType); + } + return success("获取策略信息成功", strategyInfo); + } catch (Exception e) { + log.error("获取策略信息失败: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "获取所有支付策略信息", description = "获取系统所有支付策略的详细信息") + @GetMapping("/strategies") + public ApiResult getAllPaymentStrategyInfo() { + try { + List> strategiesInfo = paymentService.getAllPaymentStrategyInfo(); + return this.>>success("获取所有策略信息成功", strategiesInfo); + } catch (Exception e) { + log.error("获取所有策略信息失败: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "检查支付类型支持情况", description = "检查指定支付类型的功能支持情况") + @GetMapping("/support/{paymentType}") + public ApiResult checkPaymentTypeSupport( + @Parameter(description = "支付类型", required = true) + @PathVariable @NotNull(message = "支付类型不能为空") PaymentType paymentType) { + + try { + Map support = Map.of( + "supported", paymentService.isPaymentTypeSupported(paymentType), + "refundSupported", paymentService.isRefundSupported(paymentType), + "querySupported", paymentService.isQuerySupported(paymentType), + "closeSupported", paymentService.isCloseSupported(paymentType), + "notifyNeeded", paymentService.isNotifyNeeded(paymentType) + ); + return this.>success("检查支持情况成功", support); + } catch (Exception e) { + log.error("检查支持情况失败: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + } + + @Operation(summary = "手动更新支付状态", description = "用于手动同步支付状态,通常用于异常情况处理") + @PutMapping("/update-status") + public ApiResult updatePaymentStatus(@Valid @RequestBody PaymentStatusUpdateRequest request) { + log.info("收到支付状态更新请求: {}", request); + + try { + // 查询并更新支付状态 + PaymentResponse response = paymentService.queryPayment( + request.getOrderNo(), + PaymentType.WECHAT_NATIVE, + request.getTenantId() + ); + + return this.success("支付状态更新成功", response); + } catch (Exception e) { + log.error("更新支付状态失败: {}", e.getMessage(), e); + return fail("更新支付状态失败: " + e.getMessage()); + } + } + + @Operation(summary = "检查支付配置", description = "检查指定租户的支付配置是否完整") + @GetMapping("/config/check") + public ApiResult checkPaymentConfig( + @Parameter(description = "租户ID", required = true) + @RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) { + + log.info("检查支付配置,租户ID: {}", tenantId); + + try { + Map configStatus = paymentService.checkPaymentConfig(tenantId); + return this.>success("配置检查完成", configStatus); + } catch (Exception e) { + log.error("检查支付配置失败: {}", e.getMessage(), e); + return fail("检查支付配置失败: " + e.getMessage()); + } + } + + @Operation(summary = "查询用户最近的支付订单", description = "当orderNo缺失时,查询用户最近创建的支付订单") + @GetMapping("/query-recent") + public ApiResult queryRecentPayment( + @Parameter(description = "支付类型", required = true) + @RequestParam @NotNull(message = "支付类型不能为空") PaymentType paymentType, + + @Parameter(description = "租户ID", required = true) + @RequestParam @NotNull(message = "租户ID不能为空") @Positive(message = "租户ID必须为正数") Integer tenantId) { + + log.info("查询用户最近支付订单: paymentType={}, tenantId={}", paymentType, tenantId); + + final User loginUser = getLoginUser(); + if(loginUser == null){ + return fail("请先登录"); + } + + try { + // 这里需要实现查询用户最近订单的逻辑 + // 可以通过用户ID和租户ID查询最近创建的订单 + return fail("此功能需要实现查询用户最近订单的业务逻辑"); + + } catch (Exception e) { + log.error("查询用户最近支付订单失败: {}", e.getMessage(), e); + return fail("查询失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/payment/controller/PaymentNotifyController.java b/src/main/java/com/gxwebsoft/payment/controller/PaymentNotifyController.java new file mode 100644 index 0000000..c181514 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/controller/PaymentNotifyController.java @@ -0,0 +1,188 @@ +package com.gxwebsoft.payment.controller; + +import com.gxwebsoft.payment.constants.PaymentConstants; +import com.gxwebsoft.payment.enums.PaymentType; +import com.gxwebsoft.payment.exception.PaymentException; +import com.gxwebsoft.payment.service.PaymentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * 统一支付回调控制器 + * 处理所有支付方式的异步通知回调 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Tag(name = "统一支付回调接口", description = "处理所有支付方式的异步通知回调") +@RestController +@RequestMapping("/api/payment/notify") +public class PaymentNotifyController { + + @Resource + private PaymentService paymentService; + + @Operation(summary = "微信支付回调通知", description = "处理微信支付的异步通知") + @PostMapping("/wechat/{tenantId}") + public String wechatNotify( + @Parameter(description = "租户ID", required = true) + @PathVariable("tenantId") Integer tenantId, + @RequestBody String body, + HttpServletRequest request) { + + log.info("收到微信支付回调通知, 租户ID: {}", tenantId); + + try { + // 提取请求头 + Map headers = extractHeaders(request); + + // 处理回调 + String result = paymentService.handlePaymentNotify(PaymentType.WECHAT_NATIVE, headers, body, tenantId); + + log.info("微信支付回调处理完成, 租户ID: {}, 结果: {}", tenantId, result); + return result; + + } catch (PaymentException e) { + log.error("微信支付回调处理失败, 租户ID: {}, 错误: {}", tenantId, e.getMessage()); + return PaymentConstants.Wechat.NOTIFY_FAIL; + } catch (Exception e) { + log.error("微信支付回调系统错误, 租户ID: {}, 错误: {}", tenantId, e.getMessage(), e); + return PaymentConstants.Wechat.NOTIFY_FAIL; + } + } + + @Operation(summary = "支付宝支付回调通知", description = "处理支付宝支付的异步通知") + @PostMapping("/alipay/{tenantId}") + public String alipayNotify( + @Parameter(description = "租户ID", required = true) + @PathVariable("tenantId") Integer tenantId, + @RequestBody String body, + HttpServletRequest request) { + + log.info("收到支付宝支付回调通知, 租户ID: {}", tenantId); + + try { + // 提取请求头 + Map headers = extractHeaders(request); + + // 处理回调 + String result = paymentService.handlePaymentNotify(PaymentType.ALIPAY, headers, body, tenantId); + + log.info("支付宝支付回调处理完成, 租户ID: {}, 结果: {}", tenantId, result); + return result; + + } catch (PaymentException e) { + log.error("支付宝支付回调处理失败, 租户ID: {}, 错误: {}", tenantId, e.getMessage()); + return PaymentConstants.Alipay.NOTIFY_FAIL; + } catch (Exception e) { + log.error("支付宝支付回调系统错误, 租户ID: {}, 错误: {}", tenantId, e.getMessage(), e); + return PaymentConstants.Alipay.NOTIFY_FAIL; + } + } + + @Operation(summary = "银联支付回调通知", description = "处理银联支付的异步通知") + @PostMapping("/unionpay/{tenantId}") + public String unionPayNotify( + @Parameter(description = "租户ID", required = true) + @PathVariable("tenantId") Integer tenantId, + @RequestBody String body, + HttpServletRequest request) { + + log.info("收到银联支付回调通知, 租户ID: {}", tenantId); + + try { + // 提取请求头 + Map headers = extractHeaders(request); + + // 处理回调 + String result = paymentService.handlePaymentNotify(PaymentType.UNION_PAY, headers, body, tenantId); + + log.info("银联支付回调处理完成, 租户ID: {}, 结果: {}", tenantId, result); + return result; + + } catch (PaymentException e) { + log.error("银联支付回调处理失败, 租户ID: {}, 错误: {}", tenantId, e.getMessage()); + return "failure"; + } catch (Exception e) { + log.error("银联支付回调系统错误, 租户ID: {}, 错误: {}", tenantId, e.getMessage(), e); + return "failure"; + } + } + + @Operation(summary = "通用支付回调通知", description = "处理指定支付类型的异步通知") + @PostMapping("/{paymentType}/{tenantId}") + public String genericNotify( + @Parameter(description = "支付类型", required = true) + @PathVariable("paymentType") PaymentType paymentType, + @Parameter(description = "租户ID", required = true) + @PathVariable("tenantId") Integer tenantId, + @RequestBody String body, + HttpServletRequest request) { + + log.info("收到{}支付回调通知, 租户ID: {}", paymentType.getName(), tenantId); + + try { + // 提取请求头 + Map headers = extractHeaders(request); + + // 处理回调 + String result = paymentService.handlePaymentNotify(paymentType, headers, body, tenantId); + + log.info("{}支付回调处理完成, 租户ID: {}, 结果: {}", paymentType.getName(), tenantId, result); + return result; + + } catch (PaymentException e) { + log.error("{}支付回调处理失败, 租户ID: {}, 错误: {}", paymentType.getName(), tenantId, e.getMessage()); + return getFailureResponse(paymentType); + } catch (Exception e) { + log.error("{}支付回调系统错误, 租户ID: {}, 错误: {}", paymentType.getName(), tenantId, e.getMessage(), e); + return getFailureResponse(paymentType); + } + } + + /** + * 提取HTTP请求头 + */ + private Map extractHeaders(HttpServletRequest request) { + Map headers = new HashMap<>(); + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + String headerValue = request.getHeader(headerName); + headers.put(headerName, headerValue); + } + + // 记录关键头部信息(不记录敏感信息) + log.debug("提取请求头完成, 头部数量: {}", headers.size()); + + return headers; + } + + /** + * 根据支付类型获取失败响应 + */ + private String getFailureResponse(PaymentType paymentType) { + switch (paymentType) { + case WECHAT: + case WECHAT_NATIVE: + return PaymentConstants.Wechat.NOTIFY_FAIL; + case ALIPAY: + return PaymentConstants.Alipay.NOTIFY_FAIL; + case UNION_PAY: + return "failure"; + default: + return "fail"; + } + } +} diff --git a/src/main/java/com/gxwebsoft/payment/dto/PaymentRequest.java b/src/main/java/com/gxwebsoft/payment/dto/PaymentRequest.java new file mode 100644 index 0000000..99c7c34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/dto/PaymentRequest.java @@ -0,0 +1,207 @@ +package com.gxwebsoft.payment.dto; + +import com.gxwebsoft.payment.enums.PaymentChannel; +import com.gxwebsoft.payment.enums.PaymentType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.*; +import java.math.BigDecimal; +import java.util.Map; + +/** + * 统一支付请求DTO + * 支持所有支付方式的统一请求格式 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Data +@Schema(name = "统一支付请求", description = "支持所有支付方式的统一支付请求参数") +public class PaymentRequest { + + @Schema(description = "租户ID", required = true) + @NotNull(message = "租户ID不能为空") + @Positive(message = "租户ID必须为正数") + private Integer tenantId; + + @Schema(description = "用户ID", required = true) + @NotNull(message = "用户ID不能为空") + @Positive(message = "用户ID必须为正数") + private Integer userId; + + @Schema(description = "支付类型", required = true, example = "WECHAT_NATIVE") + @NotNull(message = "支付类型不能为空") + private PaymentType paymentType; + + @Schema(description = "支付渠道", example = "wechat_native") + private PaymentChannel paymentChannel; + + @Schema(description = "支付金额", required = true, example = "0.01") + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0.01", message = "支付金额必须大于0.01元") + @DecimalMax(value = "999999.99", message = "支付金额不能超过999999.99元") + @Digits(integer = 6, fraction = 2, message = "支付金额格式不正确,最多6位整数2位小数") + private BigDecimal amount; + + @Schema(description = "订单号(可选,不提供则自动生成)") + @Size(max = 32, message = "订单号不能超过32个字符") + @Pattern(regexp = "^[a-zA-Z0-9_-]*$", message = "订单号只能包含字母、数字、下划线和横线") + private String orderNo; + + @Schema(description = "订单标题", required = true) + @NotBlank(message = "订单标题不能为空") + @Size(max = 127, message = "订单标题不能超过127个字符") + private String subject; + + @Schema(description = "订单描述") + @Size(max = 500, message = "订单描述不能超过500个字符") + private String description; + + @Schema(description = "商品ID") + @Positive(message = "商品ID必须为正数") + private Integer goodsId; + + @Schema(description = "购买数量", example = "1") + @Min(value = 1, message = "购买数量必须大于0") + @Max(value = 9999, message = "购买数量不能超过9999") + private Integer quantity = 1; + + @Schema(description = "订单类型", example = "0") + @Min(value = 0, message = "订单类型不能为负数") + private Integer orderType = 0; + + @Schema(description = "客户端IP地址") + private String clientIp; + + @Schema(description = "用户代理") + private String userAgent; + + @Schema(description = "回调通知URL") + private String notifyUrl; + + @Schema(description = "支付成功跳转URL") + private String returnUrl; + + @Schema(description = "支付取消跳转URL") + private String cancelUrl; + + @Schema(description = "订单超时时间(分钟)", example = "30") + @Min(value = 1, message = "订单超时时间必须大于0分钟") + @Max(value = 1440, message = "订单超时时间不能超过1440分钟(24小时)") + private Integer timeoutMinutes = 30; + + @Schema(description = "买家备注") + @Size(max = 500, message = "买家备注不能超过500个字符") + private String buyerRemarks; + + @Schema(description = "商户备注") + @Size(max = 500, message = "商户备注不能超过500个字符") + private String merchantRemarks; + + @Schema(description = "收货地址ID") + @Positive(message = "收货地址ID必须为正数") + private Integer addressId; + + @Schema(description = "扩展参数") + private Map extraParams; + + // 微信支付特有参数 + @Schema(description = "微信OpenID(JSAPI支付必填)") + private String openId; + + @Schema(description = "微信UnionID") + private String unionId; + + // 支付宝特有参数 + @Schema(description = "支付宝用户ID") + private String alipayUserId; + + @Schema(description = "花呗分期数") + private Integer hbFqNum; + + // 银联支付特有参数 + @Schema(description = "银行卡号") + private String cardNo; + + @Schema(description = "银行代码") + private String bankCode; + + /** + * 获取有效的支付渠道 + */ + public PaymentChannel getEffectivePaymentChannel() { + if (paymentChannel != null) { + return paymentChannel; + } + return PaymentChannel.getDefaultByPaymentType(paymentType); + } + + /** + * 获取有效的订单描述 + */ + public String getEffectiveDescription() { + if (description != null && !description.trim().isEmpty()) { + return description.trim(); + } + return subject; + } + + /** + * 获取格式化的金额字符串 + */ + public String getFormattedAmount() { + if (amount == null) { + return "0.00"; + } + return String.format("%.2f", amount); + } + + /** + * 转换为分(微信支付API需要) + */ + public Integer getAmountInCents() { + if (amount == null) { + return 0; + } + return amount.multiply(new BigDecimal(100)).intValue(); + } + + /** + * 验证必要参数是否完整 + */ + public boolean isValid() { + return tenantId != null && tenantId > 0 + && userId != null && userId > 0 + && paymentType != null + && amount != null && amount.compareTo(BigDecimal.ZERO) > 0 + && subject != null && !subject.trim().isEmpty(); + } + + /** + * 验证微信JSAPI支付参数 + */ + public boolean isValidForWechatJsapi() { + return isValid() && paymentType.isWechatPay() && openId != null && !openId.trim().isEmpty(); + } + + /** + * 验证支付宝支付参数 + */ + public boolean isValidForAlipay() { + return isValid() && paymentType == PaymentType.ALIPAY; + } + + /** + * 获取订单超时时间(秒) + */ + public long getTimeoutSeconds() { + return timeoutMinutes * 60L; + } + + @Override + public String toString() { + return String.format("PaymentRequest{tenantId=%d, userId=%d, paymentType=%s, amount=%s, orderNo='%s', subject='%s'}", + tenantId, userId, paymentType, getFormattedAmount(), orderNo, subject); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/dto/PaymentResponse.java b/src/main/java/com/gxwebsoft/payment/dto/PaymentResponse.java new file mode 100644 index 0000000..dea992f --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/dto/PaymentResponse.java @@ -0,0 +1,294 @@ +package com.gxwebsoft.payment.dto; + +import com.gxwebsoft.payment.enums.PaymentChannel; +import com.gxwebsoft.payment.enums.PaymentStatus; +import com.gxwebsoft.payment.enums.PaymentType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 统一支付响应DTO + * 支持所有支付方式的统一响应格式 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Data +@Schema(name = "统一支付响应", description = "支持所有支付方式的统一支付响应") +public class PaymentResponse { + + @Schema(description = "是否成功") + private Boolean success; + + @Schema(description = "错误代码") + private String errorCode; + + @Schema(description = "错误信息") + private String errorMessage; + + @Schema(description = "订单号") + private String orderNo; + + @Schema(description = "第三方交易号") + private String transactionId; + + @Schema(description = "支付类型") + private PaymentType paymentType; + + @Schema(description = "支付渠道") + private PaymentChannel paymentChannel; + + @Schema(description = "支付状态") + private PaymentStatus paymentStatus; + + @Schema(description = "支付金额") + private BigDecimal amount; + + @Schema(description = "实际支付金额") + private BigDecimal paidAmount; + + @Schema(description = "货币类型") + private String currency; + + @Schema(description = "租户ID") + private Integer tenantId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "支付时间") + private LocalDateTime payTime; + + @Schema(description = "过期时间") + private LocalDateTime expireTime; + + // 微信支付特有字段 + @Schema(description = "微信支付二维码URL(Native支付)") + private String codeUrl; + + @Schema(description = "微信支付参数(JSAPI支付)") + private WechatPayParams wechatPayParams; + + @Schema(description = "微信H5支付URL") + private String h5Url; + + // 支付宝特有字段 + @Schema(description = "支付宝支付表单(网页支付)") + private String alipayForm; + + @Schema(description = "支付宝支付URL(手机网站支付)") + private String alipayUrl; + + @Schema(description = "支付宝支付参数(APP支付)") + private String alipayParams; + + // 银联支付特有字段 + @Schema(description = "银联支付表单") + private String unionPayForm; + + @Schema(description = "银联支付URL") + private String unionPayUrl; + + @Schema(description = "扩展参数") + private Map extraParams; + + /** + * 微信支付参数 + */ + @Data + @Schema(name = "微信支付参数", description = "微信JSAPI支付所需参数") + public static class WechatPayParams { + @Schema(description = "应用ID") + private String appId; + + @Schema(description = "时间戳") + private String timeStamp; + + @Schema(description = "随机字符串") + private String nonceStr; + + @Schema(description = "订单详情扩展字符串") + private String packageValue; + + @Schema(description = "签名方式") + private String signType; + + @Schema(description = "签名") + private String paySign; + } + + /** + * 创建成功响应 + */ + public static PaymentResponse success(String orderNo, PaymentType paymentType) { + PaymentResponse response = new PaymentResponse(); + response.setSuccess(true); + response.setOrderNo(orderNo); + response.setPaymentType(paymentType); + response.setPaymentStatus(PaymentStatus.PENDING); + response.setCreateTime(LocalDateTime.now()); + return response; + } + + /** + * 创建失败响应 + */ + public static PaymentResponse failure(String errorCode, String errorMessage) { + PaymentResponse response = new PaymentResponse(); + response.setSuccess(false); + response.setErrorCode(errorCode); + response.setErrorMessage(errorMessage); + return response; + } + + /** + * 创建微信Native支付响应 + */ + public static PaymentResponse wechatNative(String orderNo, String codeUrl, BigDecimal amount, Integer tenantId) { + PaymentResponse response = success(orderNo, PaymentType.WECHAT_NATIVE); + response.setCodeUrl(codeUrl); + response.setPaymentChannel(PaymentChannel.WECHAT_NATIVE); + response.setAmount(amount); + response.setTenantId(tenantId); + response.setCurrency("CNY"); + return response; + } + + /** + * 创建微信JSAPI支付响应 + */ + public static PaymentResponse wechatJsapi(String orderNo, WechatPayParams payParams, BigDecimal amount, Integer tenantId) { + PaymentResponse response = success(orderNo, PaymentType.WECHAT); + response.setWechatPayParams(payParams); + response.setPaymentChannel(PaymentChannel.WECHAT_JSAPI); + response.setAmount(amount); + response.setTenantId(tenantId); + response.setCurrency("CNY"); + return response; + } + + /** + * 创建微信H5支付响应 + */ + public static PaymentResponse wechatH5(String orderNo, String h5Url, BigDecimal amount, Integer tenantId) { + PaymentResponse response = success(orderNo, PaymentType.WECHAT); + response.setH5Url(h5Url); + response.setPaymentChannel(PaymentChannel.WECHAT_H5); + response.setAmount(amount); + response.setTenantId(tenantId); + response.setCurrency("CNY"); + return response; + } + + /** + * 创建支付宝网页支付响应 + */ + public static PaymentResponse alipayWeb(String orderNo, String alipayForm, BigDecimal amount, Integer tenantId) { + PaymentResponse response = success(orderNo, PaymentType.ALIPAY); + response.setAlipayForm(alipayForm); + response.setPaymentChannel(PaymentChannel.ALIPAY_WEB); + response.setAmount(amount); + response.setTenantId(tenantId); + response.setCurrency("CNY"); + return response; + } + + /** + * 创建支付宝手机网站支付响应 + */ + public static PaymentResponse alipayWap(String orderNo, String alipayUrl, BigDecimal amount, Integer tenantId) { + PaymentResponse response = success(orderNo, PaymentType.ALIPAY); + response.setAlipayUrl(alipayUrl); + response.setPaymentChannel(PaymentChannel.ALIPAY_WAP); + response.setAmount(amount); + response.setTenantId(tenantId); + response.setCurrency("CNY"); + return response; + } + + /** + * 创建支付宝APP支付响应 + */ + public static PaymentResponse alipayApp(String orderNo, String alipayParams, BigDecimal amount, Integer tenantId) { + PaymentResponse response = success(orderNo, PaymentType.ALIPAY); + response.setAlipayParams(alipayParams); + response.setPaymentChannel(PaymentChannel.ALIPAY_APP); + response.setAmount(amount); + response.setTenantId(tenantId); + response.setCurrency("CNY"); + return response; + } + + /** + * 创建余额支付响应 + */ + public static PaymentResponse balance(String orderNo, BigDecimal amount, Integer tenantId, Integer userId) { + PaymentResponse response = success(orderNo, PaymentType.BALANCE); + response.setPaymentChannel(PaymentChannel.BALANCE); + response.setPaymentStatus(PaymentStatus.SUCCESS); + response.setAmount(amount); + response.setPaidAmount(amount); + response.setTenantId(tenantId); + response.setUserId(userId); + response.setCurrency("CNY"); + response.setPayTime(LocalDateTime.now()); + return response; + } + + /** + * 判断是否为成功响应 + */ + public boolean isSuccess() { + return Boolean.TRUE.equals(success); + } + + /** + * 判断是否需要用户进一步操作 + */ + public boolean needUserAction() { + return isSuccess() && paymentStatus == PaymentStatus.PENDING; + } + + /** + * 获取支付结果描述 + */ + public String getResultDescription() { + if (!isSuccess()) { + return errorMessage != null ? errorMessage : "支付失败"; + } + + if (paymentStatus == null) { + return "支付状态未知"; + } + + switch (paymentStatus) { + case SUCCESS: + return "支付成功"; + case PENDING: + return "等待支付"; + case PROCESSING: + return "支付处理中"; + case FAILED: + return "支付失败"; + case CANCELLED: + return "支付已取消"; + case TIMEOUT: + return "支付超时"; + default: + return paymentStatus.getName(); + } + } + + @Override + public String toString() { + return String.format("PaymentResponse{success=%s, orderNo='%s', paymentType=%s, paymentStatus=%s, amount=%s}", + success, orderNo, paymentType, paymentStatus, amount); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/dto/PaymentStatusUpdateRequest.java b/src/main/java/com/gxwebsoft/payment/dto/PaymentStatusUpdateRequest.java new file mode 100644 index 0000000..5cee3bc --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/dto/PaymentStatusUpdateRequest.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.payment.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; + +/** + * 支付状态更新请求DTO + * 用于手动更新支付状态的请求参数 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Data +@Schema(name = "支付状态更新请求", description = "用于手动更新支付状态") +public class PaymentStatusUpdateRequest { + + @Schema(description = "订单号", required = true, example = "ORDER_1756544921075") + @NotBlank(message = "订单号不能为空") + private String orderNo; + + @Schema(description = "租户ID", required = true, example = "10398") + @NotNull(message = "租户ID不能为空") + @Positive(message = "租户ID必须为正数") + private Integer tenantId; + + @Schema(description = "第三方交易号", example = "4200001234567890123") + private String transactionId; + + @Schema(description = "支付时间", example = "2025-01-26T10:30:00") + private String payTime; + + @Override + public String toString() { + return String.format("PaymentStatusUpdateRequest{orderNo='%s', tenantId=%d, transactionId='%s', payTime='%s'}", + orderNo, tenantId, transactionId, payTime); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/dto/PaymentWithOrderRequest.java b/src/main/java/com/gxwebsoft/payment/dto/PaymentWithOrderRequest.java new file mode 100644 index 0000000..06ee407 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/dto/PaymentWithOrderRequest.java @@ -0,0 +1,158 @@ +package com.gxwebsoft.payment.dto; + +import com.gxwebsoft.payment.enums.PaymentType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.*; +import java.math.BigDecimal; +import java.util.List; + +/** + * 支付与订单创建请求DTO + * 用于统一支付模块中的订单创建和支付 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Data +@Schema(name = "PaymentWithOrderRequest", description = "支付与订单创建请求") +public class PaymentWithOrderRequest { + + // ========== 支付相关字段 ========== + + @Schema(description = "支付类型", required = true) + @NotNull(message = "支付类型不能为空") + private PaymentType paymentType; + + @Schema(description = "支付金额", required = true) + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0.01", message = "支付金额必须大于0") + @Digits(integer = 10, fraction = 2, message = "支付金额格式不正确") + private BigDecimal amount; + + @Schema(description = "订单标题", required = true) + @NotBlank(message = "订单标题不能为空") + @Size(max = 60, message = "订单标题长度不能超过60个字符") + private String subject; + + @Schema(description = "订单描述") + @Size(max = 500, message = "订单描述长度不能超过500个字符") + private String description; + + @Schema(description = "租户ID", required = true) + @NotNull(message = "租户ID不能为空") + @Positive(message = "租户ID必须为正数") + private Integer tenantId; + + // ========== 订单相关字段 ========== + + @Schema(description = "订单信息", required = true) + @Valid + @NotNull(message = "订单信息不能为空") + private OrderInfo orderInfo; + + /** + * 订单信息 + */ + @Data + @Schema(name = "OrderInfo", description = "订单信息") + public static class OrderInfo { + + @Schema(description = "订单类型,0商城订单 1预定订单/外卖 2会员卡") + @NotNull(message = "订单类型不能为空") + @Min(value = 0, message = "订单类型值无效") + @Max(value = 2, message = "订单类型值无效") + private Integer type; + + @Schema(description = "收货人姓名") + @Size(max = 50, message = "收货人姓名长度不能超过50个字符") + private String realName; + + @Schema(description = "收货地址") + @Size(max = 200, message = "收货地址长度不能超过200个字符") + private String address; + + @Schema(description = "关联收货地址ID") + private Integer addressId; + + @Schema(description = "快递/自提,0快递 1自提") + private Integer deliveryType; + + @Schema(description = "下单渠道,0小程序预定 1俱乐部训练场 3活动订场") + private Integer channel; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "使用的优惠券ID") + private Integer couponId; + + @Schema(description = "备注") + @Size(max = 500, message = "备注长度不能超过500字符") + private String comments; + + @Schema(description = "订单商品列表", required = true) + @Valid + @NotEmpty(message = "订单商品列表不能为空") + private List goodsItems; + } + + /** + * 订单商品项 + */ + @Data + @Schema(name = "OrderGoodsItem", description = "订单商品项") + public static class OrderGoodsItem { + + @Schema(description = "商品ID", required = true) + @NotNull(message = "商品ID不能为空") + @Positive(message = "商品ID必须为正数") + private Integer goodsId; + + @Schema(description = "商品SKU ID") + private Integer skuId; + + @Schema(description = "商品数量", required = true) + @NotNull(message = "商品数量不能为空") + @Min(value = 1, message = "商品数量必须大于0") + private Integer quantity; + + @Schema(description = "规格信息,如:颜色:红色|尺寸:L") + private String specInfo; + } + + /** + * 获取格式化的金额字符串 + */ + public String getFormattedAmount() { + if (amount == null) { + return "0.00"; + } + return String.format("%.2f", amount); + } + + /** + * 验证订单商品总金额是否与支付金额一致 + */ + public boolean isAmountConsistent() { + if (amount == null || orderInfo == null || orderInfo.getGoodsItems() == null) { + return false; + } + + // 这里可以添加商品金额计算逻辑 + // 实际实现时需要查询数据库获取商品价格 + return true; + } + + @Override + public String toString() { + return String.format("PaymentWithOrderRequest{paymentType=%s, amount=%s, subject='%s', tenantId=%d, goodsCount=%d}", + paymentType, getFormattedAmount(), subject, tenantId, + orderInfo != null && orderInfo.getGoodsItems() != null ? orderInfo.getGoodsItems().size() : 0); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/enums/PaymentChannel.java b/src/main/java/com/gxwebsoft/payment/enums/PaymentChannel.java new file mode 100644 index 0000000..f7396a0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/enums/PaymentChannel.java @@ -0,0 +1,159 @@ +package com.gxwebsoft.payment.enums; + +/** + * 支付渠道枚举 + * 定义具体的支付渠道类型 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public enum PaymentChannel { + + /** 微信JSAPI支付 */ + WECHAT_JSAPI("wechat_jsapi", "微信JSAPI支付", PaymentType.WECHAT), + + /** 微信Native支付 */ + WECHAT_NATIVE("wechat_native", "微信Native支付", PaymentType.WECHAT_NATIVE), + + /** 微信H5支付 */ + WECHAT_H5("wechat_h5", "微信H5支付", PaymentType.WECHAT), + + /** 微信APP支付 */ + WECHAT_APP("wechat_app", "微信APP支付", PaymentType.WECHAT), + + /** 微信小程序支付 */ + WECHAT_MINI("wechat_mini", "微信小程序支付", PaymentType.WECHAT), + + /** 支付宝网页支付 */ + ALIPAY_WEB("alipay_web", "支付宝网页支付", PaymentType.ALIPAY), + + /** 支付宝手机网站支付 */ + ALIPAY_WAP("alipay_wap", "支付宝手机网站支付", PaymentType.ALIPAY), + + /** 支付宝APP支付 */ + ALIPAY_APP("alipay_app", "支付宝APP支付", PaymentType.ALIPAY), + + /** 支付宝小程序支付 */ + ALIPAY_MINI("alipay_mini", "支付宝小程序支付", PaymentType.ALIPAY), + + /** 银联网关支付 */ + UNION_WEB("union_web", "银联网关支付", PaymentType.UNION_PAY), + + /** 银联手机支付 */ + UNION_WAP("union_wap", "银联手机支付", PaymentType.UNION_PAY), + + /** 余额支付 */ + BALANCE("balance", "余额支付", PaymentType.BALANCE), + + /** 现金支付 */ + CASH("cash", "现金支付", PaymentType.CASH), + + /** POS机支付 */ + POS("pos", "POS机支付", PaymentType.POS); + + private final String code; + private final String name; + private final PaymentType paymentType; + + PaymentChannel(String code, String name, PaymentType paymentType) { + this.code = code; + this.name = name; + this.paymentType = paymentType; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + public PaymentType getPaymentType() { + return paymentType; + } + + /** + * 根据代码获取支付渠道 + */ + public static PaymentChannel getByCode(String code) { + if (code == null) { + return null; + } + for (PaymentChannel channel : values()) { + if (channel.code.equals(code)) { + return channel; + } + } + return null; + } + + /** + * 根据支付类型获取默认渠道 + */ + public static PaymentChannel getDefaultByPaymentType(PaymentType paymentType) { + if (paymentType == null) { + return null; + } + + switch (paymentType) { + case WECHAT: + return WECHAT_JSAPI; + case WECHAT_NATIVE: + return WECHAT_NATIVE; + case ALIPAY: + return ALIPAY_WEB; + case UNION_PAY: + return UNION_WEB; + case BALANCE: + return BALANCE; + case CASH: + return CASH; + case POS: + return POS; + default: + return null; + } + } + + /** + * 是否为微信支付渠道 + */ + public boolean isWechatChannel() { + return paymentType.isWechatPay(); + } + + /** + * 是否为支付宝支付渠道 + */ + public boolean isAlipayChannel() { + return paymentType == PaymentType.ALIPAY; + } + + /** + * 是否为银联支付渠道 + */ + public boolean isUnionPayChannel() { + return paymentType == PaymentType.UNION_PAY; + } + + /** + * 是否为第三方支付渠道 + */ + public boolean isThirdPartyChannel() { + return paymentType.isThirdPartyPay(); + } + + /** + * 是否支持退款 + */ + public boolean supportRefund() { + return isThirdPartyChannel(); + } + + @Override + public String toString() { + return String.format("PaymentChannel{code='%s', name='%s', paymentType=%s}", + code, name, paymentType); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/enums/PaymentStatus.java b/src/main/java/com/gxwebsoft/payment/enums/PaymentStatus.java new file mode 100644 index 0000000..809bd9a --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/enums/PaymentStatus.java @@ -0,0 +1,141 @@ +package com.gxwebsoft.payment.enums; + +/** + * 支付状态枚举 + * 定义支付过程中的各种状态 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public enum PaymentStatus { + + /** 待支付 */ + PENDING(0, "待支付", "PENDING"), + + /** 支付中 */ + PROCESSING(1, "支付中", "PROCESSING"), + + /** 支付成功 */ + SUCCESS(2, "支付成功", "SUCCESS"), + + /** 支付失败 */ + FAILED(3, "支付失败", "FAILED"), + + /** 支付取消 */ + CANCELLED(4, "支付取消", "CANCELLED"), + + /** 支付超时 */ + TIMEOUT(5, "支付超时", "TIMEOUT"), + + /** 退款中 */ + REFUNDING(6, "退款中", "REFUNDING"), + + /** 退款成功 */ + REFUNDED(7, "退款成功", "REFUNDED"), + + /** 退款失败 */ + REFUND_FAILED(8, "退款失败", "REFUND_FAILED"), + + /** 部分退款 */ + PARTIAL_REFUNDED(9, "部分退款", "PARTIAL_REFUNDED"); + + private final Integer code; + private final String name; + private final String status; + + PaymentStatus(Integer code, String name, String status) { + this.code = code; + this.name = name; + this.status = status; + } + + public Integer getCode() { + return code; + } + + public String getName() { + return name; + } + + public String getStatus() { + return status; + } + + /** + * 根据代码获取支付状态 + */ + public static PaymentStatus getByCode(Integer code) { + if (code == null) { + return null; + } + for (PaymentStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + return null; + } + + /** + * 根据状态字符串获取支付状态 + */ + public static PaymentStatus getByStatus(String status) { + if (status == null) { + return null; + } + for (PaymentStatus paymentStatus : values()) { + if (paymentStatus.status.equals(status)) { + return paymentStatus; + } + } + return null; + } + + /** + * 是否为最终状态(不会再变化) + */ + public boolean isFinalStatus() { + return this == SUCCESS || this == FAILED || this == CANCELLED || + this == TIMEOUT || this == REFUNDED || this == REFUND_FAILED; + } + + /** + * 是否为成功状态 + */ + public boolean isSuccessStatus() { + return this == SUCCESS; + } + + /** + * 是否为失败状态 + */ + public boolean isFailedStatus() { + return this == FAILED || this == CANCELLED || this == TIMEOUT || this == REFUND_FAILED; + } + + /** + * 是否为退款相关状态 + */ + public boolean isRefundStatus() { + return this == REFUNDING || this == REFUNDED || this == REFUND_FAILED || this == PARTIAL_REFUNDED; + } + + /** + * 是否可以退款 + */ + public boolean canRefund() { + return this == SUCCESS || this == PARTIAL_REFUNDED; + } + + /** + * 是否可以取消 + */ + public boolean canCancel() { + return this == PENDING || this == PROCESSING; + } + + @Override + public String toString() { + return String.format("PaymentStatus{code=%d, name='%s', status='%s'}", code, name, status); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/enums/PaymentType.java b/src/main/java/com/gxwebsoft/payment/enums/PaymentType.java new file mode 100644 index 0000000..946b7ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/enums/PaymentType.java @@ -0,0 +1,224 @@ +package com.gxwebsoft.payment.enums; + +/** + * 支付类型枚举 + * 定义系统支持的所有支付方式 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public enum PaymentType { + + /** 余额支付 */ + BALANCE(0, "余额支付", "balance"), + + /** 微信支付(包含JSAPI和Native) */ + WECHAT(1, "微信支付", "wechat"), + + /** 支付宝支付 */ + ALIPAY(2, "支付宝支付", "alipay"), + + /** 银联支付 */ + UNION_PAY(3, "银联支付", "union_pay"), + + /** 现金支付 */ + CASH(4, "现金支付", "cash"), + + /** POS机支付 */ + POS(5, "POS机支付", "pos"), + + /** 免费 */ + FREE(6, "免费", "free"), + + /** 积分支付 */ + POINTS(7, "积分支付", "points"), + + // ========== 已废弃的支付方式(保留用于数据兼容) ========== + + /** @deprecated 微信Native支付 - 已合并到WECHAT */ + @Deprecated + WECHAT_NATIVE(102, "微信Native支付", "wechat_native"), + + /** @deprecated 会员卡支付 - 建议使用余额支付 */ + @Deprecated + MEMBER_CARD_OLD(8, "会员卡支付", "member_card"), + + /** @deprecated VIP月卡 - 建议使用余额支付 */ + @Deprecated + VIP_MONTHLY(9, "VIP月卡", "vip_monthly"), + + /** @deprecated VIP年卡 - 建议使用余额支付 */ + @Deprecated + VIP_YEARLY(10, "VIP年卡", "vip_yearly"), + + /** @deprecated VIP次卡 - 建议使用余额支付 */ + @Deprecated + VIP_COUNT(11, "VIP次卡", "vip_count"), + + /** @deprecated 免费(旧编号) - 已迁移到新编号6 */ + @Deprecated + FREE_OLD(12, "免费", "free"), + + /** @deprecated VIP充值卡 - 建议使用余额支付 */ + @Deprecated + VIP_RECHARGE(13, "VIP充值卡", "vip_recharge"), + + /** @deprecated IC充值卡 - 建议使用余额支付 */ + @Deprecated + IC_RECHARGE(14, "IC充值卡", "ic_recharge"), + + /** @deprecated 积分支付(旧编号) - 已迁移到新编号7 */ + @Deprecated + POINTS_OLD(15, "积分支付", "points"), + + /** @deprecated VIP季卡 - 建议使用余额支付 */ + @Deprecated + VIP_QUARTERLY(16, "VIP季卡", "vip_quarterly"), + + /** @deprecated IC月卡 - 建议使用余额支付 */ + @Deprecated + IC_MONTHLY(17, "IC月卡", "ic_monthly"), + + /** @deprecated IC年卡 - 建议使用余额支付 */ + @Deprecated + IC_YEARLY(18, "IC年卡", "ic_yearly"), + + /** @deprecated IC次卡 - 建议使用余额支付 */ + @Deprecated + IC_COUNT(19, "IC次卡", "ic_count"), + + /** @deprecated IC季卡 - 建议使用余额支付 */ + @Deprecated + IC_QUARTERLY(20, "IC季卡", "ic_quarterly"), + + /** @deprecated 代付 - 建议通过业务逻辑实现 */ + @Deprecated + PROXY_PAY(21, "代付", "proxy_pay"), + + /** @deprecated 支付宝(旧编号) - 已迁移到新编号2 */ + @Deprecated + ALIPAY_OLD(22, "支付宝支付", "alipay"), + + /** @deprecated 银联支付(旧编号) - 已迁移到新编号3 */ + @Deprecated + UNION_PAY_OLD(23, "银联支付", "union_pay"); + + private final Integer code; + private final String name; + private final String channel; + + PaymentType(Integer code, String name, String channel) { + this.code = code; + this.name = name; + this.channel = channel; + } + + public Integer getCode() { + return code; + } + + public String getName() { + return name; + } + + public String getChannel() { + return channel; + } + + /** + * 根据代码获取支付类型 + */ + public static PaymentType getByCode(Integer code) { + if (code == null) { + return null; + } + for (PaymentType type : values()) { + if (type.code.equals(code)) { + return type; + } + } + return null; + } + + /** + * 根据渠道获取支付类型 + */ + public static PaymentType getByChannel(String channel) { + if (channel == null) { + return null; + } + for (PaymentType type : values()) { + if (type.channel.equals(channel)) { + return type; + } + } + return null; + } + + /** + * 是否为微信支付类型 + */ + public boolean isWechatPay() { + return this == WECHAT || this == WECHAT_NATIVE; + } + + /** + * 获取微信支付的具体类型 + * @param openid 用户openid + * @return JSAPI 或 NATIVE + */ + public String getWechatPayType(String openid) { + if (!isWechatPay()) { + return null; + } + + // 有openid使用JSAPI,无openid使用Native + return (openid != null && !openid.trim().isEmpty()) ? "JSAPI" : "NATIVE"; + } + + /** + * 是否为第三方支付 + */ + public boolean isThirdPartyPay() { + return isWechatPay() || this == ALIPAY || this == UNION_PAY; + } + + /** + * 是否需要在线支付 + */ + public boolean isOnlinePay() { + return isThirdPartyPay(); + } + + /** + * 是否为卡类支付(已废弃的支付方式) + * @deprecated 卡类支付已废弃,建议使用余额支付 + */ + @Deprecated + public boolean isCardPay() { + return this == MEMBER_CARD_OLD || + this == VIP_MONTHLY || this == VIP_YEARLY || this == VIP_COUNT || this == VIP_QUARTERLY || + this == IC_MONTHLY || this == IC_YEARLY || this == IC_COUNT || this == IC_QUARTERLY || + this == VIP_RECHARGE; + } + + /** + * 是否为推荐使用的核心支付方式 + */ + public boolean isCorePaymentType() { + return this == BALANCE || this == WECHAT || this == ALIPAY || this == UNION_PAY || + this == CASH || this == POS || this == FREE || this == POINTS; + } + + /** + * 是否为已废弃的支付方式 + */ + public boolean isDeprecated() { + return !isCorePaymentType(); + } + + @Override + public String toString() { + return String.format("PaymentType{code=%d, name='%s', channel='%s'}", code, name, channel); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/exception/PaymentException.java b/src/main/java/com/gxwebsoft/payment/exception/PaymentException.java new file mode 100644 index 0000000..35d2ac3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/exception/PaymentException.java @@ -0,0 +1,221 @@ +package com.gxwebsoft.payment.exception; + +import com.gxwebsoft.payment.enums.PaymentType; + +/** + * 支付异常基类 + * 统一处理支付相关的业务异常 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public class PaymentException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * 错误代码 + */ + private String errorCode; + + /** + * 支付类型 + */ + private PaymentType paymentType; + + /** + * 租户ID + */ + private Integer tenantId; + + /** + * 订单号 + */ + private String orderNo; + + public PaymentException(String message) { + super(message); + } + + public PaymentException(String message, Throwable cause) { + super(message, cause); + } + + public PaymentException(String errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + public PaymentException(String errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + public PaymentException(String errorCode, String message, PaymentType paymentType) { + super(message); + this.errorCode = errorCode; + this.paymentType = paymentType; + } + + public PaymentException(String errorCode, String message, PaymentType paymentType, Integer tenantId) { + super(message); + this.errorCode = errorCode; + this.paymentType = paymentType; + this.tenantId = tenantId; + } + + public PaymentException(String errorCode, String message, PaymentType paymentType, Integer tenantId, String orderNo) { + super(message); + this.errorCode = errorCode; + this.paymentType = paymentType; + this.tenantId = tenantId; + this.orderNo = orderNo; + } + + // Getters and Setters + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public PaymentType getPaymentType() { + return paymentType; + } + + public void setPaymentType(PaymentType paymentType) { + this.paymentType = paymentType; + } + + public Integer getTenantId() { + return tenantId; + } + + public void setTenantId(Integer tenantId) { + this.tenantId = tenantId; + } + + public String getOrderNo() { + return orderNo; + } + + public void setOrderNo(String orderNo) { + this.orderNo = orderNo; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("PaymentException{"); + if (errorCode != null) { + sb.append("errorCode='").append(errorCode).append("', "); + } + if (paymentType != null) { + sb.append("paymentType=").append(paymentType).append(", "); + } + if (tenantId != null) { + sb.append("tenantId=").append(tenantId).append(", "); + } + if (orderNo != null) { + sb.append("orderNo='").append(orderNo).append("', "); + } + sb.append("message='").append(getMessage()).append("'"); + sb.append("}"); + return sb.toString(); + } + + /** + * 支付错误代码常量 + */ + public static class ErrorCode { + /** 参数错误 */ + public static final String PARAM_ERROR = "PARAM_ERROR"; + /** 配置错误 */ + public static final String CONFIG_ERROR = "CONFIG_ERROR"; + /** 证书错误 */ + public static final String CERTIFICATE_ERROR = "CERTIFICATE_ERROR"; + /** 网络错误 */ + public static final String NETWORK_ERROR = "NETWORK_ERROR"; + /** 签名错误 */ + public static final String SIGNATURE_ERROR = "SIGNATURE_ERROR"; + /** 金额错误 */ + public static final String AMOUNT_ERROR = "AMOUNT_ERROR"; + /** 订单错误 */ + public static final String ORDER_ERROR = "ORDER_ERROR"; + /** 状态错误 */ + public static final String STATUS_ERROR = "STATUS_ERROR"; + /** 余额不足 */ + public static final String INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE"; + /** 支付超时 */ + public static final String TIMEOUT_ERROR = "TIMEOUT_ERROR"; + /** 重复支付 */ + public static final String DUPLICATE_PAYMENT = "DUPLICATE_PAYMENT"; + /** 不支持的支付方式 */ + public static final String UNSUPPORTED_PAYMENT = "UNSUPPORTED_PAYMENT"; + /** 系统错误 */ + public static final String SYSTEM_ERROR = "SYSTEM_ERROR"; + } + + // 静态工厂方法 + public static PaymentException paramError(String message) { + return new PaymentException(ErrorCode.PARAM_ERROR, message); + } + + public static PaymentException configError(String message, PaymentType paymentType, Integer tenantId) { + return new PaymentException(ErrorCode.CONFIG_ERROR, message, paymentType, tenantId); + } + + public static PaymentException certificateError(String message, PaymentType paymentType) { + return new PaymentException(ErrorCode.CERTIFICATE_ERROR, message, paymentType); + } + + public static PaymentException networkError(String message, PaymentType paymentType, Throwable cause) { + return new PaymentException(ErrorCode.NETWORK_ERROR, message, cause); + } + + public static PaymentException signatureError(String message, PaymentType paymentType) { + return new PaymentException(ErrorCode.SIGNATURE_ERROR, message, paymentType); + } + + public static PaymentException amountError(String message) { + return new PaymentException(ErrorCode.AMOUNT_ERROR, message); + } + + public static PaymentException orderError(String message, String orderNo) { + PaymentException exception = new PaymentException(ErrorCode.ORDER_ERROR, message); + exception.setOrderNo(orderNo); + return exception; + } + + public static PaymentException statusError(String message, String orderNo) { + PaymentException exception = new PaymentException(ErrorCode.STATUS_ERROR, message); + exception.setOrderNo(orderNo); + return exception; + } + + public static PaymentException insufficientBalance(String message, Integer tenantId) { + return new PaymentException(ErrorCode.INSUFFICIENT_BALANCE, message, PaymentType.BALANCE, tenantId); + } + + public static PaymentException timeoutError(String message, PaymentType paymentType, String orderNo) { + PaymentException exception = new PaymentException(ErrorCode.TIMEOUT_ERROR, message, paymentType); + exception.setOrderNo(orderNo); + return exception; + } + + public static PaymentException duplicatePayment(String message, String orderNo) { + PaymentException exception = new PaymentException(ErrorCode.DUPLICATE_PAYMENT, message); + exception.setOrderNo(orderNo); + return exception; + } + + public static PaymentException unsupportedPayment(String message, PaymentType paymentType) { + return new PaymentException(ErrorCode.UNSUPPORTED_PAYMENT, message, paymentType); + } + + public static PaymentException systemError(String message, Throwable cause) { + return new PaymentException(ErrorCode.SYSTEM_ERROR, message, cause); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/exception/PaymentExceptionHandler.java b/src/main/java/com/gxwebsoft/payment/exception/PaymentExceptionHandler.java new file mode 100644 index 0000000..04c6cda --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/exception/PaymentExceptionHandler.java @@ -0,0 +1,153 @@ +package com.gxwebsoft.payment.exception; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.payment.constants.PaymentConstants; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 统一支付异常处理器 + * 处理所有支付相关的异常和参数验证异常 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@RestControllerAdvice(basePackages = {"com.gxwebsoft.payment.controller", "com.gxwebsoft.shop.controller"}) +public class PaymentExceptionHandler extends BaseController { + + /** + * 处理支付业务异常 + */ + @ExceptionHandler(PaymentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResult handlePaymentException(PaymentException e) { + log.warn("支付业务异常: {}", e.getMessage()); + + // 记录详细的异常信息 + if (e.getTenantId() != null) { + log.warn("异常租户ID: {}", e.getTenantId()); + } + + if (e.getPaymentType() != null) { + log.warn("异常支付类型: {}", e.getPaymentType()); + } + + if (e.getOrderNo() != null) { + log.warn("异常订单号: {}", e.getOrderNo()); + } + + if (e.getErrorCode() != null) { + log.warn("错误代码: {}", e.getErrorCode()); + } + + return fail(e.getMessage()); + } + + + + /** + * 处理参数验证异常(@Valid注解) + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + List fieldErrors = e.getBindingResult().getFieldErrors(); + + String errorMessage = fieldErrors.stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + + log.warn("参数验证失败: {}", errorMessage); + + return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + errorMessage); + } + + /** + * 处理绑定异常 + */ + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResult handleBindException(BindException e) { + List fieldErrors = e.getBindingResult().getFieldErrors(); + + String errorMessage = fieldErrors.stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + + log.warn("数据绑定失败: {}", errorMessage); + + return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + errorMessage); + } + + /** + * 处理约束违反异常(@Validated注解) + */ + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResult handleConstraintViolationException(ConstraintViolationException e) { + Set> violations = e.getConstraintViolations(); + + String errorMessage = violations.stream() + .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) + .collect(Collectors.joining("; ")); + + log.warn("约束验证失败: {}", errorMessage); + + return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + errorMessage); + } + + /** + * 处理非法参数异常 + */ + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResult handleIllegalArgumentException(IllegalArgumentException e) { + log.warn("非法参数异常: {}", e.getMessage()); + return fail(PaymentConstants.ErrorMessage.PARAM_ERROR + ": " + e.getMessage()); + } + + /** + * 处理空指针异常 + */ + @ExceptionHandler(NullPointerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResult handleNullPointerException(NullPointerException e) { + log.error("空指针异常", e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + + /** + * 处理其他运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResult handleRuntimeException(RuntimeException e) { + log.error("运行时异常: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } + + /** + * 处理其他异常 + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResult handleException(Exception e) { + log.error("未知异常: {}", e.getMessage(), e); + return fail(PaymentConstants.ErrorMessage.SYSTEM_ERROR); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/service/PaymentService.java b/src/main/java/com/gxwebsoft/payment/service/PaymentService.java new file mode 100644 index 0000000..0f90fd5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/service/PaymentService.java @@ -0,0 +1,182 @@ +package com.gxwebsoft.payment.service; + +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.payment.dto.PaymentRequest; +import com.gxwebsoft.payment.dto.PaymentResponse; +import com.gxwebsoft.payment.dto.PaymentWithOrderRequest; +import com.gxwebsoft.payment.enums.PaymentType; +import com.gxwebsoft.payment.exception.PaymentException; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 统一支付服务接口 + * 提供所有支付方式的统一入口 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public interface PaymentService { + + /** + * 创建支付订单 + * + * @param request 支付请求 + * @return 支付响应 + * @throws PaymentException 支付创建失败时抛出 + */ + PaymentResponse createPayment(PaymentRequest request) throws PaymentException; + + /** + * 创建支付订单(包含订单信息) + * 统一支付模块:先创建订单,再发起支付 + * + * @param request 支付与订单创建请求 + * @param loginUser 当前登录用户 + * @return 支付响应 + * @throws PaymentException 创建失败时抛出 + */ + PaymentResponse createPaymentWithOrder(PaymentWithOrderRequest request, User loginUser) throws PaymentException; + + /** + * 查询支付状态 + * + * @param orderNo 订单号 + * @param paymentType 支付类型 + * @param tenantId 租户ID + * @return 支付响应 + * @throws PaymentException 查询失败时抛出 + */ + PaymentResponse queryPayment(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException; + + /** + * 处理支付回调通知 + * + * @param paymentType 支付类型 + * @param headers 请求头 + * @param body 请求体 + * @param tenantId 租户ID + * @return 处理结果,返回给第三方的响应内容 + * @throws PaymentException 处理失败时抛出 + */ + String handlePaymentNotify(PaymentType paymentType, Map headers, String body, Integer tenantId) throws PaymentException; + + /** + * 申请退款 + * + * @param orderNo 订单号 + * @param refundNo 退款单号 + * @param paymentType 支付类型 + * @param totalAmount 订单总金额 + * @param refundAmount 退款金额 + * @param reason 退款原因 + * @param tenantId 租户ID + * @return 退款响应 + * @throws PaymentException 退款申请失败时抛出 + */ + PaymentResponse refund(String orderNo, String refundNo, PaymentType paymentType, + BigDecimal totalAmount, BigDecimal refundAmount, + String reason, Integer tenantId) throws PaymentException; + + /** + * 查询退款状态 + * + * @param refundNo 退款单号 + * @param paymentType 支付类型 + * @param tenantId 租户ID + * @return 退款查询响应 + * @throws PaymentException 查询失败时抛出 + */ + PaymentResponse queryRefund(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException; + + /** + * 关闭订单 + * + * @param orderNo 订单号 + * @param paymentType 支付类型 + * @param tenantId 租户ID + * @return 关闭结果 + * @throws PaymentException 关闭失败时抛出 + */ + boolean closeOrder(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException; + + /** + * 获取支持的支付类型列表 + * + * @return 支付类型列表 + */ + List getSupportedPaymentTypes(); + + /** + * 检查支付类型是否支持 + * + * @param paymentType 支付类型 + * @return true表示支持 + */ + boolean isPaymentTypeSupported(PaymentType paymentType); + + /** + * 检查支付类型是否支持退款 + * + * @param paymentType 支付类型 + * @return true表示支持退款 + */ + boolean isRefundSupported(PaymentType paymentType); + + /** + * 检查支付类型是否支持查询 + * + * @param paymentType 支付类型 + * @return true表示支持查询 + */ + boolean isQuerySupported(PaymentType paymentType); + + /** + * 检查支付类型是否支持关闭订单 + * + * @param paymentType 支付类型 + * @return true表示支持关闭订单 + */ + boolean isCloseSupported(PaymentType paymentType); + + /** + * 检查支付类型是否需要异步通知 + * + * @param paymentType 支付类型 + * @return true表示需要异步通知 + */ + boolean isNotifyNeeded(PaymentType paymentType); + + /** + * 验证支付请求参数 + * + * @param request 支付请求 + * @throws PaymentException 参数验证失败时抛出 + */ + void validatePaymentRequest(PaymentRequest request) throws PaymentException; + + /** + * 获取支付策略信息 + * + * @param paymentType 支付类型 + * @return 策略信息Map,包含策略名称、描述等 + */ + Map getPaymentStrategyInfo(PaymentType paymentType); + + /** + * 获取所有支付策略信息 + * + * @return 所有策略信息列表 + */ + List> getAllPaymentStrategyInfo(); + + /** + * 检查支付配置 + * + * @param tenantId 租户ID + * @return 配置检查结果 + */ + Map checkPaymentConfig(Integer tenantId); +} diff --git a/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java b/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java new file mode 100644 index 0000000..f072786 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java @@ -0,0 +1,338 @@ +package com.gxwebsoft.payment.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.service.CertificateService; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.utils.WxNativeUtil; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.param.PaymentParam; +import com.gxwebsoft.common.system.service.PaymentService; +import com.gxwebsoft.payment.exception.PaymentException; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * 微信支付配置服务 + * 负责管理微信支付的配置信息和证书 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Service +public class WxPayConfigService { + + @Resource + private RedisUtil redisUtil; + + @Resource + private CertificateService certificateService; + + @Resource + private CertificateProperties certificateProperties; + + @Resource + private PaymentService paymentService; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + /** + * 获取支付配置信息(Payment对象)- 公开方法 + * + * @param tenantId 租户ID + * @return 支付配置信息 + * @throws PaymentException 配置获取失败时抛出 + */ + public Payment getPaymentConfigForStrategy(Integer tenantId) throws PaymentException { + if (tenantId == null) { + throw PaymentException.paramError("租户ID不能为空"); + } + return getPaymentConfig(tenantId); + } + + /** + * 获取微信支付配置 + * + * @param tenantId 租户ID + * @return 微信支付配置 + * @throws PaymentException 配置获取失败时抛出 + */ + public Config getWxPayConfig(Integer tenantId) throws PaymentException { + if (tenantId == null) { + throw PaymentException.paramError("租户ID不能为空"); + } + + // 先从缓存获取已构建的配置 + Config cachedConfig = WxNativeUtil.getConfig(tenantId); + if (cachedConfig != null) { + log.debug("从缓存获取微信支付配置成功,租户ID: {}", tenantId); + return cachedConfig; + } + + // 构建新的配置 + Config newConfig = buildWxPayConfig(tenantId); + + // 缓存配置 + WxNativeUtil.addConfig(tenantId, newConfig); + log.info("微信支付配置创建并缓存成功,租户ID: {}", tenantId); + + return newConfig; + } + + /** + * 构建微信支付配置 + */ + private Config buildWxPayConfig(Integer tenantId) throws PaymentException { + try { + // 获取支付配置信息 + Payment payment = getPaymentConfig(tenantId); + + // 获取证书文件路径 + String certificatePath = getCertificatePath(tenantId, payment); + + // 创建微信支付配置对象 + return createWxPayConfig(payment, certificatePath); + + } catch (Exception e) { + if (e instanceof PaymentException) { + throw e; + } + throw PaymentException.systemError("构建微信支付配置失败: " + e.getMessage(), e); + } + } + + /** + * 获取支付配置信息 + * 优先从缓存获取,缓存没有则查询数据库,最后兜底到开发环境测试配置 + */ + private Payment getPaymentConfig(Integer tenantId) throws PaymentException { + String cacheKey = "Payment:wxPay:" + tenantId; + Payment payment = redisUtil.get(cacheKey, Payment.class); + System.out.println("payment = " + payment); + if (payment != null) { + log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId); +// return payment; + } + + // 缓存中没有,尝试从数据库查询 + try { + final PaymentParam paymentParam = new PaymentParam(); + paymentParam.setType(102); + paymentParam.setTenantId(tenantId); + + log.debug("查询数据库支付配置,参数: type=102, tenantId={}", tenantId); + payment = paymentService.getByType(paymentParam); + log.debug("数据库查询结果: {}", payment != null ? "找到配置" : "未找到配置"); + + if (payment != null) { + log.info("从数据库获取支付配置成功,租户ID: {},将缓存配置", tenantId); + // 将查询到的配置缓存起来,缓存1天 + redisUtil.set(cacheKey, payment, 1L, TimeUnit.DAYS); + return payment; + } else { + log.warn("数据库中未找到支付配置,租户ID: {}, type: 102", tenantId); + } + } catch (Exception e) { + log.error("从数据库查询支付配置失败,租户ID: {},错误: {}", tenantId, e.getMessage(), e); + // 抛出更详细的异常信息 + throw PaymentException.systemError("查询支付配置失败,租户ID: " + tenantId + ",错误: " + e.getMessage(), e); + } + + // 数据库也没有配置 + if (!"dev".equals(activeProfile)) { + throw PaymentException.systemError("微信支付配置未找到,租户ID: " + tenantId + ",请检查数据库配置", null); + } + + log.debug("开发环境模式,将使用测试配置,租户ID: {}", tenantId); + // 开发环境返回测试Payment配置 + return createDevTestPayment(tenantId); + } + + /** + * 获取证书文件路径 + */ + private String getCertificatePath(Integer tenantId, Payment payment) throws PaymentException { + if ("dev".equals(activeProfile)) { + return getDevCertificatePath(tenantId); + } else { + return getProdCertificatePath(payment); + } + } + + /** + * 获取开发环境证书路径 + */ + private String getDevCertificatePath(Integer tenantId) throws PaymentException { + try { + // 根据租户ID构建证书路径 + String certPath = "dev/wechat/" + tenantId + "/apiclient_key.pem"; + ClassPathResource resource = new ClassPathResource(certPath); + + if (!resource.exists()) { + throw PaymentException.systemError("开发环境微信支付证书文件不存在: " + certPath, null); + } + + String absolutePath = resource.getFile().getAbsolutePath(); + log.debug("开发环境证书路径: {}", absolutePath); + return absolutePath; + + } catch (IOException e) { + throw PaymentException.systemError("获取开发环境证书路径失败: " + e.getMessage(), e); + } + } + + /** + * 获取生产环境证书路径 + */ + private String getProdCertificatePath(Payment payment) throws PaymentException { + if (payment == null || payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) { + throw PaymentException.systemError("生产环境支付配置或证书密钥文件为空", null); + } + + try { + // 使用微信支付证书路径 + String certificatePath = certificateService.getWechatPayCertPath(payment.getApiclientKey()); + if (certificatePath == null) { + throw PaymentException.systemError("证书文件路径获取失败,证书文件: " + payment.getApiclientKey(), null); + } + + log.debug("生产环境证书路径: {}", certificatePath); + return certificatePath; + + } catch (Exception e) { + throw PaymentException.systemError("获取生产环境证书路径失败: " + e.getMessage(), e); + } + } + + /** + * 创建微信支付配置对象 + */ + private Config createWxPayConfig(Payment payment, String certificatePath) throws PaymentException { + try { + if ("dev".equals(activeProfile) && payment == null) { + // 开发环境测试配置 + return createDevTestConfig(certificatePath); + } else if (payment != null) { + // 正常配置 + return createNormalConfig(payment, certificatePath); + } else { + throw PaymentException.systemError("无法创建微信支付配置:配置信息不完整", null); + } + } catch (Exception e) { + if (e instanceof PaymentException) { + throw e; + } + throw PaymentException.systemError("创建微信支付配置对象失败: " + e.getMessage(), e); + } + } + + /** + * 创建开发环境测试Payment配置 + */ + private Payment createDevTestPayment(Integer tenantId) { + Payment testPayment = new Payment(); + testPayment.setTenantId(tenantId); + testPayment.setType(102); // Native支付 + testPayment.setAppId("wxa67c676fc445590e"); // 开发环境测试AppID + testPayment.setMchId("1246610101"); // 开发环境测试商户号 + testPayment.setMerchantSerialNumber("48749613B40AA8F1D768583FC352358E13EB5AF0"); + testPayment.setApiKey(certificateProperties.getWechatPay().getDev().getApiV3Key()); + testPayment.setNotifyUrl("http://frps-10550.s209.websoft.top/api/payment/notify"); + testPayment.setName("微信Native支付-开发环境"); + testPayment.setStatus(true); // 启用 + + log.info("创建开发环境测试Payment配置,租户ID: {}, AppID: {}, 商户号: {}", + tenantId, testPayment.getAppId(), testPayment.getMchId()); + + return testPayment; + } + + /** + * 创建开发环境测试配置 + */ + private Config createDevTestConfig(String certificatePath) throws PaymentException { + String testMerchantId = "1246610101"; + String testMerchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0"; + String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key(); + + if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) { + throw PaymentException.systemError("开发环境APIv3密钥未配置", null); + } + + log.info("使用开发环境测试配置"); + log.debug("测试商户号: {}", testMerchantId); + log.debug("测试序列号: {}", testMerchantSerialNumber); + + return new RSAAutoCertificateConfig.Builder() + .merchantId(testMerchantId) + .privateKeyFromPath(certificatePath) + .merchantSerialNumber(testMerchantSerialNumber) + .apiV3Key(testApiV3Key) + .build(); + } + + /** + * 创建正常配置 + */ + private Config createNormalConfig(Payment payment, String certificatePath) throws PaymentException { + // 验证配置完整性 + validatePaymentConfig(payment); + + log.info("使用数据库支付配置"); + log.debug("商户号: {}", payment.getMchId()); + + return new RSAAutoCertificateConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(certificatePath) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + } + + /** + * 验证支付配置完整性 + */ + private void validatePaymentConfig(Payment payment) throws PaymentException { + if (payment == null) { + throw PaymentException.systemError("支付配置为空", null); + } + + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + throw PaymentException.systemError("商户号(mchId)未配置", null); + } + + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + throw PaymentException.systemError("商户证书序列号(merchantSerialNumber)未配置", null); + } + + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + throw PaymentException.systemError("APIv3密钥(apiKey)未配置", null); + } + + if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) { + throw PaymentException.systemError("证书文件名(apiclientKey)未配置", null); + } + + log.debug("支付配置验证通过,租户ID: {}, 商户号: {}", payment.getTenantId(), payment.getMchId()); + } + + /** + * 清除指定租户的配置缓存 + * + * @param tenantId 租户ID + */ + public void clearConfigCache(Integer tenantId) { + WxNativeUtil.addConfig(tenantId, null); + log.info("清除微信支付配置缓存,租户ID: {}", tenantId); + } +} diff --git a/src/main/java/com/gxwebsoft/payment/service/WxPayNotifyService.java b/src/main/java/com/gxwebsoft/payment/service/WxPayNotifyService.java new file mode 100644 index 0000000..9378dcd --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/service/WxPayNotifyService.java @@ -0,0 +1,366 @@ +package com.gxwebsoft.payment.service; + + +import com.gxwebsoft.payment.constants.PaymentConstants; +import com.gxwebsoft.payment.exception.PaymentException; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.service.ShopOrderService; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.notification.NotificationConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.service.payments.model.Transaction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 微信支付回调通知处理服务 + * 负责处理微信支付的异步通知回调 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Service +public class WxPayNotifyService { + + @Resource + private WxPayConfigService wxPayConfigService; + + @Resource + private ShopOrderService shopOrderService; + + /** + * 处理微信支付回调通知 + * + * @param headers 请求头 + * @param body 请求体 + * @param tenantId 租户ID + * @return 处理结果响应 + */ + public String handlePaymentNotify(Map headers, String body, Integer tenantId) { + log.info("{}, 租户ID: {}", PaymentConstants.LogMessage.NOTIFY_START, tenantId); + + try { + // 参数验证 + validateNotifyParams(headers, body, tenantId); + + // 获取微信支付配置 + Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId); + + // 解析并验证回调数据 + Transaction transaction = parseAndVerifyNotification(headers, body, wxPayConfig); + + // 处理支付结果 + processPaymentResult(transaction, tenantId); + + log.info("{}, 租户ID: {}, 订单号: {}", + PaymentConstants.LogMessage.NOTIFY_SUCCESS, tenantId, transaction.getOutTradeNo()); + + return "SUCCESS"; + + } catch (Exception e) { + log.error("{}, 租户ID: {}, 错误: {}", + PaymentConstants.LogMessage.NOTIFY_FAILED, tenantId, e.getMessage(), e); + return "FAIL"; + } + } + + /** + * 验证回调通知参数 + */ + private void validateNotifyParams(Map headers, String body, Integer tenantId) throws PaymentException { + if (tenantId == null) { + throw PaymentException.paramError("租户ID不能为空"); + } + + if (headers == null || headers.isEmpty()) { + throw PaymentException.paramError("请求头不能为空"); + } + + if (!StringUtils.hasText(body)) { + throw PaymentException.paramError("请求体不能为空"); + } + + // 验证必要的微信支付头部信息 + String signature = headers.get("Wechatpay-Signature"); + String timestamp = headers.get("Wechatpay-Timestamp"); + String nonce = headers.get("Wechatpay-Nonce"); + String serial = headers.get("Wechatpay-Serial"); + + if (!StringUtils.hasText(signature)) { + throw PaymentException.paramError("微信支付签名不能为空"); + } + + if (!StringUtils.hasText(timestamp)) { + throw PaymentException.paramError("微信支付时间戳不能为空"); + } + + if (!StringUtils.hasText(nonce)) { + throw PaymentException.paramError("微信支付随机数不能为空"); + } + + if (!StringUtils.hasText(serial)) { + throw PaymentException.paramError("微信支付序列号不能为空"); + } + + log.debug("回调通知参数验证通过, 租户ID: {}", tenantId); + } + + /** + * 解析并验证回调通知 + */ + private Transaction parseAndVerifyNotification(Map headers, String body, Config wxPayConfig) throws PaymentException { + if (wxPayConfig == null) { + throw PaymentException.systemError("微信支付配置为空", null); + } + + try { + // 构建请求参数 + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(headers.get("Wechatpay-Serial")) + .nonce(headers.get("Wechatpay-Nonce")) + .signature(headers.get("Wechatpay-Signature")) + .timestamp(headers.get("Wechatpay-Timestamp")) + .body(body) + .build(); + + // 创建通知解析器 + NotificationParser parser = new NotificationParser((NotificationConfig) wxPayConfig); + + // 解析并验证通知 + Transaction transaction = parser.parse(requestParam, Transaction.class); + + if (transaction == null) { + throw PaymentException.systemError("解析回调通知失败:transaction为空", null); + } + + log.debug("回调通知解析成功, 订单号: {}, 交易状态: {}", + transaction.getOutTradeNo(), transaction.getTradeState()); + + return transaction; + + } catch (Exception e) { + if (e instanceof PaymentException) { + throw e; + } + throw PaymentException.systemError("解析回调通知失败: " + e.getMessage(), e); + } + } + + /** + * 处理支付结果 + */ + private void processPaymentResult(Transaction transaction, Integer tenantId) throws PaymentException { + String outTradeNo = transaction.getOutTradeNo(); + String tradeState = String.valueOf(transaction.getTradeState()); + + if (!StringUtils.hasText(outTradeNo)) { + throw PaymentException.paramError("商户订单号不能为空"); + } + + // 查询订单 + ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo); + if (order == null) { + throw PaymentException.systemError("订单不存在: " + outTradeNo, null); + } + + // 验证租户ID + if (!tenantId.equals(order.getTenantId())) { + throw PaymentException.paramError("订单租户ID不匹配"); + } + + // 验证订单状态 - 使用Boolean类型的payStatus字段 + if (Boolean.TRUE.equals(order.getPayStatus())) { + log.info("订单已支付,跳过处理, 订单号: {}", outTradeNo); + return; + } + + // 根据交易状态处理 + switch (tradeState) { + case "SUCCESS": + handlePaymentSuccess(order, transaction); + break; + case "REFUND": + handlePaymentRefund(order, transaction); + break; + case "CLOSED": + case "REVOKED": + case "PAYERROR": + handlePaymentFailed(order, transaction); + break; + default: + log.warn("未处理的交易状态: {}, 订单号: {}", tradeState, outTradeNo); + break; + } + } + + /** + * 处理支付成功 + */ + private void handlePaymentSuccess(ShopOrder order, Transaction transaction) throws PaymentException { + try { + // 验证金额 + validateAmount(order, transaction); + + // 更新订单状态 + order.setPayStatus(true); // 使用Boolean类型 + order.setTransactionId(transaction.getTransactionId()); + order.setPayTime(LocalDateTime.parse(transaction.getSuccessTime())); + + // 使用专门的更新方法,会触发支付成功后的业务逻辑 + shopOrderService.updateByOutTradeNo(order); + + // 推送支付结果通知 + pushPaymentNotification(order, transaction); + + log.info("支付成功处理完成, 订单号: {}, 微信交易号: {}", + order.getOrderNo(), transaction.getTransactionId()); + + } catch (Exception e) { + throw PaymentException.systemError("处理支付成功回调失败: " + e.getMessage(), e); + } + } + + /** + * 处理支付退款 + */ + private void handlePaymentRefund(ShopOrder order, Transaction transaction) throws PaymentException { + try { + log.info("处理支付退款, 订单号: {}, 微信交易号: {}", + order.getOrderNo(), transaction.getTransactionId()); + + // 这里可以添加退款相关的业务逻辑 + // 例如:更新订单状态、处理库存、发送通知等 + + } catch (Exception e) { + throw PaymentException.systemError("处理支付退款回调失败: " + e.getMessage(), e); + } + } + + /** + * 处理支付失败 + */ + private void handlePaymentFailed(ShopOrder order, Transaction transaction) throws PaymentException { + try { + log.info("处理支付失败, 订单号: {}, 交易状态: {}", + order.getOrderNo(), transaction.getTradeState()); + + // 这里可以添加支付失败相关的业务逻辑 + // 例如:释放库存、发送通知等 + + } catch (Exception e) { + throw PaymentException.systemError("处理支付失败回调失败: " + e.getMessage(), e); + } + } + + /** + * 验证支付金额 + */ + private void validateAmount(ShopOrder order, Transaction transaction) throws PaymentException { + if (transaction.getAmount() == null || transaction.getAmount().getTotal() == null) { + throw PaymentException.amountError("回调通知中金额信息为空"); + } + + // 将订单金额转换为分 + BigDecimal orderAmount = order.getMoney(); + if (orderAmount == null) { + throw PaymentException.amountError("订单金额为空"); + } + + int orderAmountFen = orderAmount.multiply(new BigDecimal(100)).intValue(); + int callbackAmountFen = transaction.getAmount().getTotal(); + + if (orderAmountFen != callbackAmountFen) { + throw PaymentException.amountError( + String.format("订单金额不匹配,订单金额: %d分, 回调金额: %d分", + orderAmountFen, callbackAmountFen)); + } + + log.debug("金额验证通过, 订单号: {}, 金额: {}分", order.getOrderNo(), orderAmountFen); + } + + /** + * 推送支付结果通知 + */ + private void pushPaymentNotification(ShopOrder order, Transaction transaction) { + try { + log.info("开始推送支付成功通知, 订单号: {}, 交易号: {}, 用户ID: {}", + order.getOrderNo(), transaction.getTransactionId(), order.getUserId()); + + // 1. 记录支付成功日志 + logPaymentSuccess(order, transaction); + + // 2. 发送支付成功通知(可扩展) + sendPaymentSuccessNotification(order, transaction); + + // 3. 触发其他业务逻辑(可扩展) + triggerPostPaymentActions(order, transaction); + + log.info("支付结果通知推送完成, 订单号: {}, 交易号: {}", + order.getOrderNo(), transaction.getTransactionId()); + } catch (Exception e) { + log.warn("支付结果通知推送失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage()); + // 推送失败不影响主流程,只记录日志 + } + } + + /** + * 记录支付成功日志 + */ + private void logPaymentSuccess(ShopOrder order, Transaction transaction) { + try { + log.info("=== 支付成功详细信息 ==="); + log.info("订单号: {}", order.getOrderNo()); + log.info("微信交易号: {}", transaction.getTransactionId()); + log.info("支付金额: {}元", order.getPayPrice()); + log.info("支付时间: {}", transaction.getSuccessTime()); + log.info("用户ID: {}", order.getUserId()); + log.info("租户ID: {}", order.getTenantId()); + log.info("订单标题: {}", order.getTitle()); + log.info("========================"); + } catch (Exception e) { + log.warn("记录支付成功日志失败: {}", e.getMessage()); + } + } + + /** + * 发送支付成功通知 + */ + private void sendPaymentSuccessNotification(ShopOrder order, Transaction transaction) { + try { + // TODO: 实现具体的通知逻辑 + // 1. 发送邮件通知 + // 2. 发送短信通知 + // 3. 站内消息通知 + // 4. 微信模板消息通知 + + log.debug("支付成功通知发送完成, 订单号: {}", order.getOrderNo()); + } catch (Exception e) { + log.warn("发送支付成功通知失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage()); + } + } + + /** + * 触发支付成功后的其他业务逻辑 + */ + private void triggerPostPaymentActions(ShopOrder order, Transaction transaction) { + try { + // TODO: 根据业务需求实现 + // 1. 开通网站服务 + // 2. 激活会员权益 + // 3. 发放积分奖励 + // 4. 触发营销活动 + + log.debug("支付后业务逻辑触发完成, 订单号: {}", order.getOrderNo()); + } catch (Exception e) { + log.warn("触发支付后业务逻辑失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/payment/service/impl/PaymentServiceImpl.java b/src/main/java/com/gxwebsoft/payment/service/impl/PaymentServiceImpl.java new file mode 100644 index 0000000..cfc4b27 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/service/impl/PaymentServiceImpl.java @@ -0,0 +1,668 @@ +package com.gxwebsoft.payment.service.impl; + +import cn.hutool.core.util.IdUtil; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.payment.constants.PaymentConstants; +import com.gxwebsoft.payment.dto.PaymentRequest; +import com.gxwebsoft.payment.dto.PaymentResponse; +import com.gxwebsoft.payment.dto.PaymentWithOrderRequest; +import com.gxwebsoft.payment.enums.PaymentType; +import com.gxwebsoft.payment.exception.PaymentException; +import com.gxwebsoft.payment.service.PaymentService; +import com.gxwebsoft.payment.service.WxPayConfigService; +import com.gxwebsoft.payment.strategy.PaymentStrategy; +import com.gxwebsoft.shop.dto.OrderCreateRequest; +import com.gxwebsoft.shop.service.OrderBusinessService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 统一支付服务实现 + * 基于策略模式实现多种支付方式的统一管理 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Service("unifiedPaymentServiceImpl") +public class PaymentServiceImpl implements PaymentService { + + /** + * 支付策略映射表 + */ + private final Map strategyMap = new ConcurrentHashMap<>(); + + /** + * 注入所有支付策略实现 + */ + @Resource + private List paymentStrategies; + + /** + * 订单业务服务 + */ + @Resource + private OrderBusinessService orderBusinessService; + + /** + * 微信支付配置服务 + */ + @Resource + private WxPayConfigService wxPayConfigService; + + /** + * 初始化策略映射 + */ + @PostConstruct + public void initStrategies() { + if (paymentStrategies != null && !paymentStrategies.isEmpty()) { + for (PaymentStrategy strategy : paymentStrategies) { + try { + PaymentType paymentType = strategy.getSupportedPaymentType(); + strategyMap.put(paymentType, strategy); + log.info("注册支付策略: {} -> {}", paymentType.getName(), strategy.getClass().getSimpleName()); + } catch (Exception e) { + log.warn("注册支付策略失败: {}, 错误: {}", strategy.getClass().getSimpleName(), e.getMessage()); + } + } + } + log.info("支付策略初始化完成,共注册 {} 种支付方式", strategyMap.size()); + + if (strategyMap.isEmpty()) { + log.warn("⚠️ 没有可用的支付策略,支付功能将不可用"); + } + } + + @Override + public PaymentResponse createPayment(PaymentRequest request) throws PaymentException { + log.info("{}, 支付类型: {}, 租户ID: {}, 用户ID: {}, 金额: {}", + PaymentConstants.LogMessage.PAYMENT_START, request.getPaymentType(), + request.getTenantId(), request.getUserId(), request.getFormattedAmount()); + + try { + // 基础参数验证 + validatePaymentRequest(request); + + // 获取支付策略 + PaymentStrategy strategy = getPaymentStrategy(request.getPaymentType()); + + // 执行支付 + PaymentResponse response = strategy.createPayment(request); + + log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}", + PaymentConstants.LogMessage.PAYMENT_SUCCESS, request.getPaymentType(), + request.getTenantId(), response.getOrderNo(), request.getFormattedAmount()); + + return response; + + } catch (PaymentException e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}", + PaymentConstants.LogMessage.PAYMENT_FAILED, request.getPaymentType(), + request.getTenantId(), e.getMessage()); + throw e; + } catch (Exception e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}", + PaymentConstants.LogMessage.PAYMENT_FAILED, request.getPaymentType(), + request.getTenantId(), e.getMessage(), e); + throw PaymentException.systemError("支付创建失败: " + e.getMessage(), e); + } + } + + @Override + public PaymentResponse createPaymentWithOrder(PaymentWithOrderRequest request, User loginUser) throws PaymentException { + log.info("开始创建支付订单(包含订单信息), 支付类型: {}, 租户ID: {}, 用户ID: {}, 金额: {}", + request.getPaymentType(), request.getTenantId(), loginUser.getUserId(), request.getFormattedAmount()); + + try { + // 1. 参数验证 + validatePaymentWithOrderRequest(request, loginUser); + + // 2. 转换为订单创建请求 + OrderCreateRequest orderRequest = convertToOrderCreateRequest(request, loginUser); + + // 3. 创建订单(包含商品验证、库存扣减等完整业务逻辑) + Map wxOrderInfo = orderBusinessService.createOrder(orderRequest, loginUser); + + // 4. 构建支付响应(复用现有的微信支付返回格式) + PaymentResponse response = buildPaymentResponseFromWxOrder(wxOrderInfo, request, orderRequest.getOrderNo()); + + log.info("支付订单创建成功(包含订单信息), 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}", + request.getPaymentType(), request.getTenantId(), response.getOrderNo(), request.getFormattedAmount()); + + return response; + + } catch (PaymentException e) { + log.error("创建支付订单失败(包含订单信息), 支付类型: {}, 租户ID: {}, 错误: {}", + request.getPaymentType(), request.getTenantId(), e.getMessage()); + throw e; + } catch (Exception e) { + log.error("创建支付订单系统错误(包含订单信息), 支付类型: {}, 租户ID: {}, 系统错误: {}", + request.getPaymentType(), request.getTenantId(), e.getMessage(), e); + throw PaymentException.systemError("支付订单创建失败: " + e.getMessage(), e); + } + } + + @Override + public PaymentResponse queryPayment(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException { + log.info("开始查询支付状态, 支付类型: {}, 租户ID: {}, 订单号: {}", + paymentType, tenantId, orderNo); + + try { + // 参数验证 + validateQueryParams(orderNo, paymentType, tenantId); + + // 获取支付策略 + PaymentStrategy strategy = getPaymentStrategy(paymentType); + + // 检查是否支持查询 + if (!strategy.supportQuery()) { + throw PaymentException.unsupportedPayment("该支付方式不支持查询", paymentType); + } + + // 执行查询 + PaymentResponse response = strategy.queryPayment(orderNo, tenantId); + + log.info("支付状态查询成功, 支付类型: {}, 租户ID: {}, 订单号: {}, 状态: {}", + paymentType, tenantId, orderNo, response.getPaymentStatus()); + + return response; + + } catch (PaymentException e) { + log.error("支付状态查询失败, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}", + paymentType, tenantId, orderNo, e.getMessage()); + throw e; + } catch (Exception e) { + log.error("支付状态查询系统错误, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}", + paymentType, tenantId, orderNo, e.getMessage(), e); + throw PaymentException.systemError("支付查询失败: " + e.getMessage(), e); + } + } + + @Override + public String handlePaymentNotify(PaymentType paymentType, Map headers, String body, Integer tenantId) throws PaymentException { + log.info("{}, 支付类型: {}, 租户ID: {}", + PaymentConstants.LogMessage.NOTIFY_START, paymentType, tenantId); + + try { + // 参数验证 + validateNotifyParams(paymentType, headers, body, tenantId); + + // 获取支付策略 + PaymentStrategy strategy = getPaymentStrategy(paymentType); + + // 检查是否需要异步通知 + if (!strategy.needNotify()) { + log.warn("该支付方式不需要异步通知, 支付类型: {}", paymentType); + return PaymentConstants.Wechat.NOTIFY_SUCCESS; + } + + // 处理回调 + String result = strategy.handleNotify(headers, body, tenantId); + + log.info("{}, 支付类型: {}, 租户ID: {}", + PaymentConstants.LogMessage.NOTIFY_SUCCESS, paymentType, tenantId); + + return result; + + } catch (PaymentException e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}", + PaymentConstants.LogMessage.NOTIFY_FAILED, paymentType, tenantId, e.getMessage()); + throw e; + } catch (Exception e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}", + PaymentConstants.LogMessage.NOTIFY_FAILED, paymentType, tenantId, e.getMessage(), e); + throw PaymentException.systemError("支付回调处理失败: " + e.getMessage(), e); + } + } + + @Override + public PaymentResponse refund(String orderNo, String refundNo, PaymentType paymentType, + BigDecimal totalAmount, BigDecimal refundAmount, + String reason, Integer tenantId) throws PaymentException { + log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 退款金额: {}", + PaymentConstants.LogMessage.REFUND_START, paymentType, tenantId, orderNo, refundNo, refundAmount); + + try { + // 参数验证 + validateRefundParams(orderNo, refundNo, paymentType, totalAmount, refundAmount, tenantId); + + // 获取支付策略 + PaymentStrategy strategy = getPaymentStrategy(paymentType); + + // 检查是否支持退款 + if (!strategy.supportRefund()) { + throw PaymentException.unsupportedPayment("该支付方式不支持退款", paymentType); + } + + // 执行退款 + PaymentResponse response = strategy.refund(orderNo, refundNo, totalAmount, refundAmount, reason, tenantId); + + log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 退款金额: {}", + PaymentConstants.LogMessage.REFUND_SUCCESS, paymentType, tenantId, orderNo, refundNo, refundAmount); + + return response; + + } catch (PaymentException e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 错误: {}", + PaymentConstants.LogMessage.REFUND_FAILED, paymentType, tenantId, orderNo, refundNo, e.getMessage()); + throw e; + } catch (Exception e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 系统错误: {}", + PaymentConstants.LogMessage.REFUND_FAILED, paymentType, tenantId, orderNo, refundNo, e.getMessage(), e); + throw PaymentException.systemError("退款申请失败: " + e.getMessage(), e); + } + } + + @Override + public PaymentResponse queryRefund(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException { + log.info("开始查询退款状态, 支付类型: {}, 租户ID: {}, 退款单号: {}", + paymentType, tenantId, refundNo); + + try { + // 参数验证 + validateRefundQueryParams(refundNo, paymentType, tenantId); + + // 获取支付策略 + PaymentStrategy strategy = getPaymentStrategy(paymentType); + + // 检查是否支持退款查询 + if (!strategy.supportRefund()) { + throw PaymentException.unsupportedPayment("该支付方式不支持退款查询", paymentType); + } + + // 执行查询 + PaymentResponse response = strategy.queryRefund(refundNo, tenantId); + + log.info("退款状态查询成功, 支付类型: {}, 租户ID: {}, 退款单号: {}, 状态: {}", + paymentType, tenantId, refundNo, response.getPaymentStatus()); + + return response; + + } catch (PaymentException e) { + log.error("退款状态查询失败, 支付类型: {}, 租户ID: {}, 退款单号: {}, 错误: {}", + paymentType, tenantId, refundNo, e.getMessage()); + throw e; + } catch (Exception e) { + log.error("退款状态查询系统错误, 支付类型: {}, 租户ID: {}, 退款单号: {}, 错误: {}", + paymentType, tenantId, refundNo, e.getMessage(), e); + throw PaymentException.systemError("退款查询失败: " + e.getMessage(), e); + } + } + + @Override + public boolean closeOrder(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException { + log.info("开始关闭订单, 支付类型: {}, 租户ID: {}, 订单号: {}", + paymentType, tenantId, orderNo); + + try { + // 参数验证 + validateCloseParams(orderNo, paymentType, tenantId); + + // 获取支付策略 + PaymentStrategy strategy = getPaymentStrategy(paymentType); + + // 检查是否支持关闭订单 + if (!strategy.supportClose()) { + throw PaymentException.unsupportedPayment("该支付方式不支持关闭订单", paymentType); + } + + // 执行关闭 + boolean result = strategy.closeOrder(orderNo, tenantId); + + log.info("订单关闭{}, 支付类型: {}, 租户ID: {}, 订单号: {}", + result ? "成功" : "失败", paymentType, tenantId, orderNo); + + return result; + + } catch (PaymentException e) { + log.error("订单关闭失败, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}", + paymentType, tenantId, orderNo, e.getMessage()); + throw e; + } catch (Exception e) { + log.error("订单关闭系统错误, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}", + paymentType, tenantId, orderNo, e.getMessage(), e); + throw PaymentException.systemError("订单关闭失败: " + e.getMessage(), e); + } + } + + @Override + public List getSupportedPaymentTypes() { + return new ArrayList<>(strategyMap.keySet()); + } + + @Override + public boolean isPaymentTypeSupported(PaymentType paymentType) { + return strategyMap.containsKey(paymentType); + } + + @Override + public boolean isRefundSupported(PaymentType paymentType) { + PaymentStrategy strategy = strategyMap.get(paymentType); + return strategy != null && strategy.supportRefund(); + } + + @Override + public boolean isQuerySupported(PaymentType paymentType) { + PaymentStrategy strategy = strategyMap.get(paymentType); + return strategy != null && strategy.supportQuery(); + } + + @Override + public boolean isCloseSupported(PaymentType paymentType) { + PaymentStrategy strategy = strategyMap.get(paymentType); + return strategy != null && strategy.supportClose(); + } + + @Override + public boolean isNotifyNeeded(PaymentType paymentType) { + PaymentStrategy strategy = strategyMap.get(paymentType); + return strategy != null && strategy.needNotify(); + } + + @Override + public void validatePaymentRequest(PaymentRequest request) throws PaymentException { + if (request == null) { + throw PaymentException.paramError("支付请求不能为空"); + } + + if (request.getPaymentType() == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + + if (request.getTenantId() == null || request.getTenantId() <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + + if (request.getUserId() == null || request.getUserId() <= 0) { + throw PaymentException.paramError("用户ID不能为空且必须大于0"); + } + + if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) { + throw PaymentException.amountError("支付金额必须大于0"); + } + + if (!StringUtils.hasText(request.getSubject())) { + throw PaymentException.paramError("订单标题不能为空"); + } + + // 检查支付类型是否支持 + if (!isPaymentTypeSupported(request.getPaymentType())) { + throw PaymentException.unsupportedPayment("不支持的支付类型: " + request.getPaymentType(), request.getPaymentType()); + } + } + + @Override + public Map getPaymentStrategyInfo(PaymentType paymentType) { + PaymentStrategy strategy = strategyMap.get(paymentType); + if (strategy == null) { + return null; + } + + Map info = new HashMap<>(); + info.put("paymentType", paymentType); + info.put("strategyName", strategy.getStrategyName()); + info.put("strategyDescription", strategy.getStrategyDescription()); + info.put("supportRefund", strategy.supportRefund()); + info.put("supportQuery", strategy.supportQuery()); + info.put("supportClose", strategy.supportClose()); + info.put("needNotify", strategy.needNotify()); + return info; + } + + @Override + public List> getAllPaymentStrategyInfo() { + return strategyMap.keySet().stream() + .map(this::getPaymentStrategyInfo) + .collect(Collectors.toList()); + } + + @Override + public Map checkPaymentConfig(Integer tenantId) { + Map result = new HashMap<>(); + result.put("tenantId", tenantId); + + try { + // 检查微信支付配置 + wxPayConfigService.getPaymentConfigForStrategy(tenantId); + result.put("wechatConfigExists", true); + result.put("wechatConfigError", null); + } catch (Exception e) { + result.put("wechatConfigExists", false); + result.put("wechatConfigError", e.getMessage()); + } + + try { + // 检查微信支付Config构建 + wxPayConfigService.getWxPayConfig(tenantId); + result.put("wechatConfigValid", true); + result.put("wechatConfigValidError", null); + } catch (Exception e) { + result.put("wechatConfigValid", false); + result.put("wechatConfigValidError", e.getMessage()); + } + + return result; + } + + /** + * 获取支付策略 + */ + private PaymentStrategy getPaymentStrategy(PaymentType paymentType) throws PaymentException { + PaymentStrategy strategy = strategyMap.get(paymentType); + if (strategy == null) { + throw PaymentException.unsupportedPayment("不支持的支付类型: " + paymentType, paymentType); + } + return strategy; + } + + /** + * 验证查询参数 + */ + private void validateQueryParams(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException { + if (!StringUtils.hasText(orderNo)) { + throw PaymentException.paramError("订单号不能为空"); + } + if (paymentType == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + if (tenantId == null || tenantId <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + } + + /** + * 验证回调参数 + */ + private void validateNotifyParams(PaymentType paymentType, Map headers, String body, Integer tenantId) throws PaymentException { + if (paymentType == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + if (headers == null || headers.isEmpty()) { + throw PaymentException.paramError("请求头不能为空"); + } + if (!StringUtils.hasText(body)) { + throw PaymentException.paramError("请求体不能为空"); + } + if (tenantId == null || tenantId <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + } + + /** + * 验证退款参数 + */ + private void validateRefundParams(String orderNo, String refundNo, PaymentType paymentType, + BigDecimal totalAmount, BigDecimal refundAmount, Integer tenantId) throws PaymentException { + if (!StringUtils.hasText(orderNo)) { + throw PaymentException.paramError("订单号不能为空"); + } + if (!StringUtils.hasText(refundNo)) { + throw PaymentException.paramError("退款单号不能为空"); + } + if (paymentType == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) <= 0) { + throw PaymentException.amountError("订单总金额必须大于0"); + } + if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) { + throw PaymentException.amountError("退款金额必须大于0"); + } + if (refundAmount.compareTo(totalAmount) > 0) { + throw PaymentException.amountError("退款金额不能大于订单总金额"); + } + if (tenantId == null || tenantId <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + } + + /** + * 验证退款查询参数 + */ + private void validateRefundQueryParams(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException { + if (!StringUtils.hasText(refundNo)) { + throw PaymentException.paramError("退款单号不能为空"); + } + if (paymentType == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + if (tenantId == null || tenantId <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + } + + /** + * 验证关闭订单参数 + */ + private void validateCloseParams(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException { + if (!StringUtils.hasText(orderNo)) { + throw PaymentException.paramError("订单号不能为空"); + } + if (paymentType == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + if (tenantId == null || tenantId <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + } + + /** + * 验证支付与订单创建请求参数 + */ + private void validatePaymentWithOrderRequest(PaymentWithOrderRequest request, User loginUser) throws PaymentException { + if (request == null) { + throw PaymentException.paramError("请求参数不能为空"); + } + if (loginUser == null) { + throw PaymentException.paramError("用户未登录"); + } + if (request.getPaymentType() == null) { + throw PaymentException.paramError("支付类型不能为空"); + } + if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) { + throw PaymentException.amountError("支付金额必须大于0"); + } + if (!StringUtils.hasText(request.getSubject())) { + throw PaymentException.paramError("订单标题不能为空"); + } + if (request.getTenantId() == null || request.getTenantId() <= 0) { + throw PaymentException.paramError("租户ID不能为空且必须大于0"); + } + if (request.getOrderInfo() == null) { + throw PaymentException.paramError("订单信息不能为空"); + } + if (request.getOrderInfo().getGoodsItems() == null || request.getOrderInfo().getGoodsItems().isEmpty()) { + throw PaymentException.paramError("订单商品列表不能为空"); + } + } + + /** + * 转换为订单创建请求 + */ + private OrderCreateRequest convertToOrderCreateRequest(PaymentWithOrderRequest request, User loginUser) { + OrderCreateRequest orderRequest = new OrderCreateRequest(); + + // 生成订单号(使用雪花算法保证全局唯一) + String orderNo = Long.toString(IdUtil.getSnowflakeNextId()); + orderRequest.setOrderNo(orderNo); + log.info("为订单创建请求生成订单号(雪花算法): {}", orderNo); + + // 设置基本信息 + orderRequest.setType(request.getOrderInfo().getType()); + orderRequest.setTitle(request.getSubject()); + orderRequest.setComments(request.getOrderInfo().getComments()); + orderRequest.setTenantId(request.getTenantId()); + + // 设置收货信息 + orderRequest.setRealName(request.getOrderInfo().getRealName()); + orderRequest.setAddress(request.getOrderInfo().getAddress()); + orderRequest.setAddressId(request.getOrderInfo().getAddressId()); + orderRequest.setDeliveryType(request.getOrderInfo().getDeliveryType()); + + // 设置商户信息 + orderRequest.setMerchantId(request.getOrderInfo().getMerchantId()); + orderRequest.setMerchantName(request.getOrderInfo().getMerchantName()); + + // 设置支付信息 + orderRequest.setPayType(request.getPaymentType().getCode()); + orderRequest.setTotalPrice(request.getAmount()); + orderRequest.setPayPrice(request.getAmount()); + + // 设置优惠券 + orderRequest.setCouponId(request.getOrderInfo().getCouponId()); + + // 转换商品列表 + List goodsItems = request.getOrderInfo().getGoodsItems().stream() + .map(this::convertToOrderGoodsItem) + .collect(java.util.stream.Collectors.toList()); + orderRequest.setGoodsItems(goodsItems); + + return orderRequest; + } + + /** + * 转换商品项 + */ + private OrderCreateRequest.OrderGoodsItem convertToOrderGoodsItem(PaymentWithOrderRequest.OrderGoodsItem item) { + OrderCreateRequest.OrderGoodsItem orderItem = new OrderCreateRequest.OrderGoodsItem(); + orderItem.setGoodsId(item.getGoodsId()); + orderItem.setSkuId(item.getSkuId()); + orderItem.setQuantity(item.getQuantity()); + orderItem.setSpecInfo(item.getSpecInfo()); + return orderItem; + } + + /** + * 从微信订单信息构建支付响应 + */ + private PaymentResponse buildPaymentResponseFromWxOrder(Map wxOrderInfo, + PaymentWithOrderRequest request, + String orderNo) { + PaymentResponse response = PaymentResponse.wechatNative( + orderNo, + wxOrderInfo.get("codeUrl"), + request.getAmount(), + request.getTenantId() + ); + + // 设置额外信息 + response.setSuccess(true); + // 确保orderNo被正确设置 + response.setOrderNo(orderNo); + + // 调试日志 + log.info("构建支付响应成功, 订单号: {}, 二维码URL: {}, 响应中的orderNo: {}", + orderNo, wxOrderInfo.get("codeUrl"), response.getOrderNo()); + + return response; + } +} diff --git a/src/main/java/com/gxwebsoft/payment/strategy/PaymentStrategy.java b/src/main/java/com/gxwebsoft/payment/strategy/PaymentStrategy.java new file mode 100644 index 0000000..560c365 --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/strategy/PaymentStrategy.java @@ -0,0 +1,153 @@ +package com.gxwebsoft.payment.strategy; + +import com.gxwebsoft.payment.dto.PaymentRequest; +import com.gxwebsoft.payment.dto.PaymentResponse; +import com.gxwebsoft.payment.enums.PaymentType; +import com.gxwebsoft.payment.exception.PaymentException; + +import java.util.Map; + +/** + * 支付策略接口 + * 定义所有支付方式的统一接口 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public interface PaymentStrategy { + + /** + * 获取支持的支付类型 + * + * @return 支付类型 + */ + PaymentType getSupportedPaymentType(); + + /** + * 验证支付请求参数 + * + * @param request 支付请求 + * @throws PaymentException 参数验证失败时抛出 + */ + void validateRequest(PaymentRequest request) throws PaymentException; + + /** + * 创建支付订单 + * + * @param request 支付请求 + * @return 支付响应 + * @throws PaymentException 支付创建失败时抛出 + */ + PaymentResponse createPayment(PaymentRequest request) throws PaymentException; + + /** + * 查询支付状态 + * + * @param orderNo 订单号 + * @param tenantId 租户ID + * @return 支付响应 + * @throws PaymentException 查询失败时抛出 + */ + PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException; + + /** + * 处理支付回调通知 + * + * @param headers 请求头 + * @param body 请求体 + * @param tenantId 租户ID + * @return 处理结果,返回给第三方的响应内容 + * @throws PaymentException 处理失败时抛出 + */ + String handleNotify(Map headers, String body, Integer tenantId) throws PaymentException; + + /** + * 申请退款 + * + * @param orderNo 订单号 + * @param refundNo 退款单号 + * @param totalAmount 订单总金额 + * @param refundAmount 退款金额 + * @param reason 退款原因 + * @param tenantId 租户ID + * @return 退款响应 + * @throws PaymentException 退款申请失败时抛出 + */ + PaymentResponse refund(String orderNo, String refundNo, + java.math.BigDecimal totalAmount, java.math.BigDecimal refundAmount, + String reason, Integer tenantId) throws PaymentException; + + /** + * 查询退款状态 + * + * @param refundNo 退款单号 + * @param tenantId 租户ID + * @return 退款查询响应 + * @throws PaymentException 查询失败时抛出 + */ + PaymentResponse queryRefund(String refundNo, Integer tenantId) throws PaymentException; + + /** + * 关闭订单 + * + * @param orderNo 订单号 + * @param tenantId 租户ID + * @return 关闭结果 + * @throws PaymentException 关闭失败时抛出 + */ + boolean closeOrder(String orderNo, Integer tenantId) throws PaymentException; + + /** + * 是否支持退款 + * + * @return true表示支持退款 + */ + default boolean supportRefund() { + return false; + } + + /** + * 是否支持查询 + * + * @return true表示支持查询 + */ + default boolean supportQuery() { + return false; + } + + /** + * 是否支持关闭订单 + * + * @return true表示支持关闭订单 + */ + default boolean supportClose() { + return false; + } + + /** + * 是否需要异步通知 + * + * @return true表示需要异步通知 + */ + default boolean needNotify() { + return false; + } + + /** + * 获取策略名称 + * + * @return 策略名称 + */ + default String getStrategyName() { + return getSupportedPaymentType().getName(); + } + + /** + * 获取策略描述 + * + * @return 策略描述 + */ + default String getStrategyDescription() { + return getSupportedPaymentType().getName() + "支付策略"; + } +} diff --git a/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java b/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java new file mode 100644 index 0000000..6c43eaa --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java @@ -0,0 +1,401 @@ +package com.gxwebsoft.payment.strategy; + +import cn.hutool.core.util.IdUtil; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.payment.constants.PaymentConstants; +import com.gxwebsoft.payment.dto.PaymentRequest; +import com.gxwebsoft.payment.dto.PaymentResponse; +import com.gxwebsoft.payment.enums.PaymentStatus; +import com.gxwebsoft.payment.enums.PaymentType; +import com.gxwebsoft.payment.exception.PaymentException; +import com.gxwebsoft.payment.service.WxPayConfigService; +import com.gxwebsoft.payment.service.WxPayNotifyService; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; +import com.wechat.pay.java.service.payments.nativepay.model.Amount; +import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest; +import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse; +import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByOutTradeNoRequest; +import com.wechat.pay.java.service.payments.model.Transaction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.Map; + +/** + * 微信Native支付策略实现 + * 处理微信Native扫码支付 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Component +public class WechatNativeStrategy implements PaymentStrategy { + + @Resource + private WxPayConfigService wxPayConfigService; + + @Resource + private WxPayNotifyService wxPayNotifyService; + + @Override + public PaymentType getSupportedPaymentType() { + return PaymentType.WECHAT_NATIVE; + } + + @Override + public void validateRequest(PaymentRequest request) throws PaymentException { + if (request == null) { + throw PaymentException.paramError("支付请求不能为空"); + } + + if (request.getTenantId() == null) { + throw PaymentException.paramError("租户ID不能为空"); + } + + if (request.getUserId() == null) { + throw PaymentException.paramError("用户ID不能为空"); + } + + if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) { + throw PaymentException.amountError("支付金额必须大于0"); + } + + if (!StringUtils.hasText(request.getSubject())) { + throw PaymentException.paramError("订单标题不能为空"); + } + + // 验证金额范围 + if (request.getAmount().compareTo(new BigDecimal("0.01")) < 0) { + throw PaymentException.amountError("支付金额不能小于0.01元"); + } + + if (request.getAmount().compareTo(new BigDecimal("999999.99")) > 0) { + throw PaymentException.amountError("支付金额不能超过999999.99元"); + } + + // 验证订单标题长度 + if (request.getSubject().length() > PaymentConstants.Config.DESCRIPTION_MAX_LENGTH) { + throw PaymentException.paramError("订单标题长度不能超过" + PaymentConstants.Config.DESCRIPTION_MAX_LENGTH + "个字符"); + } + + log.debug("微信Native支付请求参数验证通过, 租户ID: {}, 金额: {}", + request.getTenantId(), request.getFormattedAmount()); + } + + @Override + public PaymentResponse createPayment(PaymentRequest request) throws PaymentException { + log.info("{}, 支付类型: {}, 租户ID: {}, 金额: {}", + PaymentConstants.LogMessage.PAYMENT_START, getSupportedPaymentType(), + request.getTenantId(), request.getFormattedAmount()); + + try { + // 验证请求参数 + validateRequest(request); + + // 生成订单号 + String orderNo = generateOrderNo(request); + log.info("生成的订单号: {}", orderNo); + + // 获取Native支付的Payment配置(包含appId等信息) + Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(request.getTenantId()); + + // 获取微信支付配置 + Config wxPayConfig = wxPayConfigService.getWxPayConfig(request.getTenantId()); + + // 构建预支付请求 + PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo, paymentConfig); + + // 调用微信支付API + PrepayResponse prepayResponse = callWechatPayApi(prepayRequest, wxPayConfig); + + // 构建响应 + PaymentResponse response = PaymentResponse.wechatNative( + orderNo, prepayResponse.getCodeUrl(), request.getAmount(), request.getTenantId()); + response.setUserId(request.getUserId()); + + // 确保orderNo被正确设置 + response.setOrderNo(orderNo); + + // 调试日志:检查响应对象的orderNo + log.info("构建的响应对象 - orderNo: {}, codeUrl: {}, success: {}", + response.getOrderNo(), response.getCodeUrl(), response.getSuccess()); + + log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}", + PaymentConstants.LogMessage.PAYMENT_SUCCESS, getSupportedPaymentType(), + request.getTenantId(), orderNo, request.getFormattedAmount()); + + return response; + + } catch (PaymentException e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}", + PaymentConstants.LogMessage.PAYMENT_FAILED, getSupportedPaymentType(), + request.getTenantId(), e.getMessage()); + throw e; + } catch (Exception e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}", + PaymentConstants.LogMessage.PAYMENT_FAILED, getSupportedPaymentType(), + request.getTenantId(), e.getMessage(), e); + throw PaymentException.systemError("微信Native支付创建失败: " + e.getMessage(), e); + } + } + + @Override + public PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException { + log.info("开始查询微信Native支付状态, 订单号: {}, 租户ID: {}", orderNo, tenantId); + + try { + // 参数验证 + if (!StringUtils.hasText(orderNo)) { + throw PaymentException.paramError("订单号不能为空"); + } + if (tenantId == null) { + throw PaymentException.paramError("租户ID不能为空"); + } + + // 获取支付配置(包含商户号等信息) + Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(tenantId); + + // 获取微信支付配置 + Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId); + + // 调用微信支付查询API + return queryWechatPaymentStatus(orderNo, tenantId, paymentConfig, wxPayConfig); + + } catch (Exception e) { + if (e instanceof PaymentException) { + throw e; + } + log.error("查询微信Native支付状态失败, 订单号: {}, 租户ID: {}, 错误: {}", + orderNo, tenantId, e.getMessage(), e); + throw PaymentException.systemError("查询微信支付状态失败: " + e.getMessage(), e); + } + } + + @Override + public String handleNotify(Map headers, String body, Integer tenantId) throws PaymentException { + log.info("{}, 支付类型: {}, 租户ID: {}", + PaymentConstants.LogMessage.NOTIFY_START, getSupportedPaymentType(), tenantId); + + try { + // 委托给专门的回调处理服务 + return wxPayNotifyService.handlePaymentNotify(headers, body, tenantId); + } catch (Exception e) { + log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}", + PaymentConstants.LogMessage.NOTIFY_FAILED, getSupportedPaymentType(), + tenantId, e.getMessage(), e); + throw PaymentException.systemError("微信支付回调处理失败: " + e.getMessage(), e); + } + } + + @Override + public PaymentResponse refund(String orderNo, String refundNo, BigDecimal totalAmount, + BigDecimal refundAmount, String reason, Integer tenantId) throws PaymentException { + // TODO: 实现微信支付退款逻辑 + throw PaymentException.unsupportedPayment("暂不支持微信支付退款", PaymentType.WECHAT_NATIVE); + } + + @Override + public PaymentResponse queryRefund(String refundNo, Integer tenantId) throws PaymentException { + // TODO: 实现微信退款查询逻辑 + throw PaymentException.unsupportedPayment("暂不支持微信退款查询", PaymentType.WECHAT_NATIVE); + } + + @Override + public boolean closeOrder(String orderNo, Integer tenantId) throws PaymentException { + // TODO: 实现微信订单关闭逻辑 + throw PaymentException.unsupportedPayment("暂不支持微信订单关闭", PaymentType.WECHAT_NATIVE); + } + + @Override + public boolean supportRefund() { + return true; + } + + @Override + public boolean supportQuery() { + return true; + } + + @Override + public boolean supportClose() { + return true; + } + + @Override + public boolean needNotify() { + return true; + } + + /** + * 生成订单号(使用雪花算法保证全局唯一) + */ + private String generateOrderNo(PaymentRequest request) { + if (StringUtils.hasText(request.getOrderNo())) { + return request.getOrderNo(); + } + return Long.toString(IdUtil.getSnowflakeNextId()); + } + + + + /** + * 构建微信预支付请求 + */ + private PrepayRequest buildPrepayRequest(PaymentRequest request, String orderNo, Payment paymentConfig) { + PrepayRequest prepayRequest = new PrepayRequest(); + + // 设置应用ID和商户号(关键修复) + prepayRequest.setAppid(paymentConfig.getAppId()); + prepayRequest.setMchid(paymentConfig.getMchId()); + + // 设置金额 + Amount amount = new Amount(); + amount.setTotal(request.getAmountInCents()); + amount.setCurrency(PaymentConstants.Wechat.CURRENCY); + prepayRequest.setAmount(amount); + + // 设置基本信息 + prepayRequest.setOutTradeNo(orderNo); + prepayRequest.setDescription(request.getEffectiveDescription()); + + log.info("创建微信支付订单 - 订单号: {}, 商户号: {}, 金额: {}分", + orderNo, paymentConfig.getMchId(), request.getAmountInCents()); + + // 设置回调URL(必填字段) + String notifyUrl = null; + if (StringUtils.hasText(request.getNotifyUrl())) { + // 优先使用请求中的回调URL + notifyUrl = request.getNotifyUrl(); + } else if (StringUtils.hasText(paymentConfig.getNotifyUrl())) { + // 使用配置中的回调URL + notifyUrl = paymentConfig.getNotifyUrl(); + } else { + // 如果都没有,抛出异常 + throw new RuntimeException("回调通知地址不能为空,请在支付请求中设置notifyUrl或在支付配置中设置notifyUrl"); + } + prepayRequest.setNotifyUrl(notifyUrl); + + log.debug("构建微信预支付请求完成, 订单号: {}, 金额: {}分, AppID: {}, 商户号: {}, 回调URL: {}", + orderNo, request.getAmountInCents(), paymentConfig.getAppId(), paymentConfig.getMchId(), notifyUrl); + + return prepayRequest; + } + + /** + * 查询微信支付状态 + */ + private PaymentResponse queryWechatPaymentStatus(String orderNo, Integer tenantId, Payment paymentConfig, Config wxPayConfig) throws PaymentException { + try { + log.info("开始查询微信支付状态 - 订单号: {}, 商户号: {}, 租户ID: {}", + orderNo, paymentConfig.getMchId(), tenantId); + + // 构建查询请求 + QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest(); + queryRequest.setOutTradeNo(orderNo); + queryRequest.setMchid(paymentConfig.getMchId()); + + // 构建服务 + NativePayService service = new NativePayService.Builder().config(wxPayConfig).build(); + + // 调用查询接口 + Transaction transaction = service.queryOrderByOutTradeNo(queryRequest); + + if (transaction == null) { + throw PaymentException.systemError("微信支付查询返回空结果", null); + } + + // 转换支付状态 + PaymentStatus paymentStatus = convertWechatPaymentStatus(transaction.getTradeState()); + + // 构建响应 + PaymentResponse response = new PaymentResponse(); + response.setSuccess(true); + response.setOrderNo(orderNo); + response.setPaymentStatus(paymentStatus); + response.setTenantId(tenantId); + response.setPaymentType(PaymentType.WECHAT_NATIVE); + + if (transaction.getAmount() != null) { + // 微信返回的金额是分,需要转换为元 + BigDecimal amount = new BigDecimal(transaction.getAmount().getTotal()).divide(new BigDecimal("100")); + response.setAmount(amount); + } + + if (transaction.getTransactionId() != null) { + response.setTransactionId(transaction.getTransactionId()); + } + + log.info("微信Native支付状态查询成功, 订单号: {}, 状态: {}, 微信交易号: {}", + orderNo, paymentStatus, transaction.getTransactionId()); + + return response; + + } catch (Exception e) { + if (e instanceof PaymentException) { + throw e; + } + log.error("查询微信支付状态失败, 订单号: {}, 错误: {}", orderNo, e.getMessage(), e); + throw PaymentException.networkError("查询微信支付状态失败: " + e.getMessage(), PaymentType.WECHAT_NATIVE, e); + } + } + + /** + * 转换微信支付状态 + */ + private PaymentStatus convertWechatPaymentStatus(Transaction.TradeStateEnum tradeState) { + if (tradeState == null) { + return PaymentStatus.PENDING; + } + + switch (tradeState) { + case SUCCESS: + return PaymentStatus.SUCCESS; + case REFUND: + return PaymentStatus.REFUNDED; + case NOTPAY: + return PaymentStatus.PENDING; + case CLOSED: + return PaymentStatus.CANCELLED; + case REVOKED: + return PaymentStatus.CANCELLED; + case USERPAYING: + return PaymentStatus.PROCESSING; + case PAYERROR: + return PaymentStatus.FAILED; + default: + return PaymentStatus.PENDING; + } + } + + /** + * 调用微信支付API + */ + private PrepayResponse callWechatPayApi(PrepayRequest request, Config wxPayConfig) throws PaymentException { + try { + // 构建服务 + NativePayService service = new NativePayService.Builder().config(wxPayConfig).build(); + + // 调用预支付接口 + PrepayResponse response = service.prepay(request); + + if (response == null || !StringUtils.hasText(response.getCodeUrl())) { + throw PaymentException.networkError("微信支付API返回数据异常", PaymentType.WECHAT_NATIVE, null); + } + + log.debug("微信支付API调用成功, 订单号: {}", request.getOutTradeNo()); + return response; + + } catch (Exception e) { + if (e instanceof PaymentException) { + throw e; + } + throw PaymentException.networkError("调用微信支付API失败: " + e.getMessage(), PaymentType.WECHAT_NATIVE, e); + } + } +} diff --git a/src/main/java/com/gxwebsoft/payment/utils/PaymentTypeCompatibilityUtil.java b/src/main/java/com/gxwebsoft/payment/utils/PaymentTypeCompatibilityUtil.java new file mode 100644 index 0000000..682bfef --- /dev/null +++ b/src/main/java/com/gxwebsoft/payment/utils/PaymentTypeCompatibilityUtil.java @@ -0,0 +1,165 @@ +package com.gxwebsoft.payment.utils; + +import com.gxwebsoft.payment.enums.PaymentType; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.Map; + +/** + * 支付方式兼容性处理工具类 + * 处理废弃支付方式到核心支付方式的映射转换 + * + * @author 科技小王子 + * @since 2025-08-30 + */ +@Slf4j +public class PaymentTypeCompatibilityUtil { + + /** + * 废弃支付方式到核心支付方式的映射表 + */ + private static final Map DEPRECATED_TO_CORE_MAPPING = new HashMap<>(); + + static { + // 旧编号到新编号的映射 + DEPRECATED_TO_CORE_MAPPING.put(3, 2); // 支付宝(旧3) -> 支付宝(新2) + DEPRECATED_TO_CORE_MAPPING.put(12, 6); // 免费(旧12) -> 免费(新6) + DEPRECATED_TO_CORE_MAPPING.put(15, 7); // 积分支付(旧15) -> 积分支付(新7) + DEPRECATED_TO_CORE_MAPPING.put(19, 3); // 银联支付(旧19) -> 银联支付(新3) + + // 会员卡类支付 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(2, 0); // 会员卡支付 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(6, 0); // VIP月卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(7, 0); // VIP年卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(8, 0); // VIP次卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(9, 0); // IC月卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(10, 0); // IC年卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(11, 0); // IC次卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(13, 0); // VIP充值卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(14, 0); // IC充值卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(16, 0); // VIP季卡 -> 余额支付 + DEPRECATED_TO_CORE_MAPPING.put(17, 0); // IC季卡 -> 余额支付 + + // 微信Native -> 微信支付 + DEPRECATED_TO_CORE_MAPPING.put(102, 1); // 微信Native -> 微信支付 + + // 代付 -> 微信支付(默认) + DEPRECATED_TO_CORE_MAPPING.put(18, 1); // 代付 -> 微信支付 + } + + /** + * 将废弃的支付方式转换为核心支付方式 + * + * @param originalPayType 原始支付方式代码 + * @return 转换后的核心支付方式代码 + */ + public static Integer convertToCore(Integer originalPayType) { + if (originalPayType == null) { + return null; + } + + // 检查是否为废弃的支付方式 + if (DEPRECATED_TO_CORE_MAPPING.containsKey(originalPayType)) { + Integer corePayType = DEPRECATED_TO_CORE_MAPPING.get(originalPayType); + log.warn("检测到废弃的支付方式: {} -> {},建议升级到核心支付方式", + originalPayType, corePayType); + return corePayType; + } + + // 如果是核心支付方式,直接返回 + return originalPayType; + } + + /** + * 检查支付方式是否已废弃 + * + * @param payType 支付方式代码 + * @return true表示已废弃 + */ + public static boolean isDeprecated(Integer payType) { + return payType != null && DEPRECATED_TO_CORE_MAPPING.containsKey(payType); + } + + /** + * 获取支付方式的迁移说明 + * + * @param payType 支付方式代码 + * @return 迁移说明文本 + */ + public static String getMigrationMessage(Integer payType) { + if (payType == null || !isDeprecated(payType)) { + return null; + } + + PaymentType originalType = PaymentType.getByCode(payType); + PaymentType coreType = PaymentType.getByCode(convertToCore(payType)); + + if (originalType != null && coreType != null) { + return String.format("支付方式 %s(%d) 已废弃,建议使用 %s(%d)", + originalType.getName(), payType, + coreType.getName(), coreType.getCode()); + } + + return "该支付方式已废弃,请使用核心支付方式"; + } + + /** + * 获取所有核心支付方式代码 + * + * @return 核心支付方式代码数组 + */ + public static Integer[] getCorePaymentTypeCodes() { + return new Integer[]{0, 1, 2, 3, 4, 5, 6, 7}; + } + + /** + * 检查是否为核心支付方式 + * + * @param payType 支付方式代码 + * @return true表示是核心支付方式 + */ + public static boolean isCorePaymentType(Integer payType) { + if (payType == null) { + return false; + } + + for (Integer coreType : getCorePaymentTypeCodes()) { + if (coreType.equals(payType)) { + return true; + } + } + return false; + } + + /** + * 生成支付方式迁移报告 + * + * @return 迁移报告文本 + */ + public static String generateMigrationReport() { + StringBuilder report = new StringBuilder(); + report.append("=== 支付方式迁移报告 ===\n"); + report.append("核心支付方式(8种):\n"); + + for (Integer coreType : getCorePaymentTypeCodes()) { + PaymentType type = PaymentType.getByCode(coreType); + if (type != null) { + report.append(String.format(" %d - %s\n", coreType, type.getName())); + } + } + + report.append("\n废弃支付方式映射:\n"); + for (Map.Entry entry : DEPRECATED_TO_CORE_MAPPING.entrySet()) { + PaymentType originalType = PaymentType.getByCode(entry.getKey()); + PaymentType coreType = PaymentType.getByCode(entry.getValue()); + if (originalType != null && coreType != null) { + report.append(String.format(" %d(%s) -> %d(%s)\n", + entry.getKey(), originalType.getName(), + entry.getValue(), coreType.getName())); + } + } + + return report.toString(); + } +} diff --git a/src/main/java/com/gxwebsoft/project/controller/ProjectCollectionController.java b/src/main/java/com/gxwebsoft/project/controller/ProjectCollectionController.java new file mode 100644 index 0000000..1a61c7c --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/controller/ProjectCollectionController.java @@ -0,0 +1,128 @@ +package com.gxwebsoft.project.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.project.service.ProjectCollectionService; +import com.gxwebsoft.project.entity.ProjectCollection; +import com.gxwebsoft.project.param.ProjectCollectionParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 我的收藏控制器 + * + * @author 科技小王子 + * @since 2025-03-16 12:12:13 + */ +@Tag(name = "我的收藏管理") +@RestController +@RequestMapping("/api/project/project-collection") +public class ProjectCollectionController extends BaseController { + @Resource + private ProjectCollectionService projectCollectionService; + + @Operation(summary = "分页查询我的收藏") + @GetMapping("/page") + public ApiResult> page(ProjectCollectionParam param) { + // 使用关联查询 + return success(projectCollectionService.pageRel(param)); + } + + @Operation(summary = "查询全部我的收藏") + @GetMapping() + public ApiResult> list(ProjectCollectionParam param) { + // 使用关联查询 + return success(projectCollectionService.listRel(param)); + } + + @Operation(summary = "是否收藏") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + final ProjectCollection projectCollection = projectCollectionService.getOne(new LambdaQueryWrapper().eq(ProjectCollection::getAppId, id).eq(ProjectCollection::getUserId, getLoginUserId()).last("limit 1")); + if(ObjectUtil.isNotEmpty(projectCollection)){ + return success(true); + } + return success(false); + } + + @OperationLog + @Operation(summary = "加入收藏") + @PostMapping() + public ApiResult save(@RequestBody ProjectCollection projectCollection) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + projectCollection.setUserId(loginUser.getUserId()); + } + if (projectCollectionService.save(projectCollection)) { + return success("收藏成功"); + } + return fail("操作失败"); + } + + @OperationLog + @Operation(summary = "修改我的收藏") + @PutMapping() + public ApiResult update(@RequestBody ProjectCollection projectCollection) { + if (projectCollectionService.updateById(projectCollection)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @OperationLog + @Operation(summary = "取消收藏") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (projectCollectionService.remove(new LambdaQueryWrapper().eq(ProjectCollection::getAppId,id).eq(ProjectCollection::getUserId,getLoginUserId()))) { + return success("已取消收藏"); + } + return fail("操作失败"); + } + + @PreAuthorize("hasAuthority('project:projectCollection:save')") + @OperationLog + @Operation(summary = "批量添加我的收藏") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (projectCollectionService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:projectCollection:update')") + @OperationLog + @Operation(summary = "批量修改我的收藏") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(projectCollectionService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectCollection:remove')") + @OperationLog + @Operation(summary = "批量删除我的收藏") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (projectCollectionService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/controller/ProjectController.java b/src/main/java/com/gxwebsoft/project/controller/ProjectController.java new file mode 100644 index 0000000..ae031dd --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/controller/ProjectController.java @@ -0,0 +1,297 @@ +package com.gxwebsoft.project.controller; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.cms.param.CmsWebsiteParam; +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.project.entity.ProjectUser; +import com.gxwebsoft.project.service.ProjectService; +import com.gxwebsoft.project.entity.Project; +import com.gxwebsoft.project.param.ProjectParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.project.service.ProjectUserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 应用控制器 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Tag(name = "应用管理") +@RestController +@RequestMapping("/api/project/project") +public class ProjectController extends BaseController { + @Resource + private ProjectService projectService; + @Resource + private ProjectUserService projectUserService; + @Resource + private CmsWebsiteService cmsWebsiteService; + + @PreAuthorize("hasAuthority('project:project:list')") + @Operation(summary = "分页查询应用") + @GetMapping("/page") + public ApiResult> page(ProjectParam param) { + final User loginUser = getLoginUser(); + if (loginUser != null) { + param.setLoginUserId(loginUser.getUserId()); + final List roles = loginUser.getRoles(); + if (!CommonUtil.hasRole(roles, "admin") && !CommonUtil.hasRole(roles, "superAdmin")) { + final List projectUsers = projectUserService.list(new LambdaQueryWrapper().eq(ProjectUser::getUserId, loginUser.getUserId())); + final Set appIds = projectUsers.stream().map(ProjectUser::getAppId).collect(Collectors.toSet()); + param.setAppIds(appIds); + } + final PageResult result = projectService.pageRel(param); + final CmsWebsiteParam websiteParam = new CmsWebsiteParam(); + final List projects = result.getList(); + if(!CollectionUtils.isEmpty(projects)){ + final Set collectByProject = projects.stream().map(Project::getWebsiteId).collect(Collectors.toSet()); + websiteParam.setWebsiteIds(collectByProject); + final PageResult websitePageResult = cmsWebsiteService.pageRelAll(websiteParam); + if (!CollectionUtils.isEmpty(websitePageResult.getList())) { + final Map> collectByWebsite = websitePageResult.getList().stream().collect(Collectors.groupingBy(CmsWebsite::getWebsiteId)); + result.getList().forEach(item -> { + final List cmsWebsites = collectByWebsite.get(item.getWebsiteId()); + if (!CollectionUtils.isEmpty(cmsWebsites)) { + final CmsWebsite website = cmsWebsites.get(0); + item.setAppUrl(website.getDomain()); + item.setAdminUrl(website.getAdminUrl()); + item.setAppIcon(website.getWebsiteLogo()); + item.setAppType(website.getWebsiteType()); + item.setSuperAdminPhone(website.getSuperAdminPhone()); + } + }); + } + } + return success(result); + } + return fail("获取失败",null); + } + + @PreAuthorize("hasAuthority('project:project:list')") + @Operation(summary = "查询全部应用") + @GetMapping() + public ApiResult> list(ProjectParam param) { + // 使用关联查询 + return success(projectService.listRel(param)); + } + + @PreAuthorize("hasAuthority('project:project:list')") + @Operation(summary = "根据id查询应用") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(projectService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('project:project:save')") + @OperationLog + @Operation(summary = "添加应用") + @PostMapping() + public ApiResult save(@RequestBody Project project) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + project.setUserId(loginUser.getUserId()); + final Project one = projectService.getOne(new LambdaQueryWrapper().eq(Project::getAppCode, project.getAppCode()).last("limit 1")); + if (!ObjectUtil.isEmpty(one)) { + return fail("应用标识已存在"); + } + if (projectService.save(project)) { + final ProjectUser user = new ProjectUser(); + user.setUserId(loginUser.getUserId()); + user.setAppId(project.getAppId()); + user.setRole(30); + user.setNickname(loginUser.getNickname()); + projectUserService.save(user); + return success("添加成功"); + } + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:project:update')") + @OperationLog + @Operation(summary = "修改应用") + @PutMapping() + public ApiResult update(@RequestBody Project project) { + if(project.getAppStatus() != null && project.getAppStatus().equals("已上架")){ + project.setProgress(100); + } + if (projectService.updateById(project)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:project:remove')") + @OperationLog + @Operation(summary = "删除应用") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (projectService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('project:project:save')") + @OperationLog + @Operation(summary = "批量添加应用") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (projectService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:project:update')") + @OperationLog + @Operation(summary = "批量修改应用") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(projectService, "app_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:project:remove')") + @OperationLog + @Operation(summary = "批量删除应用") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (projectService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + + @Operation(summary = "统计信息") + @GetMapping("/data") + public ApiResult> data() { + Map data = new HashMap<>(); + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + final User loginUser = getLoginUser(); + if(loginUser != null){ + + final List roles = loginUser.getRoles(); + if (!CommonUtil.hasRole(roles, "admin") && !CommonUtil.hasRole(roles, "superAdmin")) { + final List projectUsers = projectUserService.list(new LambdaQueryWrapper().eq(ProjectUser::getUserId, loginUser.getUserId())); + final Set userIds = projectUsers.stream().map(ProjectUser::getAppId).collect(Collectors.toSet()); + wrapper.in(Project::getUserId,userIds); + } + + Integer totalNum = Math.toIntExact(projectService.count(wrapper)); + + wrapper.eq(Project::getAppStatus, "开发中"); + Integer totalNum2 = Math.toIntExact(projectService.count(wrapper)); + + wrapper.clear(); + wrapper.eq(Project::getAppStatus,"已上架"); + Integer totalNum3 = Math.toIntExact(projectService.count(wrapper)); + + wrapper.clear(); + wrapper.eq(Project::getAppStatus,"已上架").eq(Project::getShowExpiration,true); + Integer totalNum4 = Math.toIntExact(projectService.count(wrapper)); + + wrapper.clear(); + wrapper.eq(Project::getAppStatus, "已下架"); + Integer totalNum5 = Math.toIntExact(projectService.count(wrapper)); + + wrapper.clear(); + wrapper.eq(Project::getShowCase,true); + Integer totalNum6 = Math.toIntExact(projectService.count(wrapper)); + + wrapper.clear(); + wrapper.eq(Project::getShowIndex,true); + Integer totalNum7 = Math.toIntExact(projectService.count(wrapper)); + + data.put("totalNum", totalNum); + data.put("totalNum2", totalNum2); + data.put("totalNum3", totalNum3); + data.put("totalNum4", totalNum4); + data.put("totalNum5", totalNum5); + data.put("totalNum6", totalNum6); + data.put("totalNum7", totalNum7); + } + return success(data); + } + + @Operation(summary = "统计信息") + @GetMapping("/count") + public ApiResult> count() { + Map data = new HashMap<>(); + final User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录", null); + } + + // 今天日期 + DateTime date = DateUtil.date(); + // 获取当前年份的起止时间 + LocalDateTime startOfYear = LocalDateTime.now().withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfYear = startOfYear.plusYears(1).minusNanos(1); + // 去年的起止时间 + LocalDateTime startOfLastYear = LocalDateTime.now().minusYears(1).withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfLastYear = startOfLastYear.plusYears(1).minusNanos(1); + + // TODO 近30天可催收的续费总额 + // 下个月的今天 + final DateTime nextMonth = DateUtil.nextMonth(); + final BigDecimal totalPrice30 = projectService.sumMoney(new LambdaQueryWrapper() + .lt(Project::getExpirationTime, nextMonth) + .gt(Project::getExpirationTime, date) + .eq(Project::getDeleted, 0) + ); + data.put("totalPrice30", totalPrice30); + + + // TODO 今年已收续费总额 + BigDecimal yearTotalPrice = projectService.sumMoney(new LambdaQueryWrapper() + .between(Project::getUpdateTime, startOfYear, endOfYear) + .eq(Project::getDeleted, 0) + ); + data.put("yearTotalPrice", yearTotalPrice); + + // TODO 去年已收续费总额 + BigDecimal lastTotalPrice = projectService.sumMoney(new LambdaQueryWrapper() + .between(Project::getUpdateTime, startOfLastYear, endOfLastYear) + .eq(Project::getDeleted, 0) + .eq(Project::getDeleted,0) + ); + // 去年已收续费总额 + data.put("lastTotalPrice", lastTotalPrice); + + return success(data); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/controller/ProjectFieldController.java b/src/main/java/com/gxwebsoft/project/controller/ProjectFieldController.java new file mode 100644 index 0000000..d1cf3d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/controller/ProjectFieldController.java @@ -0,0 +1,134 @@ +package com.gxwebsoft.project.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.project.service.ProjectFieldService; +import com.gxwebsoft.project.entity.ProjectField; +import com.gxwebsoft.project.param.ProjectFieldParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用参数控制器 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Tag(name = "应用参数管理") +@RestController +@RequestMapping("/api/project/project-field") +public class ProjectFieldController extends BaseController { + @Resource + private ProjectFieldService projectFieldService; + + @PreAuthorize("hasAuthority('project:project:list')") + @Operation(summary = "分页查询应用参数") + @GetMapping("/page") + public ApiResult> page(ProjectFieldParam param) { + // 使用关联查询 + return success(projectFieldService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('project:project:list')") + @Operation(summary = "查询全部应用参数") + @GetMapping() + public ApiResult> list(ProjectFieldParam param) { + // 使用关联查询 + return success(projectFieldService.listRel(param)); + } + + @PreAuthorize("hasAuthority('project:project:list')") + @Operation(summary = "根据id查询应用参数") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(projectFieldService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('project:project:save')") + @OperationLog + @Operation(summary = "添加应用参数") + @PostMapping() + public ApiResult save(@RequestBody ProjectField projectField) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + projectField.setUserId(loginUser.getUserId()); + } + if (projectFieldService.save(projectField)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:project:update')") + @OperationLog + @Operation(summary = "修改应用参数") + @PutMapping() + public ApiResult update(@RequestBody ProjectField projectField) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + projectField.setUpdateUserId(loginUser.getUserId()); + } + if (projectFieldService.updateById(projectField)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:project:remove')") + @OperationLog + @Operation(summary = "删除应用参数") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (projectFieldService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('project:project:save')") + @OperationLog + @Operation(summary = "批量添加应用参数") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (projectFieldService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:project:update')") + @OperationLog + @Operation(summary = "批量修改应用参数") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(projectFieldService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:project:remove')") + @OperationLog + @Operation(summary = "批量删除应用参数") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (projectFieldService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/controller/ProjectRenewController.java b/src/main/java/com/gxwebsoft/project/controller/ProjectRenewController.java new file mode 100644 index 0000000..8910d02 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/controller/ProjectRenewController.java @@ -0,0 +1,290 @@ +package com.gxwebsoft.project.controller; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.project.entity.Project; +import com.gxwebsoft.project.param.ProjectParam; +import com.gxwebsoft.project.service.ProjectRenewService; +import com.gxwebsoft.project.entity.ProjectRenew; +import com.gxwebsoft.project.param.ProjectRenewParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.project.service.ProjectService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +/** + * 续费管理控制器 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Tag(name = "续费管理管理") +@RestController +@RequestMapping("/api/project/project-renew") +public class ProjectRenewController extends BaseController { + @Resource + private ProjectRenewService projectRenewService; + @Resource + private ProjectService projectService; + + @PreAuthorize("hasAuthority('project:projectRenew:list')") + @Operation(summary = "分页查询续费管理") + @GetMapping("/page") + public ApiResult> page(ProjectRenewParam param) { + return success(projectRenewService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('project:projectRenew:list')") + @Operation(summary = "查询全部续费管理") + @GetMapping() + public ApiResult> list(ProjectRenewParam param) { + // 使用关联查询 + return success(projectRenewService.listRel(param)); + } + + @PreAuthorize("hasAuthority('project:projectRenew:list')") + @Operation(summary = "根据id查询续费管理") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(projectRenewService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('project:projectRenew:save')") + @OperationLog + @Operation(summary = "添加续费管理") + @PostMapping() + public ApiResult save(@RequestBody ProjectRenew projectRenew) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + projectRenew.setUserId(loginUser.getUserId()); + } + // 更新项目状态 + if (projectRenewService.save(projectRenew)) { + projectService.updateByRenew(projectRenew); + return success("操作成功"); + } + return fail("操作失败"); + } + + @PreAuthorize("hasAuthority('project:projectRenew:update')") + @OperationLog + @Operation(summary = "修改续费管理") + @PutMapping() + public ApiResult update(@RequestBody ProjectRenew projectRenew) { + if (projectRenewService.updateById(projectRenew)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectRenew:remove')") + @OperationLog + @Operation(summary = "删除续费管理") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + // 撤销操作 + final ProjectRenew renew = projectRenewService.getByIdRel(id); + if (ObjectUtil.isNotEmpty(renew)) { + final Project project = projectService.getOne(new LambdaQueryWrapper().eq(Project::getAppId, renew.getAppId())); + if (renew.getDays() > 0) { + // 按天续费 + project.setExpirationTime(project.getExpirationTime().minusDays(renew.getDays())); + } else { + // 按年续费 + project.setExpirationTime(project.getExpirationTime().minusMonths(12 * renew.getDuration())); + // 回退上一年的续费金额 + final List renews = projectRenewService.list(new LambdaQueryWrapper().eq(ProjectRenew::getAppId, renew.getAppId()).orderByDesc(ProjectRenew::getAppRenewId).last("limit 2")); + if(renews.size() > 1){ + final ProjectRenew projectRenew = renews.get(1); + project.setRenewMoney(projectRenew.getPayPrice()); + } + projectService.updateById(project); + } + // 保存到期时间所在的年月日 + final LocalDateTime expirationTime = project.getExpirationTime(); + LocalDate localDate = expirationTime.toLocalDate(); + int year = localDate.getYear(); + int month = localDate.getMonthValue(); // 获取月份,范围是1到12 + int day = localDate.getDayOfMonth(); // 获取日,范围是1到31 + project.setYear(year); + project.setMonth(month); + project.setDay(day); + } + + if (projectRenewService.removeById(id)) { + return success("该笔续费操作已撤销"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('project:projectRenew:save')") + @OperationLog + @Operation(summary = "批量添加续费管理") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (projectRenewService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:projectRenew:update')") + @OperationLog + @Operation(summary = "批量修改续费管理") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(projectRenewService, "app_renew_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectRenew:remove')") + @OperationLog + @Operation(summary = "批量删除续费管理") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (projectRenewService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + + @Operation(summary = "统计信息") + @GetMapping("/data") + public ApiResult> data(ProjectParam param) { + Map data = new HashMap<>(); + final User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录", null); + } + + // 今天日期 + DateTime date = DateUtil.date(); + // 获取当前年份的起止时间 + LocalDateTime startOfYear = LocalDateTime.now().withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfYear = startOfYear.plusYears(1).minusNanos(1); + // 去年的起止时间 + LocalDateTime startOfLastYear = LocalDateTime.now().minusYears(1).withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfLastYear = startOfLastYear.plusYears(1).minusNanos(1); + // 本月起止时间 + LocalDateTime startOfMonth = LocalDateTime.now().withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfMonth = startOfMonth.plusMonths(1).minusNanos(1); + + + // TODO 近30天可催收的续费总额 + // 下个月的今天 + final DateTime nextMonth = DateUtil.nextMonth(); + BigDecimal totalPrice30 = projectService.sumMoney(new LambdaQueryWrapper() + .lt(Project::getExpirationTime, nextMonth) + .gt(Project::getExpirationTime, date) + .lt(Project::getCreateTime, date) + .eq(Project::getShowExpiration, true) + .eq(Project::getAppStatus, "已上架") + .eq(Project::getDeleted, 0) + ); + data.put("totalPrice30", totalPrice30); + + // TODO 按所属月份查询续费总金额 + if(param.getMonth() != null){ + BigDecimal currentQueryTotalPrice = projectService.sumMoney(new LambdaQueryWrapper() + .eq(Project::getMonth, param.getMonth()) + .eq(Project::getShowExpiration, true) + .eq(Project::getAppStatus, "已上架") + .eq(Project::getDeleted, 0) + ); + data.put("currentQueryTotalPrice", currentQueryTotalPrice); + } + + // TODO 有效的续费总金额 + final BigDecimal effectiveTotalPrice = projectService.sumMoney(new LambdaQueryWrapper() + .eq(Project::getDeleted, 0) + .eq(Project::getShowExpiration, true) + .eq(Project::getAppStatus, "已上架") + .gt(Project::getExpirationTime, date)); + data.put("effectiveTotalPrice", effectiveTotalPrice); + + // TODO 已超过催收时间的续费总额 + final BigDecimal expiredPrice = projectService.sumMoney(new LambdaQueryWrapper() + .eq(Project::getDeleted, 0) + .eq(Project::getShowExpiration, true) + .eq(Project::getAppStatus, "已上架") + .lt(Project::getExpirationTime, date)); + data.put("expiredPrice", expiredPrice); + + // TODO 计算每年可收续费总额 + final BigDecimal totalRenewPrice = projectService.sumMoney(new LambdaQueryWrapper() + .eq(Project::getDeleted, 0) + .eq(Project::getShowExpiration, true) + .eq(Project::getAppStatus, "已上架") + ); + + // TODO 本月已收续费总额 + final BigDecimal monthTotalPrice = projectRenewService.sumMoney(new LambdaQueryWrapper() + .between(ProjectRenew::getCreateTime, startOfMonth, endOfMonth) + .eq(ProjectRenew::getDeleted, 0) + ); + data.put("monthTotalPrice", monthTotalPrice); + + // TODO 今年已收续费总额 + BigDecimal yearTotalPrice = projectRenewService.sumMoney(new LambdaQueryWrapper() + .between(ProjectRenew::getCreateTime, startOfYear, endOfYear) + .eq(ProjectRenew::getDeleted, 0) + ); + data.put("yearTotalPrice", yearTotalPrice); + + // TODO 去年已收续费总额 + BigDecimal lastTotalPrice = projectRenewService.sumMoney(new LambdaQueryWrapper() + .eq(ProjectRenew::getDeleted, 0) + .between(ProjectRenew::getEndTime, startOfLastYear, endOfLastYear)); + // 去年已收续费总额 + data.put("lastTotalPrice", lastTotalPrice); + + data.put("totalRenewPrice", totalRenewPrice); + + return success(data); + } + + @Operation(summary = "统计每个月的续费总金额") + @GetMapping("/listMonthRenewPrice") + public ApiResult> listMonthRenewPrice() { + final User loginUser = getLoginUser(); + if (loginUser != null) { + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + List monthPrice = new ArrayList<>(); + for (int i = 1; i < 13; i++) { + wrapper.clear(); + wrapper.eq(Project::getDeleted, 0) + .eq(Project::getShowExpiration, true) + .eq(Project::getAppStatus, "已上架"); + monthPrice.add(projectService.sumMoney(wrapper.eq(Project::getMonth,i))); + } + return success(monthPrice); + } + return fail("请先登录", null); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/controller/ProjectUrlController.java b/src/main/java/com/gxwebsoft/project/controller/ProjectUrlController.java new file mode 100644 index 0000000..4300e9f --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/controller/ProjectUrlController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.project.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.project.service.ProjectUrlService; +import com.gxwebsoft.project.entity.ProjectUrl; +import com.gxwebsoft.project.param.ProjectUrlParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 项目域名控制器 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Tag(name = "项目域名管理") +@RestController +@RequestMapping("/api/project/project-url") +public class ProjectUrlController extends BaseController { + @Resource + private ProjectUrlService projectUrlService; + + @PreAuthorize("hasAuthority('project:projectUrl:list')") + @Operation(summary = "分页查询项目域名") + @GetMapping("/page") + public ApiResult> page(ProjectUrlParam param) { + // 使用关联查询 + return success(projectUrlService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('project:projectUrl:list')") + @Operation(summary = "查询全部项目域名") + @GetMapping() + public ApiResult> list(ProjectUrlParam param) { + // 使用关联查询 + return success(projectUrlService.listRel(param)); + } + + @PreAuthorize("hasAuthority('project:projectUrl:list')") + @Operation(summary = "根据id查询项目域名") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(projectUrlService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('project:projectUrl:save')") + @OperationLog + @Operation(summary = "添加项目域名") + @PostMapping() + public ApiResult save(@RequestBody ProjectUrl projectUrl) { + if (projectUrlService.save(projectUrl)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:projectUrl:update')") + @OperationLog + @Operation(summary = "修改项目域名") + @PutMapping() + public ApiResult update(@RequestBody ProjectUrl projectUrl) { + if (projectUrlService.updateById(projectUrl)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectUrl:remove')") + @OperationLog + @Operation(summary = "删除项目域名") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (projectUrlService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('project:projectUrl:save')") + @OperationLog + @Operation(summary = "批量添加项目域名") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (projectUrlService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:projectUrl:update')") + @OperationLog + @Operation(summary = "批量修改项目域名") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(projectUrlService, "app_url_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectUrl:remove')") + @OperationLog + @Operation(summary = "批量删除项目域名") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (projectUrlService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/controller/ProjectUserController.java b/src/main/java/com/gxwebsoft/project/controller/ProjectUserController.java new file mode 100644 index 0000000..0697174 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/controller/ProjectUserController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.project.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.project.service.ProjectUserService; +import com.gxwebsoft.project.entity.ProjectUser; +import com.gxwebsoft.project.param.ProjectUserParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 应用成员控制器 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Tag(name = "应用成员管理") +@RestController +@RequestMapping("/api/project/project-user") +public class ProjectUserController extends BaseController { + @Resource + private ProjectUserService projectUserService; + + @PreAuthorize("hasAuthority('project:projectUser:list')") + @Operation(summary = "分页查询应用成员") + @GetMapping("/page") + public ApiResult> page(ProjectUserParam param) { + // 使用关联查询 + return success(projectUserService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('project:projectUser:list')") + @Operation(summary = "查询全部应用成员") + @GetMapping() + public ApiResult> list(ProjectUserParam param) { + // 使用关联查询 + return success(projectUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('project:projectUser:list')") + @Operation(summary = "根据id查询应用成员") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(projectUserService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('project:projectUser:save')") + @OperationLog + @Operation(summary = "添加应用成员") + @PostMapping() + public ApiResult save(@RequestBody ProjectUser projectUser) { + if (projectUserService.save(projectUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:projectUser:update')") + @OperationLog + @Operation(summary = "修改应用成员") + @PutMapping() + public ApiResult update(@RequestBody ProjectUser projectUser) { + if (projectUserService.updateById(projectUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectUser:remove')") + @OperationLog + @Operation(summary = "删除应用成员") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (projectUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('project:projectUser:save')") + @OperationLog + @Operation(summary = "批量添加应用成员") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (projectUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('project:projectUser:update')") + @OperationLog + @Operation(summary = "批量修改应用成员") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(projectUserService, "app_user_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('project:projectUser:remove')") + @OperationLog + @Operation(summary = "批量删除应用成员") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (projectUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/entity/Project.java b/src/main/java/com/gxwebsoft/project/entity/Project.java new file mode 100644 index 0000000..55ad688 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/entity/Project.java @@ -0,0 +1,287 @@ +package com.gxwebsoft.project.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.List; +import java.util.Set; + +import com.gxwebsoft.cms.entity.CmsWebsite; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "Project对象", description = "应用") +public class Project implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "应用ID") + @TableId(value = "app_id", type = IdType.AUTO) + private Integer appId; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "应用标识") + private String appCode; + + @Schema(description = "应用秘钥") + private String appSecret; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "应用类型") + private String appTypeMultiple; + + @Schema(description = "类型, 0菜单, 1按钮") + private Integer menuType; + + @Schema(description = "企业(存用户ID)") + private Integer companyId; + + @Schema(description = "企业名称") + @TableField(exist = false) + private String companyName; + + @Schema(description = "超管账号") + @TableField(exist = false) + private String superAdminPhone; + + @Schema(description = "应用图标") + private String appIcon; + + @Schema(description = "二维码") + private String appQrcode; + + @Schema(description = "链接地址") + private String appUrl; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "下载地址") + private String downUrl; + + @Schema(description = "链接地址") + private String serverUrl; + + @Schema(description = "文件服务器") + private String fileUrl; + + @Schema(description = "回调地址") + private String callbackUrl; + + @Schema(description = "腾讯文档地址") + private String docsUrl; + + @Schema(description = "代码仓库地址") + private String gitUrl; + + @Schema(description = "原型图地址") + private String prototypeUrl; + + @Schema(description = "IP白名单") + private String ipAddress; + + @Schema(description = "应用截图") + private String images; + + @Schema(description = "应用包名") + private String packageName; + + @Schema(description = "下载次数") + private Integer clicks; + + @Schema(description = "安装次数") + private Integer installs; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "应用介绍") + private String content; + + @Schema(description = "项目需求") + private String requirement; + + @Schema(description = "开发者(个人或公司)") + private String developer; + + @Schema(description = "项目负责人") + private String director; + + @Schema(description = "项目经理") + private String projectDirector; + + @Schema(description = "业务员") + private String salesman; + + @Schema(description = "软件定价") + private BigDecimal price; + + @Schema(description = "划线价格") + private BigDecimal linePrice; + + @Schema(description = "评分") + private String score; + + @Schema(description = "星级") + private String star; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "禁止搜索,1禁止 0 允许") + private Integer search; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "版本,0正式版 1体验版 2开发版") + private String edition; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "是否已安装") + private Integer isUse; + + @Schema(description = "附近1") + private String file1; + + @Schema(description = "附件2") + private String file2; + + @Schema(description = "附件3") + private String file3; + + @Schema(description = "是否显示续费提醒") + private Boolean showExpiration; + + @Schema(description = "是否作为案例展示") + private Integer showCase; + + @Schema(description = "是否显示在首页") + private Integer showIndex; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "所属年份") + private Integer year; + + @Schema(description = "所属月份") + private Integer month; + + @Schema(description = "所属日期") + private Integer day; + + @Schema(description = "状态, 0正常, 1 即将过期") + private Integer soon; + + @Schema(description = "是否过期") + @TableField(exist = false) + private Integer expired; + + @Schema(description = "剩余天数") + @TableField(exist = false) + private Long expiredDays; + + @Schema(description = "续费金额") + private BigDecimal renewMoney; + + @Schema(description = "续费总金额") + @TableField(exist = false) + private BigDecimal totalRenewMoney; + + @Schema(description = "续费次数") + private Long renewCount; + + @Schema(description = "应用状态") + private String appStatus; + + @Schema(description = "开发进度") + private Integer progress; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "网站id") + private Integer websiteId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "是否收藏") + @TableField(exist = false) + private Boolean collection; + + @Schema(description = "应用成员") + @TableField(exist = false) + private List projectUsers; + +} diff --git a/src/main/java/com/gxwebsoft/project/entity/ProjectCollection.java b/src/main/java/com/gxwebsoft/project/entity/ProjectCollection.java new file mode 100644 index 0000000..bf80392 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/entity/ProjectCollection.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.project.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 我的收藏 + * + * @author 科技小王子 + * @since 2025-03-16 12:12:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ProjectCollection对象", description = "我的收藏") +public class ProjectCollection implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/project/entity/ProjectField.java b/src/main/java/com/gxwebsoft/project/entity/ProjectField.java new file mode 100644 index 0000000..c3944ec --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/entity/ProjectField.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.project.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ProjectField对象", description = "应用参数") +public class ProjectField implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "类型") + private String type; + + @Schema(description = "名称") + private String name; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "用户昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "用户头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "最后修改人") + private Integer updateUserId; + + @Schema(description = "最后修改人") + @TableField(exist = false) + private String updateUserName; + + @Schema(description = "最后修改人") + @TableField(exist = false) + private String updateUserAvatar; + + @Schema(description = "状态, 0正常, 1删除") + private Integer status; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/project/entity/ProjectRenew.java b/src/main/java/com/gxwebsoft/project/entity/ProjectRenew.java new file mode 100644 index 0000000..cd3336b --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/entity/ProjectRenew.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.project.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 续费管理 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ProjectRenew对象", description = "续费管理") +public class ProjectRenew implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "app_renew_id", type = IdType.AUTO) + private Integer appRenewId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "类型, 0续费, 1新购") + private Integer type; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String appName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String appIcon; + + @Schema(description = "续费金额") + private BigDecimal money; + + @Schema(description = "订单总额") + private BigDecimal totalPrice; + + @Schema(description = "实际付款") + private BigDecimal payPrice; + + @Schema(description = "优惠金额") + private BigDecimal reducePrice; + + @Schema(description = "续费时长(按年)") + private Integer duration; + + @Schema(description = "续费时长(按天)") + private Integer days; + + @Schema(description = "支付方式, 0余额, 1微信,102微信Native, 3支付宝, 4现金") + private Integer payType; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "到期时间") + @TableField(exist = false) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "状态, 0正常, 1 即将过期") + private Integer soon; + + @Schema(description = "客户(用户ID)") + @TableField(exist = false) + private Integer customerId; + + @Schema(description = "客户名称") + @TableField(exist = false) + private String customerName; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "付款凭证") + private String images; + + @Schema(description = "操作员姓名") + @TableField(exist = false) + private String nickname; + + @Schema(description = "操作员头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/project/entity/ProjectUrl.java b/src/main/java/com/gxwebsoft/project/entity/ProjectUrl.java new file mode 100644 index 0000000..6e7b177 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/entity/ProjectUrl.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.project.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 项目域名 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ProjectUrl对象", description = "项目域名") +public class ProjectUrl implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "app_url_id", type = IdType.AUTO) + private Integer appUrlId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "域名类型") + private String name; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/project/entity/ProjectUser.java b/src/main/java/com/gxwebsoft/project/entity/ProjectUser.java new file mode 100644 index 0000000..455099f --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/entity/ProjectUser.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.project.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.Set; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用成员 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ProjectUser对象", description = "应用成员") +public class ProjectUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "app_user_id", type = IdType.AUTO) + private Integer appUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + private Integer role; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "头像") + private String avatar; + + @Schema(description = "状态, 0正常, 1待确认") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/ProjectCollectionMapper.java b/src/main/java/com/gxwebsoft/project/mapper/ProjectCollectionMapper.java new file mode 100644 index 0000000..c16dc51 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/ProjectCollectionMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.project.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.project.entity.ProjectCollection; +import com.gxwebsoft.project.param.ProjectCollectionParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 我的收藏Mapper + * + * @author 科技小王子 + * @since 2025-03-16 12:12:13 + */ +public interface ProjectCollectionMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ProjectCollectionParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ProjectCollectionParam param); + +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/ProjectFieldMapper.java b/src/main/java/com/gxwebsoft/project/mapper/ProjectFieldMapper.java new file mode 100644 index 0000000..a8fc6b5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/ProjectFieldMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.project.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.project.entity.ProjectField; +import com.gxwebsoft.project.param.ProjectFieldParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用参数Mapper + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectFieldMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ProjectFieldParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ProjectFieldParam param); + +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/ProjectMapper.java b/src/main/java/com/gxwebsoft/project/mapper/ProjectMapper.java new file mode 100644 index 0000000..f7f080e --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/ProjectMapper.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.project.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.project.entity.Project; +import com.gxwebsoft.project.param.ProjectParam; +import org.apache.ibatis.annotations.Param; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 应用Mapper + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ProjectParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ProjectParam param); + + + /** + * 统计金额总和 + * + * @param wrapper 查询条件 + * @return 金额总和 + */ + BigDecimal selectSumMoney(@Param("ew") Wrapper wrapper); +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/ProjectRenewMapper.java b/src/main/java/com/gxwebsoft/project/mapper/ProjectRenewMapper.java new file mode 100644 index 0000000..4881b00 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/ProjectRenewMapper.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.project.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.project.entity.ProjectRenew; +import com.gxwebsoft.project.param.ProjectRenewParam; +import org.apache.ibatis.annotations.Param; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 续费管理Mapper + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectRenewMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ProjectRenewParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ProjectRenewParam param); + + + /** + * 统计金额总和 + * + * @param wrapper 查询条件 + * @return 金额总和 + */ + BigDecimal selectSumMoney(@Param("ew") Wrapper wrapper); + +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/ProjectUrlMapper.java b/src/main/java/com/gxwebsoft/project/mapper/ProjectUrlMapper.java new file mode 100644 index 0000000..22ba4e2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/ProjectUrlMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.project.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.project.entity.ProjectUrl; +import com.gxwebsoft.project.param.ProjectUrlParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 项目域名Mapper + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectUrlMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ProjectUrlParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ProjectUrlParam param); + +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/ProjectUserMapper.java b/src/main/java/com/gxwebsoft/project/mapper/ProjectUserMapper.java new file mode 100644 index 0000000..aa29153 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/ProjectUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.project.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.project.entity.ProjectUser; +import com.gxwebsoft.project.param.ProjectUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 应用成员Mapper + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ProjectUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ProjectUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectCollectionMapper.xml b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectCollectionMapper.xml new file mode 100644 index 0000000..74d6f66 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectCollectionMapper.xml @@ -0,0 +1,42 @@ + + + + + + + SELECT a.* + FROM project_collection a + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.app_id = #{param.appId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectFieldMapper.xml b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectFieldMapper.xml new file mode 100644 index 0000000..39af3c1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectFieldMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.*, b.avatar AS avatar, b.nickname, c.nickname as updateUserName, c.avatar as updateUserAvatar + FROM project_field a + LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id + LEFT JOIN gxwebsoft_core.sys_user c ON a.update_user_id = c.user_id + + + AND a.id = #{param.id} + + + AND a.app_id = #{param.appId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND c.update_user_id = #{param.updateUserId} + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectMapper.xml b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectMapper.xml new file mode 100644 index 0000000..51b6c85 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectMapper.xml @@ -0,0 +1,275 @@ + + + + + + + SELECT a.*, b.real_name as nickname, b.avatar,c.website_type as appType, u.real_name as companyName, u.phone as superAdminPhone + FROM project a + LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id + LEFT JOIN cms_website c ON a.website_id = c.website_id + LEFT JOIN gxwebsoft_core.sys_user u ON a.company_id = u.user_id + + + AND a.app_id = #{param.appId} + + + AND a.app_name LIKE CONCAT('%', #{param.appName}, '%') + + + AND a.app_code LIKE CONCAT('%', #{param.appCode}, '%') + + + AND a.app_secret LIKE CONCAT('%', #{param.appSecret}, '%') + + + AND a.parent_id = #{param.parentId} + + + AND a.website_id = #{param.websiteId} + + + AND a.app_type LIKE CONCAT('%', #{param.appType}, '%') + + + AND a.app_type_multiple LIKE CONCAT('%', #{param.appTypeMultiple}, '%') + + + AND a.menu_type = #{param.menuType} + + + AND a.company_id = #{param.companyId} + + + AND a.company_name LIKE CONCAT('%', #{param.companyName}, '%') + + + AND u.phone = #{param.loginPhone} + + + AND a.app_icon LIKE CONCAT('%', #{param.appIcon}, '%') + + + AND a.app_qrcode LIKE CONCAT('%', #{param.appQrcode}, '%') + + + AND a.app_url LIKE CONCAT('%', #{param.appUrl}, '%') + + + AND a.admin_url LIKE CONCAT('%', #{param.adminUrl}, '%') + + + AND a.down_url LIKE CONCAT('%', #{param.downUrl}, '%') + + + AND a.server_url LIKE CONCAT('%', #{param.serverUrl}, '%') + + + AND a.file_url LIKE CONCAT('%', #{param.fileUrl}, '%') + + + AND a.callback_url LIKE CONCAT('%', #{param.callbackUrl}, '%') + + + AND a.docs_url LIKE CONCAT('%', #{param.docsUrl}, '%') + + + AND a.git_url LIKE CONCAT('%', #{param.gitUrl}, '%') + + + AND a.prototype_url LIKE CONCAT('%', #{param.prototypeUrl}, '%') + + + AND a.ip_address LIKE CONCAT('%', #{param.ipAddress}, '%') + + + AND a.images LIKE CONCAT('%', #{param.images}, '%') + + + AND a.package_name LIKE CONCAT('%', #{param.packageName}, '%') + + + AND a.clicks = #{param.clicks} + + + AND a.installs = #{param.installs} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.requirement LIKE CONCAT('%', #{param.requirement}, '%') + + + AND a.developer LIKE CONCAT('%', #{param.developer}, '%') + + + AND a.director LIKE CONCAT('%', #{param.director}, '%') + + + AND a.project_director LIKE CONCAT('%', #{param.projectDirector}, '%') + + + AND a.salesman LIKE CONCAT('%', #{param.salesman}, '%') + + + AND a.price = #{param.price} + + + AND a.line_price = #{param.linePrice} + + + AND a.score LIKE CONCAT('%', #{param.score}, '%') + + + AND a.star LIKE CONCAT('%', #{param.star}, '%') + + + AND a.year = #{param.year} + + + AND a.month = #{param.month} + + + AND a.day = #{param.day} + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.authority LIKE CONCAT('%', #{param.authority}, '%') + + + AND a.target LIKE CONCAT('%', #{param.target}, '%') + + + AND a.hide = #{param.hide} + + + AND a.search = #{param.search} + + + AND a.active LIKE CONCAT('%', #{param.active}, '%') + + + AND a.meta LIKE CONCAT('%', #{param.meta}, '%') + + + AND a.edition LIKE CONCAT('%', #{param.edition}, '%') + + + AND a.version LIKE CONCAT('%', #{param.version}, '%') + + + AND a.is_use = #{param.isUse} + + + AND a.file1 LIKE CONCAT('%', #{param.file1}, '%') + + + AND a.file2 LIKE CONCAT('%', #{param.file2}, '%') + + + AND a.file3 LIKE CONCAT('%', #{param.file3}, '%') + + + AND a.show_expiration = #{param.showExpiration} + + + AND a.show_case = #{param.showCase} + + + AND a.show_index = #{param.showIndex} + + + AND a.recommend = #{param.recommend} + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.soon = #{param.soon} + + + AND a.renew_money = #{param.renewMoney} + + + AND a.app_status LIKE CONCAT('%', #{param.appStatus}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.organization_id = #{param.organizationId} + + + AND a.tenant_code LIKE CONCAT('%', #{param.tenantCode}, '%') + + + AND a.expiration_time >= #{param.expirationTimeStart} + + + AND a.expiration_time <= #{param.expirationTimeEnd} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.app_id IN + + #{item} + + + + AND (a.app_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.app_id = #{param.keywords} + OR a.app_code = #{param.keywords} + OR a.app_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectRenewMapper.xml b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectRenewMapper.xml new file mode 100644 index 0000000..54a7e29 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectRenewMapper.xml @@ -0,0 +1,104 @@ + + + + + + + SELECT a.*, b.app_icon, b.app_name, b.expiration_time,c.real_name as nickname + FROM project_renew a + LEFT JOIN project b ON a.app_id = b.app_id + LEFT JOIN gxwebsoft_core.sys_user c ON a.user_id = c.user_id + + + AND a.app_renew_id = #{param.appRenewId} + + + AND a.app_id = #{param.appId} + + + AND a.type = #{param.type} + + + AND a.order_no = #{param.} + + + AND a.money = #{param.money} + + + AND a.duration = #{param.duration} + + + AND a.days = #{param.days} + + + AND a.pay_type = #{param.payType} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.soon = #{param.soon} + + + AND a.renew_count = #{param.renewCount} + + + AND a.user_id = #{param.userId} + + + AND a.images LIKE CONCAT('%', #{param.images}, '%') + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (b.app_name LIKE CONCAT('%', #{param.keywords}, '%') + OR b.app_id = #{param.keywords} + OR b.app_code = #{param.keywords} + ) + + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUrlMapper.xml b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUrlMapper.xml new file mode 100644 index 0000000..9e16161 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUrlMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM project_url a + + + AND a.app_url_id = #{param.appUrlId} + + + AND a.app_id = #{param.appId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.domain LIKE CONCAT('%', #{param.domain}, '%') + + + AND a.account LIKE CONCAT('%', #{param.account}, '%') + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUserMapper.xml b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUserMapper.xml new file mode 100644 index 0000000..ffdd4bc --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/mapper/xml/ProjectUserMapper.xml @@ -0,0 +1,57 @@ + + + + + + + SELECT a.* + FROM project_user a + + + AND a.app_user_id = #{param.appUserId} + + + AND a.role = #{param.role} + + + AND a.user_id = #{param.userId} + + + AND a.app_id = #{param.appId} + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.app_id IN + + #{item} + + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/project/param/ProjectCollectionParam.java b/src/main/java/com/gxwebsoft/project/param/ProjectCollectionParam.java new file mode 100644 index 0000000..073b886 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/param/ProjectCollectionParam.java @@ -0,0 +1,38 @@ +package com.gxwebsoft.project.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 我的收藏查询参数 + * + * @author 科技小王子 + * @since 2025-03-16 12:12:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ProjectCollectionParam对象", description = "我的收藏查询参数") +public class ProjectCollectionParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + +} diff --git a/src/main/java/com/gxwebsoft/project/param/ProjectFieldParam.java b/src/main/java/com/gxwebsoft/project/param/ProjectFieldParam.java new file mode 100644 index 0000000..50b8068 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/param/ProjectFieldParam.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.project.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用参数查询参数 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ProjectFieldParam对象", description = "应用参数查询参数") +public class ProjectFieldParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "类型") + @QueryField(type = QueryType.EQ) + private String type; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "最后更新用户ID") + @QueryField(type = QueryType.EQ) + private Integer updateUserId; + + @Schema(description = "状态, 0正常, 1删除") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/project/param/ProjectParam.java b/src/main/java/com/gxwebsoft/project/param/ProjectParam.java new file mode 100644 index 0000000..7b430c2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/param/ProjectParam.java @@ -0,0 +1,285 @@ +package com.gxwebsoft.project.param; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Set; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用查询参数 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ProjectParam对象", description = "应用查询参数") +public class ProjectParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "应用标识") + private String appCode; + + @Schema(description = "应用秘钥") + private String appSecret; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "应用类型") + private String appTypeMultiple; + + @Schema(description = "类型, 0菜单, 1按钮") + @QueryField(type = QueryType.EQ) + private Integer menuType; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "企业名称") + private String companyName; + + @Schema(description = "应用图标") + private String appIcon; + + @Schema(description = "二维码") + private String appQrcode; + + @Schema(description = "链接地址") + private String appUrl; + + @Schema(description = "后台管理地址") + private String adminUrl; + + @Schema(description = "下载地址") + private String downUrl; + + @Schema(description = "链接地址") + private String serverUrl; + + @Schema(description = "文件服务器") + private String fileUrl; + + @Schema(description = "回调地址") + private String callbackUrl; + + @Schema(description = "腾讯文档地址") + private String docsUrl; + + @Schema(description = "代码仓库地址") + private String gitUrl; + + @Schema(description = "原型图地址") + private String prototypeUrl; + + @Schema(description = "IP白名单") + private String ipAddress; + + @Schema(description = "应用截图") + private String images; + + @Schema(description = "应用包名") + private String packageName; + + @Schema(description = "下载次数") + @QueryField(type = QueryType.EQ) + private Integer clicks; + + @Schema(description = "安装次数") + @QueryField(type = QueryType.EQ) + private Integer installs; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "应用介绍") + private String content; + + @Schema(description = "项目需求") + private String requirement; + + @Schema(description = "开发者(个人或公司)") + private String developer; + + @Schema(description = "项目负责人") + private String director; + + @Schema(description = "项目经理") + private String projectDirector; + + @Schema(description = "业务员") + private String salesman; + + @Schema(description = "软件定价") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "划线价格") + @QueryField(type = QueryType.EQ) + private BigDecimal linePrice; + + @Schema(description = "评分") + private String score; + + @Schema(description = "星级") + private String star; + + @Schema(description = "菜单路由地址") + private String path; + + @Schema(description = "菜单组件地址, 目录可为空") + private String component; + + @Schema(description = "权限标识") + private String authority; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "禁止搜索,1禁止 0 允许") + @QueryField(type = QueryType.EQ) + private Integer search; + + @Schema(description = "菜单侧栏选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "版本,0正式版 1体验版 2开发版") + private String edition; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "是否已安装") + @QueryField(type = QueryType.EQ) + private Integer isUse; + + @Schema(description = "附近1") + private String file1; + + @Schema(description = "附件2") + private String file2; + + @Schema(description = "附件3") + private String file3; + + @Schema(description = "是否显示续费提醒") + @QueryField(type = QueryType.EQ) + private Boolean showExpiration; + + @Schema(description = "是否作为案例展示") + @QueryField(type = QueryType.EQ) + private Integer showCase; + + @Schema(description = "是否显示在首页") + @QueryField(type = QueryType.EQ) + private Integer showIndex; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "到期时间") + private String expirationTime; + + @Schema(description = "所属年份") + @QueryField(type = QueryType.EQ) + private Integer year; + + @Schema(description = "所属月份") + @QueryField(type = QueryType.EQ) + private Integer month; + + @Schema(description = "所属日期") + @QueryField(type = QueryType.EQ) + private Integer day; + + @Schema(description = "状态, 0正常, 1 即将过期") + @QueryField(type = QueryType.EQ) + private Integer soon; + + @Schema(description = "续费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal renewMoney; + + @Schema(description = "应用状态") + private String appStatus; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "ID集合") + @QueryField(type = QueryType.IN) + private Set appIds; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "租户编号") + private String tenantCode; + + @Schema(description = "登录用户ID") + @QueryField(type = QueryType.EQ) + private Integer loginUserId; + + @Schema(description = "登录手机号") + @QueryField(type = QueryType.EQ) + private String loginPhone; + + @Schema(description = "网站id") + @QueryField(type = QueryType.EQ) + private Integer websiteId; + + @QueryField(value = "expiration_time", type = QueryType.GE) + @TableField(exist = false) + @Schema(description = "到期时间起始值") + private String expirationTimeStart; + + @QueryField(value = "expiration_time", type = QueryType.LE) + @TableField(exist = false) + @Schema(description = "到期时间结束值") + private String expirationTimeEnd; + + +} diff --git a/src/main/java/com/gxwebsoft/project/param/ProjectRenewParam.java b/src/main/java/com/gxwebsoft/project/param/ProjectRenewParam.java new file mode 100644 index 0000000..04fd922 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/param/ProjectRenewParam.java @@ -0,0 +1,111 @@ +package com.gxwebsoft.project.param; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 续费管理查询参数 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ProjectRenewParam对象", description = "续费管理查询参数") +public class ProjectRenewParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer appRenewId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "类型, 0续费, 1新购") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "订单编号") + @QueryField(type = QueryType.EQ) + private String orderNo; + + @Schema(description = "续费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "订单总额") + @QueryField(type = QueryType.EQ) + private BigDecimal totalPrice; + + @Schema(description = "实际付款") + @QueryField(type = QueryType.EQ) + private BigDecimal payPrice; + + @Schema(description = "优惠金额") + @QueryField(type = QueryType.EQ) + private BigDecimal reducePrice; + + @Schema(description = "续费时长") + @QueryField(type = QueryType.EQ) + private BigDecimal duration; + + @Schema(description = "续费时长(按天)") + @QueryField(type = QueryType.EQ) + private Integer days; + + @Schema(description = "支付方式") + @QueryField(type = QueryType.EQ) + private Integer payType; + + @Schema(description = "续费次数") + @QueryField(type = QueryType.EQ) + private Integer renewCount; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "开始时间") + private String startTime; + + @Schema(description = "到期时间") + private String endTime; + + @Schema(description = "状态, 0正常, 1 即将过期") + @QueryField(type = QueryType.EQ) + private Integer soon; + + @Schema(description = "企业ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "付款凭证") + private String images; + + @Schema(description = "用户姓名") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/project/param/ProjectUrlParam.java b/src/main/java/com/gxwebsoft/project/param/ProjectUrlParam.java new file mode 100644 index 0000000..766a4ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/param/ProjectUrlParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.project.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 项目域名查询参数 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ProjectUrlParam对象", description = "项目域名查询参数") +public class ProjectUrlParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer appUrlId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "域名类型") + private String name; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "账号") + private String account; + + @Schema(description = "密码") + private String password; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/project/param/ProjectUserParam.java b/src/main/java/com/gxwebsoft/project/param/ProjectUserParam.java new file mode 100644 index 0000000..b3c08e6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/param/ProjectUserParam.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.project.param; + +import java.math.BigDecimal; +import java.util.Set; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用成员查询参数 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ProjectUserParam对象", description = "应用成员查询参数") +public class ProjectUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer appUserId; + + @Schema(description = "角色,10体验成员 20开发者成员 30管理员 ") + @QueryField(type = QueryType.EQ) + private Integer role; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "应用ID") + @QueryField(type = QueryType.EQ) + private Integer appId; + + @Schema(description = "应用ID集合") + @QueryField(type = QueryType.IN) + private Set appIds; + + @Schema(description = "昵称") + private String nickname; + + @Schema(description = "状态, 0正常, 1待确认") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/project/service/ProjectCollectionService.java b/src/main/java/com/gxwebsoft/project/service/ProjectCollectionService.java new file mode 100644 index 0000000..fe1afba --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/ProjectCollectionService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.project.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.entity.ProjectCollection; +import com.gxwebsoft.project.param.ProjectCollectionParam; + +import java.util.List; + +/** + * 我的收藏Service + * + * @author 科技小王子 + * @since 2025-03-16 12:12:13 + */ +public interface ProjectCollectionService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ProjectCollectionParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ProjectCollectionParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return ProjectCollection + */ + ProjectCollection getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/project/service/ProjectFieldService.java b/src/main/java/com/gxwebsoft/project/service/ProjectFieldService.java new file mode 100644 index 0000000..1fd2a5a --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/ProjectFieldService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.project.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.entity.ProjectField; +import com.gxwebsoft.project.param.ProjectFieldParam; + +import java.util.List; + +/** + * 应用参数Service + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectFieldService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ProjectFieldParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ProjectFieldParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return ProjectField + */ + ProjectField getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/project/service/ProjectRenewService.java b/src/main/java/com/gxwebsoft/project/service/ProjectRenewService.java new file mode 100644 index 0000000..a607ddd --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/ProjectRenewService.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.project.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.entity.ProjectRenew; +import com.gxwebsoft.project.param.ProjectRenewParam; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 续费管理Service + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectRenewService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ProjectRenewParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ProjectRenewParam param); + + /** + * 根据id查询 + * + * @param appRenewId 自增ID + * @return ProjectRenew + */ + ProjectRenew getByIdRel(Integer appRenewId); + + BigDecimal sumMoney(LambdaQueryWrapper between); +} diff --git a/src/main/java/com/gxwebsoft/project/service/ProjectService.java b/src/main/java/com/gxwebsoft/project/service/ProjectService.java new file mode 100644 index 0000000..7438170 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/ProjectService.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.project.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.entity.Project; +import com.gxwebsoft.project.entity.ProjectRenew; +import com.gxwebsoft.project.param.ProjectParam; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 应用Service + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ProjectParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ProjectParam param); + + /** + * 根据id查询 + * + * @param appId 应用ID + * @return Project + */ + Project getByIdRel(Integer appId); + + BigDecimal sumMoney(LambdaQueryWrapper between); + + void updateByRenew(ProjectRenew projectRenew); + + +} diff --git a/src/main/java/com/gxwebsoft/project/service/ProjectUrlService.java b/src/main/java/com/gxwebsoft/project/service/ProjectUrlService.java new file mode 100644 index 0000000..f67069a --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/ProjectUrlService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.project.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.entity.ProjectUrl; +import com.gxwebsoft.project.param.ProjectUrlParam; + +import java.util.List; + +/** + * 项目域名Service + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectUrlService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ProjectUrlParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ProjectUrlParam param); + + /** + * 根据id查询 + * + * @param appUrlId 自增ID + * @return ProjectUrl + */ + ProjectUrl getByIdRel(Integer appUrlId); + +} diff --git a/src/main/java/com/gxwebsoft/project/service/ProjectUserService.java b/src/main/java/com/gxwebsoft/project/service/ProjectUserService.java new file mode 100644 index 0000000..5e8407e --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/ProjectUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.project.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.entity.ProjectUser; +import com.gxwebsoft.project.param.ProjectUserParam; + +import java.util.List; + +/** + * 应用成员Service + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +public interface ProjectUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ProjectUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ProjectUserParam param); + + /** + * 根据id查询 + * + * @param appUserId 自增ID + * @return ProjectUser + */ + ProjectUser getByIdRel(Integer appUserId); + +} diff --git a/src/main/java/com/gxwebsoft/project/service/impl/ProjectCollectionServiceImpl.java b/src/main/java/com/gxwebsoft/project/service/impl/ProjectCollectionServiceImpl.java new file mode 100644 index 0000000..7cd076d --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/impl/ProjectCollectionServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.project.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.project.mapper.ProjectCollectionMapper; +import com.gxwebsoft.project.service.ProjectCollectionService; +import com.gxwebsoft.project.entity.ProjectCollection; +import com.gxwebsoft.project.param.ProjectCollectionParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 我的收藏Service实现 + * + * @author 科技小王子 + * @since 2025-03-16 12:12:13 + */ +@Service +public class ProjectCollectionServiceImpl extends ServiceImpl implements ProjectCollectionService { + + @Override + public PageResult pageRel(ProjectCollectionParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ProjectCollectionParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ProjectCollection getByIdRel(Integer id) { + ProjectCollectionParam param = new ProjectCollectionParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/service/impl/ProjectFieldServiceImpl.java b/src/main/java/com/gxwebsoft/project/service/impl/ProjectFieldServiceImpl.java new file mode 100644 index 0000000..081c013 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/impl/ProjectFieldServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.project.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.project.mapper.ProjectFieldMapper; +import com.gxwebsoft.project.service.ProjectFieldService; +import com.gxwebsoft.project.entity.ProjectField; +import com.gxwebsoft.project.param.ProjectFieldParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用参数Service实现 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Service +public class ProjectFieldServiceImpl extends ServiceImpl implements ProjectFieldService { + + @Override + public PageResult pageRel(ProjectFieldParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ProjectFieldParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ProjectField getByIdRel(Integer id) { + ProjectFieldParam param = new ProjectFieldParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/service/impl/ProjectRenewServiceImpl.java b/src/main/java/com/gxwebsoft/project/service/impl/ProjectRenewServiceImpl.java new file mode 100644 index 0000000..98665c4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/impl/ProjectRenewServiceImpl.java @@ -0,0 +1,155 @@ +package com.gxwebsoft.project.service.impl; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.gxwebsoft.oa.entity.OaAppRenew; +import com.gxwebsoft.project.entity.Project; +import com.gxwebsoft.project.mapper.ProjectRenewMapper; +import com.gxwebsoft.project.param.ProjectParam; +import com.gxwebsoft.project.service.ProjectRenewService; +import com.gxwebsoft.project.entity.ProjectRenew; +import com.gxwebsoft.project.param.ProjectRenewParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.service.ProjectService; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 续费管理Service实现 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Service +public class ProjectRenewServiceImpl extends ServiceImpl implements ProjectRenewService { + @Resource + private ProjectService projectService; + + @Override + public PageResult pageRel(ProjectRenewParam param) { + final String sceneType = param.getSceneType(); + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + + // TODO 默认查询条件:读取符合续费条件的项目 + if (sceneType == null) { + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + + + // 特殊场景查询 + List list = null; + DateTime date = DateUtil.date(); + final DateTime nextMonth = DateUtil.nextMonth(); + // 获取当前年份的起止时间 + LocalDateTime startOfYear = LocalDateTime.now().withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfYear = startOfYear.plusYears(1).minusNanos(1); + // 去年的起止时间 + LocalDateTime startOfLastYear = LocalDateTime.now().minusYears(1).withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfLastYear = startOfLastYear.plusYears(1).minusNanos(1); + // 本月起止时间 + LocalDateTime startOfMonth = LocalDateTime.now().withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfMonth = startOfMonth.plusMonths(1).minusNanos(1); + + if (StrUtil.isNotBlank(sceneType)) { + // TODO 近30天可催收的续费总额 + if (sceneType.equals("totalPrice30")) { + list = list(new LambdaQueryWrapper() + .lt(ProjectRenew::getEndTime, nextMonth) + .gt(ProjectRenew::getEndTime, date) + .lt(ProjectRenew::getCreateTime, date) + .eq(ProjectRenew::getDeleted, 0) + .orderByAsc(ProjectRenew::getEndTime) + ); + } + + // TODO 本月已收续费总额 + if (sceneType.equals("monthTotalPrice")) { + list = list(new LambdaQueryWrapper() + .between(ProjectRenew::getCreateTime, startOfYear, endOfMonth) + .eq(ProjectRenew::getDeleted, 0) + .orderByDesc(ProjectRenew::getEndTime) + ); + } + + // TODO 今年已收续费总额 + if (sceneType.equals("yearTotalPrice")) { + list = list(new LambdaQueryWrapper() + .between(ProjectRenew::getCreateTime, startOfMonth, endOfYear) + .eq(ProjectRenew::getDeleted, 0) + .orderByDesc(ProjectRenew::getEndTime) + ); + } + + // TODO 去年已收续费列表 + if (sceneType.equals("lastTotalPrice")) { + list = list(new LambdaQueryWrapper() + .between(ProjectRenew::getCreateTime, startOfLastYear, endOfLastYear) + .eq(ProjectRenew::getDeleted, 0) + .orderByDesc(ProjectRenew::getEndTime) + ); + } + } + + // TODO 获取项目名称 + assert list != null; + final Set collectByAppIds = list.stream().map(ProjectRenew::getAppId).collect(Collectors.toSet()); + final ProjectParam projectParam = new ProjectParam(); + projectParam.setAppIds(collectByAppIds); + final List projects = projectService.listRel(projectParam); + + final Map> collect = projects.stream().collect(Collectors.groupingBy(Project::getAppId)); + list.forEach(d -> { + final List projectsItem = collect.get(d.getAppId()); + if (!CollectionUtils.isEmpty(projectsItem)) { + final Project project = projectsItem.get(0); + d.setAppName(project.getAppName()); + d.setNickname(project.getNickname()); + d.setAvatar(project.getAvatar()); + d.setCustomerId(project.getCompanyId()); + d.setCustomerName(project.getCompanyName()); + } + }); + return new PageResult<>(list, (long) list.size()); + } + + @Override + public List listRel(ProjectRenewParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ProjectRenew getByIdRel(Integer appRenewId) { + ProjectRenewParam param = new ProjectRenewParam(); + param.setAppRenewId(appRenewId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public BigDecimal sumMoney(LambdaQueryWrapper wrapper) { + return baseMapper.selectSumMoney(wrapper); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/service/impl/ProjectServiceImpl.java b/src/main/java/com/gxwebsoft/project/service/impl/ProjectServiceImpl.java new file mode 100644 index 0000000..5fa5d76 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/impl/ProjectServiceImpl.java @@ -0,0 +1,267 @@ +package com.gxwebsoft.project.service.impl; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.project.entity.*; +import com.gxwebsoft.project.mapper.ProjectMapper; +import com.gxwebsoft.project.param.ProjectUserParam; +import com.gxwebsoft.project.service.ProjectCollectionService; +import com.gxwebsoft.project.service.ProjectRenewService; +import com.gxwebsoft.project.service.ProjectService; +import com.gxwebsoft.project.param.ProjectParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.project.service.ProjectUserService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 应用Service实现 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Service +public class ProjectServiceImpl extends ServiceImpl implements ProjectService { + + @Resource + private ProjectService projectService; + @Resource + private ProjectUserService projectUserService; + @Resource + private ProjectCollectionService projectCollectionService; + @Resource + private ProjectRenewService projectRenewService; + @Resource + private CmsWebsiteService cmsWebsiteService; + + @Override + public PageResult pageRel(ProjectParam param) { + final String sceneType = param.getSceneType(); + + // TODO 特殊场景查询 + if (sceneType != null) { + param.setUserId(null); + param.setAppStatus(null); + param.setAppIds(null); + + List list = null; + DateTime date = DateUtil.date(); + final DateTime nextMonth = DateUtil.nextMonth(); + // 获取当前年份的起止时间 + LocalDateTime startOfYear = LocalDateTime.now().withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfYear = startOfYear.plusYears(1).minusNanos(1); + // 去年的起止时间 + LocalDateTime startOfLastYear = LocalDateTime.now().minusYears(1).withMonth(1).withDayOfMonth(1) + .withHour(0).withMinute(0).withSecond(0); + LocalDateTime endOfLastYear = startOfLastYear.plusYears(1).minusNanos(1); + + // TODO 我的项目 + if (sceneType.equals("myProject")) { + param.setUserId(param.getLoginUserId()); + list = listRel(param); + } + + // TODO 我的参与 + if (sceneType.equals("involved")) { + final List projectUsers = projectUserService.list(new LambdaQueryWrapper().eq(ProjectUser::getUserId, param.getLoginUserId())); + final Set collect = projectUsers.stream().map(ProjectUser::getAppId).collect(Collectors.toSet()); + param.setAppIds(collect); + list = listRel(param); + } + // TODO 我的收藏 + if (sceneType.equals("collection")) { + final List projectCollections = projectCollectionService.list(new LambdaQueryWrapper().eq(ProjectCollection::getUserId, param.getLoginUserId())); + final Set collect = projectCollections.stream().map(ProjectCollection::getAppId).collect(Collectors.toSet()); + param.setAppIds(collect); + param.setUserId(null); + list = listRel(param); + list.forEach(d -> { + d.setCollection(true); + }); + } + + if (param.getAppStatus() != null && param.getAppStatus().equals("全部")) { + param.setAppStatus(null); + } + + + // TODO 近30天可催收的续费总额 + if (sceneType.equals("totalPrice30")) { + list = list(new LambdaQueryWrapper() + .lt(Project::getExpirationTime, nextMonth) + .gt(Project::getExpirationTime, date) + .eq(Project::getDeleted, 0) + .orderByAsc(Project::getExpirationTime) + ); + } + + // TODO 今年已收续费总额 + if (sceneType.equals("yearTotalPrice")) { + list = list(new LambdaQueryWrapper() + .between(Project::getUpdateTime, startOfYear, endOfYear) + .eq(Project::getDeleted, 0) + .orderByDesc(Project::getCreateTime) + ); + } + + // TODO 去年已收续费列表 + if (sceneType.equals("lastTotalPrice")) { + list = list(new LambdaQueryWrapper() + .between(Project::getUpdateTime, startOfLastYear, endOfLastYear) + .eq(Project::getDeleted, 0) + .orderByDesc(Project::getCreateTime) + ); + } + + // TODO 已流失的续费总额 + if(sceneType.equals("Expired")){ + list = list(new LambdaQueryWrapper() + .lt(Project::getExpirationTime, date) + .eq(Project::getDeleted, 0) + .eq(Project::getAppStatus,"已上架") + .eq(Project::getShowExpiration,true) + .orderByAsc(Project::getExpirationTime) + ); + } + + // TODO 有效续费总金额 + if (sceneType.equals("effectiveTotalPrice")) { + list = list(new LambdaQueryWrapper() + .gt(Project::getExpirationTime, date) + .eq(Project::getDeleted, 0) + .eq(Project::getAppStatus,"已上架") + .eq(Project::getShowExpiration,true) + .orderByAsc(Project::getExpirationTime) + ); + } + + // TODO 全部续费总额 + if (sceneType.equals("AllRenewPrice")) { + list = list(new LambdaQueryWrapper() + .eq(Project::getDeleted, 0) + .eq(Project::getAppStatus,"已上架") + .eq(Project::getShowExpiration,true) + .orderByAsc(Project::getExpirationTime) + ); + } + + assert list != null; + return new PageResult<>(getProjectList(list, param.getLoginUserId()), (long) list.size()); + } + + // 常规搜索 + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + + return new PageResult<>(getProjectList(list, param.getLoginUserId()), page.getTotal()); + } + + @Override + public List listRel(ProjectParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public Project getByIdRel(Integer appId) { + ProjectParam param = new ProjectParam(); + param.setAppId(appId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public BigDecimal sumMoney(LambdaQueryWrapper wrapper) { + return baseMapper.selectSumMoney(wrapper); + } + + @Override + public void updateByRenew(ProjectRenew projectRenew) { + final Project project = projectService.getByIdRel(projectRenew.getAppId()); + if(project.getExpirationTime() != null){ + if (projectRenew.getDays() != null) { + // 按天续费 + project.setExpirationTime(project.getExpirationTime().plusDays(projectRenew.getDays())); + } else { + // 按年续费 + project.setExpirationTime(project.getExpirationTime().plusMonths(12 * projectRenew.getDuration().intValue())); + // 更新下一年的续费金额 + project.setRenewMoney(projectRenew.getPayPrice()); + project.setShowExpiration(true); + } + project.setRenewCount(project.getRenewCount() + 1); + + // 保存到期时间所在的年月日 + final LocalDateTime expirationTime = project.getExpirationTime(); + LocalDate localDate = expirationTime.toLocalDate(); + int year = localDate.getYear(); + int month = localDate.getMonthValue(); // 获取月份,范围是1到12 + int day = localDate.getDayOfMonth(); // 获取日,范围是1到31 + project.setYear(year); + project.setMonth(month); + project.setDay(day); + projectService.updateById(project); + // 更新明细的到期时间 + projectRenew.setStartTime(expirationTime); + projectRenew.setEndTime(expirationTime); + // 同步网站状态 + if (!project.getWebsiteId().equals(0)) { + final CmsWebsite website = new CmsWebsite(); + website.setVersion(20); + website.setExpirationTime(expirationTime); + website.setWebsiteId(project.getWebsiteId()); + cmsWebsiteService.updateByIdAll(website); + } + } + projectRenewService.updateById(projectRenew); + } + + /** + * 整理列表数据并返回 + * @return List + */ + private List getProjectList(List list, Integer loginUserId) { + + final List projectCollections = projectCollectionService.list(new LambdaQueryWrapper().eq(ProjectCollection::getUserId, loginUserId)); + final Set collect = projectCollections.stream().map(ProjectCollection::getAppId).collect(Collectors.toSet()); + + list.forEach(d -> { + // 收藏状态 + if (collect.contains(d.getAppId())) { + d.setCollection(true); + } + // 应用成员 + d.setProjectUsers(projectUserService.list(new LambdaQueryWrapper().eq(ProjectUser::getAppId, d.getAppId()))); + LocalDateTime now = LocalDateTime.now(); + // 即将过期(30天内过期的) + d.setSoon(d.getExpirationTime().minusDays(30).compareTo(now)); + // 是否过期 -1已过期 大于0 未过期 + d.setExpired(d.getExpirationTime().compareTo(now)); + // 剩余天数 + d.setExpiredDays(java.time.temporal.ChronoUnit.DAYS.between(now, d.getExpirationTime())); + // 续费次数 + d.setRenewCount((long) projectRenewService.count(new LambdaQueryWrapper().eq(ProjectRenew::getAppId, d.getAppId()).eq(ProjectRenew::getDeleted, 0))); + }); + return list; + } + +} diff --git a/src/main/java/com/gxwebsoft/project/service/impl/ProjectUrlServiceImpl.java b/src/main/java/com/gxwebsoft/project/service/impl/ProjectUrlServiceImpl.java new file mode 100644 index 0000000..462e04d --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/impl/ProjectUrlServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.project.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.project.mapper.ProjectUrlMapper; +import com.gxwebsoft.project.service.ProjectUrlService; +import com.gxwebsoft.project.entity.ProjectUrl; +import com.gxwebsoft.project.param.ProjectUrlParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 项目域名Service实现 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Service +public class ProjectUrlServiceImpl extends ServiceImpl implements ProjectUrlService { + + @Override + public PageResult pageRel(ProjectUrlParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ProjectUrlParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ProjectUrl getByIdRel(Integer appUrlId) { + ProjectUrlParam param = new ProjectUrlParam(); + param.setAppUrlId(appUrlId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/project/service/impl/ProjectUserServiceImpl.java b/src/main/java/com/gxwebsoft/project/service/impl/ProjectUserServiceImpl.java new file mode 100644 index 0000000..223cb38 --- /dev/null +++ b/src/main/java/com/gxwebsoft/project/service/impl/ProjectUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.project.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.project.mapper.ProjectUserMapper; +import com.gxwebsoft.project.service.ProjectUserService; +import com.gxwebsoft.project.entity.ProjectUser; +import com.gxwebsoft.project.param.ProjectUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 应用成员Service实现 + * + * @author 科技小王子 + * @since 2025-03-14 16:21:11 + */ +@Service +public class ProjectUserServiceImpl extends ServiceImpl implements ProjectUserService { + + @Override + public PageResult pageRel(ProjectUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time asc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ProjectUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time asc"); + return page.sortRecords(list); + } + + @Override + public ProjectUser getByIdRel(Integer appUserId) { + ProjectUserParam param = new ProjectUserParam(); + param.setAppUserId(appUserId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/pwl/controller/PwlProjectController.java b/src/main/java/com/gxwebsoft/pwl/controller/PwlProjectController.java new file mode 100644 index 0000000..18c0fca --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/controller/PwlProjectController.java @@ -0,0 +1,297 @@ +package com.gxwebsoft.pwl.controller; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.cms.entity.CmsArticle; +import com.gxwebsoft.cms.entity.CmsArticleContent; +import com.gxwebsoft.cms.param.CmsArticleImportParam; +import com.gxwebsoft.common.core.utils.CommonUtil; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Role; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.project.entity.ProjectUser; +import com.gxwebsoft.pwl.param.PwlProjectImportParam; +import com.gxwebsoft.pwl.service.PwlProjectService; +import com.gxwebsoft.pwl.entity.PwlProject; +import com.gxwebsoft.pwl.param.PwlProjectParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +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.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 卫兰的项目项目系统控制器 + * + * @author 科技小王子 + * @since 2025-03-22 14:34:35 + */ +@Tag(name = "卫兰的项目项目系统管理") +@RestController +@RequestMapping("/api/pwl/pwl-project") +public class PwlProjectController extends BaseController { + @Resource + private PwlProjectService pwlProjectService; + + @Autowired + private KnowledgeBaseService knowledgeBaseService; + + @PreAuthorize("hasAuthority('pwl:pwlProject:list')") + @Operation(summary = "分页查询卫兰的项目项目系统") + @GetMapping("/page") + public ApiResult> page(PwlProjectParam param) { + final User loginUser = getLoginUser(); + if (loginUser != null) { + param.setLoginUserId(loginUser.getUserId()); + final List roles = loginUser.getRoles(); + if (!CommonUtil.hasRole(roles, "admin") && !CommonUtil.hasRole(roles, "superAdmin")) { + final List projectUsers = pwlProjectService.list(new LambdaQueryWrapper() + .like(PwlProject::getUserIds, loginUser.getUserId()) + .or() + .eq(PwlProject::getUserId, loginUser.getUserId()) + ); + if (!CollectionUtils.isEmpty(projectUsers)) { + final Set appIds = projectUsers.stream().map(PwlProject::getId).collect(Collectors.toSet()); + param.setProjectIds(appIds); + } else { + param.setUserId(loginUser.getUserId()); + } + } + } + return success(pwlProjectService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:list')") + @Operation(summary = "查询全部卫兰的项目项目系统") + @GetMapping() + public ApiResult> list(PwlProjectParam param) { + // 使用关联查询 + return success(pwlProjectService.listRel(param)); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:list')") + @Operation(summary = "根据id查询卫兰的项目项目系统") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(pwlProjectService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:save')") + @OperationLog + @Operation(summary = "添加卫兰的项目项目系统") + @PostMapping() + public ApiResult save(@RequestBody PwlProject pwlProject) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + pwlProject.setUserId(loginUser.getUserId()); + } + if (pwlProject.getCode() != null) { + final PwlProject one = pwlProjectService.getOne(new LambdaQueryWrapper().eq(PwlProject::getCode, pwlProject.getCode())); + if (ObjectUtil.isNotEmpty(one)) { + return fail("报告编号不能重复"); + } + } + if (pwlProjectService.save(pwlProject)) { + createAnalysisLib(pwlProject); + createProjectLib(pwlProject); + pwlProjectService.updateById(pwlProject); + return success("添加成功"); + } + return fail("添加失败"); + } + + //创建材料分析库 + private void createAnalysisLib(PwlProject pwlProject) { + if(StrUtil.isEmpty(pwlProject.getCode())) { + return ; + } + try { + String asLibName = "as"+pwlProject.getName(); + //asLibCode = as00000000001 + String asLibCode = "as" + StrUtil.padPre(pwlProject.getId().toString(), 11, '0'); +// String asLibCode = "as"+pwlProject.getCode(); +// String analysisKbId = knowledgeBaseService.createKnowledgeBase(asLibName, asLibCode); + String analysisKbId = RandomUtil.randomString(10); + pwlProject.setAnalysisLibrary(analysisKbId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + //创建项目资料库 + private void createProjectLib(PwlProject pwlProject) { + if(StrUtil.isEmpty(pwlProject.getCode())) { + return ; + } + try { + String pjLibName = "pj"+pwlProject.getName(); + //pjLibCode = pj00000000001 + String pjLibCode = "pj" + StrUtil.padPre(pwlProject.getId().toString(), 11, '0'); +// String pjLibCode = "pj"+pwlProject.getCode(); +// String projectKbId = knowledgeBaseService.createKnowledgeBase(pjLibName, pjLibCode); + String projectKbId = RandomUtil.randomString(10); + pwlProject.setProjectLibrary(projectKbId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:update')") + @OperationLog + @Operation(summary = "修改卫兰的项目项目系统") + @PutMapping() + public ApiResult update(@RequestBody PwlProject pwlProject) { + if(StrUtil.isEmpty(pwlProject.getAnalysisLibrary())) { + createAnalysisLib(pwlProject); + } + if(StrUtil.isEmpty(pwlProject.getProjectLibrary())) { + createProjectLib(pwlProject); + } + if (pwlProjectService.updateById(pwlProject)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:remove')") + @OperationLog + @Operation(summary = "删除卫兰的项目项目系统") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (pwlProjectService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:save')") + @OperationLog + @Operation(summary = "批量添加卫兰的项目项目系统") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (pwlProjectService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:update')") + @OperationLog + @Operation(summary = "批量修改卫兰的项目项目系统") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(pwlProjectService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:remove')") + @OperationLog + @Operation(summary = "批量删除卫兰的项目项目系统") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (pwlProjectService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProject:save')") + @Operation(summary = "批量导入项目") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/import") + public ApiResult> importBatch(MultipartFile file) { + ImportParams importParams = new ImportParams(); + importParams.setHeadRows(2); + importParams.setTitleRows(1); + importParams.setSheetNum(1); + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), PwlProjectImportParam.class, importParams); + list.forEach(d -> { + PwlProject item = JSONUtil.parseObject(JSONUtil.toJSONString(d), PwlProject.class); + assert item != null; + if (ObjectUtil.isNotEmpty(item)) { + if (item.getStatus() == null) { + item.setStatus(0); + item.setUserId(getLoginUserId()); + } + if (item.getName() != null) { + pwlProjectService.save(item); + } + } + }); + return success("成功导入" + list.size() + "条", null); + } catch (Exception e) { + e.printStackTrace(); + } + return fail("导入失败", null); + } + + @Operation(summary = "统计项目完成情况") + @GetMapping("/count") + public ApiResult count() { + final User loginUser = getLoginUser(); + if (loginUser != null) { + final List list = pwlProjectService.listByCount(); + list.forEach(d -> { + // 已完成项目数量 + final long completed = pwlProjectService.count(new LambdaQueryWrapper() + .like(PwlProject::getDraftUserId, d.getUserId()) + .and( + i -> i.eq(PwlProject::getStatus, 0) + .isNotNull(PwlProject::getDraftUserId) + ) + ); + d.setBalance(new BigDecimal(completed)); + // 未完成项目数量 + final long incomplete = pwlProjectService.count(new LambdaQueryWrapper() + .like(PwlProject::getDraftUserId, d.getUserId()) + .and( + i -> i.eq(PwlProject::getStatus, 1) + .isNotNull(PwlProject::getDraftUserId) + ) + ); + // 签字会计数量 + final long signUsers = pwlProjectService.count(new LambdaQueryWrapper() + .like(PwlProject::getSignUserId, d.getUserId()) + .and( + i -> i.eq(PwlProject::getStatus, 1) + .isNotNull(PwlProject::getSignUserId) + ) + ); + d.setPoints(Math.toIntExact(incomplete)); + d.setFans(Math.toIntExact(signUsers)); + }); + return success(list); + } + return fail("没有找到结果", null); + } + +} diff --git a/src/main/java/com/gxwebsoft/pwl/controller/PwlProjectLibraryController.java b/src/main/java/com/gxwebsoft/pwl/controller/PwlProjectLibraryController.java new file mode 100644 index 0000000..2c73945 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/controller/PwlProjectLibraryController.java @@ -0,0 +1,162 @@ +package com.gxwebsoft.pwl.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.pwl.service.PwlProjectLibraryService; + +import cn.hutool.core.util.StrUtil; + +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.param.PwlProjectLibraryParam; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 材料库资料库表控制器 + * + * @author 科技小王子 + * @since 2025-09-28 09:38:17 + */ +@Tag(name = "材料库资料库表管理") +@RestController +@RequestMapping("/api/pwl/pwl-project-library") +public class PwlProjectLibraryController extends BaseController { + @Resource + private PwlProjectLibraryService pwlProjectLibraryService; + + @Resource + private KnowledgeBaseService knowledgeBaseService; + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:list')") + @Operation(summary = "分页查询材料库资料库表") + @GetMapping("/page") + public ApiResult> page(PwlProjectLibraryParam param) { + // 使用关联查询 + return success(pwlProjectLibraryService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:list')") + @Operation(summary = "查询全部材料库资料库表") + @GetMapping() + public ApiResult> list(PwlProjectLibraryParam param) { +// User loginUser = getLoginUser(); +// if(!loginUser.getIsAdmin()) { +// param.setUserId(loginUser.getUserId()); +// } + // 使用关联查询 + return success(pwlProjectLibraryService.listRel(param)); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:list')") + @Operation(summary = "根据id查询材料库资料库表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(pwlProjectLibraryService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:list')") + @Operation(summary = "根据ids查询材料库资料库表") + @GetMapping("/byIds/{ids}") + public ApiResult> getByIds(@PathVariable("ids") String ids) { + List idList = StrUtil.split(ids, ','); + List ret = pwlProjectLibraryService.list(new LambdaQueryWrapper().in(PwlProjectLibrary::getId, idList)); + return success(ret); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:save')") + @Operation(summary = "添加材料库资料库表") + @PostMapping() + public ApiResult save(@RequestBody PwlProjectLibrary pwlProjectLibrary) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + pwlProjectLibrary.setUserId(loginUser.getUserId()); + } + if (pwlProjectLibraryService.save(pwlProjectLibrary)) { + try { + //kbName = biz00000000001 + String kbName = pwlProjectLibrary.getType() + StrUtil.padPre(pwlProjectLibrary.getId().toString(), 11, '0'); + String kbId = knowledgeBaseService.createKnowledgeBase(pwlProjectLibrary.getName(),kbName); + //绑定知识库 + pwlProjectLibrary.setKbId(kbId); + pwlProjectLibraryService.updateById(pwlProjectLibrary); + } catch (Exception e) { + return fail("未绑定知识库:"+e.getMessage()); + } + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:update')") + @Operation(summary = "修改材料库资料库表") + @PutMapping() + public ApiResult update(@RequestBody PwlProjectLibrary pwlProjectLibrary) { + if(StrUtil.isEmpty(pwlProjectLibrary.getKbId())) { + //kbName = biz00000000001 + String kbName = pwlProjectLibrary.getType() + StrUtil.padPre(pwlProjectLibrary.getId().toString(), 11, '0'); + String kbId = knowledgeBaseService.createKnowledgeBase(pwlProjectLibrary.getName(),kbName); + //绑定知识库 + pwlProjectLibrary.setKbId(kbId); + } + if (pwlProjectLibraryService.updateById(pwlProjectLibrary)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:remove')") + @Operation(summary = "删除材料库资料库表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (pwlProjectLibraryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:save')") + @Operation(summary = "批量添加材料库资料库表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (pwlProjectLibraryService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:update')") + @Operation(summary = "批量修改材料库资料库表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(pwlProjectLibraryService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('pwl:pwlProjectLibrary:remove')") + @Operation(summary = "批量删除材料库资料库表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (pwlProjectLibraryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java b/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java new file mode 100644 index 0000000..d174e00 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java @@ -0,0 +1,222 @@ +package com.gxwebsoft.pwl.entity; + +import java.math.BigDecimal; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import com.gxwebsoft.common.core.utils.JSONUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 卫兰的项目项目系统 + * + * @author 科技小王子 + * @since 2025-03-22 14:34:35 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "PwlProject对象", description = "卫兰的项目项目系统") +public class PwlProject implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "公司id") + private Integer companyId; + + @Schema(description = "项目名称") + private String name; + + @Schema(description = "项目标识") + private String code; + + @Schema(description = "案引号") + private String caseIndex; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "项目类型") + private String type; + + @Schema(description = "项目图标") + private String image; + + @Schema(description = "二维码") + private String qrcode; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "应用截图") + private String images; + + @Schema(description = "底稿情况") + private String files; + + @Schema(description = "应用介绍") + private String content; + + @Schema(description = "年末资产总额(万元)") + private BigDecimal totalAssets; + + @Schema(description = "合同金额") + private BigDecimal contractPrice; + + @Schema(description = "实收金额") + private BigDecimal payPrice; + + @Schema(description = "软件定价") + private BigDecimal price; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "到期时间") + private String expirationTime; + + @Schema(description = "项目信息-开票单位/汇款人") + private String itemName; + + @Schema(description = "项目信息-年度") + private String itemYear; + + @Schema(description = "项目信息-类型") + private String itemType; + + @Schema(description = "项目信息-审计意见") + private String itemOpinion; + + @Schema(description = "到账信息-银行名称") + private String bankName; + + @Schema(description = "到账日期") + private String bankPayTime; + + @Schema(description = "到账金额") + private BigDecimal bankPrice; + + @Schema(description = "发票类型") + private String invoiceType; + + @Schema(description = "发票类型") + @TableField(exist = false) + private String invoiceTypeName; + + @Schema(description = "开票日期") + private String invoiceTime; + + @Schema(description = "开票金额") + private BigDecimal invoicePrice; + + @Schema(description = "报告份数") + private String reportNum; + + @Schema(description = "底稿人员") + private String draftUserId; + + @Schema(description = "底稿人员") + private String draftUser; + + @Schema(description = "参与成员") + private String userIds; + + @Schema(description = "参与成员") + private String users; + + @Schema(description = "签字注会") + private String signUserId; + + @Schema(description = "签字注会") + private String signUser; + + @Schema(description = "展业人员") + private String saleUserId; + + @Schema(description = "展业人员") + private String saleUser; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "纸质底稿完成情况") + private Integer paper; + + @Schema(description = "电子底稿完成情况") + private Integer electron; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "客户ID") + private Integer userId; + + @Schema(description = "真实姓名") + @TableField(exist = false) + private String realName; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "手机号") + @TableField(exist = false) + private String phone; + + @Schema(description = "知识库ID") + private String kbId; + + @Schema(description = "资料库库IDs") + private String libraryIds; //英文逗号分割 + + @Schema(description = "材料分析库") + private String analysisLibrary; + + @Schema(description = "项目资料库") + private String projectLibrary; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +// public Object getDraftUser() { +// return JSON.parse(draftUser); +// } +// public Object getUsers() { +// return JSON.parse(users); +// } +// public Object getSignUser() { +// return JSON.parse(signUser); +// } +// public Object getSaleUser() { +// return JSON.parse(saleUser); +// } +// public Object setDraftUser() { +// return JSON.toJSON(draftUser); +// } +} diff --git a/src/main/java/com/gxwebsoft/pwl/entity/PwlProjectLibrary.java b/src/main/java/com/gxwebsoft/pwl/entity/PwlProjectLibrary.java new file mode 100644 index 0000000..bb1e6c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/entity/PwlProjectLibrary.java @@ -0,0 +1,74 @@ +package com.gxwebsoft.pwl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 材料库资料库表 + * + * @author 科技小王子 + * @since 2025-09-28 09:38:17 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "PwlProjectLibrary对象", description = "材料库资料库表") +public class PwlProjectLibrary implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "资料库名称") + private String name; + + @Schema(description = "资料库类型: biz-行业案例库, pub-公共知识库") + private String type; + + @Schema(description = "关联知识库ID") + private String kbId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "资料库图标") + private String image; + + @Schema(description = "资料库描述") + private String content; + + @Schema(description = "关联文件") + private String files; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectLibraryMapper.java b/src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectLibraryMapper.java new file mode 100644 index 0000000..f0adaa4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectLibraryMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.pwl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.param.PwlProjectLibraryParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 材料库资料库表Mapper + * + * @author 科技小王子 + * @since 2025-09-28 09:38:17 + */ +public interface PwlProjectLibraryMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") PwlProjectLibraryParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") PwlProjectLibraryParam param); + +} diff --git a/src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectMapper.java b/src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectMapper.java new file mode 100644 index 0000000..138b719 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/mapper/PwlProjectMapper.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.pwl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.pwl.entity.PwlProject; +import com.gxwebsoft.pwl.param.PwlProjectParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 卫兰的项目项目系统Mapper + * + * @author 科技小王子 + * @since 2025-03-22 14:34:35 + */ +public interface PwlProjectMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") PwlProjectParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") PwlProjectParam param); + + + List listByCount(); +} diff --git a/src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectLibraryMapper.xml b/src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectLibraryMapper.xml new file mode 100644 index 0000000..46fefe2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectLibraryMapper.xml @@ -0,0 +1,75 @@ + + + + + + + SELECT a.* + FROM pwl_project_library a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.kb_id LIKE CONCAT('%', #{param.kbId}, '%') + + + AND a.sort = #{param.sort} + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectMapper.xml b/src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectMapper.xml new file mode 100644 index 0000000..88b993b --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/mapper/xml/PwlProjectMapper.xml @@ -0,0 +1,169 @@ + + + + + + + SELECT a.*, u.real_name as realName, u.phone, u.avatar, dt.dict_data_name as invoiceTypeName + FROM pwl_project a + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + LEFT JOIN gxwebsoft_core.sys_dict_data dt ON a.invoice_type = dt.dict_data_id + + + AND a.id = #{param.id} + + + AND a.company_id = #{param.companyId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.case_index LIKE CONCAT('%', #{param.caseIndex}, '%') + + + AND a.parent_id = #{param.parentId} + + + AND a.type = #{param.type} + + + AND a.avatar LIKE CONCAT('%', #{param.avatar}, '%') + + + AND a.qrcode LIKE CONCAT('%', #{param.qrcode}, '%') + + + AND a.url LIKE CONCAT('%', #{param.url}, '%') + + + AND a.images LIKE CONCAT('%', #{param.images}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.total_assets = #{param.totalAssets} + + + AND a.contract_price = #{param.contractPrice} + + + AND a.pay_price = #{param.payPrice} + + + AND a.price = #{param.price} + + + AND a.recommend = #{param.recommend} + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.item_name LIKE CONCAT('%', #{param.itemName}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.itemYear}, '%') + + + AND a.item_type LIKE CONCAT('%', #{param.itemType}, '%') + + + AND a.item_opinion LIKE CONCAT('%', #{param.itemOpinion}, '%') + + + AND a.bank_name LIKE CONCAT('%', #{param.bankName}, '%') + + + AND a.bank_pay_time LIKE CONCAT('%', #{param.bankPayTime}, '%') + + + AND a.bank_price = #{param.bankPrice} + + + AND a.invoice_type LIKE CONCAT('%', #{param.invoiceType}, '%') + + + AND a.invoice_time LIKE CONCAT('%', #{param.invoiceTime}, '%') + + + AND a.invoice_price = #{param.invoicePrice} + + + AND a.report_num = #{param.reportNum} + + + AND a.draft_user_id = #{param.draftUserId} + + + AND a.user_ids LIKE CONCAT('%', #{param.userIds}, '%') + + + AND a.sign_user_id = #{param.signUserId} + + + AND a.sale_user_id = #{param.saleUserId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.id IN + + #{item} + + + + AND (a.name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.id = #{param.keywords} + OR a.code = #{param.keywords} + ) + + + + + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/pwl/param/PwlProjectImportParam.java b/src/main/java/com/gxwebsoft/pwl/param/PwlProjectImportParam.java new file mode 100644 index 0000000..b0b2517 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/param/PwlProjectImportParam.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.pwl.param; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户导入参数 + * + * @author WebSoft + * @since 2011-10-15 17:33:34 + */ +@Data +public class PwlProjectImportParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Excel(name = "报告时间") + @JsonFormat(pattern = "yyyy.MM.dd", timezone = "GMT+8") + private String expirationTime; + + @Excel(name = "报告编号") + private String code; + + @Excel(name = "审计单位") + private String name; + + @Excel(name = "开项目信息") + private String itemName; + + @Excel(name = "所属年度") + private String itemYear; + + @Excel(name = "类型") + private String itemType; + + @Excel(name = "审计意见") + private String itemOpinion; + + @Excel(name = "年末资产总额(万元)") + private String totalAssets; + + @Excel(name = "合同金额") + private String contractPrice; + + @Excel(name = "实收金额") + private String payPrice; + + @Excel(name = "到账银行") + private String bankName; + + @Excel(name = "到账日期") + @JsonFormat(pattern = "yyyy.MM.dd", timezone = "GMT+8") + private String bankPayTime; + + @Excel(name = "到账金额") + private String bankPrice; + + @Excel(name = "日期") + @JsonFormat(pattern = "yyyy.MM.dd", timezone = "GMT+8") + private String invoiceTime; + + @Excel(name = "金额") + private String invoicePrice; + + @Excel(name = "发票类型") + private String invoiceType; + + @Excel(name = "报告份数") + private String reportNum; + + +} diff --git a/src/main/java/com/gxwebsoft/pwl/param/PwlProjectLibraryParam.java b/src/main/java/com/gxwebsoft/pwl/param/PwlProjectLibraryParam.java new file mode 100644 index 0000000..04d0bb0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/param/PwlProjectLibraryParam.java @@ -0,0 +1,71 @@ +package com.gxwebsoft.pwl.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 材料库资料库表查询参数 + * + * @author 科技小王子 + * @since 2025-09-28 09:38:17 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "PwlProjectLibraryParam对象", description = "材料库资料库表查询参数") +public class PwlProjectLibraryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "资料库名称") + private String name; + + @Schema(description = "资料库类型: biz-项目案例库, pub-公共知识库") + private String type; + + @Schema(description = "关联知识库ID") + private String kbId; + + @Schema(description = "资料库库IDs") + private String libraryIds; //英文逗号分割 + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "资料库图标") + private String image; + + @Schema(description = "资料库描述") + private String content; + + @Schema(description = "关联文件") + private String files; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "创建用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/pwl/param/PwlProjectParam.java b/src/main/java/com/gxwebsoft/pwl/param/PwlProjectParam.java new file mode 100644 index 0000000..b8b34f4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/param/PwlProjectParam.java @@ -0,0 +1,173 @@ +package com.gxwebsoft.pwl.param; + +import java.math.BigDecimal; +import java.util.Set; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 卫兰的项目项目系统查询参数 + * + * @author 科技小王子 + * @since 2025-03-22 14:34:35 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "PwlProjectParam对象", description = "卫兰的项目项目系统查询参数") +public class PwlProjectParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "公司id") + private Integer companyId; + + @Schema(description = "项目名称") + private String name; + + @Schema(description = "项目标识") + private String code; + + @Schema(description = "案引号") + private String caseIndex; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "项目类型") + @QueryField(type = QueryType.EQ) + private String type; + + @Schema(description = "项目图标") + private String avatar; + + @Schema(description = "二维码") + private String qrcode; + + @Schema(description = "链接地址") + private String url; + + @Schema(description = "应用截图") + private String images; + + @Schema(description = "底稿情况") + private String files; + + @Schema(description = "应用介绍") + private String content; + + @Schema(description = "年末资产总额(万元)") + @QueryField(type = QueryType.EQ) + private BigDecimal totalAssets; + + @Schema(description = "合同金额") + @QueryField(type = QueryType.EQ) + private BigDecimal contractPrice; + + @Schema(description = "实收金额") + @QueryField(type = QueryType.EQ) + private BigDecimal payPrice; + + @Schema(description = "软件定价") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "到期时间") + private String expirationTime; + + @Schema(description = "项目信息-开票单位/汇款人") + private String itemName; + + @Schema(description = "项目信息-年度") + private String itemYear; + + @Schema(description = "项目信息-类型") + private String itemType; + + @Schema(description = "项目信息-审计意见") + private String itemOpinion; + + @Schema(description = "到账信息-银行名称") + private String bankName; + + @Schema(description = "到账日期") + private String bankPayTime; + + @Schema(description = "到账金额") + @QueryField(type = QueryType.EQ) + private BigDecimal bankPrice; + + @Schema(description = "发票类型") + private String invoiceType; + + @Schema(description = "开票日期") + private String invoiceTime; + + @Schema(description = "开票金额") + @QueryField(type = QueryType.EQ) + private BigDecimal invoicePrice; + + @Schema(description = "报告份数") + @QueryField(type = QueryType.EQ) + private String reportNum; + + @Schema(description = "底稿人员") + @QueryField(type = QueryType.EQ) + private Integer draftUserId; + + @Schema(description = "参与成员") + private String userIds; + + @Schema(description = "签字注会") + @QueryField(type = QueryType.EQ) + private Integer signUserId; + + @Schema(description = "展业人员") + @QueryField(type = QueryType.EQ) + private Integer saleUserId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "客户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "当前登录用户") + @TableField(exist = false) + private Integer loginUserId; + + @Schema(description = "项目ID集合") + @TableField(exist = false) + private Set projectIds; + +} diff --git a/src/main/java/com/gxwebsoft/pwl/service/PwlProjectLibraryService.java b/src/main/java/com/gxwebsoft/pwl/service/PwlProjectLibraryService.java new file mode 100644 index 0000000..139af33 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/service/PwlProjectLibraryService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.pwl.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.param.PwlProjectLibraryParam; + +import java.util.List; + +/** + * 材料库资料库表Service + * + * @author 科技小王子 + * @since 2025-09-28 09:38:17 + */ +public interface PwlProjectLibraryService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(PwlProjectLibraryParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(PwlProjectLibraryParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return PwlProjectLibrary + */ + PwlProjectLibrary getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/pwl/service/PwlProjectService.java b/src/main/java/com/gxwebsoft/pwl/service/PwlProjectService.java new file mode 100644 index 0000000..3261ec1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/service/PwlProjectService.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.pwl.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.pwl.entity.PwlProject; +import com.gxwebsoft.pwl.param.PwlProjectParam; + +import java.util.List; + +/** + * 卫兰的项目项目系统Service + * + * @author 科技小王子 + * @since 2025-03-22 14:34:35 + */ +public interface PwlProjectService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(PwlProjectParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(PwlProjectParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return PwlProject + */ + PwlProject getByIdRel(Integer id); + + List listByCount(); +} diff --git a/src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectLibraryServiceImpl.java b/src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectLibraryServiceImpl.java new file mode 100644 index 0000000..0c3d174 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectLibraryServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.pwl.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.pwl.mapper.PwlProjectLibraryMapper; +import com.gxwebsoft.pwl.service.PwlProjectLibraryService; +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.param.PwlProjectLibraryParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 材料库资料库表Service实现 + * + * @author 科技小王子 + * @since 2025-09-28 09:38:17 + */ +@Service +public class PwlProjectLibraryServiceImpl extends ServiceImpl implements PwlProjectLibraryService { + + @Override + public PageResult pageRel(PwlProjectLibraryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(PwlProjectLibraryParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public PwlProjectLibrary getByIdRel(Integer id) { + PwlProjectLibraryParam param = new PwlProjectLibraryParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectServiceImpl.java b/src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectServiceImpl.java new file mode 100644 index 0000000..01a3b00 --- /dev/null +++ b/src/main/java/com/gxwebsoft/pwl/service/impl/PwlProjectServiceImpl.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.pwl.service.impl; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.pwl.mapper.PwlProjectMapper; +import com.gxwebsoft.pwl.service.PwlProjectService; +import com.gxwebsoft.pwl.entity.PwlProject; +import com.gxwebsoft.pwl.param.PwlProjectParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; + +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + * 卫兰的项目项目系统Service实现 + * + * @author 科技小王子 + * @since 2025-03-22 14:34:35 + */ +@Service +public class PwlProjectServiceImpl extends ServiceImpl implements PwlProjectService { + @Override + public PageResult pageRel(PwlProjectParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(PwlProjectParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public PwlProject getByIdRel(Integer id) { + PwlProjectParam param = new PwlProjectParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public List listByCount() { + return baseMapper.listByCount(); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/config/OrderConfigProperties.java b/src/main/java/com/gxwebsoft/shop/config/OrderConfigProperties.java new file mode 100644 index 0000000..887db88 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/config/OrderConfigProperties.java @@ -0,0 +1,235 @@ +package com.gxwebsoft.shop.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 订单相关配置属性 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Data +@Component +@ConfigurationProperties(prefix = "shop.order") +public class OrderConfigProperties { + + /** + * 测试账号配置 + */ + private TestAccount testAccount = new TestAccount(); + + /** + * 租户特殊规则配置 + */ + private List tenantRules; + + /** + * 默认订单配置 + */ + private DefaultConfig defaultConfig = new DefaultConfig(); + + /** + * 订单自动取消配置 + */ + private AutoCancel autoCancel = new AutoCancel(); + + /** + * 错误信息配置 + */ + private ErrorMessages errorMessages = new ErrorMessages(); + + @Data + public static class TestAccount { + /** + * 测试手机号列表 + */ + private List phoneNumbers; + + /** + * 测试支付金额 + */ + private BigDecimal testPayAmount = new BigDecimal("0.01"); + + /** + * 是否启用测试模式 + */ + private boolean enabled = false; + } + + @Data + public static class TenantRule { + /** + * 租户ID + */ + private Integer tenantId; + + /** + * 租户名称 + */ + private String tenantName; + + /** + * 最小金额限制 + */ + private BigDecimal minAmount; + + /** + * 金额限制提示信息 + */ + private String minAmountMessage; + + /** + * 是否启用 + */ + private boolean enabled = true; + } + + @Data + public static class DefaultConfig { + + /** + * 默认标题 + */ + private String defaultTitle = "订单标题"; + + /** + * 默认备注 + */ + private String defaultComments = "暂无"; + + /** + * 最小订单金额 + */ + private BigDecimal minOrderAmount = BigDecimal.ZERO; + + /** + * 订单超时时间(分钟) + */ + private Integer orderTimeoutMinutes = 30; + } + + /** + * 检查是否为测试账号 + */ + public boolean isTestAccount(String phone) { + return testAccount.isEnabled() && + testAccount.getPhoneNumbers() != null && + testAccount.getPhoneNumbers().contains(phone); + } + + @Data + public static class AutoCancel { + /** + * 是否启用自动取消功能 + */ + private boolean enabled = true; + + /** + * 默认超时时间(分钟) + */ + private Integer defaultTimeoutMinutes = 30; + + /** + * 定时任务检查间隔(分钟) + */ + private Integer checkIntervalMinutes = 5; + + /** + * 批量处理大小 + */ + private Integer batchSize = 100; + + /** + * 租户特殊配置 + */ + private List tenantConfigs; + } + + @Data + public static class TenantCancelConfig { + /** + * 租户ID + */ + private Integer tenantId; + + /** + * 租户名称 + */ + private String tenantName; + + /** + * 超时时间(分钟) + */ + private Integer timeoutMinutes; + + /** + * 是否启用 + */ + private boolean enabled = true; + } + + /** + * 获取指定租户的超时时间 + */ + public Integer getTimeoutMinutes(Integer tenantId) { + if (autoCancel.getTenantConfigs() != null) { + for (TenantCancelConfig config : autoCancel.getTenantConfigs()) { + if (config.isEnabled() && config.getTenantId().equals(tenantId)) { + return config.getTimeoutMinutes(); + } + } + } + return autoCancel.getDefaultTimeoutMinutes(); + } + + /** + * 获取租户规则 + */ + public TenantRule getTenantRule(Integer tenantId) { + if (tenantRules == null) { + return null; + } + return tenantRules.stream() + .filter(rule -> rule.isEnabled() && rule.getTenantId().equals(tenantId)) + .findFirst() + .orElse(null); + } + + @Data + public static class ErrorMessages { + /** + * 订单金额计算错误信息 + */ + private String amountCalculationError = "订单金额计算错误,请刷新重试"; + + /** + * 商品不存在错误信息 + */ + private String goodsNotFound = "商品不存在"; + + /** + * 商品已下架错误信息 + */ + private String goodsOffline = "商品已下架"; + + /** + * 库存不足错误信息 + */ + private String stockInsufficient = "商品库存不足"; + + /** + * 购买数量超限错误信息 + */ + private String quantityExceeded = "商品购买数量超过限制"; + + /** + * 商品价格异常错误信息 + */ + private String priceAbnormal = "商品价格异常"; + } +} diff --git a/src/main/java/com/gxwebsoft/shop/constants/WxPayConstants.java b/src/main/java/com/gxwebsoft/shop/constants/WxPayConstants.java new file mode 100644 index 0000000..1618dd8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/constants/WxPayConstants.java @@ -0,0 +1,200 @@ +package com.gxwebsoft.shop.constants; + +/** + * 微信支付常量类 + * 管理微信支付相关的常量配置 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +public class WxPayConstants { + + /** + * 微信支付类型 + */ + public static class PayType { + /** Native支付 */ + public static final String NATIVE = "NATIVE"; + /** JSAPI支付 */ + public static final String JSAPI = "JSAPI"; + /** H5支付 */ + public static final String MWEB = "MWEB"; + /** APP支付 */ + public static final String APP = "APP"; + } + + /** + * 支付状态 + */ + public static class PayStatus { + /** 支付成功 */ + public static final String SUCCESS = "SUCCESS"; + /** 转入退款 */ + public static final String REFUND = "REFUND"; + /** 未支付 */ + public static final String NOTPAY = "NOTPAY"; + /** 已关闭 */ + public static final String CLOSED = "CLOSED"; + /** 已撤销(付款码支付) */ + public static final String REVOKED = "REVOKED"; + /** 用户支付中(付款码支付) */ + public static final String USERPAYING = "USERPAYING"; + /** 支付失败(其他原因,如银行返回失败) */ + public static final String PAYERROR = "PAYERROR"; + } + + /** + * 回调通知相关 + */ + public static class Notify { + /** 成功响应 */ + public static final String SUCCESS_RESPONSE = "SUCCESS"; + /** 失败响应 */ + public static final String FAIL_RESPONSE = "FAIL"; + + /** 通知类型 - 支付成功 */ + public static final String EVENT_TYPE_PAYMENT = "TRANSACTION.SUCCESS"; + /** 通知类型 - 退款成功 */ + public static final String EVENT_TYPE_REFUND = "REFUND.SUCCESS"; + } + + /** + * 缓存键前缀 + */ + public static class CacheKey { + /** 支付配置缓存键前缀 */ + public static final String PAYMENT_CONFIG_PREFIX = "Payment:wxPay:"; + /** 微信小程序配置缓存键 */ + public static final String MP_WEIXIN_CONFIG = "mp-weixin"; + } + + /** + * 配置相关 + */ + public static class Config { + /** 货币类型 - 人民币 */ + public static final String CURRENCY_CNY = "CNY"; + /** 金额转换倍数(元转分) */ + public static final int AMOUNT_MULTIPLIER = 100; + + /** 开发环境标识 */ + public static final String PROFILE_DEV = "dev"; + /** 生产环境标识 */ + public static final String PROFILE_PROD = "prod"; + } + + /** + * 订单相关 + */ + public static class Order { + /** 订单超时时间(分钟) */ + public static final int TIMEOUT_MINUTES = 30; + /** 订单描述最大长度 */ + public static final int DESCRIPTION_MAX_LENGTH = 127; + } + + /** + * HTTP头部相关 + */ + public static class Header { + /** 微信支付签名 */ + public static final String WECHATPAY_SIGNATURE = "Wechatpay-Signature"; + /** 微信支付时间戳 */ + public static final String WECHATPAY_TIMESTAMP = "Wechatpay-Timestamp"; + /** 微信支付随机数 */ + public static final String WECHATPAY_NONCE = "Wechatpay-Nonce"; + /** 微信支付序列号 */ + public static final String WECHATPAY_SERIAL = "Wechatpay-Serial"; + /** 请求ID */ + public static final String REQUEST_ID = "Request-ID"; + } + + /** + * 错误信息 + */ + public static class ErrorMessage { + /** 配置未找到 */ + public static final String CONFIG_NOT_FOUND = "微信支付配置未找到"; + /** 证书文件不存在 */ + public static final String CERTIFICATE_NOT_FOUND = "微信支付证书文件不存在"; + /** 参数验证失败 */ + public static final String PARAM_VALIDATION_FAILED = "参数验证失败"; + /** 签名验证失败 */ + public static final String SIGNATURE_VERIFICATION_FAILED = "签名验证失败"; + /** 订单不存在 */ + public static final String ORDER_NOT_FOUND = "订单不存在"; + /** 订单状态异常 */ + public static final String ORDER_STATUS_INVALID = "订单状态异常"; + /** 金额不匹配 */ + public static final String AMOUNT_MISMATCH = "订单金额不匹配"; + /** 网络请求失败 */ + public static final String NETWORK_REQUEST_FAILED = "网络请求失败"; + /** 系统内部错误 */ + public static final String SYSTEM_INTERNAL_ERROR = "系统内部错误"; + } + + /** + * 日志相关 + */ + public static class LogMessage { + /** 支付请求开始 */ + public static final String PAY_REQUEST_START = "开始处理微信支付请求"; + /** 支付请求成功 */ + public static final String PAY_REQUEST_SUCCESS = "微信支付请求处理成功"; + /** 支付请求失败 */ + public static final String PAY_REQUEST_FAILED = "微信支付请求处理失败"; + + /** 回调处理开始 */ + public static final String CALLBACK_START = "开始处理微信支付回调"; + /** 回调处理成功 */ + public static final String CALLBACK_SUCCESS = "微信支付回调处理成功"; + /** 回调处理失败 */ + public static final String CALLBACK_FAILED = "微信支付回调处理失败"; + + /** 配置加载成功 */ + public static final String CONFIG_LOADED = "微信支付配置加载成功"; + /** 配置加载失败 */ + public static final String CONFIG_LOAD_FAILED = "微信支付配置加载失败"; + } + + /** + * 正则表达式 + */ + public static class Regex { + /** 商户号格式 */ + public static final String MERCHANT_ID = "^\\d{10}$"; + /** 订单号格式 */ + public static final String ORDER_NO = "^[a-zA-Z0-9_-]{1,32}$"; + /** 金额格式(分) */ + public static final String AMOUNT = "^[1-9]\\d*$"; + } + + /** + * 时间相关 + */ + public static class Time { + /** 签名有效期(秒) */ + public static final long SIGNATURE_VALID_SECONDS = 300; + /** 配置缓存有效期(秒) */ + public static final long CONFIG_CACHE_SECONDS = 3600; + } + + /** + * 文件相关 + */ + public static class File { + /** 证书文件扩展名 */ + public static final String CERT_EXTENSION = ".pem"; + /** 私钥文件名 */ + public static final String PRIVATE_KEY_FILE = "apiclient_key.pem"; + /** 商户证书文件名 */ + public static final String MERCHANT_CERT_FILE = "apiclient_cert.pem"; + /** 平台证书文件名 */ + public static final String PLATFORM_CERT_FILE = "wechatpay_cert.pem"; + } + + // 私有构造函数,防止实例化 + private WxPayConstants() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/CouponStatusController.java b/src/main/java/com/gxwebsoft/shop/controller/CouponStatusController.java new file mode 100644 index 0000000..ded942f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/CouponStatusController.java @@ -0,0 +1,189 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.service.CouponStatusService; +import com.gxwebsoft.shop.service.CouponStatusService.CouponStatusResult; +import com.gxwebsoft.shop.service.CouponStatusService.CouponValidationResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 优惠券状态管理控制器 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Tag(name = "优惠券状态管理") +@RestController +@RequestMapping("/api/shop/coupon-status") +public class CouponStatusController extends BaseController { + + @Autowired + private CouponStatusService couponStatusService; + + @Operation(summary = "获取当前用户可用优惠券") + @GetMapping("/available") + public ApiResult> getAvailableCoupons() { + try { + List coupons = couponStatusService.getAvailableCoupons(getLoginUserId()); + return success("获取成功", coupons); + } catch (Exception e) { + log.error("获取可用优惠券失败", e); + return fail("获取失败",null); + } + } + + @Operation(summary = "获取当前用户已使用优惠券") + @GetMapping("/used") + public ApiResult> getUsedCoupons() { + try { + List coupons = couponStatusService.getUsedCoupons(getLoginUserId()); + return success("获取成功", coupons); + } catch (Exception e) { + log.error("获取已使用优惠券失败", e); + return fail("获取失败",null); + } + } + + @Operation(summary = "获取当前用户已过期优惠券") + @GetMapping("/expired") + public ApiResult> getExpiredCoupons() { + try { + List coupons = couponStatusService.getExpiredCoupons(getLoginUserId()); + return success("获取成功", coupons); + } catch (Exception e) { + log.error("获取已过期优惠券失败", e); + return fail("获取失败",null); + } + } + + @Operation(summary = "获取当前用户所有优惠券(按状态分类)") + @GetMapping("/all-grouped") + public ApiResult getAllCouponsGrouped() { + try { + CouponStatusResult result = couponStatusService.getUserCouponsGroupByStatus(getLoginUserId()); + return success("获取成功", result); + } catch (Exception e) { + log.error("获取优惠券分类失败", e); + return fail("获取失败",null); + } + } + + @Operation(summary = "验证优惠券是否可用于订单") + @PostMapping("/validate") + public ApiResult validateCouponForOrder( + @Parameter(description = "用户优惠券ID") @RequestParam Long userCouponId, + @Parameter(description = "订单总金额") @RequestParam BigDecimal totalAmount, + @Parameter(description = "商品ID列表") @RequestBody List goodsIds) { + try { + CouponValidationResult result = couponStatusService.validateCouponForOrder( + userCouponId, totalAmount, goodsIds); + return success(result.getMessage(), result); + } catch (Exception e) { + log.error("验证优惠券失败", e); + return fail("验证失败",null); + } + } + + @Operation(summary = "使用优惠券") + @PostMapping("/use") + public ApiResult useCoupon( + @Parameter(description = "用户优惠券ID") @RequestParam Long userCouponId, + @Parameter(description = "订单ID") @RequestParam Integer orderId, + @Parameter(description = "订单号") @RequestParam String orderNo) { + try { + boolean success = couponStatusService.useCoupon(userCouponId, orderId, orderNo); + if (success) { + return success("使用成功"); + } else { + return fail("使用失败"); + } + } catch (Exception e) { + log.error("使用优惠券失败", e); + return fail("使用失败"); + } + } + + @Operation(summary = "退还优惠券(订单取消时)") + @PostMapping("/return/{orderId}") + public ApiResult returnCoupon( + @Parameter(description = "订单ID") @PathVariable Integer orderId) { + try { + boolean success = couponStatusService.returnCoupon(orderId); + if (success) { + return success("退还成功"); + } else { + return fail("退还失败"); + } + } catch (Exception e) { + log.error("退还优惠券失败", e); + return fail("退还失败"); + } + } + + @PreAuthorize("hasAuthority('shop:coupon:manage')") + @Operation(summary = "批量更新过期优惠券状态(管理员)") + @PostMapping("/update-expired") + public ApiResult updateExpiredCoupons() { + try { + int updatedCount = couponStatusService.updateExpiredCoupons(); + return success("更新完成,共更新 " + updatedCount + " 张优惠券"); + } catch (Exception e) { + log.error("批量更新过期优惠券失败", e); + return fail("更新失败"); + } + } + + @Operation(summary = "获取优惠券状态统计") + @GetMapping("/statistics") + public ApiResult getCouponStatistics() { + try { + CouponStatusResult result = couponStatusService.getUserCouponsGroupByStatus(getLoginUserId()); + + CouponStatistics statistics = new CouponStatistics(); + statistics.setAvailableCount(result.getAvailableCoupons().size()); + statistics.setUsedCount(result.getUsedCoupons().size()); + statistics.setExpiredCount(result.getExpiredCoupons().size()); + statistics.setTotalCount(result.getTotalCount()); + + return success("获取成功", statistics); + } catch (Exception e) { + log.error("获取优惠券统计失败", e); + return fail("获取失败",null); + } + } + + /** + * 优惠券统计信息 + */ + public static class CouponStatistics { + private int availableCount; // 可用数量 + private int usedCount; // 已使用数量 + private int expiredCount; // 已过期数量 + private int totalCount; // 总数量 + + // Getters and Setters + public int getAvailableCount() { return availableCount; } + public void setAvailableCount(int availableCount) { this.availableCount = availableCount; } + + public int getUsedCount() { return usedCount; } + public void setUsedCount(int usedCount) { this.usedCount = usedCount; } + + public int getExpiredCount() { return expiredCount; } + public void setExpiredCount(int expiredCount) { this.expiredCount = expiredCount; } + + public int getTotalCount() { return totalCount; } + public void setTotalCount(int totalCount) { this.totalCount = totalCount; } + } +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopArticleController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopArticleController.java new file mode 100644 index 0000000..d882ba2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopArticleController.java @@ -0,0 +1,129 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopArticleService; +import com.gxwebsoft.shop.entity.ShopArticle; +import com.gxwebsoft.shop.param.ShopArticleParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品文章控制器 + * + * @author 科技小王子 + * @since 2025-08-13 05:14:53 + */ +@Tag(name = "商品文章管理") +@RestController +@RequestMapping("/api/shop/shop-article") +public class ShopArticleController extends BaseController { + @Resource + private ShopArticleService shopArticleService; + + @PreAuthorize("hasAuthority('shop:shopArticle:list')") + @Operation(summary = "分页查询商品文章") + @GetMapping("/page") + public ApiResult> page(ShopArticleParam param) { + // 使用关联查询 + return success(shopArticleService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:list')") + @Operation(summary = "查询全部商品文章") + @GetMapping() + public ApiResult> list(ShopArticleParam param) { + // 使用关联查询 + return success(shopArticleService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:list')") + @Operation(summary = "根据id查询商品文章") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopArticleService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:save')") + @OperationLog + @Operation(summary = "添加商品文章") + @PostMapping() + public ApiResult save(@RequestBody ShopArticle shopArticle) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopArticle.setUserId(loginUser.getUserId()); + } + if (shopArticleService.save(shopArticle)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:update')") + @OperationLog + @Operation(summary = "修改商品文章") + @PutMapping() + public ApiResult update(@RequestBody ShopArticle shopArticle) { + if (shopArticleService.updateById(shopArticle)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:remove')") + @OperationLog + @Operation(summary = "删除商品文章") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopArticleService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:save')") + @OperationLog + @Operation(summary = "批量添加商品文章") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopArticleService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:update')") + @OperationLog + @Operation(summary = "批量修改商品文章") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopArticleService, "article_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopArticle:remove')") + @OperationLog + @Operation(summary = "批量删除商品文章") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopArticleService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopBrandController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopBrandController.java new file mode 100644 index 0000000..ec2c25c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopBrandController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopBrandService; +import com.gxwebsoft.shop.entity.ShopBrand; +import com.gxwebsoft.shop.param.ShopBrandParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 品牌控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "品牌管理") +@RestController +@RequestMapping("/api/shop/shop-brand") +public class ShopBrandController extends BaseController { + @Resource + private ShopBrandService shopBrandService; + + @Operation(summary = "分页查询品牌") + @GetMapping("/page") + public ApiResult> page(ShopBrandParam param) { + // 使用关联查询 + return success(shopBrandService.pageRel(param)); + } + + @Operation(summary = "查询全部品牌") + @GetMapping() + public ApiResult> list(ShopBrandParam param) { + // 使用关联查询 + return success(shopBrandService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopBrand:list')") + @Operation(summary = "根据id查询品牌") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopBrandService.getByIdRel(id)); + } + + @Operation(summary = "添加品牌") + @PostMapping() + public ApiResult save(@RequestBody ShopBrand shopBrand) { + if (shopBrandService.save(shopBrand)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改品牌") + @PutMapping() + public ApiResult update(@RequestBody ShopBrand shopBrand) { + if (shopBrandService.updateById(shopBrand)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除品牌") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopBrandService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加品牌") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopBrandService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改品牌") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopBrandService, "brand_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除品牌") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopBrandService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCartController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCartController.java new file mode 100644 index 0000000..22ada86 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCartController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopCartService; +import com.gxwebsoft.shop.entity.ShopCart; +import com.gxwebsoft.shop.param.ShopCartParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 购物车控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "购物车管理") +@RestController +@RequestMapping("/api/shop/shop-cart") +public class ShopCartController extends BaseController { + @Resource + private ShopCartService shopCartService; + + @Operation(summary = "分页查询购物车") + @GetMapping("/page") + public ApiResult> page(ShopCartParam param) { + // 使用关联查询 + return success(shopCartService.pageRel(param)); + } + + @Operation(summary = "查询全部购物车") + @GetMapping() + public ApiResult> list(ShopCartParam param) { + // 使用关联查询 + return success(shopCartService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopCart:list')") + @Operation(summary = "根据id查询购物车") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Long id) { + // 使用关联查询 + return success(shopCartService.getByIdRel(id)); + } + + @Operation(summary = "添加购物车") + @PostMapping() + public ApiResult save(@RequestBody ShopCart shopCart) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopCart.setUserId(loginUser.getUserId()); + } + if (shopCartService.save(shopCart)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改购物车") + @PutMapping() + public ApiResult update(@RequestBody ShopCart shopCart) { + if (shopCartService.updateById(shopCart)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除购物车") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCartService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加购物车") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCartService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改购物车") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCartService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除购物车") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCartService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCategoryController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCategoryController.java new file mode 100644 index 0000000..bcffdea --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCategoryController.java @@ -0,0 +1,126 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopCategoryService; +import com.gxwebsoft.shop.entity.ShopCategory; +import com.gxwebsoft.shop.param.ShopCategoryParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品分类控制器 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Tag(name = "商品分类管理") +@RestController +@RequestMapping("/api/shop/shop-category") +public class ShopCategoryController extends BaseController { + @Resource + private ShopCategoryService shopCategoryService; + + @Operation(summary = "分页查询商品分类") + @GetMapping("/page") + public ApiResult> page(ShopCategoryParam param) { + // 使用关联查询 + return success(shopCategoryService.pageRel(param)); + } + + @Operation(summary = "查询全部商品分类") + @GetMapping() + public ApiResult> list(ShopCategoryParam param) { + // 使用关联查询 + return success(shopCategoryService.listRel(param)); + } + + @Operation(summary = "根据id查询商品分类") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopCategoryService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopCategory:save')") + @OperationLog + @Operation(summary = "添加商品分类") + @PostMapping() + public ApiResult save(@RequestBody ShopCategory shopCategory) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopCategory.setUserId(loginUser.getUserId()); + } + if (shopCategoryService.save(shopCategory)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCategory:update')") + @OperationLog + @Operation(summary = "修改商品分类") + @PutMapping() + public ApiResult update(@RequestBody ShopCategory shopCategory) { + if (shopCategoryService.updateById(shopCategory)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCategory:remove')") + @OperationLog + @Operation(summary = "删除商品分类") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCategoryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCategory:save')") + @OperationLog + @Operation(summary = "批量添加商品分类") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCategoryService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCategory:update')") + @OperationLog + @Operation(summary = "批量修改商品分类") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCategoryService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCategory:remove')") + @OperationLog + @Operation(summary = "批量删除商品分类") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCategoryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopChatConversationController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopChatConversationController.java new file mode 100644 index 0000000..9ef5377 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopChatConversationController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopChatConversationService; +import com.gxwebsoft.shop.entity.ShopChatConversation; +import com.gxwebsoft.shop.param.ShopChatConversationParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 聊天消息表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "聊天消息表管理") +@RestController +@RequestMapping("/api/shop/shop-chat-conversation") +public class ShopChatConversationController extends BaseController { + @Resource + private ShopChatConversationService shopChatConversationService; + + @Operation(summary = "分页查询聊天消息表") + @GetMapping("/page") + public ApiResult> page(ShopChatConversationParam param) { + // 使用关联查询 + return success(shopChatConversationService.pageRel(param)); + } + + @Operation(summary = "查询全部聊天消息表") + @GetMapping() + public ApiResult> list(ShopChatConversationParam param) { + // 使用关联查询 + return success(shopChatConversationService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopChatConversation:list')") + @Operation(summary = "根据id查询聊天消息表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopChatConversationService.getByIdRel(id)); + } + + @Operation(summary = "添加聊天消息表") + @PostMapping() + public ApiResult save(@RequestBody ShopChatConversation shopChatConversation) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopChatConversation.setUserId(loginUser.getUserId()); + } + if (shopChatConversationService.save(shopChatConversation)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改聊天消息表") + @PutMapping() + public ApiResult update(@RequestBody ShopChatConversation shopChatConversation) { + if (shopChatConversationService.updateById(shopChatConversation)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除聊天消息表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopChatConversationService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加聊天消息表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopChatConversationService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改聊天消息表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopChatConversationService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除聊天消息表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopChatConversationService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopChatMessageController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopChatMessageController.java new file mode 100644 index 0000000..f68bae2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopChatMessageController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopChatMessageService; +import com.gxwebsoft.shop.entity.ShopChatMessage; +import com.gxwebsoft.shop.param.ShopChatMessageParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 聊天消息表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "聊天消息表管理") +@RestController +@RequestMapping("/api/shop/shop-chat-message") +public class ShopChatMessageController extends BaseController { + @Resource + private ShopChatMessageService shopChatMessageService; + + @Operation(summary = "分页查询聊天消息表") + @GetMapping("/page") + public ApiResult> page(ShopChatMessageParam param) { + // 使用关联查询 + return success(shopChatMessageService.pageRel(param)); + } + + @Operation(summary = "查询全部聊天消息表") + @GetMapping() + public ApiResult> list(ShopChatMessageParam param) { + // 使用关联查询 + return success(shopChatMessageService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopChatMessage:list')") + @Operation(summary = "根据id查询聊天消息表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopChatMessageService.getByIdRel(id)); + } + + @Operation(summary = "添加聊天消息表") + @PostMapping() + public ApiResult save(@RequestBody ShopChatMessage shopChatMessage) { + // 记录当前登录用户id +// User loginUser = getLoginUser(); +// if (loginUser != null) { +// shopChatMessage.setUserId(loginUser.getUserId()); +// } + if (shopChatMessageService.save(shopChatMessage)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改聊天消息表") + @PutMapping() + public ApiResult update(@RequestBody ShopChatMessage shopChatMessage) { + if (shopChatMessageService.updateById(shopChatMessage)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除聊天消息表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopChatMessageService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加聊天消息表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopChatMessageService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改聊天消息表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopChatMessageService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除聊天消息表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopChatMessageService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCommissionRoleController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCommissionRoleController.java new file mode 100644 index 0000000..f6aa5ad --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCommissionRoleController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopCommissionRoleService; +import com.gxwebsoft.shop.entity.ShopCommissionRole; +import com.gxwebsoft.shop.param.ShopCommissionRoleParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分红角色控制器 + * + * @author 科技小王子 + * @since 2025-05-01 10:01:15 + */ +@Tag(name = "分红角色管理") +@RestController +@RequestMapping("/api/shop/shop-commission-role") +public class ShopCommissionRoleController extends BaseController { + @Resource + private ShopCommissionRoleService shopCommissionRoleService; + + @Operation(summary = "分页查询分红角色") + @GetMapping("/page") + public ApiResult> page(ShopCommissionRoleParam param) { + // 使用关联查询 + return success(shopCommissionRoleService.pageRel(param)); + } + + @Operation(summary = "查询全部分红角色") + @GetMapping() + public ApiResult> list(ShopCommissionRoleParam param) { + // 使用关联查询 + return success(shopCommissionRoleService.listRel(param)); + } + + @Operation(summary = "根据id查询分红角色") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopCommissionRoleService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopCommissionRole:save')") + @OperationLog + @Operation(summary = "添加分红角色") + @PostMapping() + public ApiResult save(@RequestBody ShopCommissionRole shopCommissionRole) { + if (shopCommissionRoleService.save(shopCommissionRole)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCommissionRole:update')") + @OperationLog + @Operation(summary = "修改分红角色") + @PutMapping() + public ApiResult update(@RequestBody ShopCommissionRole shopCommissionRole) { + if (shopCommissionRoleService.updateById(shopCommissionRole)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCommissionRole:remove')") + @OperationLog + @Operation(summary = "删除分红角色") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCommissionRoleService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCommissionRole:save')") + @OperationLog + @Operation(summary = "批量添加分红角色") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCommissionRoleService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCommissionRole:update')") + @OperationLog + @Operation(summary = "批量修改分红角色") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCommissionRoleService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCommissionRole:remove')") + @OperationLog + @Operation(summary = "批量删除分红角色") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCommissionRoleService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCountController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCountController.java new file mode 100644 index 0000000..3664a24 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCountController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopCountService; +import com.gxwebsoft.shop.entity.ShopCount; +import com.gxwebsoft.shop.param.ShopCountParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商城销售统计表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商城销售统计表管理") +@RestController +@RequestMapping("/api/shop/shop-count") +public class ShopCountController extends BaseController { + @Resource + private ShopCountService shopCountService; + + @Operation(summary = "分页查询商城销售统计表") + @GetMapping("/page") + public ApiResult> page(ShopCountParam param) { + // 使用关联查询 + return success(shopCountService.pageRel(param)); + } + + @Operation(summary = "查询全部商城销售统计表") + @GetMapping() + public ApiResult> list(ShopCountParam param) { + // 使用关联查询 + return success(shopCountService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopCount:list')") + @Operation(summary = "根据id查询商城销售统计表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopCountService.getByIdRel(id)); + } + + @Operation(summary = "添加商城销售统计表") + @PostMapping() + public ApiResult save(@RequestBody ShopCount shopCount) { + if (shopCountService.save(shopCount)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改商城销售统计表") + @PutMapping() + public ApiResult update(@RequestBody ShopCount shopCount) { + if (shopCountService.updateById(shopCount)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商城销售统计表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCountService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商城销售统计表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCountService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商城销售统计表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCountService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商城销售统计表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCountService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyCateController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyCateController.java new file mode 100644 index 0000000..ada1a79 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyCateController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopCouponApplyCateService; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.param.ShopCouponApplyCateParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 优惠券可用分类控制器 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Tag(name = "优惠券可用分类管理") +@RestController +@RequestMapping("/api/shop/shop-coupon-apply-cate") +public class ShopCouponApplyCateController extends BaseController { + @Resource + private ShopCouponApplyCateService shopCouponApplyCateService; + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:list')") + @Operation(summary = "分页查询优惠券可用分类") + @GetMapping("/page") + public ApiResult> page(ShopCouponApplyCateParam param) { + // 使用关联查询 + return success(shopCouponApplyCateService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:list')") + @Operation(summary = "查询全部优惠券可用分类") + @GetMapping() + public ApiResult> list(ShopCouponApplyCateParam param) { + // 使用关联查询 + return success(shopCouponApplyCateService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:list')") + @Operation(summary = "根据id查询优惠券可用分类") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopCouponApplyCateService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:save')") + @OperationLog + @Operation(summary = "添加优惠券可用分类") + @PostMapping() + public ApiResult save(@RequestBody ShopCouponApplyCate shopCouponApplyCate) { + if (shopCouponApplyCateService.save(shopCouponApplyCate)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:update')") + @OperationLog + @Operation(summary = "修改优惠券可用分类") + @PutMapping() + public ApiResult update(@RequestBody ShopCouponApplyCate shopCouponApplyCate) { + if (shopCouponApplyCateService.updateById(shopCouponApplyCate)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:remove')") + @OperationLog + @Operation(summary = "删除优惠券可用分类") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCouponApplyCateService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:save')") + @OperationLog + @Operation(summary = "批量添加优惠券可用分类") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCouponApplyCateService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:update')") + @OperationLog + @Operation(summary = "批量修改优惠券可用分类") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCouponApplyCateService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyCate:remove')") + @OperationLog + @Operation(summary = "批量删除优惠券可用分类") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCouponApplyCateService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyItemController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyItemController.java new file mode 100644 index 0000000..02091c9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCouponApplyItemController.java @@ -0,0 +1,125 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopCouponApplyItemService; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.param.ShopCouponApplyItemParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 优惠券可用分类控制器 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Tag(name = "优惠券可用分类管理") +@RestController +@RequestMapping("/api/shop/shop-coupon-apply-item") +public class ShopCouponApplyItemController extends BaseController { + @Resource + private ShopCouponApplyItemService shopCouponApplyItemService; + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:list')") + @Operation(summary = "分页查询优惠券可用分类") + @GetMapping("/page") + public ApiResult> page(ShopCouponApplyItemParam param) { + // 使用关联查询 + return success(shopCouponApplyItemService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:list')") + @Operation(summary = "查询全部优惠券可用分类") + @GetMapping() + public ApiResult> list(ShopCouponApplyItemParam param) { + // 使用关联查询 + return success(shopCouponApplyItemService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:list')") + @Operation(summary = "根据id查询优惠券可用分类") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopCouponApplyItemService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:save')") + @OperationLog + @Operation(summary = "添加优惠券可用分类") + @PostMapping() + public ApiResult save(@RequestBody ShopCouponApplyItem shopCouponApplyItem) { + + if (shopCouponApplyItemService.save(shopCouponApplyItem)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:update')") + @OperationLog + @Operation(summary = "修改优惠券可用分类") + @PutMapping() + public ApiResult update(@RequestBody ShopCouponApplyItem shopCouponApplyItem) { + if (shopCouponApplyItemService.updateById(shopCouponApplyItem)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:remove')") + @OperationLog + @Operation(summary = "删除优惠券可用分类") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCouponApplyItemService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:save')") + @OperationLog + @Operation(summary = "批量添加优惠券可用分类") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCouponApplyItemService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:update')") + @OperationLog + @Operation(summary = "批量修改优惠券可用分类") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCouponApplyItemService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCouponApplyItem:remove')") + @OperationLog + @Operation(summary = "批量删除优惠券可用分类") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCouponApplyItemService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopCouponController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopCouponController.java new file mode 100644 index 0000000..1328aa9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopCouponController.java @@ -0,0 +1,217 @@ +package com.gxwebsoft.shop.controller; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.service.ShopCouponApplyCateService; +import com.gxwebsoft.shop.service.ShopCouponApplyItemService; +import com.gxwebsoft.shop.service.ShopCouponService; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.param.ShopCouponParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.service.ShopUserCouponService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * 优惠券控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:24 + */ +@Tag(name = "优惠券管理") +@RestController +@RequestMapping("/api/shop/shop-coupon") +public class ShopCouponController extends BaseController { + @Resource + private ShopCouponService shopCouponService; + @Resource + private ShopUserCouponService userCouponService; + @Resource + private ShopCouponService couponService; + @Resource + private ShopCouponApplyItemService couponApplyItemService; + @Resource + private ShopCouponApplyCateService couponApplyCateService; + + @Operation(summary = "可领取优惠券列表") + @PostMapping("/list") + public ApiResult> page() { + Integer uid = getLoginUserId(); + // 用户已经领取的优惠券 + List userCouponList = userCouponService.userList(uid); + List hasTakeCouponIdList = new ArrayList<>(); + for (ShopUserCoupon userCoupon : userCouponList) { + hasTakeCouponIdList.add(userCoupon.getCouponId()); + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); +// if (!hasTakeCouponIdList.isEmpty()) queryWrapper.notIn(Coupon::getCouponId, hasTakeCouponIdList); + queryWrapper.eq(ShopCoupon::getStatus, 0); + queryWrapper.apply("(expire_type = 0 OR (expire_type = 1 AND end_time > '" + + DateUtil.date() + "'))"); + queryWrapper.orderByAsc(ShopCoupon::getSortNumber); + List couponList = couponService.list(queryWrapper); + for (ShopCoupon coupon : couponList) { + coupon.setCouponApplyItemList(couponApplyItemService.list( + new LambdaQueryWrapper().eq(ShopCouponApplyItem::getCouponId, coupon.getId()) + )); + coupon.setCouponApplyCateList(couponApplyCateService.list( + new LambdaQueryWrapper().eq(ShopCouponApplyCate::getCouponId, coupon.getId()) + )); + boolean hasTake = hasTakeCouponIdList.contains(coupon.getId()); + coupon.setHasTake(hasTake); + if (hasTake) { + int userUseNum = 0; + List userCouponList1 = userCouponService.list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getCouponId, coupon.getId()) + ); + coupon.setUserTakeNum(userCouponList1.size()); + for (ShopUserCoupon userCoupon : userCouponList1) { + if (userCoupon.getIsUse().equals(1)) userUseNum++; + } + coupon.setUserUseNum(userUseNum); + } + } + return success(couponList); + } + + + @Operation(summary = "分页查询优惠券") + @GetMapping("/page") + public ApiResult> page(ShopCouponParam param) { + // 使用关联查询 + return success(shopCouponService.pageRel(param)); + } + + @Operation(summary = "查询全部优惠券") + @GetMapping() + public ApiResult> list(ShopCouponParam param) { + // 使用关联查询 + return success(shopCouponService.listRel(param)); + } + + @Operation(summary = "根据id查询优惠券") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopCouponService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopCoupon:save')") + @OperationLog + @Operation(summary = "添加优惠券") + @PostMapping() + public ApiResult save(@RequestBody ShopCoupon shopCoupon) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopCoupon.setUserId(loginUser.getUserId()); + } + if (shopCouponService.save(shopCoupon)) { + if (shopCoupon.getCouponApplyCateList() != null && !shopCoupon.getCouponApplyCateList().isEmpty()) { + for (ShopCouponApplyCate couponApplyCate : shopCoupon.getCouponApplyCateList()) { + couponApplyCate.setCouponId(shopCoupon.getId()); + } + couponApplyCateService.saveBatch(shopCoupon.getCouponApplyCateList()); + } + + if (shopCoupon.getCouponApplyItemList() != null && !shopCoupon.getCouponApplyItemList().isEmpty()) { + for (ShopCouponApplyItem couponApplyItem : shopCoupon.getCouponApplyItemList()) { + couponApplyItem.setCouponId(shopCoupon.getId()); + } + couponApplyItemService.saveBatch(shopCoupon.getCouponApplyItemList()); + } + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCoupon:update')") + @OperationLog + @Operation(summary = "修改优惠券") + @PutMapping() + public ApiResult update(@RequestBody ShopCoupon shopCoupon) { + if (shopCouponService.updateById(shopCoupon)) { + couponApplyCateService.removeByCouponId(shopCoupon.getId()); + if (shopCoupon.getCouponApplyCateList() != null && !shopCoupon.getCouponApplyCateList().isEmpty()) { + for (ShopCouponApplyCate couponApplyCate : shopCoupon.getCouponApplyCateList()) { + couponApplyCate.setCouponId(shopCoupon.getId()); + } + couponApplyCateService.saveBatch(shopCoupon.getCouponApplyCateList()); + } + + couponApplyItemService.removeByCouponId(shopCoupon.getId()); + if (shopCoupon.getCouponApplyItemList() != null && !shopCoupon.getCouponApplyItemList().isEmpty()) { + for (ShopCouponApplyItem couponApplyItem : shopCoupon.getCouponApplyItemList()) { + couponApplyItem.setCouponId(shopCoupon.getId()); + } + couponApplyItemService.saveBatch(shopCoupon.getCouponApplyItemList()); + } + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCoupon:remove')") + @OperationLog + @Operation(summary = "删除优惠券") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopCouponService.removeById(id)) { + couponApplyCateService.removeByCouponId(id); + couponApplyItemService.removeByCouponId(id); + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCoupon:save')") + @OperationLog + @Operation(summary = "批量添加优惠券") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopCouponService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCoupon:update')") + @OperationLog + @Operation(summary = "批量修改优惠券") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopCouponService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopCoupon:remove')") + @OperationLog + @Operation(summary = "批量删除优惠券") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopCouponService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerApplyController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerApplyController.java new file mode 100644 index 0000000..41cde3a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerApplyController.java @@ -0,0 +1,342 @@ +package com.gxwebsoft.shop.controller; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.service.ShopDealerApplyService; +import com.gxwebsoft.shop.entity.ShopDealerApply; +import com.gxwebsoft.shop.param.ShopDealerApplyParam; +import com.gxwebsoft.shop.param.ShopDealerApplyImportParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.service.ShopDealerUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * 分销商申请记录表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:50:19 + */ +@Tag(name = "分销商申请记录表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-apply") +public class ShopDealerApplyController extends BaseController { + @Resource + private ShopDealerApplyService shopDealerApplyService; + @Resource + private ShopDealerUserService shopDealerUserService; + + @PreAuthorize("hasAuthority('shop:shopDealerApply:list')") + @Operation(summary = "分页查询分销商申请记录表") + @GetMapping("/page") + public ApiResult> page(ShopDealerApplyParam param) { + // 使用关联查询 + return success(shopDealerApplyService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:list')") + @Operation(summary = "查询全部分销商申请记录表") + @GetMapping() + public ApiResult> list(ShopDealerApplyParam param) { + // 使用关联查询 + return success(shopDealerApplyService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:list')") + @Operation(summary = "根据id查询分销商申请记录表") + @GetMapping("/{id}") + public ApiResult getById(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerApplyService.getById(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:list')") + @Operation(summary = "根据userId查询分销商申请记录表") + @GetMapping("/getByUserId/{id}") + public ApiResult getByUserId(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerApplyService.getByUserIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:save')") + @OperationLog + @Operation(summary = "添加分销商申请记录表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerApply shopDealerApply) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopDealerApply.setApplyTime(LocalDateTime.now()); + shopDealerApply.setUserId(loginUser.getUserId()); + } + if (shopDealerApply.getRefereeId() != null) { + if(shopDealerUserService.getByIdRel(shopDealerApply.getRefereeId()) == null){ + return fail("推荐人不存在"); + } + } + + if (shopDealerApplyService.save(shopDealerApply)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:update')") + @OperationLog + @Operation(summary = "修改分销商申请记录表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerApply shopDealerApply) { + shopDealerApply.setAuditTime(null); + if (shopDealerApplyService.updateById(shopDealerApply)) { + if (shopDealerApply.getApplyStatus().equals(20)) { + LocalDateTime now = LocalDateTime.now(); + shopDealerApply.setAuditTime(now); + shopDealerApplyService.updateById(shopDealerApply); + // 同步添加经销商 + if (shopDealerUserService.count(new LambdaQueryWrapper().eq(ShopDealerUser::getUserId, shopDealerApply.getUserId())) == 0) { + final ShopDealerUser dealerUser = new ShopDealerUser(); + dealerUser.setUserId(shopDealerApply.getUserId()); + dealerUser.setRealName(shopDealerApply.getRealName()); + dealerUser.setMobile(shopDealerApply.getMobile()); + dealerUser.setRefereeId(shopDealerApply.getRefereeId()); + shopDealerUserService.save(dealerUser); + } + } + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:remove')") + @OperationLog + @Operation(summary = "删除分销商申请记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerApplyService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:save')") + @OperationLog + @Operation(summary = "批量添加分销商申请记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerApplyService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:update')") + @OperationLog + @Operation(summary = "批量修改分销商申请记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerApplyService, "apply_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerApply:remove')") + @OperationLog + @Operation(summary = "批量删除分销商申请记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerApplyService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + /** + * excel批量导入分销商申请记录 + * Excel表头格式:类型、用户ID、姓名、分销商名称、分销商编码、手机号、合同金额、详细地址、推荐人用户ID、申请方式、审核状态、合同时间、驳回原因、商城ID + */ + @PreAuthorize("hasAuthority('shop:shopDealerApply:save')") + @Transactional(rollbackFor = {Exception.class}) + @Operation(summary = "批量导入分销商申请记录") + @PostMapping("/import") + public ApiResult> importBatch(MultipartFile file) { + ImportParams importParams = new ImportParams(); + // 设置标题行,跳过第一行表头 + importParams.setTitleRows(1); + importParams.setHeadRows(1); + List errorMessages = new ArrayList<>(); + int successCount = 0; + + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), ShopDealerApplyImportParam.class, importParams); + + // 获取当前登录用户 + User loginUser = getLoginUser(); + Integer currentUserId = loginUser != null ? loginUser.getUserId() : null; + + for (int i = 0; i < list.size(); i++) { + ShopDealerApplyImportParam param = list.get(i); + try { + // 手动转换对象,避免JSON序列化问题 + ShopDealerApply item = convertImportParamToEntity(param); + + // 设置必填字段的默认值 + if (item.getUserId() == null && currentUserId != null) { + item.setUserId(currentUserId); + } + if (item.getApplyStatus() == null) { + item.setApplyStatus(10); // 默认状态为待审核 + } + if (item.getApplyType() == null) { + item.setApplyType(10); // 默认需要后台审核 + } + if (item.getType() == null) { + item.setType(0); // 默认类型为经销商 + } + + // 设置申请时间 + item.setApplyTime(LocalDateTime.now()); + + // 验证必填字段 + if (item.getRealName() == null || item.getRealName().trim().isEmpty()) { + errorMessages.add("第" + (i + 1) + "行:姓名不能为空"); + continue; + } + if (item.getDealerName() == null || item.getDealerName().trim().isEmpty()) { + errorMessages.add("第" + (i + 1) + "行:企业名称不能为空"); + continue; + } + if (item.getMobile() == null || item.getMobile().trim().isEmpty()) { + errorMessages.add("第" + (i + 1) + "行:手机号不能为空"); + continue; + } + + // 验证推荐人是否存在 + if (item.getRefereeId() != null) { + if (shopDealerUserService.getByIdRel(item.getRefereeId()) == null) { + errorMessages.add("第" + (i + 1) + "行:推荐人不存在"); + continue; + } + } + + // 保存数据 + if (shopDealerApplyService.save(item)) { + successCount++; + } else { + errorMessages.add("第" + (i + 1) + "行:保存失败"); + } + + } catch (Exception e) { + errorMessages.add("第" + (i + 1) + "行:" + e.getMessage()); + System.err.println("导入第" + (i + 1) + "行数据失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + // 返回结果 + if (errorMessages.isEmpty()) { + return success("成功导入" + successCount + "条数据", null); + } else { + return success("导入完成,成功" + successCount + "条,失败" + errorMessages.size() + "条", errorMessages); + } + + } catch (Exception e) { + System.err.println("批量导入分销商申请记录失败: " + e.getMessage()); + e.printStackTrace(); + return fail("导入失败:" + e.getMessage(), null); + } + } + + /** + * 下载分销商申请记录导入模板 + */ + @Operation(summary = "下载分销商申请记录导入模板") + @GetMapping("/import/template") + public void downloadTemplate(HttpServletResponse response) throws IOException { + // 创建空的导入参数列表作为模板 + List templateList = new ArrayList<>(); + + // 添加一行示例数据 + ShopDealerApplyImportParam example = new ShopDealerApplyImportParam(); + example.setType(0); + example.setRealName("宗馥莉"); + example.setDealerName("娃哈哈有限公司"); + example.setDealerCode("WaHaHa"); + example.setMobile("13800138000"); + example.setApplyType(10); + example.setApplyStatus(10); + example.setContractTime("2025-09-05 10:00:00"); + templateList.add(example); + + // 设置导出参数 + ExportParams exportParams = new ExportParams("分销商申请记录导入模板", "分销商申请记录"); + + // 生成Excel + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, ShopDealerApplyImportParam.class, templateList); + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=shop_dealer_apply_import_template.xlsx"); + + // 输出到响应流 + workbook.write(response.getOutputStream()); + workbook.close(); + } + + /** + * 将ShopDealerApplyImportParam转换为ShopDealerApply实体 + */ + private ShopDealerApply convertImportParamToEntity(ShopDealerApplyImportParam param) { + ShopDealerApply entity = new ShopDealerApply(); + + // 基本字段转换 + entity.setType(param.getType()); + entity.setUserId(param.getUserId()); + entity.setRealName(param.getRealName()); + entity.setDealerName(param.getDealerName()); + entity.setDealerCode(param.getDealerCode()); + entity.setMobile(param.getMobile()); + entity.setMoney(param.getMoney()); + entity.setAddress(param.getAddress()); + entity.setRefereeId(param.getRefereeId()); + entity.setApplyType(param.getApplyType()); + entity.setApplyStatus(param.getApplyStatus()); + entity.setRejectReason(param.getRejectReason()); + entity.setTenantId(param.getTenantId()); + + // 处理合同时间 + if (param.getContractTime() != null && !param.getContractTime().trim().isEmpty()) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + entity.setContractTime(LocalDateTime.parse(param.getContractTime(), formatter)); + } catch (Exception e) { + System.err.println("合同时间格式错误: " + param.getContractTime()); + } + } + + return entity; + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerCapitalController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerCapitalController.java new file mode 100644 index 0000000..d7ccaf1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerCapitalController.java @@ -0,0 +1,129 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopDealerCapitalService; +import com.gxwebsoft.shop.entity.ShopDealerCapital; +import com.gxwebsoft.shop.param.ShopDealerCapitalParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分销商资金明细表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Tag(name = "分销商资金明细表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-capital") +public class ShopDealerCapitalController extends BaseController { + @Resource + private ShopDealerCapitalService shopDealerCapitalService; + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:list')") + @Operation(summary = "分页查询分销商资金明细表") + @GetMapping("/page") + public ApiResult> page(ShopDealerCapitalParam param) { + // 使用关联查询 + return success(shopDealerCapitalService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:list')") + @Operation(summary = "查询全部分销商资金明细表") + @GetMapping() + public ApiResult> list(ShopDealerCapitalParam param) { + // 使用关联查询 + return success(shopDealerCapitalService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:list')") + @Operation(summary = "根据id查询分销商资金明细表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerCapitalService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:save')") + @OperationLog + @Operation(summary = "添加分销商资金明细表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerCapital shopDealerCapital) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopDealerCapital.setUserId(loginUser.getUserId()); + } + if (shopDealerCapitalService.save(shopDealerCapital)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:update')") + @OperationLog + @Operation(summary = "修改分销商资金明细表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerCapital shopDealerCapital) { + if (shopDealerCapitalService.updateById(shopDealerCapital)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:remove')") + @OperationLog + @Operation(summary = "删除分销商资金明细表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerCapitalService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:save')") + @OperationLog + @Operation(summary = "批量添加分销商资金明细表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerCapitalService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:update')") + @OperationLog + @Operation(summary = "批量修改分销商资金明细表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerCapitalService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerCapital:remove')") + @OperationLog + @Operation(summary = "批量删除分销商资金明细表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerCapitalService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerOrderController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerOrderController.java new file mode 100644 index 0000000..9356f7e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerOrderController.java @@ -0,0 +1,129 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopDealerOrderService; +import com.gxwebsoft.shop.entity.ShopDealerOrder; +import com.gxwebsoft.shop.param.ShopDealerOrderParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分销商订单记录表控制器 + * + * @author 科技小王子 + * @since 2025-08-12 11:55:18 + */ +@Tag(name = "分销商订单记录表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-order") +public class ShopDealerOrderController extends BaseController { + @Resource + private ShopDealerOrderService shopDealerOrderService; + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:list')") + @Operation(summary = "分页查询分销商订单记录表") + @GetMapping("/page") + public ApiResult> page(ShopDealerOrderParam param) { + // 使用关联查询 + return success(shopDealerOrderService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:list')") + @Operation(summary = "查询全部分销商订单记录表") + @GetMapping() + public ApiResult> list(ShopDealerOrderParam param) { + // 使用关联查询 + return success(shopDealerOrderService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:list')") + @Operation(summary = "根据id查询分销商订单记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerOrderService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:save')") + @OperationLog + @Operation(summary = "添加分销商订单记录表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerOrder shopDealerOrder) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopDealerOrder.setUserId(loginUser.getUserId()); + } + if (shopDealerOrderService.save(shopDealerOrder)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:update')") + @OperationLog + @Operation(summary = "修改分销商订单记录表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerOrder shopDealerOrder) { + if (shopDealerOrderService.updateById(shopDealerOrder)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:remove')") + @OperationLog + @Operation(summary = "删除分销商订单记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerOrderService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:save')") + @OperationLog + @Operation(summary = "批量添加分销商订单记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerOrderService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:update')") + @OperationLog + @Operation(summary = "批量修改分销商订单记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerOrderService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerOrder:remove')") + @OperationLog + @Operation(summary = "批量删除分销商订单记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerOrderService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerRefereeController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerRefereeController.java new file mode 100644 index 0000000..0546109 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerRefereeController.java @@ -0,0 +1,129 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopDealerRefereeService; +import com.gxwebsoft.shop.entity.ShopDealerReferee; +import com.gxwebsoft.shop.param.ShopDealerRefereeParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分销商推荐关系表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Tag(name = "分销商推荐关系表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-referee") +public class ShopDealerRefereeController extends BaseController { + @Resource + private ShopDealerRefereeService shopDealerRefereeService; + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:list')") + @Operation(summary = "分页查询分销商推荐关系表") + @GetMapping("/page") + public ApiResult> page(ShopDealerRefereeParam param) { + // 使用关联查询 + return success(shopDealerRefereeService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:list')") + @Operation(summary = "查询全部分销商推荐关系表") + @GetMapping() + public ApiResult> list(ShopDealerRefereeParam param) { + // 使用关联查询 + return success(shopDealerRefereeService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:list')") + @Operation(summary = "根据id查询分销商推荐关系表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerRefereeService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:save')") + @OperationLog + @Operation(summary = "添加分销商推荐关系表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerReferee shopDealerReferee) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopDealerReferee.setUserId(loginUser.getUserId()); + } + if (shopDealerRefereeService.save(shopDealerReferee)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:update')") + @OperationLog + @Operation(summary = "修改分销商推荐关系表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerReferee shopDealerReferee) { + if (shopDealerRefereeService.updateById(shopDealerReferee)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:remove')") + @OperationLog + @Operation(summary = "删除分销商推荐关系表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerRefereeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:save')") + @OperationLog + @Operation(summary = "批量添加分销商推荐关系表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerRefereeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:update')") + @OperationLog + @Operation(summary = "批量修改分销商推荐关系表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerRefereeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerReferee:remove')") + @OperationLog + @Operation(summary = "批量删除分销商推荐关系表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerRefereeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerSettingController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerSettingController.java new file mode 100644 index 0000000..039cf2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerSettingController.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopDealerSettingService; +import com.gxwebsoft.shop.entity.ShopDealerSetting; +import com.gxwebsoft.shop.param.ShopDealerSettingParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分销商设置表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Tag(name = "分销商设置表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-setting") +public class ShopDealerSettingController extends BaseController { + @Resource + private ShopDealerSettingService shopDealerSettingService; + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:list')") + @Operation(summary = "分页查询分销商设置表") + @GetMapping("/page") + public ApiResult> page(ShopDealerSettingParam param) { + // 使用关联查询 + return success(shopDealerSettingService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:list')") + @Operation(summary = "查询全部分销商设置表") + @GetMapping() + public ApiResult> list(ShopDealerSettingParam param) { + // 使用关联查询 + return success(shopDealerSettingService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:list')") + @Operation(summary = "根据id查询分销商设置表") + @GetMapping("/{key}") + public ApiResult get(@PathVariable("key") String key) { + // 使用关联查询 + return success(shopDealerSettingService.getByIdRel(key)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:save')") + @OperationLog + @Operation(summary = "添加分销商设置表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerSetting shopDealerSetting) { + if (shopDealerSettingService.save(shopDealerSetting)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:update')") + @OperationLog + @Operation(summary = "修改分销商设置表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerSetting shopDealerSetting) { + if (shopDealerSettingService.updateById(shopDealerSetting)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:remove')") + @OperationLog + @Operation(summary = "删除分销商设置表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerSettingService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:save')") + @OperationLog + @Operation(summary = "批量添加分销商设置表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerSettingService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:update')") + @OperationLog + @Operation(summary = "批量修改分销商设置表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerSettingService, "key")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerSetting:remove')") + @OperationLog + @Operation(summary = "批量删除分销商设置表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerSettingService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerUserController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerUserController.java new file mode 100644 index 0000000..16e06d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerUserController.java @@ -0,0 +1,171 @@ +package com.gxwebsoft.shop.controller; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.hutool.core.util.ObjectUtil; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopDealerUserService; +import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.param.ShopDealerUserParam; +import com.gxwebsoft.shop.param.ShopDealerUserImportParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分销商用户记录表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Tag(name = "分销商用户记录表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-user") +public class ShopDealerUserController extends BaseController { + @Resource + private ShopDealerUserService shopDealerUserService; + + @Operation(summary = "分页查询分销商用户记录表") + @GetMapping("/page") + public ApiResult> page(ShopDealerUserParam param) { + // 使用关联查询 + return success(shopDealerUserService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:list')") + @Operation(summary = "查询全部分销商用户记录表") + @GetMapping() + public ApiResult> list(ShopDealerUserParam param) { + // 使用关联查询 + return success(shopDealerUserService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:list')") + @Operation(summary = "根据userId查询分销商用户") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerUserService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:save')") + @OperationLog + @Operation(summary = "添加分销商用户记录表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerUser shopDealerUser) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopDealerUser.setUserId(loginUser.getUserId()); + } + if (shopDealerUserService.save(shopDealerUser)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:update')") + @OperationLog + @Operation(summary = "修改分销商用户记录表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerUser shopDealerUser) { + if (shopDealerUserService.updateById(shopDealerUser)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:remove')") + @OperationLog + @Operation(summary = "删除分销商用户记录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerUserService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:save')") + @OperationLog + @Operation(summary = "批量添加分销商用户记录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerUserService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:update')") + @OperationLog + @Operation(summary = "批量修改分销商用户记录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerUserService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:remove')") + @OperationLog + @Operation(summary = "批量删除分销商用户记录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerUserService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerUser:save')") + @Operation(summary = "批量导入分销商用户") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/import") + public ApiResult> importBatch(MultipartFile file) { + ImportParams importParams = new ImportParams(); + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), ShopDealerUserImportParam.class, importParams); + list.forEach(d -> { + ShopDealerUser item = JSONUtil.parseObject(JSONUtil.toJSONString(d), ShopDealerUser.class); + assert item != null; + if (ObjectUtil.isNotEmpty(item)) { + // 设置默认值 + if (item.getIsDelete() == null) { + item.setIsDelete(0); + } + if (item.getSortNumber() == null) { + item.setSortNumber(0); + } + // 记录当前登录用户id(如果没有指定userId) + if (item.getUserId() == null) { + User loginUser = getLoginUser(); + if (loginUser != null) { + item.setUserId(loginUser.getUserId()); + } + } + shopDealerUserService.save(item); + } + }); + return success("成功导入" + list.size() + "条", null); + } catch (Exception e) { + e.printStackTrace(); + } + return fail("导入失败", null); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopDealerWithdrawController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerWithdrawController.java new file mode 100644 index 0000000..08cbc23 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopDealerWithdrawController.java @@ -0,0 +1,129 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopDealerWithdrawService; +import com.gxwebsoft.shop.entity.ShopDealerWithdraw; +import com.gxwebsoft.shop.param.ShopDealerWithdrawParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分销商提现明细表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Tag(name = "分销商提现明细表管理") +@RestController +@RequestMapping("/api/shop/shop-dealer-withdraw") +public class ShopDealerWithdrawController extends BaseController { + @Resource + private ShopDealerWithdrawService shopDealerWithdrawService; + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:list')") + @Operation(summary = "分页查询分销商提现明细表") + @GetMapping("/page") + public ApiResult> page(ShopDealerWithdrawParam param) { + // 使用关联查询 + return success(shopDealerWithdrawService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:list')") + @Operation(summary = "查询全部分销商提现明细表") + @GetMapping() + public ApiResult> list(ShopDealerWithdrawParam param) { + // 使用关联查询 + return success(shopDealerWithdrawService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:list')") + @Operation(summary = "根据id查询分销商提现明细表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopDealerWithdrawService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:save')") + @OperationLog + @Operation(summary = "添加分销商提现明细表") + @PostMapping() + public ApiResult save(@RequestBody ShopDealerWithdraw shopDealerWithdraw) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopDealerWithdraw.setUserId(loginUser.getUserId()); + } + if (shopDealerWithdrawService.save(shopDealerWithdraw)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')") + @OperationLog + @Operation(summary = "修改分销商提现明细表") + @PutMapping() + public ApiResult update(@RequestBody ShopDealerWithdraw shopDealerWithdraw) { + if (shopDealerWithdrawService.updateById(shopDealerWithdraw)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:remove')") + @OperationLog + @Operation(summary = "删除分销商提现明细表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopDealerWithdrawService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:save')") + @OperationLog + @Operation(summary = "批量添加分销商提现明细表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopDealerWithdrawService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')") + @OperationLog + @Operation(summary = "批量修改分销商提现明细表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopDealerWithdrawService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:remove')") + @OperationLog + @Operation(summary = "批量删除分销商提现明细表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopDealerWithdrawService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopExpressController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopExpressController.java new file mode 100644 index 0000000..c638b0f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopExpressController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopExpressService; +import com.gxwebsoft.shop.entity.ShopExpress; +import com.gxwebsoft.shop.param.ShopExpressParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 物流公司控制器 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Tag(name = "物流公司管理") +@RestController +@RequestMapping("/api/shop/shop-express") +public class ShopExpressController extends BaseController { + @Resource + private ShopExpressService shopExpressService; + + @Operation(summary = "分页查询物流公司") + @GetMapping("/page") + public ApiResult> page(ShopExpressParam param) { + // 使用关联查询 + return success(shopExpressService.pageRel(param)); + } + + @Operation(summary = "查询全部物流公司") + @GetMapping() + public ApiResult> list(ShopExpressParam param) { + // 使用关联查询 + return success(shopExpressService.listRel(param)); + } + + @Operation(summary = "根据id查询物流公司") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopExpressService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopExpress:save')") + @OperationLog + @Operation(summary = "添加物流公司") + @PostMapping() + public ApiResult save(@RequestBody ShopExpress shopExpress) { + if (shopExpressService.save(shopExpress)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpress:update')") + @OperationLog + @Operation(summary = "修改物流公司") + @PutMapping() + public ApiResult update(@RequestBody ShopExpress shopExpress) { + if (shopExpressService.updateById(shopExpress)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpress:remove')") + @OperationLog + @Operation(summary = "删除物流公司") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopExpressService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpress:save')") + @OperationLog + @Operation(summary = "批量添加物流公司") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopExpressService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpress:update')") + @OperationLog + @Operation(summary = "批量修改物流公司") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopExpressService, "express_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpress:remove')") + @OperationLog + @Operation(summary = "批量删除物流公司") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopExpressService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateController.java new file mode 100644 index 0000000..bbc7ac2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopExpressTemplateService; +import com.gxwebsoft.shop.entity.ShopExpressTemplate; +import com.gxwebsoft.shop.param.ShopExpressTemplateParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 运费模板控制器 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Tag(name = "运费模板管理") +@RestController +@RequestMapping("/api/shop/shop-express-template") +public class ShopExpressTemplateController extends BaseController { + @Resource + private ShopExpressTemplateService shopExpressTemplateService; + + @Operation(summary = "分页查询运费模板") + @GetMapping("/page") + public ApiResult> page(ShopExpressTemplateParam param) { + // 使用关联查询 + return success(shopExpressTemplateService.pageRel(param)); + } + + @Operation(summary = "查询全部运费模板") + @GetMapping() + public ApiResult> list(ShopExpressTemplateParam param) { + // 使用关联查询 + return success(shopExpressTemplateService.listRel(param)); + } + + @Operation(summary = "根据id查询运费模板") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopExpressTemplateService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplate:save')") + @OperationLog + @Operation(summary = "添加运费模板") + @PostMapping() + public ApiResult save(@RequestBody ShopExpressTemplate shopExpressTemplate) { + if (shopExpressTemplateService.save(shopExpressTemplate)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplate:update')") + @OperationLog + @Operation(summary = "修改运费模板") + @PutMapping() + public ApiResult update(@RequestBody ShopExpressTemplate shopExpressTemplate) { + if (shopExpressTemplateService.updateById(shopExpressTemplate)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplate:remove')") + @OperationLog + @Operation(summary = "删除运费模板") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopExpressTemplateService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplate:save')") + @OperationLog + @Operation(summary = "批量添加运费模板") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopExpressTemplateService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplate:update')") + @OperationLog + @Operation(summary = "批量修改运费模板") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopExpressTemplateService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplate:remove')") + @OperationLog + @Operation(summary = "批量删除运费模板") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopExpressTemplateService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateDetailController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateDetailController.java new file mode 100644 index 0000000..6c1553d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopExpressTemplateDetailController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopExpressTemplateDetailService; +import com.gxwebsoft.shop.entity.ShopExpressTemplateDetail; +import com.gxwebsoft.shop.param.ShopExpressTemplateDetailParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 运费模板控制器 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Tag(name = "运费模板管理") +@RestController +@RequestMapping("/api/shop/shop-express-template-detail") +public class ShopExpressTemplateDetailController extends BaseController { + @Resource + private ShopExpressTemplateDetailService shopExpressTemplateDetailService; + + @Operation(summary = "分页查询运费模板") + @GetMapping("/page") + public ApiResult> page(ShopExpressTemplateDetailParam param) { + // 使用关联查询 + return success(shopExpressTemplateDetailService.pageRel(param)); + } + + @Operation(summary = "查询全部运费模板") + @GetMapping() + public ApiResult> list(ShopExpressTemplateDetailParam param) { + // 使用关联查询 + return success(shopExpressTemplateDetailService.listRel(param)); + } + + @Operation(summary = "根据id查询运费模板") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopExpressTemplateDetailService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplateDetail:save')") + @OperationLog + @Operation(summary = "添加运费模板") + @PostMapping() + public ApiResult save(@RequestBody ShopExpressTemplateDetail shopExpressTemplateDetail) { + if (shopExpressTemplateDetailService.save(shopExpressTemplateDetail)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplateDetail:update')") + @OperationLog + @Operation(summary = "修改运费模板") + @PutMapping() + public ApiResult update(@RequestBody ShopExpressTemplateDetail shopExpressTemplateDetail) { + if (shopExpressTemplateDetailService.updateById(shopExpressTemplateDetail)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplateDetail:remove')") + @OperationLog + @Operation(summary = "删除运费模板") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopExpressTemplateDetailService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplateDetail:save')") + @OperationLog + @Operation(summary = "批量添加运费模板") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopExpressTemplateDetailService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplateDetail:update')") + @OperationLog + @Operation(summary = "批量修改运费模板") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopExpressTemplateDetailService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopExpressTemplateDetail:remove')") + @OperationLog + @Operation(summary = "批量删除运费模板") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopExpressTemplateDetailService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGiftController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGiftController.java new file mode 100644 index 0000000..bd90024 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGiftController.java @@ -0,0 +1,261 @@ +package com.gxwebsoft.shop.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.RandomUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.shop.entity.ShopGoods; +import com.gxwebsoft.shop.service.ShopGiftService; +import com.gxwebsoft.shop.entity.ShopGift; +import com.gxwebsoft.shop.param.ShopGiftParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.service.ShopGoodsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 礼品卡控制器 + * + * @author 科技小王子 + * @since 2025-08-11 18:07:32 + */ +@Tag(name = "礼品卡管理") +@RestController +@RequestMapping("/api/shop/shop-gift") +public class ShopGiftController extends BaseController { + @Resource + private ShopGiftService shopGiftService; + @Value("${config.upload-path}") + private String uploadPath; + @Value("${config.api-url}") + private String apiUrl; + @Resource + private ShopGoodsService shopGoodsService; + + @Operation(summary = "根据code查询礼品卡") + @GetMapping("/by-code/{code}") + public ApiResult get(@PathVariable("code") String code) { + // 使用关联查询 + return success(shopGiftService.getByCode(code)); + } + + @Operation(summary = "礼品卡核销") + @PostMapping("/set-take") + public ApiResult setTake(@RequestBody ShopGift shopGift) { + if (getLoginUser() == null) return fail("请登录"); + if (shopGift.getCode() == null) { + return fail("非法请求"); + } + ShopGift shopGift1 = shopGiftService.getByCode(shopGift.getCode()); + if (shopGift1 == null) return fail("礼品卡不存在"); + if (shopGift1.getTakeTime() != null) { + return fail("礼品卡已使用"); + } + shopGift1.setTakeTime(LocalDateTime.now()); + shopGift1.setOperatorUserId(getLoginUserId()); + shopGiftService.updateById(shopGift1); + return success(); + } + + @PreAuthorize("hasAuthority('shop:shopGift:list')") + @Operation(summary = "分页查询礼品卡") + @GetMapping("/page") + public ApiResult> page(ShopGiftParam param) { + // 使用关联查询 + return success(shopGiftService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGift:list')") + @Operation(summary = "查询全部礼品卡") + @GetMapping() + public ApiResult> list(ShopGiftParam param) { + // 使用关联查询 + return success(shopGiftService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGift:list')") + @Operation(summary = "根据id查询礼品卡") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGiftService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopGift:save')") + @OperationLog + @Operation(summary = "添加礼品卡") + @PostMapping() + public ApiResult save(@RequestBody ShopGift shopGift) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGift.setUserId(loginUser.getUserId()); + } + if (shopGiftService.save(shopGift)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:update')") + @OperationLog + @Operation(summary = "修改礼品卡") + @PutMapping() + public ApiResult update(@RequestBody ShopGift shopGift) { + if (shopGiftService.updateById(shopGift)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:save')") + @OperationLog + @Operation(summary = "批量生成礼品卡") + @PostMapping("/make") + public ApiResult make(@RequestBody ShopGift shopGiftData) { + if (shopGiftData.getNum() == null || shopGiftData.getNum() <= 0) { + return fail("请输入正确的数量"); + } + if (shopGiftData.getGoodsId() == null || shopGiftData.getGoodsId() <= 0) { + return fail("请选择商品"); + } + List giftList = new ArrayList<>(); + for (int i = 0; i < shopGiftData.getNum(); i++) { + ShopGift shopGift = new ShopGift(); + shopGift.setName(shopGiftData.getName()); + shopGift.setCode(RandomUtil.randomString(8)); + shopGift.setGoodsId(shopGiftData.getGoodsId()); + shopGift.setUseLocation(shopGiftData.getUseLocation()); + shopGift.setComments(shopGiftData.getComments()); + giftList.add(shopGift); + } + if (shopGiftService.saveBatch(giftList)) { + return success("生成成功"); + } + return fail("生成失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:remove')") + @OperationLog + @Operation(summary = "删除礼品卡") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGiftService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:save')") + @OperationLog + @Operation(summary = "批量添加礼品卡") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGiftService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:update')") + @OperationLog + @Operation(summary = "批量修改礼品卡") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGiftService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:remove')") + @OperationLog + @Operation(summary = "批量删除礼品卡") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGiftService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGift:list')") + @Operation(summary = "导出礼品卡") + @PostMapping("/export") + public ApiResult export(@RequestBody(required = false) List ids) throws IOException { + String filename = "file/excel/礼品卡.xlsx"; + if (!FileUtil.exist(uploadPath + "file/excel")) { + FileUtil.mkdir(uploadPath + "file/excel"); + } + List list; + if (ids != null && !ids.isEmpty()) { + list = shopGiftService.listByIds(ids); + } else { + list = shopGiftService.list(); + } + if (!list.isEmpty()) { + Set goodsIds = list.stream().map(ShopGift::getGoodsId).collect(Collectors.toSet()); + List goodsList = shopGoodsService.listByIds(goodsIds); + for (ShopGift shopGift : list) { + ShopGoods shopGoods = goodsList.stream().filter(sG -> sG.getGoodsId().equals(shopGift.getGoodsId())).findFirst().orElse(null); + if (shopGoods != null) { + shopGift.setGoods(shopGoods); + } + } + } + String path = uploadPath + filename; + SXSSFWorkbook workbook = new SXSSFWorkbook(); + //创建工作表单 + SXSSFSheet sheet = workbook.createSheet(); + String[] headers = {"名称", "秘钥", "领取时间", "商品"}; + + SXSSFRow row0 = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + row0.createCell(i).setCellValue(headers[i]); + } + if (!list.isEmpty()) { + for (ShopGift shopGift : list) { + SXSSFRow row = sheet.createRow(sheet.getLastRowNum() + 1); + row.createCell(0).setCellValue(shopGift.getName()); + row.createCell(1).setCellValue(shopGift.getCode()); + row.createCell(2).setCellValue(shopGift.getTakeTime()); + row.createCell(3).setCellValue(shopGift.getGoods() != null ? shopGift.getGoods().getName() : ""); + } + } + FileOutputStream output = new FileOutputStream(path); + workbook.write(output); + output.flush(); + + FileRecord result = new FileRecord(); + result.setCreateUserId(getLoginUserId()); + result.setName("礼品卡"); + result.setPath(filename); + result.setUrl(apiUrl + "/" + filename); + return success(result); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCategoryController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCategoryController.java new file mode 100644 index 0000000..49077a7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCategoryController.java @@ -0,0 +1,128 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsCategoryService; +import com.gxwebsoft.shop.entity.ShopGoodsCategory; +import com.gxwebsoft.shop.param.ShopGoodsCategoryParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品分类控制器 + * + * @author 科技小王子 + * @since 2025-05-01 00:36:45 + */ +@Tag(name = "商品分类管理") +@RestController +@RequestMapping("/api/shop/shop-goods-category") +public class ShopGoodsCategoryController extends BaseController { + @Resource + private ShopGoodsCategoryService shopGoodsCategoryService; + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:list')") + @Operation(summary = "分页查询商品分类") + @GetMapping("/page") + public ApiResult> page(ShopGoodsCategoryParam param) { + // 使用关联查询 + return success(shopGoodsCategoryService.pageRel(param)); + } + + @Operation(summary = "查询全部商品分类") + @GetMapping() + public ApiResult> list(ShopGoodsCategoryParam param) { + // 使用关联查询 + return success(shopGoodsCategoryService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:list')") + @Operation(summary = "根据id查询商品分类") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsCategoryService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:save')") + @OperationLog + @Operation(summary = "添加商品分类") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsCategory shopGoodsCategory) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGoodsCategory.setUserId(loginUser.getUserId()); + } + if (shopGoodsCategoryService.save(shopGoodsCategory)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:update')") + @OperationLog + @Operation(summary = "修改商品分类") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsCategory shopGoodsCategory) { + if (shopGoodsCategoryService.updateById(shopGoodsCategory)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:remove')") + @OperationLog + @Operation(summary = "删除商品分类") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsCategoryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:save')") + @OperationLog + @Operation(summary = "批量添加商品分类") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsCategoryService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:update')") + @OperationLog + @Operation(summary = "批量修改商品分类") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsCategoryService, "category_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsCategory:remove')") + @OperationLog + @Operation(summary = "批量删除商品分类") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsCategoryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCommentController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCommentController.java new file mode 100644 index 0000000..7f370af --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsCommentController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsCommentService; +import com.gxwebsoft.shop.entity.ShopGoodsComment; +import com.gxwebsoft.shop.param.ShopGoodsCommentParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 评论表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "评论表管理") +@RestController +@RequestMapping("/api/shop/shop-goods-comment") +public class ShopGoodsCommentController extends BaseController { + @Resource + private ShopGoodsCommentService shopGoodsCommentService; + + @Operation(summary = "分页查询评论表") + @GetMapping("/page") + public ApiResult> page(ShopGoodsCommentParam param) { + // 使用关联查询 + return success(shopGoodsCommentService.pageRel(param)); + } + + @Operation(summary = "查询全部评论表") + @GetMapping() + public ApiResult> list(ShopGoodsCommentParam param) { + // 使用关联查询 + return success(shopGoodsCommentService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsComment:list')") + @Operation(summary = "根据id查询评论表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsCommentService.getByIdRel(id)); + } + + @Operation(summary = "添加评论表") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsComment shopGoodsComment) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGoodsComment.setUserId(loginUser.getUserId()); + } + if (shopGoodsCommentService.save(shopGoodsComment)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改评论表") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsComment shopGoodsComment) { + if (shopGoodsCommentService.updateById(shopGoodsComment)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除评论表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsCommentService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加评论表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsCommentService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改评论表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsCommentService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除评论表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsCommentService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java new file mode 100644 index 0000000..4074e63 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsController.java @@ -0,0 +1,163 @@ +package com.gxwebsoft.shop.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsService; +import com.gxwebsoft.shop.entity.ShopGoods; +import com.gxwebsoft.shop.param.ShopGoodsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 商品控制器 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Tag(name = "商品管理") +@RestController +@RequestMapping("/api/shop/shop-goods") +public class ShopGoodsController extends BaseController { + @Resource + private ShopGoodsService shopGoodsService; + + @Operation(summary = "分页查询商品") + @GetMapping("/page") + public ApiResult> page(ShopGoodsParam param) { + // 使用关联查询 + return success(shopGoodsService.pageRel(param)); + } + + @Operation(summary = "查询全部商品") + @GetMapping() + public ApiResult> list(ShopGoodsParam param) { + // 使用关联查询 + return success(shopGoodsService.listRel(param)); + } + + @Operation(summary = "根据id查询商品") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopGoods:save')") + @OperationLog + @Operation(summary = "添加商品") + @PostMapping() + public ApiResult save(@RequestBody ShopGoods shopGoods) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGoods.setUserId(loginUser.getUserId()); + } + if (shopGoodsService.save(shopGoods)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoods:update')") + @OperationLog + @Operation(summary = "修改商品") + @PutMapping() + public ApiResult update(@RequestBody ShopGoods shopGoods) { + if (shopGoodsService.updateById(shopGoods)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoods:remove')") + @OperationLog + @Operation(summary = "删除商品") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoods:save')") + @OperationLog + @Operation(summary = "批量添加商品") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoods:update')") + @OperationLog + @Operation(summary = "批量修改商品") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsService, "goods_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoods:remove')") + @OperationLog + @Operation(summary = "批量删除商品") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "统计信息") + @GetMapping("/data") + public ApiResult> data(ShopGoodsParam param) { + Map data = new HashMap<>(); + final LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (param.getMerchantId() != null) { + wrapper.eq(ShopGoods::getMerchantId,param.getMerchantId()); + } + + long totalNum = shopGoodsService.count( + wrapper.eq(ShopGoods::getStatus,0).gt(ShopGoods::getStock,0) + ); + data.put("totalNum", Math.toIntExact(totalNum)); + wrapper.clear(); + + long totalNum2 = shopGoodsService.count( + wrapper.gt(ShopGoods::getStatus,0) + ); + data.put("totalNum2", Math.toIntExact(totalNum2)); + wrapper.clear(); + + long totalNum3 = shopGoodsService.count( + wrapper.eq(ShopGoods::getStock,0) + ); + data.put("totalNum3", Math.toIntExact(totalNum3)); + wrapper.clear(); + + // 下架已售罄的商品 + shopGoodsService.update(new LambdaUpdateWrapper().eq(ShopGoods::getStock,0).set(ShopGoods::getStatus,1)); + + return success(data); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsIncomeConfigController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsIncomeConfigController.java new file mode 100644 index 0000000..1e91159 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsIncomeConfigController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsIncomeConfigService; +import com.gxwebsoft.shop.entity.ShopGoodsIncomeConfig; +import com.gxwebsoft.shop.param.ShopGoodsIncomeConfigParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 分润配置控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "分润配置管理") +@RestController +@RequestMapping("/api/shop/shop-goods-income-config") +public class ShopGoodsIncomeConfigController extends BaseController { + @Resource + private ShopGoodsIncomeConfigService shopGoodsIncomeConfigService; + + @Operation(summary = "分页查询分润配置") + @GetMapping("/page") + public ApiResult> page(ShopGoodsIncomeConfigParam param) { + // 使用关联查询 + return success(shopGoodsIncomeConfigService.pageRel(param)); + } + + @Operation(summary = "查询全部分润配置") + @GetMapping() + public ApiResult> list(ShopGoodsIncomeConfigParam param) { + // 使用关联查询 + return success(shopGoodsIncomeConfigService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsIncomeConfig:list')") + @Operation(summary = "根据id查询分润配置") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsIncomeConfigService.getByIdRel(id)); + } + + @Operation(summary = "添加分润配置") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsIncomeConfig shopGoodsIncomeConfig) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGoodsIncomeConfig.setUserId(loginUser.getUserId()); + } + if (shopGoodsIncomeConfigService.save(shopGoodsIncomeConfig)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改分润配置") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsIncomeConfig shopGoodsIncomeConfig) { + if (shopGoodsIncomeConfigService.updateById(shopGoodsIncomeConfig)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除分润配置") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsIncomeConfigService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加分润配置") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsIncomeConfigService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改分润配置") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsIncomeConfigService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除分润配置") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsIncomeConfigService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsLogController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsLogController.java new file mode 100644 index 0000000..2a46786 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsLogController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsLogService; +import com.gxwebsoft.shop.entity.ShopGoodsLog; +import com.gxwebsoft.shop.param.ShopGoodsLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品日志表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商品日志表管理") +@RestController +@RequestMapping("/api/shop/shop-goods-log") +public class ShopGoodsLogController extends BaseController { + @Resource + private ShopGoodsLogService shopGoodsLogService; + + @Operation(summary = "分页查询商品日志表") + @GetMapping("/page") + public ApiResult> page(ShopGoodsLogParam param) { + // 使用关联查询 + return success(shopGoodsLogService.pageRel(param)); + } + + @Operation(summary = "查询全部商品日志表") + @GetMapping() + public ApiResult> list(ShopGoodsLogParam param) { + // 使用关联查询 + return success(shopGoodsLogService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsLog:list')") + @Operation(summary = "根据id查询商品日志表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsLogService.getByIdRel(id)); + } + + @Operation(summary = "添加商品日志表") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsLog shopGoodsLog) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGoodsLog.setUserId(loginUser.getUserId()); + } + if (shopGoodsLogService.save(shopGoodsLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改商品日志表") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsLog shopGoodsLog) { + if (shopGoodsLogService.updateById(shopGoodsLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商品日志表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商品日志表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商品日志表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商品日志表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRelationController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRelationController.java new file mode 100644 index 0000000..5e0d57c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRelationController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsRelationService; +import com.gxwebsoft.shop.entity.ShopGoodsRelation; +import com.gxwebsoft.shop.param.ShopGoodsRelationParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品点赞和收藏表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商品点赞和收藏表管理") +@RestController +@RequestMapping("/api/shop/shop-goods-relation") +public class ShopGoodsRelationController extends BaseController { + @Resource + private ShopGoodsRelationService shopGoodsRelationService; + + @Operation(summary = "分页查询商品点赞和收藏表") + @GetMapping("/page") + public ApiResult> page(ShopGoodsRelationParam param) { + // 使用关联查询 + return success(shopGoodsRelationService.pageRel(param)); + } + + @Operation(summary = "查询全部商品点赞和收藏表") + @GetMapping() + public ApiResult> list(ShopGoodsRelationParam param) { + // 使用关联查询 + return success(shopGoodsRelationService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRelation:list')") + @Operation(summary = "根据id查询商品点赞和收藏表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsRelationService.getByIdRel(id)); + } + + @Operation(summary = "添加商品点赞和收藏表") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsRelation shopGoodsRelation) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopGoodsRelation.setUserId(loginUser.getUserId()); + } + if (shopGoodsRelationService.save(shopGoodsRelation)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改商品点赞和收藏表") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsRelation shopGoodsRelation) { + if (shopGoodsRelationService.updateById(shopGoodsRelation)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商品点赞和收藏表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsRelationService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商品点赞和收藏表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsRelationService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商品点赞和收藏表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsRelationService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商品点赞和收藏表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsRelationService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRoleCommissionController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRoleCommissionController.java new file mode 100644 index 0000000..21508e2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsRoleCommissionController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsRoleCommissionService; +import com.gxwebsoft.shop.entity.ShopGoodsRoleCommission; +import com.gxwebsoft.shop.param.ShopGoodsRoleCommissionParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品绑定角色的分润金额控制器 + * + * @author 科技小王子 + * @since 2025-05-01 09:53:38 + */ +@Tag(name = "商品绑定角色的分润金额管理") +@RestController +@RequestMapping("/api/shop/shop-goods-role-commission") +public class ShopGoodsRoleCommissionController extends BaseController { + @Resource + private ShopGoodsRoleCommissionService shopGoodsRoleCommissionService; + + @Operation(summary = "分页查询商品绑定角色的分润金额") + @GetMapping("/page") + public ApiResult> page(ShopGoodsRoleCommissionParam param) { + // 使用关联查询 + return success(shopGoodsRoleCommissionService.pageRel(param)); + } + + @Operation(summary = "查询全部商品绑定角色的分润金额") + @GetMapping() + public ApiResult> list(ShopGoodsRoleCommissionParam param) { + // 使用关联查询 + return success(shopGoodsRoleCommissionService.listRel(param)); + } + + @Operation(summary = "根据id查询商品绑定角色的分润金额") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsRoleCommissionService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRoleCommission:save')") + @OperationLog + @Operation(summary = "添加商品绑定角色的分润金额") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsRoleCommission shopGoodsRoleCommission) { + if (shopGoodsRoleCommissionService.save(shopGoodsRoleCommission)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRoleCommission:update')") + @OperationLog + @Operation(summary = "修改商品绑定角色的分润金额") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsRoleCommission shopGoodsRoleCommission) { + if (shopGoodsRoleCommissionService.updateById(shopGoodsRoleCommission)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRoleCommission:remove')") + @OperationLog + @Operation(summary = "删除商品绑定角色的分润金额") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsRoleCommissionService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRoleCommission:save')") + @OperationLog + @Operation(summary = "批量添加商品绑定角色的分润金额") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsRoleCommissionService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRoleCommission:update')") + @OperationLog + @Operation(summary = "批量修改商品绑定角色的分润金额") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsRoleCommissionService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsRoleCommission:remove')") + @OperationLog + @Operation(summary = "批量删除商品绑定角色的分润金额") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsRoleCommissionService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSkuController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSkuController.java new file mode 100644 index 0000000..2884bdc --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSkuController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsSkuService; +import com.gxwebsoft.shop.entity.ShopGoodsSku; +import com.gxwebsoft.shop.param.ShopGoodsSkuParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品sku列表控制器 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Tag(name = "商品sku列表管理") +@RestController +@RequestMapping("/api/shop/shop-goods-sku") +public class ShopGoodsSkuController extends BaseController { + @Resource + private ShopGoodsSkuService shopGoodsSkuService; + + @Operation(summary = "分页查询商品sku列表") + @GetMapping("/page") + public ApiResult> page(ShopGoodsSkuParam param) { + // 使用关联查询 + return success(shopGoodsSkuService.pageRel(param)); + } + + @Operation(summary = "查询全部商品sku列表") + @GetMapping() + public ApiResult> list(ShopGoodsSkuParam param) { + // 使用关联查询 + return success(shopGoodsSkuService.listRel(param)); + } + + @Operation(summary = "根据id查询商品sku列表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsSkuService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSku:save')") + @OperationLog + @Operation(summary = "添加商品sku列表") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsSku shopGoodsSku) { + if (shopGoodsSkuService.save(shopGoodsSku)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSku:update')") + @OperationLog + @Operation(summary = "修改商品sku列表") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsSku shopGoodsSku) { + if (shopGoodsSkuService.updateById(shopGoodsSku)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSku:remove')") + @OperationLog + @Operation(summary = "删除商品sku列表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsSkuService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSku:save')") + @OperationLog + @Operation(summary = "批量添加商品sku列表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsSkuService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSku:update')") + @OperationLog + @Operation(summary = "批量修改商品sku列表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsSkuService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSku:remove')") + @OperationLog + @Operation(summary = "批量删除商品sku列表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsSkuService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSpecController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSpecController.java new file mode 100644 index 0000000..398430e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopGoodsSpecController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopGoodsSpecService; +import com.gxwebsoft.shop.entity.ShopGoodsSpec; +import com.gxwebsoft.shop.param.ShopGoodsSpecParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品多规格控制器 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Tag(name = "商品多规格管理") +@RestController +@RequestMapping("/api/shop/shop-goods-spec") +public class ShopGoodsSpecController extends BaseController { + @Resource + private ShopGoodsSpecService shopGoodsSpecService; + + @Operation(summary = "分页查询商品多规格") + @GetMapping("/page") + public ApiResult> page(ShopGoodsSpecParam param) { + // 使用关联查询 + return success(shopGoodsSpecService.pageRel(param)); + } + + @Operation(summary = "查询全部商品多规格") + @GetMapping() + public ApiResult> list(ShopGoodsSpecParam param) { + // 使用关联查询 + return success(shopGoodsSpecService.listRel(param)); + } + + @Operation(summary = "根据id查询商品多规格") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopGoodsSpecService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSpec:save')") + @OperationLog + @Operation(summary = "添加商品多规格") + @PostMapping() + public ApiResult save(@RequestBody ShopGoodsSpec shopGoodsSpec) { + if (shopGoodsSpecService.save(shopGoodsSpec)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSpec:update')") + @OperationLog + @Operation(summary = "修改商品多规格") + @PutMapping() + public ApiResult update(@RequestBody ShopGoodsSpec shopGoodsSpec) { + if (shopGoodsSpecService.updateById(shopGoodsSpec)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSpec:remove')") + @OperationLog + @Operation(summary = "删除商品多规格") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopGoodsSpecService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSpec:save')") + @OperationLog + @Operation(summary = "批量添加商品多规格") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopGoodsSpecService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSpec:update')") + @OperationLog + @Operation(summary = "批量修改商品多规格") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopGoodsSpecService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopGoodsSpec:remove')") + @OperationLog + @Operation(summary = "批量删除商品多规格") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopGoodsSpecService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopMainController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopMainController.java new file mode 100644 index 0000000..5b96b22 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopMainController.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.shop.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.gxwebsoft.cms.entity.CmsWebsite; +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.shop.service.ShopWebsiteService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.vo.ShopVo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 商城主入口 + * + * @author 科技小王子 + * @since 2024-09-10 20:36:14 + */ +@Slf4j +@Tag(name = "商城") +@RestController +@RequestMapping("/api/shop") +public class ShopMainController extends BaseController { + @Resource + private ShopWebsiteService shopWebsiteService; + + @Operation(summary = "商城基本信息", description = "获取商城的基本信息,包括配置、导航、设置和过期状态等") + @GetMapping("/getShopInfo") + public ApiResult getShopInfo() { + Integer tenantId = getTenantId(); + + if (ObjectUtil.isEmpty(tenantId)) { + return fail("租户ID不能为空", null); + } + + try { + // 使用专门的商城信息获取方法 + ShopVo shopVo = shopWebsiteService.getShopInfo(tenantId); +// log.debug("获取商城信息成功: {}", shopVo); + return success(shopVo); + } catch (IllegalArgumentException e) { + return fail(e.getMessage(), null); + } catch (RuntimeException e) { + return fail(e.getMessage(), null); + } catch (Exception e) { + log.error("获取商城信息失败", e); + return fail("获取商城信息失败", null); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantAccountController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantAccountController.java new file mode 100644 index 0000000..3e5ca19 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantAccountController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopMerchantAccountService; +import com.gxwebsoft.shop.entity.ShopMerchantAccount; +import com.gxwebsoft.shop.param.ShopMerchantAccountParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商户账号控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商户账号管理") +@RestController +@RequestMapping("/api/shop/shop-merchant-account") +public class ShopMerchantAccountController extends BaseController { + @Resource + private ShopMerchantAccountService shopMerchantAccountService; + + @Operation(summary = "分页查询商户账号") + @GetMapping("/page") + public ApiResult> page(ShopMerchantAccountParam param) { + // 使用关联查询 + return success(shopMerchantAccountService.pageRel(param)); + } + + @Operation(summary = "查询全部商户账号") + @GetMapping() + public ApiResult> list(ShopMerchantAccountParam param) { + // 使用关联查询 + return success(shopMerchantAccountService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopMerchantAccount:list')") + @Operation(summary = "根据id查询商户账号") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopMerchantAccountService.getByIdRel(id)); + } + + @Operation(summary = "添加商户账号") + @PostMapping() + public ApiResult save(@RequestBody ShopMerchantAccount shopMerchantAccount) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopMerchantAccount.setUserId(loginUser.getUserId()); + } + if (shopMerchantAccountService.save(shopMerchantAccount)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改商户账号") + @PutMapping() + public ApiResult update(@RequestBody ShopMerchantAccount shopMerchantAccount) { + if (shopMerchantAccountService.updateById(shopMerchantAccount)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商户账号") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopMerchantAccountService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商户账号") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopMerchantAccountService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商户账号") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopMerchantAccountService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商户账号") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopMerchantAccountService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantApplyController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantApplyController.java new file mode 100644 index 0000000..3b5d1cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantApplyController.java @@ -0,0 +1,192 @@ +package com.gxwebsoft.shop.controller; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopMerchant; +import com.gxwebsoft.shop.entity.ShopMerchantAccount; +import com.gxwebsoft.shop.service.ShopMerchantApplyService; +import com.gxwebsoft.shop.entity.ShopMerchantApply; +import com.gxwebsoft.shop.param.ShopMerchantApplyParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.service.ShopMerchantService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.beans.BeanUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商户入驻申请控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商户入驻申请管理") +@RestController +@RequestMapping("/api/shop/shop-merchant-apply") +public class ShopMerchantApplyController extends BaseController { + @Resource + private ShopMerchantApplyService shopMerchantApplyService; + @Resource + private ShopMerchantService shopMerchantService; + + @Operation(summary = "分页查询商户入驻申请") + @GetMapping("/page") + public ApiResult> page(ShopMerchantApplyParam param) { + // 使用关联查询 + return success(shopMerchantApplyService.pageRel(param)); + } + + @Operation(summary = "查询全部商户入驻申请") + @GetMapping() + public ApiResult> list(ShopMerchantApplyParam param) { + // 使用关联查询 + return success(shopMerchantApplyService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopMerchantApply:list')") + @Operation(summary = "根据id查询商户入驻申请") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopMerchantApplyService.getByIdRel(id)); + } + + @Operation(summary = "添加商户入驻申请") + @PostMapping() + public ApiResult save(@RequestBody ShopMerchantApply shopMerchantApply) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopMerchantApply.setUserId(loginUser.getUserId()); + if(shopMerchantApplyService.count(new LambdaQueryWrapper().eq(ShopMerchantApply::getPhone,shopMerchantApply.getPhone())) > 0){ + return fail("该手机号码已存在"); + } + // 个人开发者认证材料:使用姓名+身份证号码 + if (shopMerchantApply.getType().equals(0)) { + shopMerchantApply.setMerchantName(shopMerchantApply.getRealName()); + shopMerchantApply.setMerchantCode(shopMerchantApply.getIdCard()); + } + shopMerchantApply.setCheckStatus(true); + shopMerchantApply.setTenantId(loginUser.getTenantId()); + if (shopMerchantApplyService.save(shopMerchantApply)) { + return success("您的申请已提交,请耐心等待工作人员的审核,非常感谢"); + } + } + return fail("提交失败"); + } + + @Operation(summary = "修改商户入驻申请") + @PutMapping() + public ApiResult update(@RequestBody ShopMerchantApply shopMerchantApply) { + if (shopMerchantApplyService.updateById(shopMerchantApply)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商户入驻申请") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopMerchantApplyService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商户入驻申请") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopMerchantApplyService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商户入驻申请") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopMerchantApplyService, "apply_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商户入驻申请") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopMerchantApplyService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "我的入驻信息") + @GetMapping("/getByUserId") + public ApiResult getByUserId() { + final User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录", null); + } + final ShopMerchantApply shopMerchantApply = shopMerchantApplyService.getOne(new LambdaQueryWrapper().eq(ShopMerchantApply::getUserId, getLoginUser().getUserId()).last("limit 1")); + return success(shopMerchantApply); + } + + @PreAuthorize("hasAuthority('shop:shopMerchantApply:update')") + @Operation(summary = "入驻审核") + @PutMapping("/check") + public ApiResult check(@RequestBody ShopMerchantApply shopMerchantApply) { + // 审核中? + shopMerchantApply.setCheckStatus(true); + // TODO 审核通过则创建商户 + if (shopMerchantApply.getStatus().equals(1)) { + final ShopMerchant one = shopMerchantService.getOne(new LambdaQueryWrapper().eq(ShopMerchant::getPhone, shopMerchantApply.getPhone()).last("limit 1")); + final ShopMerchantAccount merchantAccount = new ShopMerchantAccount(); + BeanUtils.copyProperties(shopMerchantApply, merchantAccount); + + final User user = new User(); + + if (ObjectUtil.isNotEmpty(one)) { + BeanUtils.copyProperties(shopMerchantApply, one); + one.setStatus(0); + shopMerchantService.updateById(one); + user.setRealName(shopMerchantApply.getRealName()); + } else { + final ShopMerchant merchant = new ShopMerchant(); + BeanUtils.copyProperties(shopMerchantApply, merchant); + merchant.setStatus(0); + shopMerchantService.save(merchant); + user.setRealName(shopMerchantApply.getRealName()); + } + + // TODO 创建商户账号 + // TODO 更新用户表的商户信息 + user.setUserId(shopMerchantApply.getUserId()); + shopMerchantApplyService.updateById(shopMerchantApply); + // TODO 入驻开发者中心(添加会员) + return success("操作成功"); + } + // TODO 驳回 + if (shopMerchantApply.getStatus().equals(2)) { + shopMerchantApply.setCheckStatus(false); + shopMerchantApplyService.updateById(shopMerchantApply); + return success("操作成功"); + } + // 审核状态 + shopMerchantApply.setStatus(0); + if (shopMerchantApplyService.updateById(shopMerchantApply)) { + return success("您的申请已提交,请耐心等待工作人员的审核,非常感谢"); + } + return fail("操作失败"); + } +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantController.java new file mode 100644 index 0000000..30eecec --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantController.java @@ -0,0 +1,128 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopMerchantService; +import com.gxwebsoft.shop.entity.ShopMerchant; +import com.gxwebsoft.shop.param.ShopMerchantParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商户控制器 + * + * @author 科技小王子 + * @since 2025-08-10 20:43:33 + */ +@Tag(name = "商户管理") +@RestController +@RequestMapping("/api/shop/shop-merchant") +public class ShopMerchantController extends BaseController { + @Resource + private ShopMerchantService shopMerchantService; + + @PreAuthorize("hasAuthority('shop:shopMerchant:list')") + @Operation(summary = "分页查询商户") + @GetMapping("/page") + public ApiResult> page(ShopMerchantParam param) { + // 使用关联查询 + return success(shopMerchantService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:list')") + @Operation(summary = "查询全部商户") + @GetMapping() + public ApiResult> list(ShopMerchantParam param) { + // 使用关联查询 + return success(shopMerchantService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:list')") + @Operation(summary = "根据id查询商户") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Long id) { + // 使用关联查询 + return success(shopMerchantService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:save')") + @OperationLog + @Operation(summary = "添加商户") + @PostMapping() + public ApiResult save(@RequestBody ShopMerchant shopMerchant) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopMerchant.setUserId(loginUser.getUserId()); + } + if (shopMerchantService.save(shopMerchant)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:update')") + @OperationLog + @Operation(summary = "修改商户") + @PutMapping() + public ApiResult update(@RequestBody ShopMerchant shopMerchant) { + if (shopMerchantService.updateById(shopMerchant)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:remove')") + @OperationLog + @Operation(summary = "删除商户") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopMerchantService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:save')") + @OperationLog + @Operation(summary = "批量添加商户") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopMerchantService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:update')") + @OperationLog + @Operation(summary = "批量修改商户") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopMerchantService, "merchant_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopMerchant:remove')") + @OperationLog + @Operation(summary = "批量删除商户") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopMerchantService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantTypeController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantTypeController.java new file mode 100644 index 0000000..4bdac19 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopMerchantTypeController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopMerchantTypeService; +import com.gxwebsoft.shop.entity.ShopMerchantType; +import com.gxwebsoft.shop.param.ShopMerchantTypeParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商户类型控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商户类型管理") +@RestController +@RequestMapping("/api/shop/shop-merchant-type") +public class ShopMerchantTypeController extends BaseController { + @Resource + private ShopMerchantTypeService shopMerchantTypeService; + + @Operation(summary = "分页查询商户类型") + @GetMapping("/page") + public ApiResult> page(ShopMerchantTypeParam param) { + // 使用关联查询 + return success(shopMerchantTypeService.pageRel(param)); + } + + @Operation(summary = "查询全部商户类型") + @GetMapping() + public ApiResult> list(ShopMerchantTypeParam param) { + // 使用关联查询 + return success(shopMerchantTypeService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopMerchantType:list')") + @Operation(summary = "根据id查询商户类型") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopMerchantTypeService.getByIdRel(id)); + } + + @Operation(summary = "添加商户类型") + @PostMapping() + public ApiResult save(@RequestBody ShopMerchantType shopMerchantType) { + if (shopMerchantTypeService.save(shopMerchantType)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改商户类型") + @PutMapping() + public ApiResult update(@RequestBody ShopMerchantType shopMerchantType) { + if (shopMerchantTypeService.updateById(shopMerchantType)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商户类型") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopMerchantTypeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商户类型") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopMerchantTypeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商户类型") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopMerchantTypeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商户类型") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopMerchantTypeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java new file mode 100644 index 0000000..3d425ce --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java @@ -0,0 +1,558 @@ +package com.gxwebsoft.shop.controller; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.core.utils.CertificateLoader; +import com.gxwebsoft.common.core.utils.WechatCertAutoConfig; +import com.gxwebsoft.common.core.utils.WechatPayConfigValidator; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.shop.service.ShopOrderGoodsService; +import com.gxwebsoft.shop.service.ShopOrderService; +import com.gxwebsoft.shop.service.OrderBusinessService; +import com.gxwebsoft.shop.service.OrderCancelService; +import com.gxwebsoft.shop.task.OrderAutoCancelTask; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.param.ShopOrderParam; +import com.gxwebsoft.shop.dto.OrderCreateRequest; +import com.gxwebsoft.shop.dto.UpdatePaymentStatusRequest; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; +import com.wechat.pay.java.core.notification.NotificationConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.Operation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 订单控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "订单管理") +@RestController +@RequestMapping("/api/shop/shop-order") +public class ShopOrderController extends BaseController { + private static final Logger logger = LoggerFactory.getLogger(ShopOrderController.class); + @Resource + private ShopOrderService shopOrderService; + @Resource + private ShopOrderGoodsService shopOrderGoodsService; + @Resource + private OrderBusinessService orderBusinessService; + @Resource + private OrderCancelService orderCancelService; + @Resource + private OrderAutoCancelTask orderAutoCancelTask; + @Resource + private RedisUtil redisUtil; + @Resource + private ConfigProperties conf; + @Resource + private CertificateProperties certConfig; + @Resource + private CertificateLoader certificateLoader; + @Resource + private WechatCertAutoConfig wechatCertAutoConfig; + @Resource + private WechatPayConfigValidator wechatPayConfigValidator; + @Value("${spring.profiles.active}") + String active; + + @Operation(summary = "分页查询订单") + @GetMapping("/page") + public ApiResult> page(ShopOrderParam param) { + // 使用关联查询 + return success(shopOrderService.pageRel(param)); + } + + @Operation(summary = "查询全部订单") + @GetMapping() + public ApiResult> list(ShopOrderParam param) { + // 使用关联查询 + return success(shopOrderService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrder:list')") + @Operation(summary = "根据id查询订单") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderService.getByIdRel(id)); + } + + @Operation(summary = "添加订单") + @PostMapping() + public ApiResult save(@RequestBody OrderCreateRequest request) { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("用户未登录"); + } + + try { + Map wxOrderInfo = orderBusinessService.createOrder(request, loginUser); + return success("下单成功", wxOrderInfo); + } catch (Exception e) { + logger.error("创建订单失败 - 用户ID:{},请求:{}", loginUser.getUserId(), request, e); + return fail(e.getMessage()); + } + } + + @Operation(summary = "添加订单(兼容旧版本)") + @PostMapping("/legacy") + public ApiResult saveLegacy(@RequestBody ShopOrder shopOrder) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopOrder.setUserId(loginUser.getUserId()); + shopOrder.setOpenid(loginUser.getOpenid()); + shopOrder.setPayUserId(loginUser.getUserId()); + if (shopOrder.getOrderNo() == null) { + shopOrder.setOrderNo(Long.toString(IdUtil.getSnowflakeNextId())); + } + if (shopOrder.getComments() == null) { + shopOrder.setComments("暂无"); + } + // 微信支付(商品金额不能为0) + if (shopOrder.getTotalPrice().compareTo(BigDecimal.ZERO) == 0) { + return fail("商品金额不能为0"); + } + // 百色中学项目捐赠金额不能低于20元 + if (shopOrder.getTenantId().equals(10324) && shopOrder.getTotalPrice().compareTo(new BigDecimal("10")) < 0) { + return fail("捐款金额最低不能少于10元,感谢您的爱心捐赠^_^"); + } + // 测试支付 + if (loginUser.getPhone().equals("13737128880")) { + shopOrder.setPrice(new BigDecimal("0.01")); + shopOrder.setTotalPrice(new BigDecimal("0.01")); + } + if (shopOrderService.save(shopOrder)) { + return success("下单成功", shopOrderService.createWxOrder(shopOrder)); + } + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopOrder:update')") + @Operation(summary = "修改订单") + @PutMapping() + public ApiResult update(@RequestBody ShopOrder shopOrder) { + // 申请退款 + if(shopOrder.getOrderStatus().equals(4)){ + shopOrder.setRefundApplyTime(LocalDateTime.now()); + } + if (shopOrderService.updateById(shopOrder)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除订单") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加订单") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改订单") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderService, "order_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除订单") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "修复订单") + @PutMapping("/repair") + public ApiResult repair(@RequestBody ShopOrder shopOrder) { + final ShopOrder order = shopOrderService.getByOutTradeNo(shopOrder.getOrderNo()); + if(order != null){ + shopOrderService.queryOrderByOutTradeNo(order); + return success("修复成功"); + } + return fail("修复失败"); + } + + @Operation(summary = "统计订单总金额") + @GetMapping("/total") + public ApiResult total() { + return success(shopOrderService.total()); + } + + @Operation(summary = "取消订单") + @PutMapping("/cancel/{id}") + public ApiResult cancelOrder(@PathVariable("id") Integer id) { + try { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("用户未登录"); + } + + ShopOrder order = shopOrderService.getById(id); + if (order == null) { + return fail("订单不存在"); + } + + // 检查订单是否属于当前用户(非管理员用户) + if (!loginUser.getUserId().equals(order.getUserId()) && + !hasOrderCancelAuthority()) { + return fail("无权限取消此订单"); + } + + // 检查订单状态 + if (order.getPayStatus() != null && order.getPayStatus()) { + return fail("订单已支付,无法取消"); + } + + if (order.getOrderStatus() != null && order.getOrderStatus() != 0) { + return fail("订单状态不允许取消"); + } + + boolean success = orderCancelService.cancelOrder(order); + if (success) { + return success("订单取消成功"); + } else { + return fail("订单取消失败"); + } + + } catch (Exception e) { + logger.error("取消订单失败,订单ID:{}", id, e); + return fail("取消订单失败:" + e.getMessage()); + } + } + + @PreAuthorize("hasAuthority('shop:shopOrder:manage')") + @Operation(summary = "手动触发订单自动取消任务(管理员)") + @PostMapping("/auto-cancel/trigger") + public ApiResult triggerAutoCancelTask() { + try { + orderAutoCancelTask.manualCancelExpiredOrders(); + return success("自动取消任务已触发"); + } catch (Exception e) { + logger.error("触发自动取消任务失败", e); + return fail("触发失败:" + e.getMessage()); + } + } + + @PreAuthorize("hasAuthority('shop:shopOrder:manage')") + @Operation(summary = "获取自动取消任务状态(管理员)") + @GetMapping("/auto-cancel/status") + public ApiResult getAutoCancelTaskStatus() { + try { + String status = orderAutoCancelTask.getTaskStatus(); + return success(status); + } catch (Exception e) { + logger.error("获取自动取消任务状态失败", e); + return fail("获取状态失败:" + e.getMessage()); + } + } + + @Schema(description = "异步通知11") + @PostMapping("/notify/{tenantId}") + public String wxNotify(@RequestHeader Map header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) { + logger.info("异步通知*************** = " + tenantId); + + // 获取支付配置信息用于解密 + String key = "Payment:1:".concat(tenantId.toString()); + Payment payment = redisUtil.get(key, Payment.class); + + // 检查支付配置 + if (ObjectUtil.isEmpty(payment)) { + throw new RuntimeException("未找到租户支付配置信息,租户ID: " + tenantId); + } + + logger.info("开始处理微信支付异步通知 - 租户ID: {}", tenantId); + logger.info("支付配置信息 - 商户号: {}, 应用ID: {}", payment.getMchId(), payment.getAppId()); + + // 验证微信支付配置 + WechatPayConfigValidator.ValidationResult validation = wechatPayConfigValidator.validateWechatPayConfig(payment, tenantId); + if (!validation.isValid()) { + logger.error("❌ 微信支付配置验证失败: {}", validation.getErrors()); + logger.info("📋 配置诊断报告:\n{}", wechatPayConfigValidator.generateDiagnosticReport(payment, tenantId)); + throw new RuntimeException("微信支付配置验证失败: " + validation.getErrors()); + } + logger.info("✅ 微信支付配置验证通过"); + + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(header.get("wechatpay-serial")) + .nonce(header.get("wechatpay-nonce")) + .signature(header.get("wechatpay-signature")) + .timestamp(header.get("wechatpay-timestamp")) + .body(body) + .build(); + + // 创建通知配置 - 使用与下单方法相同的证书配置逻辑 + NotificationConfig config; + try { + if (active.equals("dev")) { + // 开发环境 - 构建包含租户号的私钥路径 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + + logger.info("开发环境异步通知证书路径: {}", privateKeyPath); + logger.info("租户ID: {}, 证书目录: {}", tenantId, tenantCertPath); + + // 检查证书文件是否存在 + if (!certificateLoader.certificateExists(privateKeyPath)) { + logger.error("证书文件不存在: {}", privateKeyPath); + throw new RuntimeException("证书文件不存在: " + privateKeyPath); + } + + String privateKey = certificateLoader.loadCertificatePath(privateKeyPath); + + // 使用验证器获取有效的 APIv3 密钥 + String apiV3Key = wechatPayConfigValidator.getValidApiV3Key(payment); + + logger.info("私钥文件加载成功: {}", privateKey); + logger.info("使用APIv3密钥来源: {}", payment.getApiKey() != null && !payment.getApiKey().trim().isEmpty() ? "数据库配置" : "配置文件默认"); + logger.info("APIv3密钥长度: {}", apiV3Key != null ? apiV3Key.length() : 0); + logger.info("商户证书序列号: {}", payment.getMerchantSerialNumber()); + + // 使用自动证书配置 + config = new RSAAutoCertificateConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(apiV3Key) + .build(); + + logger.info("✅ 开发环境使用自动证书配置创建通知解析器成功"); + } else { + // 生产环境 - 使用自动证书配置 + final String certRootPath = certConfig.getCertRootPath(); + logger.info("生产环境证书根路径: {}", certRootPath); + + String privateKeyRelativePath = payment.getApiclientKey(); + logger.info("数据库中的私钥相对路径: {}", privateKeyRelativePath); + + // 修复路径拼接逻辑:数据库中存储的路径如果已经包含 /file,则直接拼接 + String privateKeyFullPath; + if (privateKeyRelativePath.startsWith("/file/")) { + // 路径已经包含 /file/ 前缀,直接拼接到根路径 + privateKeyFullPath = certRootPath + privateKeyRelativePath; + } else if (privateKeyRelativePath.startsWith("file/")) { + // 路径包含 file/ 前缀,添加根路径和斜杠 + privateKeyFullPath = certRootPath + "/" + privateKeyRelativePath; + } else { + // 路径不包含 file 前缀,添加完整的 /file/ 前缀 + privateKeyFullPath = certRootPath + "/file/" + privateKeyRelativePath; + } + + logger.info("生产环境私钥完整路径: {}", privateKeyFullPath); + String privateKey = certificateLoader.loadCertificatePath(privateKeyFullPath); + String apiV3Key = payment.getApiKey(); + + // 使用自动证书配置 + config = new RSAAutoCertificateConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(apiV3Key) + .build(); + + logger.info("✅ 生产环境使用自动证书配置创建通知解析器成功"); + } + } catch (Exception e) { + logger.error("❌ 创建通知配置失败 - 租户ID: {}, 商户号: {}", tenantId, payment.getMchId(), e); + logger.error("🔍 错误详情: {}", e.getMessage()); + logger.error("💡 请检查:"); + logger.error("1. 证书文件是否存在且路径正确"); + logger.error("2. APIv3密钥是否配置正确"); + logger.error("3. 商户证书序列号是否正确"); + logger.error("4. 网络连接是否正常"); + throw new RuntimeException("微信支付通知配置失败: " + e.getMessage(), e); + } + + // 初始化 NotificationParser + NotificationParser parser = new NotificationParser(config); + logger.info("✅ 通知解析器创建成功,准备解析异步通知"); + + // 以支付通知回调为例,验签、解密并转换成 Transaction + try { + logger.info("开始解析微信支付异步通知..."); + Transaction transaction = parser.parse(requestParam, Transaction.class); + logger.info("✅ 异步通知解析成功 - 交易状态: {}, 商户订单号: {}", + transaction.getTradeStateDesc(), transaction.getOutTradeNo()); + + if (StrUtil.equals("支付成功", transaction.getTradeStateDesc())) { + final String outTradeNo = transaction.getOutTradeNo(); + final String transactionId = transaction.getTransactionId(); + final Integer total = transaction.getAmount().getTotal(); + final String tradeStateDesc = transaction.getTradeStateDesc(); + final Transaction.TradeStateEnum tradeState = transaction.getTradeState(); + final Transaction.TradeTypeEnum tradeType = transaction.getTradeType(); + System.out.println("transaction = " + transaction); + System.out.println("tradeStateDesc = " + tradeStateDesc); + System.out.println("tradeType = " + tradeType); + System.out.println("tradeState = " + tradeState); + System.out.println("outTradeNo = " + outTradeNo); + System.out.println("amount = " + total); + // 1. 查询要处理的订单 + ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo); + logger.info("查询要处理的订单order = " + order); + // 2. 已支付则跳过 + if (order.getPayStatus().equals(true)) { + return "SUCCESS"; + } + // 2. 未支付则处理更新订单状态 + if (order.getPayStatus().equals(false)) { + // 5. TODO 处理订单状态 + order.setPayTime(LocalDateTime.now()); + order.setExpirationTime(order.getCreateTime()); + order.setPayStatus(true); + order.setTransactionId(transactionId); + order.setPayPrice(new BigDecimal(NumberUtil.decimalFormat("0.00", total * 0.01))); + order.setExpirationTime(LocalDateTime.now().plusYears(10)); + System.out.println("实际付款金额 = " + order.getPayPrice()); + // 更新订单状态并处理支付成功后的业务逻辑(包括累加商品销量) + shopOrderService.updateByOutTradeNo(order); + return "SUCCESS"; + } + } + } catch (Exception e) { + logger.error("❌ 处理微信支付异步通知失败 - 租户ID: {}, 商户号: {}", tenantId, payment.getMchId(), e); + logger.error("🔍 异常详情: {}", e.getMessage()); + logger.error("💡 可能的原因:"); + logger.error("1. 证书配置错误或证书文件损坏"); + logger.error("2. 微信支付平台证书已过期"); + logger.error("3. 签名验证失败"); + logger.error("4. 请求参数格式错误"); + + // 返回失败,微信会重试 + return "fail"; + } + + logger.warn("⚠️ 异步通知处理完成但未找到匹配的支付成功状态"); + return "fail"; + } + + @Operation(summary = "更新订单支付状态", description = "用户支付成功后主动同步订单状态") + @PutMapping("/payment-status") + public ApiResult updateOrderPaymentStatus(@RequestBody UpdatePaymentStatusRequest request) { + logger.info("收到更新订单支付状态请求: orderNo={}, paymentStatus={}, transactionId={}", + request.getOrderNo(), request.getPaymentStatus(), request.getTransactionId()); + + final User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录"); + } + + try { + // 参数验证 + if (StrUtil.isBlank(request.getOrderNo())) { + return fail("订单号不能为空"); + } + + // 查询订单 + ShopOrder order = shopOrderService.getByOrderNo(request.getOrderNo(), loginUser.getTenantId()); + if (order == null) { + return fail("订单不存在"); + } + + // 权限验证:只能更新自己的订单 + if (!order.getUserId().equals(loginUser.getUserId())) { + return fail("无权限操作此订单"); + } + + // 如果订单已经是支付成功状态,直接返回成功 + if (order.getPayStatus()) { + logger.info("订单已经是支付成功状态,无需更新: orderNo={}", request.getOrderNo()); + return success("订单状态已是最新"); + } + + // 调用支付状态同步服务 + boolean updated = shopOrderService.syncPaymentStatus( + request.getOrderNo(), + request.getPaymentStatus(), + request.getTransactionId(), + request.getPayTime(), + loginUser.getTenantId() + ); + + if (updated) { + logger.info("订单支付状态更新成功: orderNo={}, paymentStatus={}", + request.getOrderNo(), request.getPaymentStatus()); + return success("订单状态更新成功"); + } else { + logger.warn("订单支付状态更新失败: orderNo={}", request.getOrderNo()); + return fail("订单状态更新失败"); + } + + } catch (Exception e) { + logger.error("更新订单支付状态异常: orderNo={}, error={}", request.getOrderNo(), e.getMessage(), e); + return fail("更新订单状态失败: " + e.getMessage()); + } + } + + /** + * 检查是否有订单取消权限 + */ + private boolean hasOrderCancelAuthority() { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return false; + } + + // 检查是否有管理员权限 + return authentication.getAuthorities().stream() + .anyMatch(authority -> + authority.getAuthority().equals("shop:shopOrder:cancel") || + authority.getAuthority().equals("shop:shopOrder:update") || + authority.getAuthority().equals("ROLE_ADMIN") || + authority.getAuthority().equals("shop:shopOrder:manage")); + } catch (Exception e) { + logger.warn("检查订单取消权限时发生异常", e); + return false; + } + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryController.java new file mode 100644 index 0000000..7aa8e78 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopOrderDeliveryService; +import com.gxwebsoft.shop.entity.ShopOrderDelivery; +import com.gxwebsoft.shop.param.ShopOrderDeliveryParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 发货单控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "发货单管理") +@RestController +@RequestMapping("/api/shop/shop-order-delivery") +public class ShopOrderDeliveryController extends BaseController { + @Resource + private ShopOrderDeliveryService shopOrderDeliveryService; + + @Operation(summary = "分页查询发货单") + @GetMapping("/page") + public ApiResult> page(ShopOrderDeliveryParam param) { + // 使用关联查询 + return success(shopOrderDeliveryService.pageRel(param)); + } + + @Operation(summary = "查询全部发货单") + @GetMapping() + public ApiResult> list(ShopOrderDeliveryParam param) { + // 使用关联查询 + return success(shopOrderDeliveryService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrderDelivery:list')") + @Operation(summary = "根据id查询发货单") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderDeliveryService.getByIdRel(id)); + } + + @Operation(summary = "添加发货单") + @PostMapping() + public ApiResult save(@RequestBody ShopOrderDelivery shopOrderDelivery) { + if (shopOrderDeliveryService.save(shopOrderDelivery)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改发货单") + @PutMapping() + public ApiResult update(@RequestBody ShopOrderDelivery shopOrderDelivery) { + if (shopOrderDeliveryService.updateById(shopOrderDelivery)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除发货单") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderDeliveryService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加发货单") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderDeliveryService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改发货单") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderDeliveryService, "delivery_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除发货单") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderDeliveryService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryGoodsController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryGoodsController.java new file mode 100644 index 0000000..760becb --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderDeliveryGoodsController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopOrderDeliveryGoodsService; +import com.gxwebsoft.shop.entity.ShopOrderDeliveryGoods; +import com.gxwebsoft.shop.param.ShopOrderDeliveryGoodsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 发货单商品控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "发货单商品管理") +@RestController +@RequestMapping("/api/shop/shop-order-delivery-goods") +public class ShopOrderDeliveryGoodsController extends BaseController { + @Resource + private ShopOrderDeliveryGoodsService shopOrderDeliveryGoodsService; + + @Operation(summary = "分页查询发货单商品") + @GetMapping("/page") + public ApiResult> page(ShopOrderDeliveryGoodsParam param) { + // 使用关联查询 + return success(shopOrderDeliveryGoodsService.pageRel(param)); + } + + @Operation(summary = "查询全部发货单商品") + @GetMapping() + public ApiResult> list(ShopOrderDeliveryGoodsParam param) { + // 使用关联查询 + return success(shopOrderDeliveryGoodsService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrderDeliveryGoods:list')") + @Operation(summary = "根据id查询发货单商品") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderDeliveryGoodsService.getByIdRel(id)); + } + + @Operation(summary = "添加发货单商品") + @PostMapping() + public ApiResult save(@RequestBody ShopOrderDeliveryGoods shopOrderDeliveryGoods) { + if (shopOrderDeliveryGoodsService.save(shopOrderDeliveryGoods)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改发货单商品") + @PutMapping() + public ApiResult update(@RequestBody ShopOrderDeliveryGoods shopOrderDeliveryGoods) { + if (shopOrderDeliveryGoodsService.updateById(shopOrderDeliveryGoods)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除发货单商品") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderDeliveryGoodsService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加发货单商品") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderDeliveryGoodsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改发货单商品") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderDeliveryGoodsService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除发货单商品") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderDeliveryGoodsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderExtractController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderExtractController.java new file mode 100644 index 0000000..5b89db0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderExtractController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopOrderExtractService; +import com.gxwebsoft.shop.entity.ShopOrderExtract; +import com.gxwebsoft.shop.param.ShopOrderExtractParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 自提订单联系方式控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "自提订单联系方式管理") +@RestController +@RequestMapping("/api/shop/shop-order-extract") +public class ShopOrderExtractController extends BaseController { + @Resource + private ShopOrderExtractService shopOrderExtractService; + + @Operation(summary = "分页查询自提订单联系方式") + @GetMapping("/page") + public ApiResult> page(ShopOrderExtractParam param) { + // 使用关联查询 + return success(shopOrderExtractService.pageRel(param)); + } + + @Operation(summary = "查询全部自提订单联系方式") + @GetMapping() + public ApiResult> list(ShopOrderExtractParam param) { + // 使用关联查询 + return success(shopOrderExtractService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrderExtract:list')") + @Operation(summary = "根据id查询自提订单联系方式") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderExtractService.getByIdRel(id)); + } + + @Operation(summary = "添加自提订单联系方式") + @PostMapping() + public ApiResult save(@RequestBody ShopOrderExtract shopOrderExtract) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopOrderExtract.setUserId(loginUser.getUserId()); + } + if (shopOrderExtractService.save(shopOrderExtract)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改自提订单联系方式") + @PutMapping() + public ApiResult update(@RequestBody ShopOrderExtract shopOrderExtract) { + if (shopOrderExtractService.updateById(shopOrderExtract)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除自提订单联系方式") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderExtractService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加自提订单联系方式") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderExtractService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改自提订单联系方式") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderExtractService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除自提订单联系方式") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderExtractService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderGoodsController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderGoodsController.java new file mode 100644 index 0000000..cc01666 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderGoodsController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopOrderGoodsService; +import com.gxwebsoft.shop.entity.ShopOrderGoods; +import com.gxwebsoft.shop.param.ShopOrderGoodsParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 商品信息控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "商品信息管理") +@RestController +@RequestMapping("/api/shop/shop-order-goods") +public class ShopOrderGoodsController extends BaseController { + @Resource + private ShopOrderGoodsService shopOrderGoodsService; + + @Operation(summary = "分页查询商品信息") + @GetMapping("/page") + public ApiResult> page(ShopOrderGoodsParam param) { + // 使用关联查询 + return success(shopOrderGoodsService.pageRel(param)); + } + + @Operation(summary = "查询全部商品信息") + @GetMapping() + public ApiResult> list(ShopOrderGoodsParam param) { + // 使用关联查询 + return success(shopOrderGoodsService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrderGoods:list')") + @Operation(summary = "根据id查询商品信息") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderGoodsService.getByIdRel(id)); + } + + @Operation(summary = "添加商品信息") + @PostMapping() + public ApiResult save(@RequestBody ShopOrderGoods shopOrderGoods) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopOrderGoods.setUserId(loginUser.getUserId()); + } + if (shopOrderGoodsService.save(shopOrderGoods)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改商品信息") + @PutMapping() + public ApiResult update(@RequestBody ShopOrderGoods shopOrderGoods) { + if (shopOrderGoodsService.updateById(shopOrderGoods)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除商品信息") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderGoodsService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加商品信息") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderGoodsService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改商品信息") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderGoodsService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除商品信息") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderGoodsService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoController.java new file mode 100644 index 0000000..e9f91d8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopOrderInfoService; +import com.gxwebsoft.shop.entity.ShopOrderInfo; +import com.gxwebsoft.shop.param.ShopOrderInfoParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 场地控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "场地管理") +@RestController +@RequestMapping("/api/shop/shop-order-info") +public class ShopOrderInfoController extends BaseController { + @Resource + private ShopOrderInfoService shopOrderInfoService; + + @Operation(summary = "分页查询场地") + @GetMapping("/page") + public ApiResult> page(ShopOrderInfoParam param) { + // 使用关联查询 + return success(shopOrderInfoService.pageRel(param)); + } + + @Operation(summary = "查询全部场地") + @GetMapping() + public ApiResult> list(ShopOrderInfoParam param) { + // 使用关联查询 + return success(shopOrderInfoService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrderInfo:list')") + @Operation(summary = "根据id查询场地") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderInfoService.getByIdRel(id)); + } + + @Operation(summary = "添加场地") + @PostMapping() + public ApiResult save(@RequestBody ShopOrderInfo shopOrderInfo) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopOrderInfo.setUserId(loginUser.getUserId()); + } + if (shopOrderInfoService.save(shopOrderInfo)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改场地") + @PutMapping() + public ApiResult update(@RequestBody ShopOrderInfo shopOrderInfo) { + if (shopOrderInfoService.updateById(shopOrderInfo)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除场地") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderInfoService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加场地") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderInfoService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改场地") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderInfoService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除场地") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderInfoService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoLogController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoLogController.java new file mode 100644 index 0000000..b9c62b6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderInfoLogController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopOrderInfoLogService; +import com.gxwebsoft.shop.entity.ShopOrderInfoLog; +import com.gxwebsoft.shop.param.ShopOrderInfoLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 订单核销控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "订单核销管理") +@RestController +@RequestMapping("/api/shop/shop-order-info-log") +public class ShopOrderInfoLogController extends BaseController { + @Resource + private ShopOrderInfoLogService shopOrderInfoLogService; + + @Operation(summary = "分页查询订单核销") + @GetMapping("/page") + public ApiResult> page(ShopOrderInfoLogParam param) { + // 使用关联查询 + return success(shopOrderInfoLogService.pageRel(param)); + } + + @Operation(summary = "查询全部订单核销") + @GetMapping() + public ApiResult> list(ShopOrderInfoLogParam param) { + // 使用关联查询 + return success(shopOrderInfoLogService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopOrderInfoLog:list')") + @Operation(summary = "根据id查询订单核销") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopOrderInfoLogService.getByIdRel(id)); + } + + @Operation(summary = "添加订单核销") + @PostMapping() + public ApiResult save(@RequestBody ShopOrderInfoLog shopOrderInfoLog) { + if (shopOrderInfoLogService.save(shopOrderInfoLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改订单核销") + @PutMapping() + public ApiResult update(@RequestBody ShopOrderInfoLog shopOrderInfoLog) { + if (shopOrderInfoLogService.updateById(shopOrderInfoLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除订单核销") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopOrderInfoLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加订单核销") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopOrderInfoLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改订单核销") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopOrderInfoLogService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除订单核销") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopOrderInfoLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopRechargeOrderController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopRechargeOrderController.java new file mode 100644 index 0000000..1525208 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopRechargeOrderController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopRechargeOrderService; +import com.gxwebsoft.shop.entity.ShopRechargeOrder; +import com.gxwebsoft.shop.param.ShopRechargeOrderParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 会员充值订单表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Tag(name = "会员充值订单表管理") +@RestController +@RequestMapping("/api/shop/shop-recharge-order") +public class ShopRechargeOrderController extends BaseController { + @Resource + private ShopRechargeOrderService shopRechargeOrderService; + + @Operation(summary = "分页查询会员充值订单表") + @GetMapping("/page") + public ApiResult> page(ShopRechargeOrderParam param) { + // 使用关联查询 + return success(shopRechargeOrderService.pageRel(param)); + } + + @Operation(summary = "查询全部会员充值订单表") + @GetMapping() + public ApiResult> list(ShopRechargeOrderParam param) { + // 使用关联查询 + return success(shopRechargeOrderService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopRechargeOrder:list')") + @Operation(summary = "根据id查询会员充值订单表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopRechargeOrderService.getByIdRel(id)); + } + + @Operation(summary = "添加会员充值订单表") + @PostMapping() + public ApiResult save(@RequestBody ShopRechargeOrder shopRechargeOrder) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopRechargeOrder.setUserId(loginUser.getUserId()); + } + if (shopRechargeOrderService.save(shopRechargeOrder)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改会员充值订单表") + @PutMapping() + public ApiResult update(@RequestBody ShopRechargeOrder shopRechargeOrder) { + if (shopRechargeOrderService.updateById(shopRechargeOrder)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除会员充值订单表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopRechargeOrderService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加会员充值订单表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopRechargeOrderService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改会员充值订单表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopRechargeOrderService, "order_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除会员充值订单表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopRechargeOrderService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopSpecController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopSpecController.java new file mode 100644 index 0000000..60ddcc8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopSpecController.java @@ -0,0 +1,126 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopSpecService; +import com.gxwebsoft.shop.entity.ShopSpec; +import com.gxwebsoft.shop.param.ShopSpecParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 规格控制器 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Tag(name = "规格管理") +@RestController +@RequestMapping("/api/shop/shop-spec") +public class ShopSpecController extends BaseController { + @Resource + private ShopSpecService shopSpecService; + + @Operation(summary = "分页查询规格") + @GetMapping("/page") + public ApiResult> page(ShopSpecParam param) { + // 使用关联查询 + return success(shopSpecService.pageRel(param)); + } + + @Operation(summary = "查询全部规格") + @GetMapping() + public ApiResult> list(ShopSpecParam param) { + // 使用关联查询 + return success(shopSpecService.listRel(param)); + } + + @Operation(summary = "根据id查询规格") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopSpecService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopSpec:save')") + @OperationLog + @Operation(summary = "添加规格") + @PostMapping() + public ApiResult save(@RequestBody ShopSpec shopSpec) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopSpec.setUserId(loginUser.getUserId()); + } + if (shopSpecService.save(shopSpec)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpec:update')") + @OperationLog + @Operation(summary = "修改规格") + @PutMapping() + public ApiResult update(@RequestBody ShopSpec shopSpec) { + if (shopSpecService.updateById(shopSpec)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpec:remove')") + @OperationLog + @Operation(summary = "删除规格") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopSpecService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpec:save')") + @OperationLog + @Operation(summary = "批量添加规格") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopSpecService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpec:update')") + @OperationLog + @Operation(summary = "批量修改规格") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopSpecService, "spec_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpec:remove')") + @OperationLog + @Operation(summary = "批量删除规格") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopSpecService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopSpecValueController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopSpecValueController.java new file mode 100644 index 0000000..6d98bb3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopSpecValueController.java @@ -0,0 +1,121 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopSpecValueService; +import com.gxwebsoft.shop.entity.ShopSpecValue; +import com.gxwebsoft.shop.param.ShopSpecValueParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 规格值控制器 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Tag(name = "规格值管理") +@RestController +@RequestMapping("/api/shop/shop-spec-value") +public class ShopSpecValueController extends BaseController { + @Resource + private ShopSpecValueService shopSpecValueService; + + @Operation(summary = "分页查询规格值") + @GetMapping("/page") + public ApiResult> page(ShopSpecValueParam param) { + // 使用关联查询 + return success(shopSpecValueService.pageRel(param)); + } + + @Operation(summary = "查询全部规格值") + @GetMapping() + public ApiResult> list(ShopSpecValueParam param) { + // 使用关联查询 + return success(shopSpecValueService.listRel(param)); + } + + @Operation(summary = "根据id查询规格值") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopSpecValueService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopSpecValue:save')") + @OperationLog + @Operation(summary = "添加规格值") + @PostMapping() + public ApiResult save(@RequestBody ShopSpecValue shopSpecValue) { + if (shopSpecValueService.save(shopSpecValue)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpecValue:update')") + @OperationLog + @Operation(summary = "修改规格值") + @PutMapping() + public ApiResult update(@RequestBody ShopSpecValue shopSpecValue) { + if (shopSpecValueService.updateById(shopSpecValue)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpecValue:remove')") + @OperationLog + @Operation(summary = "删除规格值") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopSpecValueService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpecValue:save')") + @OperationLog + @Operation(summary = "批量添加规格值") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopSpecValueService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpecValue:update')") + @OperationLog + @Operation(summary = "批量修改规格值") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopSpecValueService, "spec_value_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopSpecValue:remove')") + @OperationLog + @Operation(summary = "批量删除规格值") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopSpecValueService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopSplashController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopSplashController.java new file mode 100644 index 0000000..d39e628 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopSplashController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopSplashService; +import com.gxwebsoft.shop.entity.ShopSplash; +import com.gxwebsoft.shop.param.ShopSplashParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 开屏广告控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Tag(name = "开屏广告管理") +@RestController +@RequestMapping("/api/shop/shop-splash") +public class ShopSplashController extends BaseController { + @Resource + private ShopSplashService shopSplashService; + + @Operation(summary = "分页查询开屏广告") + @GetMapping("/page") + public ApiResult> page(ShopSplashParam param) { + // 使用关联查询 + return success(shopSplashService.pageRel(param)); + } + + @Operation(summary = "查询全部开屏广告") + @GetMapping() + public ApiResult> list(ShopSplashParam param) { + // 使用关联查询 + return success(shopSplashService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopSplash:list')") + @Operation(summary = "根据id查询开屏广告") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopSplashService.getByIdRel(id)); + } + + @Operation(summary = "添加开屏广告") + @PostMapping() + public ApiResult save(@RequestBody ShopSplash shopSplash) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopSplash.setUserId(loginUser.getUserId()); + } + if (shopSplashService.save(shopSplash)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改开屏广告") + @PutMapping() + public ApiResult update(@RequestBody ShopSplash shopSplash) { + if (shopSplashService.updateById(shopSplash)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除开屏广告") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopSplashService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加开屏广告") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopSplashService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改开屏广告") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopSplashService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除开屏广告") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopSplashService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserAddressController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserAddressController.java new file mode 100644 index 0000000..e2290f9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserAddressController.java @@ -0,0 +1,133 @@ + package com.gxwebsoft.shop.controller; + + import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + import com.gxwebsoft.common.core.web.BaseController; + import com.gxwebsoft.shop.service.ShopUserAddressService; + import com.gxwebsoft.shop.entity.ShopUserAddress; + import com.gxwebsoft.shop.param.ShopUserAddressParam; + import com.gxwebsoft.common.core.web.ApiResult; + import com.gxwebsoft.common.core.web.PageResult; + import com.gxwebsoft.common.core.web.BatchParam; + import com.gxwebsoft.common.core.annotation.OperationLog; + import com.gxwebsoft.common.system.entity.User; + import io.swagger.v3.oas.annotations.tags.Tag; + import io.swagger.v3.oas.annotations.Operation; + import org.springframework.security.access.prepost.PreAuthorize; + import org.springframework.web.bind.annotation.*; + + import javax.annotation.Resource; + import java.util.List; + + /** + * 收货地址控制器 + * + * @author 科技小王子 + * @since 2025-07-22 23:06:40 + */ + @Tag(name = "收货地址管理") + @RestController + @RequestMapping("/api/shop/shop-user-address") + public class ShopUserAddressController extends BaseController { + @Resource + private ShopUserAddressService shopUserAddressService; + + @PreAuthorize("hasAuthority('shop:shopUserAddress:list')") + @Operation(summary = "分页查询收货地址") + @GetMapping("/page") + public ApiResult> page(ShopUserAddressParam param) { + // 使用关联查询 + return success(shopUserAddressService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:list')") + @Operation(summary = "查询全部收货地址") + @GetMapping() + public ApiResult> list(ShopUserAddressParam param) { + // 使用关联查询 + param.setUserId(getLoginUser().getUserId()); + return success(shopUserAddressService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:list')") + @Operation(summary = "根据id查询收货地址") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopUserAddressService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:save')") + @OperationLog + @Operation(summary = "添加收货地址") + @PostMapping() + public ApiResult save(@RequestBody ShopUserAddress shopUserAddress) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopUserAddress.setUserId(loginUser.getUserId()); + if (shopUserAddressService.count(new LambdaQueryWrapper().eq(ShopUserAddress::getUserId, loginUser.getUserId()).eq(ShopUserAddress::getAddress, shopUserAddress.getAddress())) > 0) { + return success("该地址已存在"); + } + } + if (shopUserAddressService.save(shopUserAddress)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:update')") + @OperationLog + @Operation(summary = "修改收货地址") + @PutMapping() + public ApiResult update(@RequestBody ShopUserAddress shopUserAddress) { + if (shopUserAddressService.updateById(shopUserAddress)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:remove')") + @OperationLog + @Operation(summary = "删除收货地址") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopUserAddressService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:save')") + @OperationLog + @Operation(summary = "批量添加收货地址") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopUserAddressService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:update')") + @OperationLog + @Operation(summary = "批量修改收货地址") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopUserAddressService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserAddress:remove')") + @OperationLog + @Operation(summary = "批量删除收货地址") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopUserAddressService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + } diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserBalanceLogController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserBalanceLogController.java new file mode 100644 index 0000000..0db1573 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserBalanceLogController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopUserBalanceLogService; +import com.gxwebsoft.shop.entity.ShopUserBalanceLog; +import com.gxwebsoft.shop.param.ShopUserBalanceLogParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用户余额变动明细表控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Tag(name = "用户余额变动明细表管理") +@RestController +@RequestMapping("/api/shop/shop-user-balance-log") +public class ShopUserBalanceLogController extends BaseController { + @Resource + private ShopUserBalanceLogService shopUserBalanceLogService; + + @Operation(summary = "分页查询用户余额变动明细表") + @GetMapping("/page") + public ApiResult> page(ShopUserBalanceLogParam param) { + // 使用关联查询 + return success(shopUserBalanceLogService.pageRel(param)); + } + + @Operation(summary = "查询全部用户余额变动明细表") + @GetMapping() + public ApiResult> list(ShopUserBalanceLogParam param) { + // 使用关联查询 + return success(shopUserBalanceLogService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserBalanceLog:list')") + @Operation(summary = "根据id查询用户余额变动明细表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopUserBalanceLogService.getByIdRel(id)); + } + + @Operation(summary = "添加用户余额变动明细表") + @PostMapping() + public ApiResult save(@RequestBody ShopUserBalanceLog shopUserBalanceLog) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopUserBalanceLog.setUserId(loginUser.getUserId()); + } + if (shopUserBalanceLogService.save(shopUserBalanceLog)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改用户余额变动明细表") + @PutMapping() + public ApiResult update(@RequestBody ShopUserBalanceLog shopUserBalanceLog) { + if (shopUserBalanceLogService.updateById(shopUserBalanceLog)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除用户余额变动明细表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopUserBalanceLogService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加用户余额变动明细表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopUserBalanceLogService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改用户余额变动明细表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopUserBalanceLogService, "log_id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除用户余额变动明细表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopUserBalanceLogService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserCollectionController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserCollectionController.java new file mode 100644 index 0000000..0e01d68 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserCollectionController.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopUserCollectionService; +import com.gxwebsoft.shop.entity.ShopUserCollection; +import com.gxwebsoft.shop.param.ShopUserCollectionParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 我的收藏控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Tag(name = "我的收藏管理") +@RestController +@RequestMapping("/api/shop/shop-user-collection") +public class ShopUserCollectionController extends BaseController { + @Resource + private ShopUserCollectionService shopUserCollectionService; + + @Operation(summary = "分页查询我的收藏") + @GetMapping("/page") + public ApiResult> page(ShopUserCollectionParam param) { + // 使用关联查询 + return success(shopUserCollectionService.pageRel(param)); + } + + @Operation(summary = "查询全部我的收藏") + @GetMapping() + public ApiResult> list(ShopUserCollectionParam param) { + // 使用关联查询 + return success(shopUserCollectionService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserCollection:list')") + @Operation(summary = "根据id查询我的收藏") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopUserCollectionService.getByIdRel(id)); + } + + @Operation(summary = "添加我的收藏") + @PostMapping() + public ApiResult save(@RequestBody ShopUserCollection shopUserCollection) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopUserCollection.setUserId(loginUser.getUserId()); + } + if (shopUserCollectionService.save(shopUserCollection)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改我的收藏") + @PutMapping() + public ApiResult update(@RequestBody ShopUserCollection shopUserCollection) { + if (shopUserCollectionService.updateById(shopUserCollection)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除我的收藏") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopUserCollectionService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加我的收藏") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopUserCollectionService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改我的收藏") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopUserCollectionService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除我的收藏") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopUserCollectionService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java new file mode 100644 index 0000000..a9382b2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java @@ -0,0 +1,309 @@ +package com.gxwebsoft.shop.controller; + +import cn.hutool.core.date.LocalDateTimeUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.service.ShopCouponApplyCateService; +import com.gxwebsoft.shop.service.ShopCouponApplyItemService; +import com.gxwebsoft.shop.service.ShopCouponService; +import com.gxwebsoft.shop.service.ShopUserCouponService; +import com.gxwebsoft.shop.service.CouponStatusService; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.param.ShopUserCouponParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; + +import javax.annotation.Resource; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * 用户优惠券控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Slf4j +@Tag(name = "用户优惠券管理") +@RestController +@RequestMapping("/api/shop/shop-user-coupon") +public class ShopUserCouponController extends BaseController { + @Resource + private ShopUserCouponService shopUserCouponService; + @Resource + private ShopCouponService couponService; + @Resource + private ShopCouponApplyCateService couponApplyCateService; + @Resource + private ShopCouponApplyItemService couponApplyItemService; + @Resource + private CouponStatusService couponStatusService; + + @Operation(summary = "用户优惠券列表") + @PostMapping("/list") + public ApiResult> list(@RequestBody ShopUserCoupon userCouponParam) throws ParseException { + MPJLambdaWrapper queryWrapper = new MPJLambdaWrapper() + .selectAll(ShopUserCoupon.class) + .selectAs(ShopCoupon::getName, ShopCoupon::getName) + .eq(ShopUserCoupon::getUserId, getLoginUserId()) + .leftJoin(ShopCoupon.class, ShopCoupon::getId, ShopUserCoupon::getCouponId); + if (userCouponParam.getIsExpire() != null) + queryWrapper.eq(ShopUserCoupon::getIsExpire, userCouponParam.getIsExpire()); + if (userCouponParam.getIsUse() != null) queryWrapper.eq(ShopUserCoupon::getIsUse, userCouponParam.getIsUse()); + List userCouponList = shopUserCouponService.list(queryWrapper); + for (ShopUserCoupon userCoupon : userCouponList) { + try { + // 使用新的状态管理服务检查和更新状态 + couponStatusService.checkAndUpdateCouponStatus(userCoupon); + + // 确保BigDecimal字段不为null时才处理 + if (userCoupon.getReducePrice() == null) { + userCoupon.setReducePrice(BigDecimal.ZERO); + } + if (userCoupon.getMinPrice() == null) { + userCoupon.setMinPrice(BigDecimal.ZERO); + } + + ShopCoupon coupon = couponService.getById(userCoupon.getCouponId()); + if (coupon != null) { + // 确保优惠券模板的BigDecimal字段不为null + if (coupon.getReducePrice() == null) { + coupon.setReducePrice(BigDecimal.ZERO); + } + if (coupon.getMinPrice() == null) { + coupon.setMinPrice(BigDecimal.ZERO); + } + + coupon.setCouponApplyCateList(couponApplyCateService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyCate::getCouponId, userCoupon.getCouponId()) + )); + coupon.setCouponApplyItemList(couponApplyItemService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId()) + )); + userCoupon.setCouponItem(coupon); + } + } catch (Exception e) { + log.error("处理用户优惠券数据异常: {}", e.getMessage(), e); + // 设置默认值避免序列化异常 + if (userCoupon.getReducePrice() == null) { + userCoupon.setReducePrice(BigDecimal.ZERO); + } + if (userCoupon.getMinPrice() == null) { + userCoupon.setMinPrice(BigDecimal.ZERO); + } + } + } + return success(userCouponList); + } + + @Operation(summary = "领取优惠券") + @PostMapping("/take") + public ApiResult take(@RequestBody ShopUserCoupon userCoupon) { + final User loginUser = getLoginUser(); + if (loginUser == null) return fail("请先登录"); + ShopCoupon coupon = couponService.getByIdRel(userCoupon.getCouponId()); + + // 检查优惠券是否存在 + if (coupon == null) return fail("优惠券不存在"); + + // 安全地检查已领取数量,避免空指针异常 + Integer receiveNum = coupon.getReceiveNum(); + if (receiveNum == null) receiveNum = 0; + + if (coupon.getTotalCount() != -1 && receiveNum >= coupon.getTotalCount()) return fail("已经被领完了"); + List userCouponList = shopUserCouponService.list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getCouponId, userCoupon.getCouponId()) + .eq(ShopUserCoupon::getUserId, getLoginUserId()) + ); + int userNotUsedNum = 0; + for (ShopUserCoupon userCouponItem : userCouponList) { + if (userCouponItem.getIsUse().equals(0)) userNotUsedNum++; + } + if (userNotUsedNum > 0) return fail("您还有未使用的优惠券,无法领取"); + if (coupon.getLimitPerUser() > -1) { + if (userCouponList.size() >= coupon.getLimitPerUser()) + return fail("每用户最多领取" + coupon.getLimitPerUser() + "张优惠券"); + } + userCoupon.setType(coupon.getType()); + userCoupon.setReducePrice(coupon.getReducePrice()); + userCoupon.setDiscount(coupon.getDiscount()); + userCoupon.setMinPrice(coupon.getMinPrice()); + Integer expireType = coupon.getExpireType(); + userCoupon.setExpireType(expireType); + if (expireType == 10) { + userCoupon.setStartTime(LocalDateTime.now()); + userCoupon.setEndTime(LocalDateTimeUtil.offset(userCoupon.getStartTime(), coupon.getExpireDay(), ChronoUnit.DAYS)); + } else { + userCoupon.setStartTime(coupon.getStartTime()); + userCoupon.setEndTime(coupon.getEndTime()); + } + userCoupon.setUserId(getLoginUserId()); + shopUserCouponService.save(userCoupon); + + // 安全地更新已领取数量,避免空指针异常 + Integer currentReceiveNum = coupon.getReceiveNum(); + if (currentReceiveNum == null) currentReceiveNum = 0; + coupon.setReceiveNum(currentReceiveNum + 1); + couponService.updateById(coupon); + return success("领取成功"); + } + + @Operation(summary = "分页查询用户优惠券") + @GetMapping("/page") + public ApiResult> page(ShopUserCouponParam param) { + // 使用关联查询 + return success(shopUserCouponService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:list')") + @Operation(summary = "查询全部用户优惠券") + @GetMapping() + public ApiResult> list(ShopUserCouponParam param) { + // 使用关联查询 + return success(shopUserCouponService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:list')") + @Operation(summary = "根据id查询用户优惠券") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopUserCouponService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:save')") + @OperationLog + @Operation(summary = "添加用户优惠券") + @PostMapping() + public ApiResult save(@RequestBody ShopUserCoupon shopUserCoupon) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopUserCoupon.setUserId(loginUser.getUserId()); + } + if (shopUserCouponService.save(shopUserCoupon)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:update')") + @OperationLog + @Operation(summary = "修改用户优惠券") + @PutMapping() + public ApiResult update(@RequestBody ShopUserCoupon shopUserCoupon) { + if (shopUserCouponService.updateById(shopUserCoupon)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:remove')") + @OperationLog + @Operation(summary = "删除用户优惠券") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopUserCouponService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:save')") + @OperationLog + @Operation(summary = "批量添加用户优惠券") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopUserCouponService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:update')") + @OperationLog + @Operation(summary = "批量修改用户优惠券") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopUserCouponService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserCoupon:remove')") + @OperationLog + @Operation(summary = "批量删除用户优惠券") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopUserCouponService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "获取我的可用优惠券") + @GetMapping("/my/available") + public ApiResult> getMyAvailableCoupons() { + try { + List coupons = couponStatusService.getAvailableCoupons(getLoginUserId()); + return success("获取成功", coupons); + } catch (Exception e) { + return fail("获取失败",null); + } + } + + @Operation(summary = "获取我的已使用优惠券") + @GetMapping("/my/used") + public ApiResult> getMyUsedCoupons() { + try { + List coupons = couponStatusService.getUsedCoupons(getLoginUserId()); + return success("获取成功", coupons); + } catch (Exception e) { + return fail("获取失败",null); + } + } + + @Operation(summary = "获取我的已过期优惠券") + @GetMapping("/my/expired") + public ApiResult> getMyExpiredCoupons() { + try { + List coupons = couponStatusService.getExpiredCoupons(getLoginUserId()); + return success("获取成功", coupons); + } catch (Exception e) { + return fail("获取失败",null); + } + } + + @Operation(summary = "获取我的优惠券统计") + @GetMapping("/my/statistics") + public ApiResult getMyCouponStatistics() { + try { + CouponStatusService.CouponStatusResult result = + couponStatusService.getUserCouponsGroupByStatus(getLoginUserId()); + return success("获取成功", result); + } catch (Exception e) { + return fail("获取失败",null); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserRefereeController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserRefereeController.java new file mode 100644 index 0000000..3e9970f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserRefereeController.java @@ -0,0 +1,129 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopUserRefereeService; +import com.gxwebsoft.shop.entity.ShopUserReferee; +import com.gxwebsoft.shop.param.ShopUserRefereeParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用户推荐关系表控制器 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Tag(name = "用户推荐关系表管理") +@RestController +@RequestMapping("/api/shop/shop-user-referee") +public class ShopUserRefereeController extends BaseController { + @Resource + private ShopUserRefereeService shopUserRefereeService; + + @PreAuthorize("hasAuthority('shop:shopUserReferee:list')") + @Operation(summary = "分页查询用户推荐关系表") + @GetMapping("/page") + public ApiResult> page(ShopUserRefereeParam param) { + // 使用关联查询 + return success(shopUserRefereeService.pageRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:list')") + @Operation(summary = "查询全部用户推荐关系表") + @GetMapping() + public ApiResult> list(ShopUserRefereeParam param) { + // 使用关联查询 + return success(shopUserRefereeService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:list')") + @Operation(summary = "根据id查询用户推荐关系表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopUserRefereeService.getByIdRel(id)); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:save')") + @OperationLog + @Operation(summary = "添加用户推荐关系表") + @PostMapping() + public ApiResult save(@RequestBody ShopUserReferee shopUserReferee) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + shopUserReferee.setUserId(loginUser.getUserId()); + } + if (shopUserRefereeService.save(shopUserReferee)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:update')") + @OperationLog + @Operation(summary = "修改用户推荐关系表") + @PutMapping() + public ApiResult update(@RequestBody ShopUserReferee shopUserReferee) { + if (shopUserRefereeService.updateById(shopUserReferee)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:remove')") + @OperationLog + @Operation(summary = "删除用户推荐关系表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopUserRefereeService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:save')") + @OperationLog + @Operation(summary = "批量添加用户推荐关系表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopUserRefereeService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:update')") + @OperationLog + @Operation(summary = "批量修改用户推荐关系表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopUserRefereeService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @PreAuthorize("hasAuthority('shop:shopUserReferee:remove')") + @OperationLog + @Operation(summary = "批量删除用户推荐关系表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopUserRefereeService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUsersController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUsersController.java new file mode 100644 index 0000000..458384f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUsersController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopUsersService; +import com.gxwebsoft.shop.entity.ShopUsers; +import com.gxwebsoft.shop.param.ShopUsersParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Tag(name = "管理") +@RestController +@RequestMapping("/api/shop/shop-users") +public class ShopUsersController extends BaseController { + @Resource + private ShopUsersService shopUsersService; + + @Operation(summary = "分页查询") + @GetMapping("/page") + public ApiResult> page(ShopUsersParam param) { + // 使用关联查询 + return success(shopUsersService.pageRel(param)); + } + + @Operation(summary = "查询全部") + @GetMapping() + public ApiResult> list(ShopUsersParam param) { + // 使用关联查询 + return success(shopUsersService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopUsers:list')") + @Operation(summary = "根据id查询") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopUsersService.getByIdRel(id)); + } + + @Operation(summary = "添加") + @PostMapping() + public ApiResult save(@RequestBody ShopUsers shopUsers) { + if (shopUsersService.save(shopUsers)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改") + @PutMapping() + public ApiResult update(@RequestBody ShopUsers shopUsers) { + if (shopUsersService.updateById(shopUsers)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopUsersService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopUsersService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopUsersService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopUsersService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopWechatDepositController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopWechatDepositController.java new file mode 100644 index 0000000..7c29f20 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopWechatDepositController.java @@ -0,0 +1,110 @@ +package com.gxwebsoft.shop.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.service.ShopWechatDepositService; +import com.gxwebsoft.shop.entity.ShopWechatDeposit; +import com.gxwebsoft.shop.param.ShopWechatDepositParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 押金控制器 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Tag(name = "押金管理") +@RestController +@RequestMapping("/api/shop/shop-wechat-deposit") +public class ShopWechatDepositController extends BaseController { + @Resource + private ShopWechatDepositService shopWechatDepositService; + + @Operation(summary = "分页查询押金") + @GetMapping("/page") + public ApiResult> page(ShopWechatDepositParam param) { + // 使用关联查询 + return success(shopWechatDepositService.pageRel(param)); + } + + @Operation(summary = "查询全部押金") + @GetMapping() + public ApiResult> list(ShopWechatDepositParam param) { + // 使用关联查询 + return success(shopWechatDepositService.listRel(param)); + } + + @PreAuthorize("hasAuthority('shop:shopWechatDeposit:list')") + @Operation(summary = "根据id查询押金") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(shopWechatDepositService.getByIdRel(id)); + } + + @Operation(summary = "添加押金") + @PostMapping() + public ApiResult save(@RequestBody ShopWechatDeposit shopWechatDeposit) { + if (shopWechatDepositService.save(shopWechatDeposit)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "修改押金") + @PutMapping() + public ApiResult update(@RequestBody ShopWechatDeposit shopWechatDeposit) { + if (shopWechatDepositService.updateById(shopWechatDeposit)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "删除押金") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (shopWechatDepositService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "批量添加押金") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (shopWechatDepositService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @Operation(summary = "批量修改押金") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(shopWechatDepositService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @Operation(summary = "批量删除押金") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (shopWechatDepositService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java b/src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java new file mode 100644 index 0000000..6750da1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java @@ -0,0 +1,176 @@ +package com.gxwebsoft.shop.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.*; +import java.math.BigDecimal; +import java.util.List; + +/** + * 订单创建请求DTO + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Data +@Schema(name = "OrderCreateRequest", description = "订单创建请求") +public class OrderCreateRequest { + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "订单类型,0商城订单 1预定订单/外卖 2会员卡") + @NotNull(message = "订单类型不能为空") + @Min(value = 0, message = "订单类型值无效") + @Max(value = 2, message = "订单类型值无效") + private Integer type; + + @Size(max = 60, message = "备注长度不能超过60个字符") + @Schema(description = "订单标题") + private String title; + + @Schema(description = "快递/自提") + private Integer deliveryType; + + @Schema(description = "下单渠道,0小程序预定 1俱乐部训练场 3活动订场") + private Integer channel; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商户编号") + private String merchantCode; + + @Schema(description = "使用的优惠券id") + private Integer couponId; + + @Schema(description = "使用的会员卡id") + private String cardId; + + @Schema(description = "关联收货地址") + private Integer addressId; + + @Schema(description = "收货地址") + private String address; + + @Schema(description = "收货人姓名") + private String realName; + + @Schema(description = "地址纬度") + private String addressLat; + + @Schema(description = "地址经度") + private String addressLng; + + @Schema(description = "自提店铺id") + private Integer selfTakeMerchantId; + + @Schema(description = "自提店铺") + private String selfTakeMerchantName; + + @Schema(description = "配送开始时间") + private String sendStartTime; + + @Schema(description = "配送结束时间") + private String sendEndTime; + + @Schema(description = "发货店铺id") + private Integer expressMerchantId; + + @Schema(description = "发货店铺") + private String expressMerchantName; + + @Schema(description = "订单总额") + @NotNull(message = "订单总额不能为空") + @DecimalMin(value = "0.01", message = "订单总额必须大于0") + @Digits(integer = 10, fraction = 2, message = "订单总额格式不正确") + private BigDecimal totalPrice; + + @Schema(description = "减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格") + @DecimalMin(value = "0", message = "减少金额不能为负数") + private BigDecimal reducePrice; + + @Schema(description = "实际付款") + @DecimalMin(value = "0", message = "实际付款不能为负数") + private BigDecimal payPrice; + + @Schema(description = "用于统计") + private BigDecimal price; + + @Schema(description = "价钱,用于积分赠送") + private BigDecimal money; + + @Schema(description = "教练价格") + private BigDecimal coachPrice; + + @Schema(description = "购买数量") + @Min(value = 1, message = "购买数量必须大于0") + private Integer totalNum; + + @Schema(description = "教练id") + private Integer coachId; + + @Schema(description = "来源ID,存商品ID") + private Integer formId; + + @Schema(description = "支付类型,0余额支付, 1微信支付,102微信Native,2会员卡支付,3支付宝,4现金,5POS机,6VIP月卡,7VIP年卡,8VIP次卡,9IC月卡,10IC年卡,11IC次卡,12免费,13VIP充值卡,14IC充值卡,15积分支付,16VIP季卡,17IC季卡,18代付") + private Integer payType; + + @Schema(description = "代付支付方式") + private Integer friendPayType; + + @Schema(description = "优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡") + private Integer couponType; + + @Schema(description = "优惠说明") + private String couponDesc; + + @Schema(description = "预约详情开始时间数组") + private String startTime; + + @Schema(description = "备注") + @Size(max = 500, message = "备注长度不能超过500字符") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + @NotNull(message = "租户ID不能为空") + private Integer tenantId; + + @Schema(description = "订单商品列表") + @Valid + @NotEmpty(message = "订单商品列表不能为空") + private List goodsItems; + + /** + * 订单商品项 + */ + @Data + @Schema(name = "OrderGoodsItem", description = "订单商品项") + public static class OrderGoodsItem { + @Schema(description = "商品ID", required = true) + @NotNull(message = "商品ID不能为空") + private Integer goodsId; + + @Schema(description = "商品SKU ID") + private Integer skuId; + + @Schema(description = "商品数量", required = true) + @NotNull(message = "商品数量不能为空") + @Min(value = 1, message = "商品数量必须大于0") + private Integer quantity; + + @Schema(description = "支付类型") + private Integer payType; + + @Schema(description = "规格信息,如:颜色:红色|尺寸:L") + private String specInfo; + } +} diff --git a/src/main/java/com/gxwebsoft/shop/dto/UpdatePaymentStatusRequest.java b/src/main/java/com/gxwebsoft/shop/dto/UpdatePaymentStatusRequest.java new file mode 100644 index 0000000..999b0a8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/dto/UpdatePaymentStatusRequest.java @@ -0,0 +1,32 @@ +package com.gxwebsoft.shop.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 更新订单支付状态请求DTO + * + * @author 科技小王子 + * @since 2025-08-30 + */ +@Data +@Schema(name = "UpdatePaymentStatusRequest", description = "更新订单支付状态请求") +public class UpdatePaymentStatusRequest { + + @Schema(description = "订单号", required = true) + @NotBlank(message = "订单号不能为空") + private String orderNo; + + @Schema(description = "支付状态:1=支付成功,0=支付失败", required = true) + @NotNull(message = "支付状态不能为空") + private Integer paymentStatus; + + @Schema(description = "微信交易号") + private String transactionId; + + @Schema(description = "支付时间,格式:yyyy-MM-dd HH:mm:ss") + private String payTime; +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopArticle.java b/src/main/java/com/gxwebsoft/shop/entity/ShopArticle.java new file mode 100644 index 0000000..a11ff60 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopArticle.java @@ -0,0 +1,189 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品文章 + * + * @author 科技小王子 + * @since 2025-08-13 05:14:53 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopArticle对象", description = "商品文章") +public class ShopArticle implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "文章ID") + @TableId(value = "article_id", type = IdType.AUTO) + private Integer articleId; + + @Schema(description = "文章标题") + private String title; + + @Schema(description = "文章类型 0常规 1视频") + private Integer type; + + @Schema(description = "模型") + private String model; + + @Schema(description = "详情页模板") + private String detail; + + @Schema(description = "文章分类ID") + private Integer categoryId; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "话题") + private String topic; + + @Schema(description = "标签") + private String tags; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "封面图宽") + private Integer imageWidth; + + @Schema(description = "封面图高") + private Integer imageHeight; + + @Schema(description = "付费金额") + private BigDecimal price; + + @Schema(description = "开始时间") + private LocalDateTime startTime; + + @Schema(description = "结束时间") + private LocalDateTime endTime; + + @Schema(description = "来源") + private String source; + + @Schema(description = "产品概述") + private String overview; + + @Schema(description = "虚拟阅读量(仅用作展示)") + private Integer virtualViews; + + @Schema(description = "实际阅读量") + private Integer actualViews; + + @Schema(description = "评分") + private BigDecimal rate; + + @Schema(description = "列表显示方式(10小图展示 20大图展示)") + private Integer showType; + + @Schema(description = "访问密码") + private String password; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + private Integer permission; + + @Schema(description = "发布来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "文章附件") + private String files; + + @Schema(description = "视频地址") + private String video; + + @Schema(description = "接受的文件类型") + private String accept; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "点赞数") + private Integer likes; + + @Schema(description = "评论数") + private Integer commentNumbers; + + @Schema(description = "提醒谁看") + private String toUsers; + + @Schema(description = "作者") + private String author; + + @Schema(description = "推荐") + private Integer recommend; + + @Schema(description = "报名人数") + private Integer bmUsers; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "项目ID") + private Integer projectId; + + @Schema(description = "语言") + private String lang; + + @Schema(description = "关联默认语言的文章ID") + private Integer langArticleId; + + @Schema(description = "是否自动翻译") + private Boolean translation; + + @Schema(description = "编辑器类型 0 Markdown编辑器 1 富文本编辑器 ") + private Boolean editor; + + @Schema(description = "pdf文件地址") + private String pdfUrl; + + @Schema(description = "版本号") + private Integer version; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopBrand.java b/src/main/java/com/gxwebsoft/shop/entity/ShopBrand.java new file mode 100644 index 0000000..cfa7e1e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopBrand.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 品牌 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopBrand对象", description = "品牌") +public class ShopBrand implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "brand_id", type = IdType.AUTO) + private Integer brandId; + + @Schema(description = "品牌名称") + private String brandName; + + @Schema(description = "图标") + private String image; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCart.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCart.java new file mode 100644 index 0000000..48c2e35 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCart.java @@ -0,0 +1,95 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 购物车 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCart对象", description = "购物车") +public class ShopCart implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "购物车表ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "类型 0商城 1外卖") + private Integer type; + + @Schema(description = "唯一标识") + private String code; + + @Schema(description = "商品ID") + private Long goodsId; + + @Schema(description = "商品SKU ID") + private Integer skuId; + + @Schema(description = "商品规格") + private String spec; + + @Schema(description = "规格信息,如:颜色:红色|尺寸:L") + private String specInfo; + + @Schema(description = "商品价格") + private BigDecimal price; + + @Schema(description = "商品数量") + private Integer cartNum; + + @Schema(description = "单商品合计") + private BigDecimal totalPrice; + + @Schema(description = "0 = 未购买 1 = 已购买") + private Boolean isPay; + + @Schema(description = "是否为立即购买") + private Boolean isNew; + + @Schema(description = "是否为立即购买") + private Boolean isShow; + + @Schema(description = "拼团id") + private Integer combinationId; + + @Schema(description = "秒杀产品ID") + private Integer seckillId; + + @Schema(description = "砍价id") + private Integer bargainId; + + @Schema(description = "是否选中") + private Boolean selected; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCategory.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCategory.java new file mode 100644 index 0000000..7ae2d46 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCategory.java @@ -0,0 +1,122 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品分类 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCategory对象", description = "商品分类") +public class ShopCategory implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "上级id, 0是顶级") + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "模型") + private String model; + + @Schema(description = "标识") + private String code; + + @Schema(description = "链接地址") + private String path; + + @Schema(description = "组件地址") + private String component; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "banner") + private String banner; + + @Schema(description = "图标颜色") + private String color; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + private Integer permission; + + @Schema(description = "访问密码") + private String password; + + @Schema(description = "位置 0不限 1顶部 2底部") + private Integer position; + + @Schema(description = "仅在顶部显示") + private Integer top; + + @Schema(description = "仅在底部显示") + private Integer bottom; + + @Schema(description = "菜单选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "语言") + private String lang; + + @Schema(description = "设为首页") + private Integer home; + + @Schema(description = "推荐") + private Integer recommend; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopChatConversation.java b/src/main/java/com/gxwebsoft/shop/entity/ShopChatConversation.java new file mode 100644 index 0000000..0b4d2a7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopChatConversation.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 聊天消息表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopChatConversation对象", description = "聊天消息表") +public class ShopChatConversation implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "好友ID") + private Integer friendId; + + @Schema(description = "消息类型") + private Integer type; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "未读消息") + private Integer unRead; + + @Schema(description = "状态, 0未读, 1已读") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopChatMessage.java b/src/main/java/com/gxwebsoft/shop/entity/ShopChatMessage.java new file mode 100644 index 0000000..1a8b939 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopChatMessage.java @@ -0,0 +1,78 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 聊天消息表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopChatMessage对象", description = "聊天消息表") +public class ShopChatMessage implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "发送人ID") + private Integer formUserId; + + @Schema(description = "接收人ID") + private Integer toUserId; + + @Schema(description = "消息类型") + private String type; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "屏蔽接收方") + private Integer sideTo; + + @Schema(description = "屏蔽发送方") + private Integer sideFrom; + + @Schema(description = "是否撤回") + private Integer withdraw; + + @Schema(description = "文件信息") + private String fileInfo; + + @Schema(description = "存在联系方式") + private Integer hasContact; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "状态, 0未读, 1已读") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCommissionRole.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCommissionRole.java new file mode 100644 index 0000000..6616688 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCommissionRole.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分红角色 + * + * @author 科技小王子 + * @since 2025-05-01 10:01:15 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCommissionRole对象", description = "分红角色") +public class ShopCommissionRole implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private String title; + + private Integer provinceId; + + private Integer cityId; + + private Integer regionId; + + @Schema(description = "状态, 0正常, 1异常") + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCount.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCount.java new file mode 100644 index 0000000..518c42b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCount.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商城销售统计表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCount对象", description = "商城销售统计表") +public class ShopCount implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "统计日期") + private LocalDate dateTime; + + @Schema(description = "总销售额") + private BigDecimal totalPrice; + + @Schema(description = "今日销售额") + private BigDecimal todayPrice; + + @Schema(description = "总会员数") + private BigDecimal totalUsers; + + @Schema(description = "今日新增") + private BigDecimal todayUsers; + + @Schema(description = "总订单笔数") + private BigDecimal totalOrders; + + @Schema(description = "今日订单笔数") + private BigDecimal todayOrders; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java new file mode 100644 index 0000000..fae4872 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java @@ -0,0 +1,136 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 优惠券 + * + * @author 科技小王子 + * @since 2025-08-11 09:41:38 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCoupon对象", description = "优惠券") +public class ShopCoupon implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "优惠券名称") + private String name; + + @Schema(description = "优惠券描述") + private String description; + + @Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)") + private Integer type; + + @Schema(description = "满减券-减免金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + private BigDecimal reducePrice; + + @Schema(description = "折扣券-折扣率(0-100)") + private Integer discount; + + @Schema(description = "最低消费金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + private BigDecimal minPrice; + + @Schema(description = "到期类型(10领取后生效 20固定时间)") + private Integer expireType; + + @Schema(description = "领取后生效-有效天数") + private Integer expireDay; + + @Schema(description = "有效期开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "有效期结束时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)") + private Integer applyRange; + + @Schema(description = "适用范围配置(json格式)") + private String applyRangeConfig; + + @Schema(description = "是否过期(0未过期 1已过期)") + private Integer isExpire; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1禁用") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "发放总数量(-1表示无限制)") + private Integer totalCount; + + @Schema(description = "已发放数量") + private Integer issuedCount; + + @Schema(description = "每人限领数量(-1表示无限制)") + private Integer limitPerUser; + + @Schema(description = "是否启用(0禁用 1启用)") + private Boolean enabled; + + @TableField(exist = false) + private List couponApplyItemList; + + @TableField(exist = false) + private List couponApplyCateList; + + @TableField(exist = false) + private Boolean hasTake; + + @TableField(exist = false) + private Integer userTakeNum; + + @TableField(exist = false) + private Integer userUseNum; + + @TableField(exist = false) + private Integer receiveNum; +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyCate.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyCate.java new file mode 100644 index 0000000..26bf30c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyCate.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 优惠券可用分类 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCouponApplyCate对象", description = "优惠券可用分类") +public class ShopCouponApplyCate implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Integer couponId; + + private Integer cateId; + + @Schema(description = "分类等级") + private Integer cateLevel; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyItem.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyItem.java new file mode 100644 index 0000000..504d9d7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyItem.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 优惠券可用分类 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopCouponApplyItem对象", description = "优惠券可用分类") +public class ShopCouponApplyItem implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "优惠券ID") + private Integer couponId; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "分类ID") + private Integer categoryId; + + @Schema(description = "类型(1商品 2分类)") + private Integer type; + + @Schema(description = "0服务1需求2闲置") + private Integer pk; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerApply.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerApply.java new file mode 100644 index 0000000..b605ab2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerApply.java @@ -0,0 +1,89 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商申请记录表 + * + * @author 科技小王子 + * @since 2025-08-11 23:50:18 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerApply对象", description = "分销商申请记录表") +public class ShopDealerApply implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "apply_id", type = IdType.AUTO) + private Integer applyId; + + @Schema(description = "0经销商,1企业也,2集团)") + private Integer type; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "姓名") + private String realName; + + @Schema(description = "分销商名称") + private String dealerName; + + @Schema(description = "分销商编码") + private String dealerCode; + + @Schema(description = "手机号") + private String mobile; + + @Schema(description = "合同金额") + private BigDecimal money; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "推荐人用户ID") + private Integer refereeId; + + @Schema(description = "申请方式(10需后台审核 20无需审核)") + private Integer applyType; + + @Schema(description = "申请时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime applyTime; + + @Schema(description = "审核状态 (10待审核 20审核通过 30驳回)") + private Integer applyStatus; + + @Schema(description = "审核时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime auditTime; + + @Schema(description = "合同时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime contractTime; + + @Schema(description = "驳回原因") + private String rejectReason; + + @Schema(description = "商城ID") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerCapital.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerCapital.java new file mode 100644 index 0000000..06ad58a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerCapital.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商资金明细表 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerCapital对象", description = "分销商资金明细表") +public class ShopDealerCapital implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "分销商用户ID") + private Integer userId; + + @Schema(description = "订单ID") + private Integer orderId; + + @Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)") + private Integer flowType; + + @Schema(description = "金额") + private BigDecimal money; + + @Schema(description = "描述") + private String describe; + + @Schema(description = "对方用户ID") + private Integer toUserId; + + @Schema(description = "商城ID") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java new file mode 100644 index 0000000..9157c32 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商订单记录表 + * + * @author 科技小王子 + * @since 2025-08-12 11:55:18 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerOrder对象", description = "分销商订单记录表") +public class ShopDealerOrder implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "买家用户ID") + private Integer userId; + + @Schema(description = "订单ID") + private Integer orderId; + + @Schema(description = "订单总金额(不含运费)") + private BigDecimal orderPrice; + + @Schema(description = "分销商用户id(一级)") + private Integer firstUserId; + + @Schema(description = "分销商用户id(二级)") + private Integer secondUserId; + + @Schema(description = "分销商用户id(三级)") + private Integer thirdUserId; + + @Schema(description = "分销佣金(一级)") + private BigDecimal firstMoney; + + @Schema(description = "分销佣金(二级)") + private BigDecimal secondMoney; + + @Schema(description = "分销佣金(三级)") + private BigDecimal thirdMoney; + + @Schema(description = "订单是否失效(0未失效 1已失效)") + private Integer isInvalid; + + @Schema(description = "佣金结算(0未结算 1已结算)") + private Integer isSettled; + + @Schema(description = "结算时间") + private LocalDateTime settleTime; + + @Schema(description = "商城ID") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerReferee.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerReferee.java new file mode 100644 index 0000000..4771fdf --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerReferee.java @@ -0,0 +1,73 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商推荐关系表 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerReferee对象", description = "分销商推荐关系表") +public class ShopDealerReferee implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "分销商用户ID") + private Integer dealerId; + + @Schema(description = "分销商名称") + @TableField(exist = false) + private String dealerName; + + @Schema(description = "分销商头像") + @TableField(exist = false) + private String dealerAvatar; + + @Schema(description = "分销商手机号") + @TableField(exist = false) + private String dealerPhone; + + @Schema(description = "用户id(被推荐人)") + private Integer userId; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "手机号") + @TableField(exist = false) + private String phone; + + @Schema(description = "推荐关系层级(1,2,3)") + private Integer level; + + @Schema(description = "商城ID") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerSetting.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerSetting.java new file mode 100644 index 0000000..fd1b2ad --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerSetting.java @@ -0,0 +1,38 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商设置表 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerSetting对象", description = "分销商设置表") +public class ShopDealerSetting implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "设置项标示") + @TableId(value = "key", type = IdType.AUTO) + private String key; + + @Schema(description = "设置项描述") + private String describe; + + @Schema(description = "设置内容(json格式)") + private String values; + + @Schema(description = "商城ID") + private Integer tenantId; + + @Schema(description = "更新时间") + private Integer updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerUser.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerUser.java new file mode 100644 index 0000000..a90d0f1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerUser.java @@ -0,0 +1,88 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商用户记录表 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerUser对象", description = "分销商用户记录表") +public class ShopDealerUser implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "0经销商,1企业也,2集团)") + private Integer type; + + @Schema(description = "自增ID") + private Integer userId; + + @Schema(description = "姓名") + private String realName; + + @Schema(description = "手机号") + private String mobile; + + @Schema(description = "支付密码") + private String payPassword; + + @Schema(description = "当前可提现佣金") + private BigDecimal money; + + @Schema(description = "已冻结佣金") + private BigDecimal freezeMoney; + + @Schema(description = "累积提现佣金") + private BigDecimal totalMoney; + + @Schema(description = "推荐人用户ID") + private Integer refereeId; + + @Schema(description = "成员数量(一级)") + private Integer firstNum; + + @Schema(description = "成员数量(二级)") + private Integer secondNum; + + @Schema(description = "成员数量(三级)") + private Integer thirdNum; + + @Schema(description = "专属二维码") + private String qrcode; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除") + private Integer isDelete; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerWithdraw.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerWithdraw.java new file mode 100644 index 0000000..5435f69 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerWithdraw.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商提现明细表 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopDealerWithdraw对象", description = "分销商提现明细表") +public class ShopDealerWithdraw implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "分销商用户ID") + private Integer userId; + + @Schema(description = "提现金额") + private BigDecimal money; + + @Schema(description = "打款方式 (10微信 20支付宝 30银行卡)") + private Integer payType; + + @Schema(description = "支付宝姓名") + private String alipayName; + + @Schema(description = "支付宝账号") + private String alipayAccount; + + @Schema(description = "开户行名称") + private String bankName; + + @Schema(description = "银行开户名") + private String bankAccount; + + @Schema(description = "银行卡号") + private String bankCard; + + @Schema(description = "申请状态 (10待审核 20审核通过 30驳回 40已打款)") + private Integer applyStatus; + + @Schema(description = "审核时间") + private Integer auditTime; + + @Schema(description = "驳回原因") + private String rejectReason; + + @Schema(description = "来源客户端(APP、H5、小程序等)") + private String platform; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopExpress.java b/src/main/java/com/gxwebsoft/shop/entity/ShopExpress.java new file mode 100644 index 0000000..9f0c5fe --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopExpress.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 物流公司 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopExpress对象", description = "物流公司") +public class ShopExpress implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "物流公司ID") + @TableId(value = "express_id", type = IdType.AUTO) + private Integer expressId; + + @Schema(description = "物流公司名称") + private String expressName; + + @Schema(description = "物流公司编码 (微信)") + private String wxCode; + + @Schema(description = "物流公司编码 (快递100)") + private String kuaidi100Code; + + @Schema(description = "物流公司编码 (快递鸟)") + private String kdniaoCode; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplate.java b/src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplate.java new file mode 100644 index 0000000..1f5b8fa --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplate.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 运费模板 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopExpressTemplate对象", description = "运费模板") +public class ShopExpressTemplate implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Boolean type; + + private String title; + + @Schema(description = "收件价格") + private BigDecimal firstAmount; + + @Schema(description = "续件价格") + private BigDecimal extraAmount; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + private Integer sortNumber; + + @Schema(description = "首件数量/重量") + private BigDecimal firstNum; + + @Schema(description = "续件数量/重量") + private BigDecimal extraNum; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplateDetail.java b/src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplateDetail.java new file mode 100644 index 0000000..7d8d3ec --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopExpressTemplateDetail.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 运费模板 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopExpressTemplateDetail对象", description = "运费模板") +public class ShopExpressTemplateDetail implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Integer templateId; + + @Schema(description = "0按件") + private Boolean type; + + private Integer provinceId; + + private Integer cityId; + + @Schema(description = "首件数量/重量") + private BigDecimal firstNum; + + @Schema(description = "收件价格") + private BigDecimal firstAmount; + + @Schema(description = "续件价格") + private BigDecimal extraAmount; + + @Schema(description = "续件数量/重量") + private BigDecimal extraNum; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGift.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGift.java new file mode 100644 index 0000000..261c980 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGift.java @@ -0,0 +1,112 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 礼品卡 + * + * @author 科技小王子 + * @since 2025-08-11 18:07:31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGift对象", description = "礼品卡") +public class ShopGift implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "礼品卡名称") + private String name; + + @Schema(description = "秘钥") + private String code; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "商品名称") + @TableField(exist = false) + private String goodsName; + + @Schema(description = "商品图片") + @TableField(exist = false) + private String goodsImage; + + @Schema(description = "面值") + @TableField(exist = false) + private BigDecimal faceValue; + + @Schema(description = "使用地点") + private String useLocation; + + @Schema(description = "领取时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime takeTime; + + @Schema(description = "核销时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime verificationTime; + + @Schema(description = "操作人ID") + private Integer operatorUserId; + + @Schema(description = "操作人") + @TableField(exist = false) + private String operatorUserName; + + @Schema(description = "操作备注") + private String operatorRemarks; + + @Schema(description = "是否展示") + private Boolean isShow; + + @Schema(description = "状态, 0上架 1待上架 2待审核 3审核不通过") + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickName; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @TableField(exist = false) + private Integer num; + + @TableField(exist = false) + private ShopGoods goods; +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java new file mode 100644 index 0000000..0e2935d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java @@ -0,0 +1,144 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoods对象", description = "商品") +public class ShopGoods implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "goods_id", type = IdType.AUTO) + private Integer goodsId; + + @Schema(description = "商品名称") + private String name; + + @Schema(description = "产品编码") + private String code; + + @Schema(description = "类型 0软件产品 1实物商品 2虚拟商品") + private Integer type; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "父级分类ID") + private Integer parentId; + + @Schema(description = "产品分类ID") + private Integer categoryId; + + @Schema(description = "路由地址") + private String path; + + @Schema(description = "标签") + private String tag; + + @Schema(description = "产品规格 0单规格 1多规格") + private Integer specs; + + @Schema(description = "货架") + private String position; + + @Schema(description = "单位名称 (个)") + private String unitName; + + @Schema(description = "商品价格") + private BigDecimal price; + + @Schema(description = "进货价格") + private BigDecimal buyingPrice; + + @Schema(description = "经销商价格") + private BigDecimal dealerPrice; + + @Schema(description = "佣金") + private BigDecimal commission; + + @Schema(description = "库存计算方式(10下单减库存 20付款减库存)") + private Integer deductStockType; + + @Schema(description = "交付方式(0不启用)") + private Integer deliveryMethod; + + @Schema(description = "购买时长(0不启用,1 一次性,2 按时长)") + private Integer durationMethod; + + @Schema(description = "可购买数量") + private Integer canBuyNumber; + + @Schema(description = "商品详情") + private String content; + + @Schema(description = "轮播图") + private String files; + + @Schema(description = "销量") + private Integer sales; + + @Schema(description = "库存") + private Integer stock; + + @Schema(description = "安装次数") + private Integer install; + + @Schema(description = "评分") + private BigDecimal rate; + + @Schema(description = "消费赚取积分") + private BigDecimal gainIntegral; + + @Schema(description = "推荐") + private Integer recommend; + + @Schema(description = "是否官方") + private Integer official; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "是否展示") + private Boolean isShow; + + @Schema(description = "状态, 0上架 1待上架 2待审核 3审核不通过") + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsCategory.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsCategory.java new file mode 100644 index 0000000..6b902cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsCategory.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品分类 + * + * @author 科技小王子 + * @since 2025-05-01 00:36:45 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsCategory对象", description = "商品分类") +public class ShopGoodsCategory implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "商品分类ID") + @TableId(value = "category_id", type = IdType.AUTO) + private Integer categoryId; + + @Schema(description = "分类标识") + private String categoryCode; + + @Schema(description = "分类名称") + private String title; + + @Schema(description = "类型 0商城分类 1外卖分类") + private Integer type; + + @Schema(description = "分类图片") + private String image; + + @Schema(description = "上级分类ID") + private Integer parentId; + + @Schema(description = "路由/链接地址") + private String path; + + @Schema(description = "组件路径") + private String component; + + @Schema(description = "绑定的页面") + private Integer pageId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "商品数量") + private Integer count; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + private Integer hide; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "是否显示在首页") + private Integer showIndex; + + @Schema(description = "商铺ID") + private Long merchantId; + + @Schema(description = "状态, 0正常, 1禁用") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsComment.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsComment.java new file mode 100644 index 0000000..e6ebc8e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsComment.java @@ -0,0 +1,99 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 评论表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsComment对象", description = "评论表") +public class ShopGoodsComment implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "评论ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户ID") + private Integer uid; + + @Schema(description = "订单ID") + private Integer oid; + + @Schema(description = "商品唯一id") + private String unique; + + @Schema(description = "商品id") + private Integer goodsId; + + @Schema(description = "某种商品类型(普通商品、秒杀商品)") + private String replyType; + + @Schema(description = "商品分数") + private Boolean goodsScore; + + @Schema(description = "服务分数") + private Boolean serviceScore; + + @Schema(description = "评论内容") + private String comment; + + @Schema(description = "评论图片") + private String pics; + + @Schema(description = "管理员回复内容") + private String merchantReplyContent; + + @Schema(description = "管理员回复时间") + private Integer merchantReplyTime; + + @Schema(description = "0未删除1已删除") + private Boolean isDel; + + @Schema(description = "0未回复1已回复") + private Boolean isReply; + + @Schema(description = "用户名称") + private String nickname; + + @Schema(description = "用户头像") + private String avatar; + + @Schema(description = "商品规格属性值,多个,号隔开") + private String sku; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsIncomeConfig.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsIncomeConfig.java new file mode 100644 index 0000000..9b4da71 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsIncomeConfig.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分润配置 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsIncomeConfig对象", description = "分润配置") +public class ShopGoodsIncomeConfig implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Integer goodsId; + + @Schema(description = "店铺类型") + private String merchantShopType; + + private Integer skuId; + + @Schema(description = "比例") + private BigDecimal rate; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsLog.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsLog.java new file mode 100644 index 0000000..13ac171 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsLog.java @@ -0,0 +1,86 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品日志表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsLog对象", description = "商品日志表") +public class ShopGoodsLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "统计ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "类型visit,cart,order,pay,collect,refund") + private String type; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "是否浏览") + private Boolean visitNum; + + @Schema(description = "加入购物车数量") + private Integer cartNum; + + @Schema(description = "下单数量") + private Integer orderNum; + + @Schema(description = "支付数量") + private Integer payNum; + + @Schema(description = "支付金额") + private BigDecimal payPrice; + + @Schema(description = "商品成本价") + private BigDecimal costPrice; + + @Schema(description = "支付用户ID") + private Integer payUid; + + @Schema(description = "退款数量") + private Integer refundNum; + + @Schema(description = "退款金额") + private BigDecimal refundPrice; + + @Schema(description = "收藏") + private Boolean collectNum; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRelation.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRelation.java new file mode 100644 index 0000000..c615929 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRelation.java @@ -0,0 +1,53 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品点赞和收藏表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsRelation对象", description = "商品点赞和收藏表") +public class ShopGoodsRelation implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "类型(收藏(collect)、点赞(like))") + private String type; + + @Schema(description = "某种类型的商品(普通商品、秒杀商品)") + private String category; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRoleCommission.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRoleCommission.java new file mode 100644 index 0000000..7e926cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsRoleCommission.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品绑定角色的分润金额 + * + * @author 科技小王子 + * @since 2025-05-01 09:53:38 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsRoleCommission对象", description = "商品绑定角色的分润金额") +public class ShopGoodsRoleCommission implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Integer roleId; + + private Integer goodsId; + + private String sku; + + private BigDecimal amount; + + @Schema(description = "状态, 0正常, 1异常") + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSku.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSku.java new file mode 100644 index 0000000..a57666a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSku.java @@ -0,0 +1,79 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品sku列表 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsSku对象", description = "商品sku列表") +public class ShopGoodsSku implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "商品属性索引值 (attr_value|attr_value[|....])") + private String sku; + + @Schema(description = "商品图片") + private String image; + + @Schema(description = "商品价格") + private BigDecimal price; + + @Schema(description = "市场价格") + private BigDecimal salePrice; + + @Schema(description = "成本价") + private BigDecimal cost; + + @Schema(description = "库存") + private Integer stock; + + @Schema(description = "sku编码") + private String skuNo; + + @Schema(description = "商品条码") + private String barCode; + + @Schema(description = "重量") + private BigDecimal weight; + + @Schema(description = "体积") + private BigDecimal volume; + + @Schema(description = "唯一值") + private String uuid; + + @Schema(description = "状态, 0正常, 1异常") + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSpec.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSpec.java new file mode 100644 index 0000000..4885e73 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoodsSpec.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品多规格 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopGoodsSpec对象", description = "商品多规格") +public class ShopGoodsSpec implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "规格ID") + private Integer specId; + + @Schema(description = "规格名称") + private String specName; + + @Schema(description = "规格值") + private String specValue; + + @Schema(description = "活动类型 0=商品,1=秒杀,2=砍价,3=拼团") + private Boolean type; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopMerchant.java b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchant.java new file mode 100644 index 0000000..1915909 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchant.java @@ -0,0 +1,145 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户 + * + * @author 科技小王子 + * @since 2025-08-10 20:43:33 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopMerchant对象", description = "商户") +public class ShopMerchant implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "merchant_id", type = IdType.AUTO) + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商户编号") + private String merchantCode; + + @Schema(description = "商户类型") + private Integer type; + + @Schema(description = "商户图标") + private String image; + + @Schema(description = "商户手机号") + private String phone; + + @Schema(description = "商户姓名") + private String realName; + + @Schema(description = "店铺类型") + private String shopType; + + @Schema(description = "项目分类") + private String itemType; + + @Schema(description = "商户分类") + private String category; + + @Schema(description = "商户经营分类") + private Integer merchantCategoryId; + + @Schema(description = "商户分类") + private String merchantCategoryTitle; + + @Schema(description = "经纬度") + private String lngAndLat; + + private String lng; + + private String lat; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "手续费") + private BigDecimal commission; + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "资质图片") + private String files; + + @Schema(description = "营业时间") + private String businessTime; + + @Schema(description = "文章内容") + private String content; + + @Schema(description = "每小时价格") + private BigDecimal price; + + @Schema(description = "是否自营") + private Integer ownStore; + + @Schema(description = "是否可以快递") + private Boolean canExpress; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "是否营业") + private Integer isOn; + + private String startTime; + + private String endTime; + + @Schema(description = "是否需要审核") + private Integer goodsReview; + + @Schema(description = "管理入口") + private String adminUrl; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "所有人") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantAccount.java b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantAccount.java new file mode 100644 index 0000000..308818b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantAccount.java @@ -0,0 +1,68 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户账号 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopMerchantAccount对象", description = "商户账号") +public class ShopMerchantAccount implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "商户手机号") + private String phone; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "角色ID") + private Integer roleId; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "密码") + @TableField(exist = false) + private String password; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantApply.java b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantApply.java new file mode 100644 index 0000000..461e67f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantApply.java @@ -0,0 +1,134 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户入驻申请 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopMerchantApply对象", description = "商户入驻申请") +public class ShopMerchantApply implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "apply_id", type = IdType.AUTO) + private Integer applyId; + + @Schema(description = "类型") + private Integer type; + + @Schema(description = "店铺类型") + private String shopType; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商户图标") + private String image; + + @Schema(description = "商户手机号") + private String phone; + + @Schema(description = "商户姓名") + private String realName; + + @Schema(description = "社会信用代码") + private String merchantCode; + + @Schema(description = "身份证号码") + private String idCard; + + @Schema(description = "身份证正面") + private String sfz1; + + @Schema(description = "身份证反面") + private String sfz2; + + @Schema(description = "营业执照") + private String yyzz; + + @Schema(description = "行业父级分类") + private Integer parentId; + + @Schema(description = "行业分类ID") + private Integer categoryId; + + @Schema(description = "行业分类") + private String category; + + @Schema(description = "手续费") + private BigDecimal commission; + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "资质图片") + private String files; + + @Schema(description = "所有人") + private Integer userId; + + @Schema(description = "是否自营") + private Integer ownStore; + + @Schema(description = "是否推荐") + private Integer recommend; + + @Schema(description = "是否需要审核") + private Integer goodsReview; + + @Schema(description = "工作负责人") + private String name2; + + @Schema(description = "驳回原因") + private String reason; + + @Schema(description = "审核完成时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime completedTime; + + @Schema(description = "审核状态") + private Boolean checkStatus; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "应用名称") + @TableField(exist = false) + private String tenantName; + + @Schema(description = "应用图标") + @TableField(exist = false) + private String logo; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantType.java b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantType.java new file mode 100644 index 0000000..f847eff --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopMerchantType.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户类型 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopMerchantType对象", description = "商户类型") +public class ShopMerchantType implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "店铺类型") + private String name; + + @Schema(description = "店铺入驻条件") + private String comments; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrder.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrder.java new file mode 100644 index 0000000..3a32938 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrder.java @@ -0,0 +1,308 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import java.util.List; + +import com.gxwebsoft.bszx.entity.BszxBm; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.*; + +/** + * 订单 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrder对象", description = "订单") +public class ShopOrder implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "订单号") + @TableId(value = "order_id", type = IdType.AUTO) + private Integer orderId; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "订单类型,0商城订单 1预定订单/外卖 2会员卡") + private Integer type; + + @Schema(description = "订单标题") + private String title; + + @Schema(description = "快递/自提") + private Integer deliveryType; + + @Schema(description = "下单渠道,0小程序预定 1俱乐部训练场 3活动订场") + private Integer channel; + + @Schema(description = "微信支付订单号") + private String transactionId; + + @Schema(description = "微信退款订单号") + private String refundOrder; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商户编号") + private String merchantCode; + + @Schema(description = "使用的优惠券id") + private Integer couponId; + + @Schema(description = "使用的会员卡id") + private String cardId; + + @Schema(description = "关联管理员id") + private Integer adminId; + + @Schema(description = "核销管理员id") + private Integer confirmId; + + @Schema(description = "IC卡号") + private String icCard; + + @Schema(description = "收货人id") + private Integer addressId; + + @Schema(description = "收货地址") + private String address; + + private String addressLat; + + private String addressLng; + + @Schema(description = "买家备注") + private String buyerRemarks; + + @Schema(description = "自提店铺id") + private Integer selfTakeMerchantId; + + @Schema(description = "自提店铺") + private String selfTakeMerchantName; + + @Schema(description = "配送开始时间") + private String sendStartTime; + + @Schema(description = "配送结束时间") + private String sendEndTime; + + @Schema(description = "发货店铺id") + private Integer expressMerchantId; + + @Schema(description = "发货店铺") + private String expressMerchantName; + + @Schema(description = "订单总额") + @NotNull(message = "订单总额不能为空") + @DecimalMin(value = "0.01", message = "订单总额必须大于0") + @Digits(integer = 10, fraction = 2, message = "订单总额格式不正确") + private BigDecimal totalPrice; + + @Schema(description = "减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格") + @DecimalMin(value = "0", message = "减少金额不能为负数") + private BigDecimal reducePrice; + + @Schema(description = "实际付款") + @DecimalMin(value = "0", message = "实际付款不能为负数") + private BigDecimal payPrice; + + @Schema(description = "用于统计") + private BigDecimal price; + + @Schema(description = "价钱,用于积分赠送") + private BigDecimal money; + + @Schema(description = "退款金额") + private BigDecimal refundMoney; + + @Schema(description = "教练价格") + private BigDecimal coachPrice; + + @Schema(description = "购买数量") + @Min(value = 1, message = "购买数量必须大于0") + private Integer totalNum; + + @Schema(description = "教练id") + private Integer coachId; + + @Schema(description = "来源ID,存商品ID") + private Integer formId; + + @Schema(description = "支付的用户id") + private Integer payUserId; + + @Schema(description = "支付方式:0余额支付,1微信支付,2支付宝支付,3银联支付,4现金支付,5POS机支付,6免费,7积分支付") + private Integer payType; + + @Schema(description = "微信支付子类型:JSAPI小程序支付,NATIVE扫码支付") + private String wechatPayType; + + @Schema(description = "代付支付方式:0余额支付,1微信支付,2支付宝支付,3银联支付,4现金支付,5POS机支付,6免费,7积分支付") + private Integer friendPayType; + + @Schema(description = "0未付款,1已付款") + private Boolean payStatus; + + @Schema(description = "0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款") + private Integer orderStatus; + + @Schema(description = "发货状态(10未发货 20已发货 30部分发货)") + private Integer deliveryStatus; + + @Schema(description = "发货备注") + private String deliveryNote; + + @Schema(description = "发货时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deliveryTime; + + @Schema(description = "优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡") + private Integer couponType; + + @Schema(description = "优惠说明") + private String couponDesc; + + @Schema(description = "二维码地址,保存订单号,支付成功后才生成") + private String qrcode; + + @Schema(description = "vip月卡年卡、ic月卡年卡回退次数") + private Integer returnNum; + + @Schema(description = "vip充值回退金额") + private BigDecimal returnMoney; + + @Schema(description = "预约详情开始时间数组") + private String startTime; + + @Schema(description = "是否已开具发票:0未开发票,1已开发票,2不能开具发票") + private Integer isInvoice; + + @Schema(description = "发票流水号") + private String invoiceNo; + + @Schema(description = "支付时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime payTime; + + @Schema(description = "退款时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime refundTime; + + @Schema(description = "申请退款时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime refundApplyTime; + + @Schema(description = "取消时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime cancelTime; + + @Schema(description = "取消原因") + private String cancelReason; + + @Schema(description = "过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "评价状态 0未评价 1已评价") + private Integer evaluateStatus; + + @Schema(description = "评价时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime evaluateTime; + + @Schema(description = "对账情况:0=未对账;1=已对账;3=已对账,金额对不上;4=未查询到该订单") + private Integer checkBill; + + @Schema(description = "订单是否已结算(0未结算 1已结算)") + private Integer isSettled; + + @Schema(description = "商户备注") + private String merchantRemarks; + + @Schema(description = "系统版本号 0当前版本 value=其他版本") + private Integer version; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "手机号码") + @TableField(exist = false) + private String phone; + + @Schema(description = "手机号码(脱敏)") + @TableField(exist = false) + private String mobile; + + @Schema(description = "备注") + @Size(max = 500, message = "备注长度不能超过500字符") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + @NotNull(message = "租户ID不能为空") + private Integer tenantId; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "自提码") + private String selfTakeCode; + + @Schema(description = "是否已收到赠品") + private Boolean hasTakeGift; + + @Schema(description = "accessToken") + @TableField(exist = false) + private String accessToken; + + @Schema(description = "openid") + @TableField(exist = false) + private String openid; + + @Schema(description = "订单商品") + @TableField(exist = false) + private List orderGoods; + + @Schema(description = "报名信息") + @TableField(exist = false) + private BszxBm bm; +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrderDelivery.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderDelivery.java new file mode 100644 index 0000000..6794a9d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderDelivery.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 发货单 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrderDelivery对象", description = "发货单") +public class ShopOrderDelivery implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "发货单ID") + @TableId(value = "delivery_id", type = IdType.AUTO) + private Integer deliveryId; + + @Schema(description = "订单ID") + private Integer orderId; + + @Schema(description = "发货方式(10手动录入 20无需物流 30电子面单)") + private Integer deliveryMethod; + + @Schema(description = "打包方式(废弃)") + private Integer packMethod; + + @Schema(description = "物流公司ID") + private Integer expressId; + + @Schema(description = "物流单号") + private String expressNo; + + @Schema(description = "电子面单模板内容") + private String eorderHtml; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrderDeliveryGoods.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderDeliveryGoods.java new file mode 100644 index 0000000..e990a06 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderDeliveryGoods.java @@ -0,0 +1,63 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 发货单商品 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrderDeliveryGoods对象", description = "发货单商品") +public class ShopOrderDeliveryGoods implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "发货单ID") + private Integer deliveryId; + + @Schema(description = "订单ID") + private Integer orderId; + + @Schema(description = "订单商品ID") + private Integer orderGoodsId; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "发货数量") + private Integer deliveryNum; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrderExtract.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderExtract.java new file mode 100644 index 0000000..4d3d39b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderExtract.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 自提订单联系方式 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrderExtract对象", description = "自提订单联系方式") +public class ShopOrderExtract implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "订单ID") + private Integer orderId; + + @Schema(description = "联系人姓名") + private String linkman; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrderGoods.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderGoods.java new file mode 100644 index 0000000..4b19486 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderGoods.java @@ -0,0 +1,115 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalTime; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品信息 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrderGoods对象", description = "商品信息") +public class ShopOrderGoods implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "关联订单表id") + private Integer orderId; + + @Schema(description = "订单标识") + private String orderCode; + + @Schema(description = "关联商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商品封面图") + private String image; + + @Schema(description = "关联商品id") + private Integer goodsId; + + @Schema(description = "商品名称") + private String goodsName; + + @Schema(description = "商品规格") + private String spec; + + private Integer skuId; + + @Schema(description = "单价") + private BigDecimal price; + + @Schema(description = "购买数量") + private Integer totalNum; + + @Schema(description = "0 未付款 1已付款,2无需付款或占用状态") + private Integer payStatus; + + @Schema(description = "0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款") + private Integer orderStatus; + + @Schema(description = "是否免费:0收费、1免费") + private Boolean isFree; + + @Schema(description = "系统版本 0当前版本 其他版本") + private Integer version; + + @Schema(description = "预约时间段") + private String timePeriod; + + @Schema(description = "预定日期") + private LocalDate dateTime; + + @Schema(description = "开场时间") + private LocalTime startTime; + + @Schema(description = "结束时间") + private LocalTime endTime; + + @Schema(description = "毫秒时间戳") + private Long timeFlag; + + @Schema(description = "过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfo.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfo.java new file mode 100644 index 0000000..9c26566 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfo.java @@ -0,0 +1,122 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalTime; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 场地 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrderInfo对象", description = "场地") +public class ShopOrderInfo implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "关联订单表id") + private Integer orderId; + + @Schema(description = "组合数据:日期+时间段+场馆id+场地id") + private String orderCode; + + @Schema(description = "关联商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "关联场地id") + private Integer fieldId; + + @Schema(description = "场地名称") + private String fieldName; + + @Schema(description = "单价") + private BigDecimal price; + + @Schema(description = "儿童价") + private BigDecimal childrenPrice; + + @Schema(description = "成人人数") + private Integer adultNum; + + @Schema(description = "儿童人数") + private Integer childrenNum; + + @Schema(description = "已核销的成人票数") + private Integer adultNumUse; + + @Schema(description = "已核销的儿童票数") + private Integer childrenNumUse; + + @Schema(description = "0 未付款 1已付款,2无需付款或占用状态") + private Integer payStatus; + + @Schema(description = "0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款") + private Integer orderStatus; + + @Schema(description = "是否免费:0收费、1免费") + private Boolean isFree; + + @Schema(description = "是否支持儿童票:0不支持、1支持") + private Boolean isChildren; + + @Schema(description = "系统版本 0当前版本 其他版本") + private Integer version; + + @Schema(description = "预订类型:0全场,1半场") + private Boolean isHalf; + + @Schema(description = "预约时间段") + private String timePeriod; + + @Schema(description = "预定日期") + private LocalDate dateTime; + + @Schema(description = "开场时间") + private LocalTime startTime; + + @Schema(description = "结束时间") + private LocalTime endTime; + + @Schema(description = "毫秒时间戳") + private Long timeFlag; + + @Schema(description = "过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expirationTime; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户id") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfoLog.java b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfoLog.java new file mode 100644 index 0000000..0d026ba --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopOrderInfoLog.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 订单核销 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopOrderInfoLog对象", description = "订单核销") +public class ShopOrderInfoLog implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "关联订单表id") + private Integer orderId; + + @Schema(description = "关联商户ID") + private Long merchantId; + + @Schema(description = "关联场地id") + private Integer fieldId; + + @Schema(description = "核销数量") + private Boolean useNum; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopRechargeOrder.java b/src/main/java/com/gxwebsoft/shop/entity/ShopRechargeOrder.java new file mode 100644 index 0000000..87592a8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopRechargeOrder.java @@ -0,0 +1,103 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 会员充值订单表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopRechargeOrder对象", description = "会员充值订单表") +public class ShopRechargeOrder implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "订单ID") + @TableId(value = "order_id", type = IdType.AUTO) + private Integer orderId; + + @Schema(description = "订单号") + private String orderNo; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "充值方式(10自定义金额 20套餐充值)") + private Integer rechargeType; + + @Schema(description = "机构id") + private Integer organizationId; + + @Schema(description = "充值套餐ID") + private Integer planId; + + @Schema(description = "用户支付金额") + private BigDecimal payPrice; + + @Schema(description = "赠送金额") + private BigDecimal giftMoney; + + @Schema(description = "实际到账金额") + private BigDecimal actualMoney; + + @Schema(description = "用户可用余额") + private BigDecimal balance; + + @Schema(description = "支付方式(微信/支付宝)") + private String payMethod; + + @Schema(description = "支付状态(10待支付 20已支付)") + private Integer payStatus; + + @Schema(description = "付款时间") + private Integer payTime; + + @Schema(description = "第三方交易记录ID") + private Integer tradeId; + + @Schema(description = "来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "所属门店ID") + private Integer shopId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopSpec.java b/src/main/java/com/gxwebsoft/shop/entity/ShopSpec.java new file mode 100644 index 0000000..8c32f26 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopSpec.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 规格 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopSpec对象", description = "规格") +public class ShopSpec implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "规格ID") + @TableId(value = "spec_id", type = IdType.AUTO) + private Integer specId; + + @Schema(description = "规格名称") + private String specName; + + @Schema(description = "规格值") + private String specValue; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "创建用户") + private Integer userId; + + @Schema(description = "更新者") + private Integer updater; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1待修,2异常已修,3异常未修") + private Integer status; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopSpecValue.java b/src/main/java/com/gxwebsoft/shop/entity/ShopSpecValue.java new file mode 100644 index 0000000..2cfe2c2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopSpecValue.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 规格值 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopSpecValue对象", description = "规格值") +public class ShopSpecValue implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "规格值ID") + @TableId(value = "spec_value_id", type = IdType.AUTO) + private Integer specValueId; + + @Schema(description = "规格组ID") + private Integer specId; + + @Schema(description = "规格值") + private String specValue; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopSplash.java b/src/main/java/com/gxwebsoft/shop/entity/ShopSplash.java new file mode 100644 index 0000000..1bb2655 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopSplash.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 开屏广告 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopSplash对象", description = "开屏广告") +public class ShopSplash implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "标题") + private String title; + + @Schema(description = "图片") + private String image; + + @Schema(description = "跳转类型") + private String jumpType; + + @Schema(description = "跳转主键") + private Integer jumpPk; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUserAddress.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUserAddress.java new file mode 100644 index 0000000..a033356 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUserAddress.java @@ -0,0 +1,80 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 收货地址 + * + * @author 科技小王子 + * @since 2025-07-22 23:06:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopUserAddress对象", description = "收货地址") +public class ShopUserAddress implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "收货地址") + private String address; + + @Schema(description = "收货地址") + private String fullAddress; + + private String lat; + + private String lng; + + @Schema(description = "1先生 2女士") + private Integer gender; + + @Schema(description = "家、公司、学校") + private String type; + + @Schema(description = "默认收货地址") + private Boolean isDefault; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUserBalanceLog.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUserBalanceLog.java new file mode 100644 index 0000000..77e41df --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUserBalanceLog.java @@ -0,0 +1,82 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户余额变动明细表 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopUserBalanceLog对象", description = "用户余额变动明细表") +public class ShopUserBalanceLog implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "log_id", type = IdType.AUTO) + private Integer logId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "余额变动场景(0下级下单1供应商收入2差价收益 10用户充值 20用户消费 30管理员操作 40订单退款)") + private Integer scene; + + @Schema(description = "变动金额") + private BigDecimal money; + + @Schema(description = "变动后余额") + private BigDecimal balance; + + @Schema(description = "管理员备注") + private String remark; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "操作人ID") + private Integer adminId; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "商户编码") + private String merchantCode; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUserCollection.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUserCollection.java new file mode 100644 index 0000000..6f89cf8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUserCollection.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 我的收藏 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopUserCollection对象", description = "我的收藏") +public class ShopUserCollection implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "0店铺,1商品") + private Boolean type; + + @Schema(description = "租户ID") + private Integer tid; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java new file mode 100644 index 0000000..d25ad57 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java @@ -0,0 +1,210 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户优惠券 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopUserCoupon对象", description = "用户优惠券") +public class ShopUserCoupon implements Serializable { + private static final long serialVersionUID = 1L; + + // 优惠券类型常量 + public static final int TYPE_REDUCE = 10; // 满减券 + public static final int TYPE_DISCOUNT = 20; // 折扣券 + public static final int TYPE_FREE = 30; // 免费券 + + // 使用状态常量 + public static final int STATUS_UNUSED = 0; // 未使用 + public static final int STATUS_USED = 1; // 已使用 + public static final int STATUS_EXPIRED = 2; // 已过期 + + // 获取方式常量 + public static final int OBTAIN_ACTIVE = 10; // 主动领取 + public static final int OBTAIN_SYSTEM = 20; // 系统发放 + public static final int OBTAIN_ACTIVITY = 30; // 活动赠送 + + // 适用范围常量 + public static final int APPLY_ALL = 10; // 全部商品 + public static final int APPLY_GOODS = 20; // 指定商品 + public static final int APPLY_CATEGORY = 30; // 指定分类 + + @Schema(description = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "优惠券模板ID") + private Integer couponId; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "优惠券名称") + private String name; + + @Schema(description = "优惠券描述") + private String description; + + @Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)") + private Integer type; + + @Schema(description = "满减券-减免金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + private BigDecimal reducePrice; + + @Schema(description = "折扣券-折扣率(0-100)") + private Integer discount; + + @Schema(description = "最低消费金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + private BigDecimal minPrice; + + @Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)") + private Integer applyRange; + + @Schema(description = "到期类型(10领取后生效 20固定时间)") + private Integer expireType; + + @Schema(description = "领取后生效-有效天数") + private Integer expireDay; + + @Schema(description = "适用范围配置(json格式)") + private String applyRangeConfig; + + @Schema(description = "是否过期(0未过期 1已过期)") + private Integer isExpire; + + @Schema(description = "有效期开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "有效期结束时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + @Schema(description = "使用状态(0未使用 1已使用 2已过期)") + private Integer status; + + @Schema(description = "使用时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime useTime; + + @Schema(description = "使用订单ID") + private Integer orderId; + + @Schema(description = "是否已使用") + private Integer isUse; + + @Schema(description = "使用订单号") + private String orderNo; + + @Schema(description = "获取方式(10主动领取 20系统发放 30活动赠送)") + private Integer obtainType; + + @Schema(description = "获取来源描述") + private String obtainSource; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Boolean deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @TableField(exist = false) + private ShopCoupon couponItem; + + /** + * 判断优惠券是否可用 + * @return true-可用,false-不可用 + */ + public boolean isAvailable() { + return this.status != null && this.status == STATUS_UNUSED && !isExpired(); + } + + /** + * 判断优惠券是否已使用 + * @return true-已使用,false-未使用 + */ + public boolean isUsed() { + return this.status != null && this.status == STATUS_USED; + } + + /** + * 判断优惠券是否已过期 + * @return true-已过期,false-未过期 + */ + public boolean isExpired() { + if (this.status != null && this.status == STATUS_EXPIRED) { + return true; + } + return this.endTime != null && this.endTime.isBefore(LocalDateTime.now()); + } + + /** + * 获取优惠券状态描述 + * @return 状态描述 + */ + public String getStatusDesc() { + if (isExpired()) { + return "已过期"; + } else if (isUsed()) { + return "已使用"; + } else if (isAvailable()) { + return "可使用"; + } else { + return "未知状态"; + } + } + + /** + * 更新优惠券状态为已使用 + * @param orderId 订单ID + * @param orderNo 订单号 + */ + public void markAsUsed(Integer orderId, String orderNo) { + this.status = STATUS_USED; + this.isUse = 1; + this.useTime = LocalDateTime.now(); + this.orderId = orderId; + this.orderNo = orderNo; + } + + /** + * 更新优惠券状态为已过期 + */ + public void markAsExpired() { + this.status = STATUS_EXPIRED; + this.isExpire = 1; + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUserReferee.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUserReferee.java new file mode 100644 index 0000000..231d023 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUserReferee.java @@ -0,0 +1,81 @@ +package com.gxwebsoft.shop.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户推荐关系表 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopUserReferee对象", description = "用户推荐关系表") +public class ShopUserReferee implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "推荐人ID") + private Integer dealerId; + + @Schema(description = "分销商名称") + @TableField(exist = false) + private String dealerName; + + @Schema(description = "分销商头像") + @TableField(exist = false) + private String dealerAvatar; + + @Schema(description = "分销商手机号") + @TableField(exist = false) + private String dealerPhone; + + @Schema(description = "用户id(被推荐人)") + private Integer userId; + + @Schema(description = "昵称") + @TableField(exist = false) + private String nickname; + + @Schema(description = "头像") + @TableField(exist = false) + private String avatar; + + @Schema(description = "手机号") + @TableField(exist = false) + private String phone; + + @Schema(description = "推荐关系层级(1,2,3)") + private Integer level; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUsers.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUsers.java new file mode 100644 index 0000000..e14c35e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUsers.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopUsers对象", description = "") +public class ShopUsers implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "用户唯一小程序id") + private String openId; + + @Schema(description = "小程序用户秘钥") + private String sessionKey; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "头像地址") + private String avatarUrl; + + @Schema(description = "1男,2女") + private Boolean gender; + + @Schema(description = "国家") + private String country; + + @Schema(description = "省份") + private String province; + + @Schema(description = "城市") + private String city; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "积分") + private BigDecimal integral; + + @Schema(description = "余额") + private BigDecimal money; + + @Schema(description = "排序号") + private Integer sortNumber; + + @Schema(description = "注册时间") + private Integer createTime; + + private String idCard; + + private String realName; + + @Schema(description = "是否管理员:1是;2否") + private Boolean isAdmin; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopWechatDeposit.java b/src/main/java/com/gxwebsoft/shop/entity/ShopWechatDeposit.java new file mode 100644 index 0000000..1b6100c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopWechatDeposit.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.shop.entity; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 押金 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "ShopWechatDeposit对象", description = "押金") +public class ShopWechatDeposit implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "订单id") + private Integer oid; + + @Schema(description = "用户id") + private Integer uid; + + @Schema(description = "场地订单号") + private String orderNum; + + @Schema(description = "付款订单号") + private String wechatOrder; + + @Schema(description = "退款订单号 ") + private String wechatReturn; + + @Schema(description = "场馆名称") + private String siteName; + + @Schema(description = "微信昵称") + private String username; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "物品名称") + private String name; + + @Schema(description = "押金金额") + private BigDecimal price; + + @Schema(description = "押金状态,1已付款,2未付款,已退押金") + private Boolean status; + + @Schema(description = "创建时间") + private Integer createTime; + + @Schema(description = "租户id") + private Integer tenantId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.class b/src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.class new file mode 100644 index 0000000000000000000000000000000000000000..48a4a6c36ea74e14b9b7573c1bff54b9d41a67b2 GIT binary patch literal 2066 zcmb7_%Trr*6vw|eulpmtz-@T6P)ZfCCZRO8TC|2zVsg=FAjsumtV%<;q`{DMl2BbT zqjkD)raIGwj+-u=u?xr6^Z_Fy)3tv`VanRQ)t?e&_i+=brre_wO43 zF5s#PhXQ}5xD?8M@wqiuE-q9;*+*X?bnelgPm4Fd1bNNjFg-@0}8$@=#Kn!tNIWb@{CPapg&&@3=o)49F2 zeQQO)Coo>Odc-_HE6NRPPUH&K*z(exRhmrC<*9{3 z``lUJ;KNKQo4IU(mO_b~`R&gB>$a(;tX0{`sqMMJuUqYC#CU$5e>G|?qxW<$ldp}e z(;uK49<()9j`!ff>lg|v@S4TtQpOs|$$M$tv+hpI@f()P_6c<^xF55@Shw zizRI-mb8>u(hg!t8;2$B7?!kF$dgaLP%P<<0rXC}TIg9NziC{icysjBLsEXcRh)Q) zmmZ_{9z7|@(6@)rJfwdL^Uw4hOuarOxCZ}YDF4=X$d?8V_CEk8)kE0?`?^+fI@tdZ zXM-KChZy+nSsWi+^t+Yfwc!xjaTrHn5piIFVr6lq7W+6!nXNnc0B84x?WCg|7WYvd z*sI#Dd*%4oRA)Fka8Ymg14_zSh0{#As!+|8yGpG~d8%kMQ)(5unG#jBm?@o5I|*VP zBnWnydxLl&@EP*8Ab)X7fYo&h;d{&JSVP!1yo$H)!^y~SIVBolq%wNf?UE?Y$jj(` zk6WSyqeez!sz;(EBc0I)LX~KOQ46C<-6JHMV%E-VMpt#V{g7b?!;f^Kv-Ks0U2FK{ yK2GfRDhMxiKt&6ccakRKJUu6R^u%p(+B^0=u#eV=3BHz~l literal 0 HcmV?d00001 diff --git a/src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.java b/src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.java new file mode 100644 index 0000000..3fd46c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/enums/OrderStatusEnum.java @@ -0,0 +1,32 @@ +package com.gxwebsoft.shop.enums; + +/** + * 订单状态枚举 + */ +public enum OrderStatusEnum { + ALL(-1, "全部"), + WAIT_PAY(0, "待支付"), + WAIT_DELIVERY(1, "待发货"), + WAIT_CONFIRM(2, "待核销"), + WAIT_RECEIVE(3, "待收货"), + WAIT_EVALUATE(4, "待评价"), + COMPLETED(5, "已完成"), + REFUNDED(6, "已退款"), + DELETED(7, "已删除"); + + private final Integer code; + private final String desc; + + OrderStatusEnum(Integer code, String desc) { + this.code = code; + this.desc = desc; + } + + public Integer getCode() { + return code; + } + + public String getDesc() { + return desc; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopArticleMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopArticleMapper.java new file mode 100644 index 0000000..d6d3d85 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopArticleMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopArticle; +import com.gxwebsoft.shop.param.ShopArticleParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品文章Mapper + * + * @author 科技小王子 + * @since 2025-08-13 05:14:53 + */ +public interface ShopArticleMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopArticleParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopArticleParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopBrandMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopBrandMapper.java new file mode 100644 index 0000000..49a914d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopBrandMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopBrand; +import com.gxwebsoft.shop.param.ShopBrandParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 品牌Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopBrandMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopBrandParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopBrandParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCartMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCartMapper.java new file mode 100644 index 0000000..ae8b981 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCartMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCart; +import com.gxwebsoft.shop.param.ShopCartParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 购物车Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopCartMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCartParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCartParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCategoryMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCategoryMapper.java new file mode 100644 index 0000000..2cfd98c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCategoryMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCategory; +import com.gxwebsoft.shop.param.ShopCategoryParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品分类Mapper + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +public interface ShopCategoryMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCategoryParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCategoryParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopChatConversationMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopChatConversationMapper.java new file mode 100644 index 0000000..483b761 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopChatConversationMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopChatConversation; +import com.gxwebsoft.shop.param.ShopChatConversationParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 聊天消息表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopChatConversationMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopChatConversationParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopChatConversationParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopChatMessageMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopChatMessageMapper.java new file mode 100644 index 0000000..264bc4b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopChatMessageMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopChatMessage; +import com.gxwebsoft.shop.param.ShopChatMessageParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 聊天消息表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopChatMessageMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopChatMessageParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopChatMessageParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCommissionRoleMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCommissionRoleMapper.java new file mode 100644 index 0000000..2f66f28 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCommissionRoleMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCommissionRole; +import com.gxwebsoft.shop.param.ShopCommissionRoleParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分红角色Mapper + * + * @author 科技小王子 + * @since 2025-05-01 10:01:15 + */ +public interface ShopCommissionRoleMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCommissionRoleParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCommissionRoleParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCountMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCountMapper.java new file mode 100644 index 0000000..24701c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCountMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCount; +import com.gxwebsoft.shop.param.ShopCountParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商城销售统计表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopCountMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCountParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCountParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyCateMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyCateMapper.java new file mode 100644 index 0000000..6d86d1d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyCateMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.param.ShopCouponApplyCateParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 优惠券可用分类Mapper + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +public interface ShopCouponApplyCateMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCouponApplyCateParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCouponApplyCateParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyItemMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyItemMapper.java new file mode 100644 index 0000000..077989a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponApplyItemMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.param.ShopCouponApplyItemParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 优惠券可用分类Mapper + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +public interface ShopCouponApplyItemMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCouponApplyItemParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCouponApplyItemParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponMapper.java new file mode 100644 index 0000000..a6a8ff1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopCouponMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.param.ShopCouponParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 优惠券Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:23 + */ +public interface ShopCouponMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopCouponParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopCouponParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerApplyMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerApplyMapper.java new file mode 100644 index 0000000..ce2ee91 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerApplyMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerApply; +import com.gxwebsoft.shop.param.ShopDealerApplyParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商申请记录表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:50:18 + */ +public interface ShopDealerApplyMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerApplyParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerApplyParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerCapitalMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerCapitalMapper.java new file mode 100644 index 0000000..d996c07 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerCapitalMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerCapital; +import com.gxwebsoft.shop.param.ShopDealerCapitalParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商资金明细表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerCapitalMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerCapitalParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerCapitalParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerOrderMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerOrderMapper.java new file mode 100644 index 0000000..928b066 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerOrderMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerOrder; +import com.gxwebsoft.shop.param.ShopDealerOrderParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商订单记录表Mapper + * + * @author 科技小王子 + * @since 2025-08-12 11:55:18 + */ +public interface ShopDealerOrderMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerOrderParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerOrderParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java new file mode 100644 index 0000000..70ddf37 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerReferee; +import com.gxwebsoft.shop.param.ShopDealerRefereeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商推荐关系表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerRefereeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerRefereeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerRefereeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerSettingMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerSettingMapper.java new file mode 100644 index 0000000..5d10a6c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerSettingMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerSetting; +import com.gxwebsoft.shop.param.ShopDealerSettingParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商设置表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerSettingMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerSettingParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerSettingParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerUserMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerUserMapper.java new file mode 100644 index 0000000..c1b5fd3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerUserMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.param.ShopDealerUserParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商用户记录表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerUserMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerUserParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerUserParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerWithdrawMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerWithdrawMapper.java new file mode 100644 index 0000000..0d5a427 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerWithdrawMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopDealerWithdraw; +import com.gxwebsoft.shop.param.ShopDealerWithdrawParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分销商提现明细表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerWithdrawMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopDealerWithdrawParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopDealerWithdrawParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressMapper.java new file mode 100644 index 0000000..1d236e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopExpress; +import com.gxwebsoft.shop.param.ShopExpressParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 物流公司Mapper + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +public interface ShopExpressMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopExpressParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopExpressParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateDetailMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateDetailMapper.java new file mode 100644 index 0000000..fd26f85 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateDetailMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopExpressTemplateDetail; +import com.gxwebsoft.shop.param.ShopExpressTemplateDetailParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 运费模板Mapper + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +public interface ShopExpressTemplateDetailMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopExpressTemplateDetailParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopExpressTemplateDetailParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateMapper.java new file mode 100644 index 0000000..a205498 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopExpressTemplateMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopExpressTemplate; +import com.gxwebsoft.shop.param.ShopExpressTemplateParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 运费模板Mapper + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +public interface ShopExpressTemplateMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopExpressTemplateParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopExpressTemplateParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGiftMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGiftMapper.java new file mode 100644 index 0000000..bf0a45d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGiftMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGift; +import com.gxwebsoft.shop.param.ShopGiftParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 礼品卡Mapper + * + * @author 科技小王子 + * @since 2025-08-11 18:07:31 + */ +public interface ShopGiftMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGiftParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGiftParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCategoryMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCategoryMapper.java new file mode 100644 index 0000000..ff7e088 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCategoryMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsCategory; +import com.gxwebsoft.shop.param.ShopGoodsCategoryParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品分类Mapper + * + * @author 科技小王子 + * @since 2025-05-01 00:36:45 + */ +public interface ShopGoodsCategoryMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsCategoryParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsCategoryParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCommentMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCommentMapper.java new file mode 100644 index 0000000..302a1f0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsCommentMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsComment; +import com.gxwebsoft.shop.param.ShopGoodsCommentParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 评论表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsCommentMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsCommentParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsCommentParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsIncomeConfigMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsIncomeConfigMapper.java new file mode 100644 index 0000000..9091b99 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsIncomeConfigMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsIncomeConfig; +import com.gxwebsoft.shop.param.ShopGoodsIncomeConfigParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分润配置Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsIncomeConfigMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsIncomeConfigParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsIncomeConfigParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsLogMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsLogMapper.java new file mode 100644 index 0000000..946b338 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsLog; +import com.gxwebsoft.shop.param.ShopGoodsLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品日志表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java new file mode 100644 index 0000000..20ac974 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsMapper.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoods; +import com.gxwebsoft.shop.param.ShopGoodsParam; +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +/** + * 商品Mapper + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +public interface ShopGoodsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsParam param); + + /** + * 累加商品销售数量 + * 使用@InterceptorIgnore忽略租户隔离,确保能更新成功 + * + * @param goodsId 商品ID + * @param saleCount 累加的销售数量 + * @return 影响的行数 + */ + @InterceptorIgnore(tenantLine = "true") + @Update("UPDATE shop_goods SET sales = IFNULL(sales, 0) + #{saleCount} WHERE goods_id = #{goodsId}") + int addSaleCount(@Param("goodsId") Integer goodsId, @Param("saleCount") Integer saleCount); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRelationMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRelationMapper.java new file mode 100644 index 0000000..2e02403 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRelationMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsRelation; +import com.gxwebsoft.shop.param.ShopGoodsRelationParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品点赞和收藏表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsRelationMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsRelationParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsRelationParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRoleCommissionMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRoleCommissionMapper.java new file mode 100644 index 0000000..d68181d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsRoleCommissionMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsRoleCommission; +import com.gxwebsoft.shop.param.ShopGoodsRoleCommissionParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品绑定角色的分润金额Mapper + * + * @author 科技小王子 + * @since 2025-05-01 09:53:38 + */ +public interface ShopGoodsRoleCommissionMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsRoleCommissionParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsRoleCommissionParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSkuMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSkuMapper.java new file mode 100644 index 0000000..072ebd9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSkuMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsSku; +import com.gxwebsoft.shop.param.ShopGoodsSkuParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品sku列表Mapper + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +public interface ShopGoodsSkuMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsSkuParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsSkuParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSpecMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSpecMapper.java new file mode 100644 index 0000000..503e262 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopGoodsSpecMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopGoodsSpec; +import com.gxwebsoft.shop.param.ShopGoodsSpecParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商品多规格Mapper + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +public interface ShopGoodsSpecMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopGoodsSpecParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopGoodsSpecParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantAccountMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantAccountMapper.java new file mode 100644 index 0000000..cbbe061 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantAccountMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopMerchantAccount; +import com.gxwebsoft.shop.param.ShopMerchantAccountParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商户账号Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopMerchantAccountMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopMerchantAccountParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopMerchantAccountParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantApplyMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantApplyMapper.java new file mode 100644 index 0000000..3377503 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantApplyMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopMerchantApply; +import com.gxwebsoft.shop.param.ShopMerchantApplyParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商户入驻申请Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopMerchantApplyMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopMerchantApplyParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopMerchantApplyParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantMapper.java new file mode 100644 index 0000000..747a9c3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopMerchant; +import com.gxwebsoft.shop.param.ShopMerchantParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商户Mapper + * + * @author 科技小王子 + * @since 2025-08-10 20:43:33 + */ +public interface ShopMerchantMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopMerchantParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopMerchantParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantTypeMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantTypeMapper.java new file mode 100644 index 0000000..e1ea739 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopMerchantTypeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopMerchantType; +import com.gxwebsoft.shop.param.ShopMerchantTypeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 商户类型Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopMerchantTypeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopMerchantTypeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopMerchantTypeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryGoodsMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryGoodsMapper.java new file mode 100644 index 0000000..ae5af95 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryGoodsMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrderDeliveryGoods; +import com.gxwebsoft.shop.param.ShopOrderDeliveryGoodsParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 发货单商品Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderDeliveryGoodsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderDeliveryGoodsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderDeliveryGoodsParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryMapper.java new file mode 100644 index 0000000..cfe334b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderDeliveryMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrderDelivery; +import com.gxwebsoft.shop.param.ShopOrderDeliveryParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 发货单Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderDeliveryMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderDeliveryParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderDeliveryParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderExtractMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderExtractMapper.java new file mode 100644 index 0000000..643a832 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderExtractMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrderExtract; +import com.gxwebsoft.shop.param.ShopOrderExtractParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 自提订单联系方式Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderExtractMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderExtractParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderExtractParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderGoodsMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderGoodsMapper.java new file mode 100644 index 0000000..69b747d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderGoodsMapper.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrderGoods; +import com.gxwebsoft.shop.param.ShopOrderGoodsParam; +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 商品信息Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderGoodsMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderGoodsParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderGoodsParam param); + + /** + * 根据订单ID查询订单商品列表(忽略租户隔离) + * @param orderId 订单ID + * @return List + */ + @InterceptorIgnore(tenantLine = "true") + @Select("SELECT * FROM shop_order_goods WHERE order_id = #{orderId}") + List selectListByOrderIdIgnoreTenant(@Param("orderId") Integer orderId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoLogMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoLogMapper.java new file mode 100644 index 0000000..f7f98a4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrderInfoLog; +import com.gxwebsoft.shop.param.ShopOrderInfoLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 订单核销Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderInfoLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderInfoLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderInfoLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoMapper.java new file mode 100644 index 0000000..bbbc1ab --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderInfoMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrderInfo; +import com.gxwebsoft.shop.param.ShopOrderInfoParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 场地Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderInfoMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderInfoParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderInfoParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java new file mode 100644 index 0000000..88aaf0d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.param.ShopOrderParam; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 订单Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopOrderParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopOrderParam param); + + @InterceptorIgnore(tenantLine = "true") + ShopOrder getByOutTradeNo(@Param("outTradeNo") String outTradeNo); + + @InterceptorIgnore(tenantLine = "true") + void updateByOutTradeNo(@Param("param") ShopOrder order); + + /** + * 统计订单总金额 + * 只统计已支付的订单(pay_status = 1)且未删除的订单(deleted = 0) + * + * @return 订单总金额 + */ + @Select("SELECT COALESCE(SUM(pay_price), 0) FROM shop_order WHERE pay_status = 1 AND deleted = 0 AND pay_price IS NOT NULL") + BigDecimal selectTotalAmount(); +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopRechargeOrderMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopRechargeOrderMapper.java new file mode 100644 index 0000000..1c25555 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopRechargeOrderMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopRechargeOrder; +import com.gxwebsoft.shop.param.ShopRechargeOrderParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 会员充值订单表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopRechargeOrderMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopRechargeOrderParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopRechargeOrderParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopSpecMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopSpecMapper.java new file mode 100644 index 0000000..1999c43 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopSpecMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopSpec; +import com.gxwebsoft.shop.param.ShopSpecParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 规格Mapper + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +public interface ShopSpecMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopSpecParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopSpecParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopSpecValueMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopSpecValueMapper.java new file mode 100644 index 0000000..42815c5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopSpecValueMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopSpecValue; +import com.gxwebsoft.shop.param.ShopSpecValueParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 规格值Mapper + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +public interface ShopSpecValueMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopSpecValueParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopSpecValueParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopSplashMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopSplashMapper.java new file mode 100644 index 0000000..db49cb4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopSplashMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopSplash; +import com.gxwebsoft.shop.param.ShopSplashParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 开屏广告Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopSplashMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopSplashParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopSplashParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserAddressMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserAddressMapper.java new file mode 100644 index 0000000..d89f1a6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserAddressMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopUserAddress; +import com.gxwebsoft.shop.param.ShopUserAddressParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 收货地址Mapper + * + * @author 科技小王子 + * @since 2025-07-22 23:06:40 + */ +public interface ShopUserAddressMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopUserAddressParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopUserAddressParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserBalanceLogMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserBalanceLogMapper.java new file mode 100644 index 0000000..4169458 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserBalanceLogMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopUserBalanceLog; +import com.gxwebsoft.shop.param.ShopUserBalanceLogParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户余额变动明细表Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopUserBalanceLogMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopUserBalanceLogParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopUserBalanceLogParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserCollectionMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserCollectionMapper.java new file mode 100644 index 0000000..f285ae4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserCollectionMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopUserCollection; +import com.gxwebsoft.shop.param.ShopUserCollectionParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 我的收藏Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopUserCollectionMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopUserCollectionParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopUserCollectionParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserCouponMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserCouponMapper.java new file mode 100644 index 0000000..07c4c43 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserCouponMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.param.ShopUserCouponParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户优惠券Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopUserCouponMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopUserCouponParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopUserCouponParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserRefereeMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserRefereeMapper.java new file mode 100644 index 0000000..207fd90 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserRefereeMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopUserReferee; +import com.gxwebsoft.shop.param.ShopUserRefereeParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户推荐关系表Mapper + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopUserRefereeMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopUserRefereeParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopUserRefereeParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUsersMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUsersMapper.java new file mode 100644 index 0000000..f0c8b34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUsersMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopUsers; +import com.gxwebsoft.shop.param.ShopUsersParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopUsersMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopUsersParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopUsersParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopWechatDepositMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopWechatDepositMapper.java new file mode 100644 index 0000000..3c8424c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopWechatDepositMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.shop.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.shop.entity.ShopWechatDeposit; +import com.gxwebsoft.shop.param.ShopWechatDepositParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 押金Mapper + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopWechatDepositMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") ShopWechatDepositParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") ShopWechatDepositParam param); + +} diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopArticleMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopArticleMapper.xml new file mode 100644 index 0000000..119dc2e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopArticleMapper.xml @@ -0,0 +1,189 @@ + + + + + + + SELECT a.* + FROM shop_article a + + + AND a.article_id = #{param.articleId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.type = #{param.type} + + + AND a.model LIKE CONCAT('%', #{param.model}, '%') + + + AND a.detail LIKE CONCAT('%', #{param.detail}, '%') + + + AND a.category_id = #{param.categoryId} + + + AND a.parent_id = #{param.parentId} + + + AND a.topic LIKE CONCAT('%', #{param.topic}, '%') + + + AND a.tags LIKE CONCAT('%', #{param.tags}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.image_width = #{param.imageWidth} + + + AND a.image_height = #{param.imageHeight} + + + AND a.price = #{param.price} + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.source LIKE CONCAT('%', #{param.source}, '%') + + + AND a.overview LIKE CONCAT('%', #{param.overview}, '%') + + + AND a.virtual_views = #{param.virtualViews} + + + AND a.actual_views = #{param.actualViews} + + + AND a.rate = #{param.rate} + + + AND a.show_type = #{param.showType} + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.permission = #{param.permission} + + + AND a.platform LIKE CONCAT('%', #{param.platform}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.video LIKE CONCAT('%', #{param.video}, '%') + + + AND a.accept LIKE CONCAT('%', #{param.accept}, '%') + + + AND a.longitude LIKE CONCAT('%', #{param.longitude}, '%') + + + AND a.latitude LIKE CONCAT('%', #{param.latitude}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.likes = #{param.likes} + + + AND a.comment_numbers = #{param.commentNumbers} + + + AND a.to_users LIKE CONCAT('%', #{param.toUsers}, '%') + + + AND a.author LIKE CONCAT('%', #{param.author}, '%') + + + AND a.recommend = #{param.recommend} + + + AND a.bm_users = #{param.bmUsers} + + + AND a.user_id = #{param.userId} + + + AND a.project_id = #{param.projectId} + + + AND a.lang LIKE CONCAT('%', #{param.lang}, '%') + + + AND a.lang_article_id = #{param.langArticleId} + + + AND a.translation = #{param.translation} + + + AND a.editor = #{param.editor} + + + AND a.pdf_url LIKE CONCAT('%', #{param.pdfUrl}, '%') + + + AND a.version = #{param.version} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopBrandMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopBrandMapper.xml new file mode 100644 index 0000000..bf5177f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopBrandMapper.xml @@ -0,0 +1,51 @@ + + + + + + + SELECT a.* + FROM shop_brand a + + + AND a.brand_id = #{param.brandId} + + + AND a.brand_name LIKE CONCAT('%', #{param.brandName}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCartMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCartMapper.xml new file mode 100644 index 0000000..318a402 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCartMapper.xml @@ -0,0 +1,84 @@ + + + + + + + SELECT a.* + FROM shop_cart a + + + AND a.id = #{param.id} + + + AND a.type = #{param.type} + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.goods_id LIKE CONCAT('%', #{param.goodsId}, '%') + + + AND a.spec LIKE CONCAT('%', #{param.spec}, '%') + + + AND a.price = #{param.price} + + + AND a.cart_num = #{param.cartNum} + + + AND a.total_price = #{param.totalPrice} + + + AND a.is_pay = #{param.isPay} + + + AND a.is_new = #{param.isNew} + + + AND a.is_show = #{param.isShow} + + + AND a.combination_id = #{param.combinationId} + + + AND a.seckill_id = #{param.seckillId} + + + AND a.bargain_id = #{param.bargainId} + + + AND a.selected = #{param.selected} + + + AND a.merchant_id LIKE CONCAT('%', #{param.merchantId}, '%') + + + AND a.user_id LIKE CONCAT('%', #{param.userId}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCategoryMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCategoryMapper.xml new file mode 100644 index 0000000..e3596c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCategoryMapper.xml @@ -0,0 +1,123 @@ + + + + + + + SELECT a.* + FROM shop_category a + + + AND a.id = #{param.id} + + + AND a.parent_id = #{param.parentId} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.model LIKE CONCAT('%', #{param.model}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.target LIKE CONCAT('%', #{param.target}, '%') + + + AND a.icon LIKE CONCAT('%', #{param.icon}, '%') + + + AND a.banner LIKE CONCAT('%', #{param.banner}, '%') + + + AND a.color LIKE CONCAT('%', #{param.color}, '%') + + + AND a.hide = #{param.hide} + + + AND a.permission = #{param.permission} + + + AND a.password LIKE CONCAT('%', #{param.password}, '%') + + + AND a.position = #{param.position} + + + AND a.top = #{param.top} + + + AND a.bottom = #{param.bottom} + + + AND a.active LIKE CONCAT('%', #{param.active}, '%') + + + AND a.meta LIKE CONCAT('%', #{param.meta}, '%') + + + AND a.style LIKE CONCAT('%', #{param.style}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.lang LIKE CONCAT('%', #{param.lang}, '%') + + + AND a.home = #{param.home} + + + AND a.recommend = #{param.recommend} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatConversationMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatConversationMapper.xml new file mode 100644 index 0000000..4f133fc --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatConversationMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM shop_chat_conversation a + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.friend_id = #{param.friendId} + + + AND a.type = #{param.type} + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.un_read = #{param.unRead} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatMessageMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatMessageMapper.xml new file mode 100644 index 0000000..8210d37 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopChatMessageMapper.xml @@ -0,0 +1,75 @@ + + + + + + + SELECT a.* + FROM shop_chat_message a + + + AND a.id = #{param.id} + + + AND a.form_user_id = #{param.formUserId} + + + AND a.to_user_id = #{param.toUserId} + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.side_to = #{param.sideTo} + + + AND a.side_from = #{param.sideFrom} + + + AND a.withdraw = #{param.withdraw} + + + AND a.file_info LIKE CONCAT('%', #{param.fileInfo}, '%') + + + AND a.has_contact = #{param.hasContact} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCommissionRoleMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCommissionRoleMapper.xml new file mode 100644 index 0000000..74e6faa --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCommissionRoleMapper.xml @@ -0,0 +1,57 @@ + + + + + + + SELECT a.* + FROM shop_commission_role a + + + AND a.id = #{param.id} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.province_id = #{param.provinceId} + + + AND a.city_id = #{param.cityId} + + + AND a.region_id = #{param.regionId} + + + AND a.status = #{param.status} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCountMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCountMapper.xml new file mode 100644 index 0000000..78583ae --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCountMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM shop_count a + + + AND a.id = #{param.id} + + + AND a.date_time LIKE CONCAT('%', #{param.dateTime}, '%') + + + AND a.total_price = #{param.totalPrice} + + + AND a.today_price = #{param.todayPrice} + + + AND a.total_users = #{param.totalUsers} + + + AND a.today_users = #{param.todayUsers} + + + AND a.total_orders = #{param.totalOrders} + + + AND a.today_orders = #{param.todayOrders} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyCateMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyCateMapper.xml new file mode 100644 index 0000000..ef13b35 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyCateMapper.xml @@ -0,0 +1,54 @@ + + + + + + + SELECT a.* + FROM shop_coupon_apply_cate a + + + AND a.id = #{param.id} + + + AND a.coupon_id = #{param.couponId} + + + AND a.cate_id = #{param.cateId} + + + AND a.cate_level = #{param.cateLevel} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyItemMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyItemMapper.xml new file mode 100644 index 0000000..141b156 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponApplyItemMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM shop_coupon_apply_item a + + + AND a.id = #{param.id} + + + AND a.coupon_id = #{param.couponId} + + + AND a.goods_id = #{param.goodsId} + + + AND a.category_id = #{param.categoryId} + + + AND a.type = #{param.type} + + + AND a.pk = #{param.pk} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponMapper.xml new file mode 100644 index 0000000..6030a91 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopCouponMapper.xml @@ -0,0 +1,103 @@ + + + + + + + SELECT a.*, + COALESCE((SELECT COUNT(*) FROM shop_user_coupon suc WHERE suc.coupon_id = a.id AND suc.deleted = 0), 0) AS receive_num + FROM shop_coupon a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.description LIKE CONCAT('%', #{param.description}, '%') + + + AND a.type = #{param.type} + + + AND a.reduce_price = #{param.reducePrice} + + + AND a.discount = #{param.discount} + + + AND a.min_price = #{param.minPrice} + + + AND a.expire_type = #{param.expireType} + + + AND a.expire_day = #{param.expireDay} + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.apply_range = #{param.applyRange} + + + AND a.apply_range_config LIKE CONCAT('%', #{param.applyRangeConfig}, '%') + + + AND a.is_expire = #{param.isExpire} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.total_count = #{param.totalCount} + + + AND a.issued_count = #{param.issuedCount} + + + AND a.limit_per_user = #{param.limitPerUser} + + + AND a.enabled = #{param.enabled} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerApplyMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerApplyMapper.xml new file mode 100644 index 0000000..779f01b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerApplyMapper.xml @@ -0,0 +1,68 @@ + + + + + + + SELECT a.* + FROM shop_dealer_apply a + + + AND a.apply_id = #{param.applyId} + + + AND a.type = #{param.type} + + + AND a.user_id = #{param.userId} + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.mobile LIKE CONCAT('%', #{param.mobile}, '%') + + + AND a.referee_id = #{param.refereeId} + + + AND a.apply_type = #{param.applyType} + + + AND a.apply_time = #{param.applyTime} + + + AND a.apply_status = #{param.applyStatus} + + + AND a.audit_time = #{param.auditTime} + + + AND a.reject_reason LIKE CONCAT('%', #{param.rejectReason}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.mobile = #{param.keywords} + OR a.real_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.dealer_name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerCapitalMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerCapitalMapper.xml new file mode 100644 index 0000000..9ac2841 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerCapitalMapper.xml @@ -0,0 +1,54 @@ + + + + + + + SELECT a.* + FROM shop_dealer_capital a + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.order_id = #{param.orderId} + + + AND a.flow_type = #{param.flowType} + + + AND a.money = #{param.money} + + + AND a.describe LIKE CONCAT('%', #{param.describe}, '%') + + + AND a.to_user_id = #{param.toUserId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerOrderMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerOrderMapper.xml new file mode 100644 index 0000000..29b4b81 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerOrderMapper.xml @@ -0,0 +1,72 @@ + + + + + + + SELECT a.* + FROM shop_dealer_order a + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.order_id = #{param.orderId} + + + AND a.order_price = #{param.orderPrice} + + + AND a.first_user_id = #{param.firstUserId} + + + AND a.second_user_id = #{param.secondUserId} + + + AND a.third_user_id = #{param.thirdUserId} + + + AND a.first_money = #{param.firstMoney} + + + AND a.second_money = #{param.secondMoney} + + + AND a.third_money = #{param.thirdMoney} + + + AND a.is_invalid = #{param.isInvalid} + + + AND a.is_settled = #{param.isSettled} + + + AND a.settle_time = #{param.settleTime} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml new file mode 100644 index 0000000..79b6bf7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml @@ -0,0 +1,53 @@ + + + + + + + SELECT a.*, + d.nickname AS dealerName, + d.avatar AS dealerAvatar, + d.phone AS dealerPhone, + u.nickname, + u.avatar, + u.phone + FROM shop_dealer_referee a + LEFT JOIN gxwebsoft_core.sys_user d ON a.dealer_id = d.user_id + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.dealer_id = #{param.dealerId} + + + AND a.user_id = #{param.userId} + + + AND a.level = #{param.level} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerSettingMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerSettingMapper.xml new file mode 100644 index 0000000..69d68d3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerSettingMapper.xml @@ -0,0 +1,36 @@ + + + + + + + SELECT a.* + FROM shop_dealer_setting a + + + AND a.key = #{param.key} + + + AND a.describe LIKE CONCAT('%', #{param.describe}, '%') + + + AND a.values LIKE CONCAT('%', #{param.values}, '%') + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerUserMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerUserMapper.xml new file mode 100644 index 0000000..e5a3b22 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerUserMapper.xml @@ -0,0 +1,81 @@ + + + + + + + SELECT a.* + FROM shop_dealer_user a + + + AND a.id = #{param.id} + + + AND a.type = #{param.type} + + + AND a.user_id = #{param.userId} + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.mobile LIKE CONCAT('%', #{param.mobile}, '%') + + + AND a.pay_password LIKE CONCAT('%', #{param.payPassword}, '%') + + + AND a.money = #{param.money} + + + AND a.freeze_money = #{param.freezeMoney} + + + AND a.total_money = #{param.totalMoney} + + + AND a.referee_id = #{param.refereeId} + + + AND a.first_num = #{param.firstNum} + + + AND a.second_num = #{param.secondNum} + + + AND a.third_num = #{param.thirdNum} + + + AND a.qrcode LIKE CONCAT('%', #{param.qrcode}, '%') + + + AND a.is_delete = #{param.isDelete} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerWithdrawMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerWithdrawMapper.xml new file mode 100644 index 0000000..76cd671 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerWithdrawMapper.xml @@ -0,0 +1,72 @@ + + + + + + + SELECT a.* + FROM shop_dealer_withdraw a + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.money = #{param.money} + + + AND a.pay_type = #{param.payType} + + + AND a.alipay_name LIKE CONCAT('%', #{param.alipayName}, '%') + + + AND a.alipay_account LIKE CONCAT('%', #{param.alipayAccount}, '%') + + + AND a.bank_name LIKE CONCAT('%', #{param.bankName}, '%') + + + AND a.bank_account LIKE CONCAT('%', #{param.bankAccount}, '%') + + + AND a.bank_card LIKE CONCAT('%', #{param.bankCard}, '%') + + + AND a.apply_status = #{param.applyStatus} + + + AND a.audit_time = #{param.auditTime} + + + AND a.reject_reason LIKE CONCAT('%', #{param.rejectReason}, '%') + + + AND a.platform LIKE CONCAT('%', #{param.platform}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressMapper.xml new file mode 100644 index 0000000..100e558 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressMapper.xml @@ -0,0 +1,57 @@ + + + + + + + SELECT a.* + FROM shop_express a + + + AND a.express_id = #{param.expressId} + + + AND a.express_name LIKE CONCAT('%', #{param.expressName}, '%') + + + AND a.wx_code LIKE CONCAT('%', #{param.wxCode}, '%') + + + AND a.kuaidi100_code LIKE CONCAT('%', #{param.kuaidi100Code}, '%') + + + AND a.kdniao_code LIKE CONCAT('%', #{param.kdniaoCode}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateDetailMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateDetailMapper.xml new file mode 100644 index 0000000..867265d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateDetailMapper.xml @@ -0,0 +1,72 @@ + + + + + + + SELECT a.* + FROM shop_express_template_detail a + + + AND a.id = #{param.id} + + + AND a.template_id = #{param.templateId} + + + AND a.type = #{param.type} + + + AND a.province_id = #{param.provinceId} + + + AND a.city_id = #{param.cityId} + + + AND a.first_num = #{param.firstNum} + + + AND a.first_amount = #{param.firstAmount} + + + AND a.extra_amount = #{param.extraAmount} + + + AND a.extra_num = #{param.extraNum} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateMapper.xml new file mode 100644 index 0000000..a0b1784 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopExpressTemplateMapper.xml @@ -0,0 +1,66 @@ + + + + + + + SELECT a.* + FROM shop_express_template a + + + AND a.id = #{param.id} + + + AND a.type = #{param.type} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.first_amount = #{param.firstAmount} + + + AND a.extra_amount = #{param.extraAmount} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.first_num = #{param.firstNum} + + + AND a.extra_num = #{param.extraNum} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGiftMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGiftMapper.xml new file mode 100644 index 0000000..b2f0569 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGiftMapper.xml @@ -0,0 +1,77 @@ + + + + + + + SELECT a.*,b.name as goodsName, b.price as faceValue, b.image as goodsImage,c.nickname, u.nickname as operatorUserName + FROM shop_gift a + LEFT JOIN shop_goods b ON a.goods_id = b.goods_id + LEFT JOIN gxwebsoft_core.sys_user c ON a.user_id = c.user_id + LEFT JOIN gxwebsoft_core.sys_user u ON a.operator_user_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.goods_id = #{param.goodsId} + + + AND a.take_time LIKE CONCAT('%', #{param.takeTime}, '%') + + + AND a.operator_user_id = #{param.operatorUserId} + + + AND a.is_show = #{param.isShow} + + + AND a.status = #{param.status} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.code = #{param.keywords} + OR a.name LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCategoryMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCategoryMapper.xml new file mode 100644 index 0000000..74bf790 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCategoryMapper.xml @@ -0,0 +1,93 @@ + + + + + + + SELECT a.* + FROM shop_goods_category a + + + AND a.category_id = #{param.categoryId} + + + AND a.category_code LIKE CONCAT('%', #{param.categoryCode}, '%') + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.type = #{param.type} + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.parent_id = #{param.parentId} + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.component LIKE CONCAT('%', #{param.component}, '%') + + + AND a.page_id = #{param.pageId} + + + AND a.user_id = #{param.userId} + + + AND a.count = #{param.count} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.hide = #{param.hide} + + + AND a.recommend = #{param.recommend} + + + AND a.show_index = #{param.showIndex} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCommentMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCommentMapper.xml new file mode 100644 index 0000000..a6b5545 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsCommentMapper.xml @@ -0,0 +1,96 @@ + + + + + + + SELECT a.* + FROM shop_goods_comment a + + + AND a.id = #{param.id} + + + AND a.uid = #{param.uid} + + + AND a.oid = #{param.oid} + + + AND a.unique LIKE CONCAT('%', #{param.unique}, '%') + + + AND a.goods_id = #{param.goodsId} + + + AND a.reply_type LIKE CONCAT('%', #{param.replyType}, '%') + + + AND a.goods_score = #{param.goodsScore} + + + AND a.service_score = #{param.serviceScore} + + + AND a.comment LIKE CONCAT('%', #{param.comment}, '%') + + + AND a.pics LIKE CONCAT('%', #{param.pics}, '%') + + + AND a.merchant_reply_content LIKE CONCAT('%', #{param.merchantReplyContent}, '%') + + + AND a.merchant_reply_time = #{param.merchantReplyTime} + + + AND a.is_del = #{param.isDel} + + + AND a.is_reply = #{param.isReply} + + + AND a.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.avatar LIKE CONCAT('%', #{param.avatar}, '%') + + + AND a.sku LIKE CONCAT('%', #{param.sku}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsIncomeConfigMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsIncomeConfigMapper.xml new file mode 100644 index 0000000..8819d02 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsIncomeConfigMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM shop_goods_income_config a + + + AND a.id = #{param.id} + + + AND a.goods_id = #{param.goodsId} + + + AND a.merchant_shop_type LIKE CONCAT('%', #{param.merchantShopType}, '%') + + + AND a.sku_id = #{param.skuId} + + + AND a.rate = #{param.rate} + + + AND a.user_id = #{param.userId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsLogMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsLogMapper.xml new file mode 100644 index 0000000..4d24262 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsLogMapper.xml @@ -0,0 +1,81 @@ + + + + + + + SELECT a.* + FROM shop_goods_log a + + + AND a.id = #{param.id} + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.goods_id = #{param.goodsId} + + + AND a.visit_num = #{param.visitNum} + + + AND a.cart_num = #{param.cartNum} + + + AND a.order_num = #{param.orderNum} + + + AND a.pay_num = #{param.payNum} + + + AND a.pay_price = #{param.payPrice} + + + AND a.cost_price = #{param.costPrice} + + + AND a.pay_uid = #{param.payUid} + + + AND a.refund_num = #{param.refundNum} + + + AND a.refund_price = #{param.refundPrice} + + + AND a.collect_num = #{param.collectNum} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml new file mode 100644 index 0000000..bb91f03 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsMapper.xml @@ -0,0 +1,150 @@ + + + + + + + SELECT a.* + FROM shop_goods a + + + AND a.goods_id = #{param.goodsId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.code LIKE CONCAT('%', #{param.code}, '%') + + + AND a.type = #{param.type} + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.parent_id = #{param.parentId} + + + AND a.category_id = #{param.categoryId} + + + AND a.path LIKE CONCAT('%', #{param.path}, '%') + + + AND a.tag LIKE CONCAT('%', #{param.tag}, '%') + + + AND a.specs = #{param.specs} + + + AND a.position LIKE CONCAT('%', #{param.position}, '%') + + + AND a.unit_name LIKE CONCAT('%', #{param.unitName}, '%') + + + AND a.price = #{param.price} + + + AND a.buying_price = #{param.buyingPrice} + + + AND a.dealer_price = #{param.dealerPrice} + + + AND a.deduct_stock_type = #{param.deductStockType} + + + AND a.delivery_method = #{param.deliveryMethod} + + + AND a.duration_method = #{param.durationMethod} + + + AND a.can_buy_number = #{param.canBuyNumber} + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.sales = #{param.sales} + + + AND a.stock = #{param.stock} + + + AND a.install = #{param.install} + + + AND a.rate = #{param.rate} + + + AND a.gain_integral = #{param.gainIntegral} + + + AND a.recommend = #{param.recommend} + + + AND a.official = #{param.official} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.is_show = #{param.isShow} + + + AND a.status = #{param.status} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + + AND a.status = 0 + + + AND a.status != 0 + + + AND a.stock = 0 + + + + AND (a.name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRelationMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRelationMapper.xml new file mode 100644 index 0000000..063a2a8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRelationMapper.xml @@ -0,0 +1,48 @@ + + + + + + + SELECT a.* + FROM shop_goods_relation a + + + AND a.id = #{param.id} + + + AND a.user_id = #{param.userId} + + + AND a.goods_id = #{param.goodsId} + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.category LIKE CONCAT('%', #{param.category}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRoleCommissionMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRoleCommissionMapper.xml new file mode 100644 index 0000000..a0cb44a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsRoleCommissionMapper.xml @@ -0,0 +1,57 @@ + + + + + + + SELECT a.* + FROM shop_goods_role_commission a + + + AND a.id = #{param.id} + + + AND a.role_id = #{param.roleId} + + + AND a.goods_id = #{param.goodsId} + + + AND a.sku LIKE CONCAT('%', #{param.sku}, '%') + + + AND a.amount = #{param.amount} + + + AND a.status = #{param.status} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSkuMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSkuMapper.xml new file mode 100644 index 0000000..dec4d76 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSkuMapper.xml @@ -0,0 +1,78 @@ + + + + + + + SELECT a.* + FROM shop_goods_sku a + + + AND a.id = #{param.id} + + + AND a.goods_id = #{param.goodsId} + + + AND a.sku LIKE CONCAT('%', #{param.sku}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.price = #{param.price} + + + AND a.sale_price = #{param.salePrice} + + + AND a.cost = #{param.cost} + + + AND a.stock = #{param.stock} + + + AND a.sku_no LIKE CONCAT('%', #{param.skuNo}, '%') + + + AND a.bar_code LIKE CONCAT('%', #{param.barCode}, '%') + + + AND a.weight = #{param.weight} + + + AND a.volume = #{param.volume} + + + AND a.uuid LIKE CONCAT('%', #{param.uuid}, '%') + + + AND a.status = #{param.status} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSpecMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSpecMapper.xml new file mode 100644 index 0000000..aeed405 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopGoodsSpecMapper.xml @@ -0,0 +1,45 @@ + + + + + + + SELECT a.* + FROM shop_goods_spec a + + + AND a.id = #{param.id} + + + AND a.goods_id = #{param.goodsId} + + + AND a.spec_id = #{param.specId} + + + AND a.spec_name LIKE CONCAT('%', #{param.specName}, '%') + + + AND a.spec_value LIKE CONCAT('%', #{param.specValue}, '%') + + + AND a.type = #{param.type} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantAccountMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantAccountMapper.xml new file mode 100644 index 0000000..95280c1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantAccountMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM shop_merchant_account a + + + AND a.id = #{param.id} + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.merchant_id = #{param.merchantId} + + + AND a.role_id = #{param.roleId} + + + AND a.role_name LIKE CONCAT('%', #{param.roleName}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantApplyMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantApplyMapper.xml new file mode 100644 index 0000000..52b8979 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantApplyMapper.xml @@ -0,0 +1,123 @@ + + + + + + + SELECT a.* + FROM shop_merchant_apply a + + + AND a.apply_id = #{param.applyId} + + + AND a.type = #{param.type} + + + AND a.shop_type LIKE CONCAT('%', #{param.shopType}, '%') + + + AND a.merchant_name LIKE CONCAT('%', #{param.merchantName}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.category_id = #{param.categoryId} + + + AND a.category LIKE CONCAT('%', #{param.category}, '%') + + + AND a.lng_and_lat LIKE CONCAT('%', #{param.lngAndLat}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.region_id LIKE CONCAT('%', #{param.regionId}, '%') + + + AND a.commission = #{param.commission} + + + AND a.keywords LIKE CONCAT('%', #{param.keywords}, '%') + + + AND a.yyzz LIKE CONCAT('%', #{param.yyzz}, '%') + + + AND a.sfz1 LIKE CONCAT('%', #{param.sfz1}, '%') + + + AND a.sfz2 LIKE CONCAT('%', #{param.sfz2}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.own_store = #{param.ownStore} + + + AND a.recommend = #{param.recommend} + + + AND a.goods_review = #{param.goodsReview} + + + AND a.name2 LIKE CONCAT('%', #{param.name2}, '%') + + + AND a.reason LIKE CONCAT('%', #{param.reason}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantMapper.xml new file mode 100644 index 0000000..66384d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantMapper.xml @@ -0,0 +1,150 @@ + + + + + + + SELECT a.* + FROM shop_merchant a + + + AND a.merchant_id = #{param.merchantId} + + + AND a.merchant_name LIKE CONCAT('%', #{param.merchantName}, '%') + + + AND a.merchant_code LIKE CONCAT('%', #{param.merchantCode}, '%') + + + AND a.type = #{param.type} + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.shop_type LIKE CONCAT('%', #{param.shopType}, '%') + + + AND a.item_type LIKE CONCAT('%', #{param.itemType}, '%') + + + AND a.category LIKE CONCAT('%', #{param.category}, '%') + + + AND a.merchant_category_id = #{param.merchantCategoryId} + + + AND a.merchant_category_title LIKE CONCAT('%', #{param.merchantCategoryTitle}, '%') + + + AND a.lng_and_lat LIKE CONCAT('%', #{param.lngAndLat}, '%') + + + AND a.lng LIKE CONCAT('%', #{param.lng}, '%') + + + AND a.lat LIKE CONCAT('%', #{param.lat}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.commission = #{param.commission} + + + AND a.keywords LIKE CONCAT('%', #{param.keywords}, '%') + + + AND a.files LIKE CONCAT('%', #{param.files}, '%') + + + AND a.business_time LIKE CONCAT('%', #{param.businessTime}, '%') + + + AND a.content LIKE CONCAT('%', #{param.content}, '%') + + + AND a.price = #{param.price} + + + AND a.own_store = #{param.ownStore} + + + AND a.can_express = #{param.canExpress} + + + AND a.recommend = #{param.recommend} + + + AND a.is_on = #{param.isOn} + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.goods_review = #{param.goodsReview} + + + AND a.admin_url LIKE CONCAT('%', #{param.adminUrl}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantTypeMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantTypeMapper.xml new file mode 100644 index 0000000..0ed4e42 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopMerchantTypeMapper.xml @@ -0,0 +1,48 @@ + + + + + + + SELECT a.* + FROM shop_merchant_type a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryGoodsMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryGoodsMapper.xml new file mode 100644 index 0000000..949a66f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryGoodsMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM shop_order_delivery_goods a + + + AND a.id = #{param.id} + + + AND a.delivery_id = #{param.deliveryId} + + + AND a.order_id = #{param.orderId} + + + AND a.order_goods_id = #{param.orderGoodsId} + + + AND a.goods_id = #{param.goodsId} + + + AND a.delivery_num = #{param.deliveryNum} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryMapper.xml new file mode 100644 index 0000000..eb3fd8d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderDeliveryMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM shop_order_delivery a + + + AND a.delivery_id = #{param.deliveryId} + + + AND a.order_id = #{param.orderId} + + + AND a.delivery_method = #{param.deliveryMethod} + + + AND a.pack_method = #{param.packMethod} + + + AND a.express_id = #{param.expressId} + + + AND a.express_no LIKE CONCAT('%', #{param.expressNo}, '%') + + + AND a.eorder_html LIKE CONCAT('%', #{param.eorderHtml}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderExtractMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderExtractMapper.xml new file mode 100644 index 0000000..ff26857 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderExtractMapper.xml @@ -0,0 +1,57 @@ + + + + + + + SELECT a.* + FROM shop_order_extract a + + + AND a.id = #{param.id} + + + AND a.order_id = #{param.orderId} + + + AND a.linkman LIKE CONCAT('%', #{param.linkman}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderGoodsMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderGoodsMapper.xml new file mode 100644 index 0000000..dd22115 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderGoodsMapper.xml @@ -0,0 +1,105 @@ + + + + + + + SELECT a.* + FROM shop_order_goods a + + + AND a.id = #{param.id} + + + AND a.order_id = #{param.orderId} + + + AND a.order_code LIKE CONCAT('%', #{param.orderCode}, '%') + + + AND a.merchant_id = #{param.merchantId} + + + AND a.merchant_name LIKE CONCAT('%', #{param.merchantName}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.goods_id = #{param.goodsId} + + + AND a.goods_name LIKE CONCAT('%', #{param.goodsName}, '%') + + + AND a.spec LIKE CONCAT('%', #{param.spec}, '%') + + + AND a.sku_id = #{param.skuId} + + + AND a.price = #{param.price} + + + AND a.total_num = #{param.totalNum} + + + AND a.pay_status = #{param.payStatus} + + + AND a.order_status = #{param.orderStatus} + + + AND a.is_free = #{param.isFree} + + + AND a.version = #{param.version} + + + AND a.time_period LIKE CONCAT('%', #{param.timePeriod}, '%') + + + AND a.date_time LIKE CONCAT('%', #{param.dateTime}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.time_flag LIKE CONCAT('%', #{param.timeFlag}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoLogMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoLogMapper.xml new file mode 100644 index 0000000..0dde816 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoLogMapper.xml @@ -0,0 +1,48 @@ + + + + + + + SELECT a.* + FROM shop_order_info_log a + + + AND a.id = #{param.id} + + + AND a.order_id = #{param.orderId} + + + AND a.merchant_id = #{param.merchantId} + + + AND a.field_id = #{param.fieldId} + + + AND a.use_num = #{param.useNum} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoMapper.xml new file mode 100644 index 0000000..b2e0149 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderInfoMapper.xml @@ -0,0 +1,114 @@ + + + + + + + SELECT a.* + FROM shop_order_info a + + + AND a.id = #{param.id} + + + AND a.order_id = #{param.orderId} + + + AND a.order_code LIKE CONCAT('%', #{param.orderCode}, '%') + + + AND a.merchant_id = #{param.merchantId} + + + AND a.merchant_name LIKE CONCAT('%', #{param.merchantName}, '%') + + + AND a.field_id = #{param.fieldId} + + + AND a.field_name LIKE CONCAT('%', #{param.fieldName}, '%') + + + AND a.price = #{param.price} + + + AND a.children_price = #{param.childrenPrice} + + + AND a.adult_num = #{param.adultNum} + + + AND a.children_num = #{param.childrenNum} + + + AND a.adult_num_use = #{param.adultNumUse} + + + AND a.children_num_use = #{param.childrenNumUse} + + + AND a.pay_status = #{param.payStatus} + + + AND a.order_status = #{param.orderStatus} + + + AND a.is_free = #{param.isFree} + + + AND a.is_children = #{param.isChildren} + + + AND a.version = #{param.version} + + + AND a.is_half = #{param.isHalf} + + + AND a.time_period LIKE CONCAT('%', #{param.timePeriod}, '%') + + + AND a.date_time LIKE CONCAT('%', #{param.dateTime}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.time_flag LIKE CONCAT('%', #{param.timeFlag}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml new file mode 100644 index 0000000..ab5f7c4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml @@ -0,0 +1,420 @@ + + + + + + + SELECT a.*,b.nickname, b.avatar,b.phone as phone + FROM shop_order a + LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id + + + AND a.order_id = #{param.orderId} + + + AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%') + + + AND a.type = #{param.type} + + + AND a.delivery_type = #{param.deliveryType} + + + AND a.channel = #{param.channel} + + + AND a.transaction_id LIKE CONCAT('%', #{param.transactionId}, '%') + + + AND a.refund_order LIKE CONCAT('%', #{param.refundOrder}, '%') + + + AND a.merchant_id = #{param.merchantId} + + + AND a.merchant_name LIKE CONCAT('%', #{param.merchantName}, '%') + + + AND a.merchant_code LIKE CONCAT('%', #{param.merchantCode}, '%') + + + AND a.coupon_id = #{param.couponId} + + + AND a.card_id LIKE CONCAT('%', #{param.cardId}, '%') + + + AND a.admin_id = #{param.adminId} + + + AND a.confirm_id = #{param.confirmId} + + + AND a.ic_card LIKE CONCAT('%', #{param.icCard}, '%') + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND b.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND b.nickname LIKE CONCAT('%', #{param.nickname}, '%') + + + AND a.address_id = #{param.addressId} + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.address_lat LIKE CONCAT('%', #{param.addressLat}, '%') + + + AND a.address_lng LIKE CONCAT('%', #{param.addressLng}, '%') + + + AND a.self_take_merchant_id = #{param.selfTakeMerchantId} + + + AND a.self_take_merchant_name LIKE CONCAT('%', #{param.selfTakeMerchantName}, '%') + + + AND a.send_start_time LIKE CONCAT('%', #{param.sendStartTime}, '%') + + + AND a.send_end_time LIKE CONCAT('%', #{param.sendEndTime}, '%') + + + AND a.express_merchant_id = #{param.expressMerchantId} + + + AND a.express_merchant_name LIKE CONCAT('%', #{param.expressMerchantName}, '%') + + + AND a.total_price = #{param.totalPrice} + + + AND a.reduce_price = #{param.reducePrice} + + + AND a.pay_price = #{param.payPrice} + + + AND a.price = #{param.price} + + + AND a.money = #{param.money} + + + AND a.refund_money = #{param.refundMoney} + + + AND a.coach_price = #{param.coachPrice} + + + AND a.total_num = #{param.totalNum} + + + AND a.coach_id = #{param.coachId} + + + AND a.pay_user_id = #{param.payUserId} + + + AND a.pay_type = #{param.payType} + + + AND a.friend_pay_type = #{param.friendPayType} + + + AND a.pay_status = #{param.payStatus} + + + AND a.order_status = #{param.orderStatus} + + + AND a.delivery_status = #{param.deliveryStatus} + + + AND a.delivery_time LIKE CONCAT('%', #{param.deliveryTime}, '%') + + + AND a.coupon_type = #{param.couponType} + + + AND a.coupon_desc LIKE CONCAT('%', #{param.couponDesc}, '%') + + + AND a.qrcode LIKE CONCAT('%', #{param.qrcode}, '%') + + + AND a.return_num = #{param.returnNum} + + + AND a.return_money = #{param.returnMoney} + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.is_invoice = #{param.isInvoice} + + + AND a.invoice_no LIKE CONCAT('%', #{param.invoiceNo}, '%') + + + AND a.pay_time LIKE CONCAT('%', #{param.payTime}, '%') + + + AND a.refund_time LIKE CONCAT('%', #{param.refundTime}, '%') + + + AND a.refund_apply_time LIKE CONCAT('%', #{param.refundApplyTime}, '%') + + + AND a.expiration_time LIKE CONCAT('%', #{param.expirationTime}, '%') + + + AND a.check_bill = #{param.checkBill} + + + AND a.is_settled = #{param.isSettled} + + + AND a.version = #{param.version} + + + AND a.user_id = #{param.userId} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.self_take_code LIKE CONCAT('%', #{param.selfTakeCode}, '%') + + + AND a.has_take_gift = #{param.hasTakeGift} + + + AND (a.order_no LIKE CONCAT('%', #{param.keywords}, '%') + OR a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.order_id = #{param.keywords} + OR b.phone = #{param.keywords} + OR b.phone = #{param.keywords} + OR b.nickname LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + AND a.pay_status = 0 AND a.order_status = 0 + + + + AND a.pay_status = 1 AND a.delivery_status = 10 AND a.order_status = 0 + + + + AND a.pay_status = 1 AND a.order_status = 0 + + + + AND a.delivery_status = 20 AND a.order_status != 1 + + + + AND a.order_status = 1 AND a.evaluate_status = 0 + + + + AND a.order_status = 1 + + + + AND (a.order_status = 4 OR a.order_status = 5 OR a.order_status = 6 OR a.order_status = 7) + + + + AND a.deleted = 1 + + + + AND a.order_status = 2 + + + + + + + + + + + + + + + + + UPDATE shop_order + + + pay_type = #{param.payType}, + + + pay_status = #{param.payStatus}, + + + order_status = #{param.orderStatus}, + + + delivery_status = #{param.deliveryStatus}, + + + delivery_time = #{param.deliveryTime}, + + + pay_time = #{param.payTime}, + + + refund_time = #{param.refundTime}, + + + self_take_code = #{param.selfTakeCode}, + + + invoice_no = #{param.invoiceNo}, + + + is_invoice = #{param.isInvoice}, + + + start_time = #{param.startTime}, + + + qrcode = #{param.qrcode}, + + + pay_user_id = #{param.payUserId}, + + + form_id = #{param.formId}, + + + total_price = #{param.totalPrice}, + + + reduce_price = #{param.reducePrice}, + + + pay_price = #{param.payPrice}, + + + price = #{param.price}, + + + money = #{param.money}, + + + refund_money = #{param.refundMoney}, + + + total_num = #{param.totalNum}, + + + coach_id = #{param.coachId}, + + + express_merchant_id = #{param.expressMerchantId}, + + + express_merchant_name = #{param.expressMerchantName}, + + + send_start_time = #{param.sendStartTime}, + + + send_end_time = #{param.sendEndTime}, + + + self_take_merchant_id = #{param.selfTakeMerchantId}, + + + self_take_merchant_name = #{param.selfTakeMerchantName}, + + + address_id = #{param.addressId}, + + + address = #{param.address}, + + + confirm_id = #{param.confirmId}, + + + ic_card = #{param.icCard}, + + + admin_id = #{param.adminId}, + + + card_id = #{param.cardId}, + + + coupon_id = #{param.couponId}, + + + merchant_id = #{param.merchantId}, + + + transaction_id = #{param.transactionId}, + + + refund_order = #{param.refundOrder}, + + + channel = #{param.channel}, + + + delivery_type = #{param.deliveryType}, + + + `type` = #{param.type}, + + + deleted = #{param.deleted}, + + + deleted = 0, + + + + order_no = #{param.orderNo} + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopRechargeOrderMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopRechargeOrderMapper.xml new file mode 100644 index 0000000..3ed8037 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopRechargeOrderMapper.xml @@ -0,0 +1,99 @@ + + + + + + + SELECT a.* + FROM shop_recharge_order a + + + AND a.order_id = #{param.orderId} + + + AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%') + + + AND a.user_id = #{param.userId} + + + AND a.recharge_type = #{param.rechargeType} + + + AND a.organization_id = #{param.organizationId} + + + AND a.plan_id = #{param.planId} + + + AND a.pay_price = #{param.payPrice} + + + AND a.gift_money = #{param.giftMoney} + + + AND a.actual_money = #{param.actualMoney} + + + AND a.balance = #{param.balance} + + + AND a.pay_method LIKE CONCAT('%', #{param.payMethod}, '%') + + + AND a.pay_status = #{param.payStatus} + + + AND a.pay_time = #{param.payTime} + + + AND a.trade_id = #{param.tradeId} + + + AND a.platform LIKE CONCAT('%', #{param.platform}, '%') + + + AND a.shop_id = #{param.shopId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.merchant_code LIKE CONCAT('%', #{param.merchantCode}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecMapper.xml new file mode 100644 index 0000000..9081159 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecMapper.xml @@ -0,0 +1,60 @@ + + + + + + + SELECT a.* + FROM shop_spec a + + + AND a.spec_id = #{param.specId} + + + AND a.spec_name LIKE CONCAT('%', #{param.specName}, '%') + + + AND a.spec_value LIKE CONCAT('%', #{param.specValue}, '%') + + + AND a.merchant_id = #{param.merchantId} + + + AND a.user_id = #{param.userId} + + + AND a.updater = #{param.updater} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecValueMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecValueMapper.xml new file mode 100644 index 0000000..0543252 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSpecValueMapper.xml @@ -0,0 +1,48 @@ + + + + + + + SELECT a.* + FROM shop_spec_value a + + + AND a.spec_value_id = #{param.specValueId} + + + AND a.spec_id = #{param.specId} + + + AND a.spec_value LIKE CONCAT('%', #{param.specValue}, '%') + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSplashMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSplashMapper.xml new file mode 100644 index 0000000..bac8517 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopSplashMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM shop_splash a + + + AND a.id = #{param.id} + + + AND a.title LIKE CONCAT('%', #{param.title}, '%') + + + AND a.image LIKE CONCAT('%', #{param.image}, '%') + + + AND a.jump_type LIKE CONCAT('%', #{param.jumpType}, '%') + + + AND a.jump_pk = #{param.jumpPk} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.user_id = #{param.userId} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserAddressMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserAddressMapper.xml new file mode 100644 index 0000000..37b630f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserAddressMapper.xml @@ -0,0 +1,82 @@ + + + + + + + SELECT a.* + FROM shop_user_address a + + + AND a.id = #{param.id} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.region LIKE CONCAT('%', #{param.region}, '%') + + + AND a.address LIKE CONCAT('%', #{param.address}, '%') + + + AND a.full_address LIKE CONCAT('%', #{param.fullAddress}, '%') + + + AND a.lat LIKE CONCAT('%', #{param.lat}, '%') + + + AND a.lng LIKE CONCAT('%', #{param.lng}, '%') + + + AND a.gender = #{param.gender} + + + AND a.type LIKE CONCAT('%', #{param.type}, '%') + + + AND a.is_default = #{param.isDefault} + + + AND a.user_id = #{param.userId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + OR a.phone = #{param.keywords} + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserBalanceLogMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserBalanceLogMapper.xml new file mode 100644 index 0000000..5ad4a74 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserBalanceLogMapper.xml @@ -0,0 +1,78 @@ + + + + + + + SELECT a.* + FROM shop_user_balance_log a + + + AND a.log_id = #{param.logId} + + + AND a.user_id = #{param.userId} + + + AND a.scene = #{param.scene} + + + AND a.money = #{param.money} + + + AND a.balance = #{param.balance} + + + AND a.remark LIKE CONCAT('%', #{param.remark}, '%') + + + AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%') + + + AND a.admin_id = #{param.adminId} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.merchant_id = #{param.merchantId} + + + AND a.merchant_code LIKE CONCAT('%', #{param.merchantCode}, '%') + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCollectionMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCollectionMapper.xml new file mode 100644 index 0000000..98782e2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCollectionMapper.xml @@ -0,0 +1,45 @@ + + + + + + + SELECT a.* + FROM shop_user_collection a + + + AND a.id = #{param.id} + + + AND a.type = #{param.type} + + + AND a.tid = #{param.tid} + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCouponMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCouponMapper.xml new file mode 100644 index 0000000..48eaee5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserCouponMapper.xml @@ -0,0 +1,96 @@ + + + + + + + SELECT a.* + FROM shop_user_coupon a + + + AND a.id = #{param.id} + + + AND a.coupon_id = #{param.couponId} + + + AND a.user_id = #{param.userId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.description LIKE CONCAT('%', #{param.description}, '%') + + + AND a.type = #{param.type} + + + AND a.reduce_price = #{param.reducePrice} + + + AND a.discount = #{param.discount} + + + AND a.min_price = #{param.minPrice} + + + AND a.apply_range = #{param.applyRange} + + + AND a.apply_range_config LIKE CONCAT('%', #{param.applyRangeConfig}, '%') + + + AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%') + + + AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%') + + + AND a.status = #{param.status} + + + AND a.use_time LIKE CONCAT('%', #{param.useTime}, '%') + + + AND a.order_id LIKE CONCAT('%', #{param.orderId}, '%') + + + AND a.order_no LIKE CONCAT('%', #{param.orderNo}, '%') + + + AND a.obtain_type = #{param.obtainType} + + + AND a.obtain_source LIKE CONCAT('%', #{param.obtainSource}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserRefereeMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserRefereeMapper.xml new file mode 100644 index 0000000..3a1d993 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserRefereeMapper.xml @@ -0,0 +1,62 @@ + + + + + + + SELECT a.*, + d.nickname AS dealerName, + d.avatar AS dealerAvatar, + d.phone AS dealerPhone, + u.nickname, + u.avatar, + u.phone + FROM shop_user_referee a + LEFT JOIN gxwebsoft_core.sys_user d ON a.dealer_id = d.user_id + LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id + + + AND a.id = #{param.id} + + + AND a.dealer_id = #{param.dealerId} + + + AND a.user_id = #{param.userId} + + + AND a.level = #{param.level} + + + AND a.comments LIKE CONCAT('%', #{param.comments}, '%') + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUsersMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUsersMapper.xml new file mode 100644 index 0000000..41df7a5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUsersMapper.xml @@ -0,0 +1,81 @@ + + + + + + + SELECT a.* + FROM shop_users a + + + AND a.id = #{param.id} + + + AND a.open_id LIKE CONCAT('%', #{param.openId}, '%') + + + AND a.session_key LIKE CONCAT('%', #{param.sessionKey}, '%') + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.avatar_url LIKE CONCAT('%', #{param.avatarUrl}, '%') + + + AND a.gender = #{param.gender} + + + AND a.country LIKE CONCAT('%', #{param.country}, '%') + + + AND a.province LIKE CONCAT('%', #{param.province}, '%') + + + AND a.city LIKE CONCAT('%', #{param.city}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.integral = #{param.integral} + + + AND a.money = #{param.money} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND a.id_card LIKE CONCAT('%', #{param.idCard}, '%') + + + AND a.real_name LIKE CONCAT('%', #{param.realName}, '%') + + + AND a.is_admin = #{param.isAdmin} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopWechatDepositMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopWechatDepositMapper.xml new file mode 100644 index 0000000..9840d34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopWechatDepositMapper.xml @@ -0,0 +1,69 @@ + + + + + + + SELECT a.* + FROM shop_wechat_deposit a + + + AND a.id = #{param.id} + + + AND a.oid = #{param.oid} + + + AND a.uid = #{param.uid} + + + AND a.order_num LIKE CONCAT('%', #{param.orderNum}, '%') + + + AND a.wechat_order LIKE CONCAT('%', #{param.wechatOrder}, '%') + + + AND a.wechat_return LIKE CONCAT('%', #{param.wechatReturn}, '%') + + + AND a.site_name LIKE CONCAT('%', #{param.siteName}, '%') + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.phone LIKE CONCAT('%', #{param.phone}, '%') + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.price = #{param.price} + + + AND a.status = #{param.status} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopArticleParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopArticleParam.java new file mode 100644 index 0000000..3aceda1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopArticleParam.java @@ -0,0 +1,203 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品文章查询参数 + * + * @author 科技小王子 + * @since 2025-08-13 05:14:52 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopArticleParam对象", description = "商品文章查询参数") +public class ShopArticleParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "文章ID") + @QueryField(type = QueryType.EQ) + private Integer articleId; + + @Schema(description = "文章标题") + private String title; + + @Schema(description = "文章类型 0常规 1视频") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "模型") + private String model; + + @Schema(description = "详情页模板") + private String detail; + + @Schema(description = "文章分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "话题") + private String topic; + + @Schema(description = "标签") + private String tags; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "封面图宽") + @QueryField(type = QueryType.EQ) + private Integer imageWidth; + + @Schema(description = "封面图高") + @QueryField(type = QueryType.EQ) + private Integer imageHeight; + + @Schema(description = "付费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "开始时间") + private String startTime; + + @Schema(description = "结束时间") + private String endTime; + + @Schema(description = "来源") + private String source; + + @Schema(description = "产品概述") + private String overview; + + @Schema(description = "虚拟阅读量(仅用作展示)") + @QueryField(type = QueryType.EQ) + private Integer virtualViews; + + @Schema(description = "实际阅读量") + @QueryField(type = QueryType.EQ) + private Integer actualViews; + + @Schema(description = "评分") + @QueryField(type = QueryType.EQ) + private BigDecimal rate; + + @Schema(description = "列表显示方式(10小图展示 20大图展示)") + @QueryField(type = QueryType.EQ) + private Integer showType; + + @Schema(description = "访问密码") + private String password; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + @QueryField(type = QueryType.EQ) + private Integer permission; + + @Schema(description = "发布来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "文章附件") + private String files; + + @Schema(description = "视频地址") + private String video; + + @Schema(description = "接受的文件类型") + private String accept; + + @Schema(description = "经度") + private String longitude; + + @Schema(description = "纬度") + private String latitude; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "街道地址") + private String address; + + @Schema(description = "点赞数") + @QueryField(type = QueryType.EQ) + private Integer likes; + + @Schema(description = "评论数") + @QueryField(type = QueryType.EQ) + private Integer commentNumbers; + + @Schema(description = "提醒谁看") + private String toUsers; + + @Schema(description = "作者") + private String author; + + @Schema(description = "推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "报名人数") + @QueryField(type = QueryType.EQ) + private Integer bmUsers; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "项目ID") + @QueryField(type = QueryType.EQ) + private Integer projectId; + + @Schema(description = "语言") + private String lang; + + @Schema(description = "关联默认语言的文章ID") + @QueryField(type = QueryType.EQ) + private Integer langArticleId; + + @Schema(description = "是否自动翻译") + @QueryField(type = QueryType.EQ) + private Boolean translation; + + @Schema(description = "编辑器类型 0 Markdown编辑器 1 富文本编辑器 ") + @QueryField(type = QueryType.EQ) + private Boolean editor; + + @Schema(description = "pdf文件地址") + private String pdfUrl; + + @Schema(description = "版本号") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopBrandParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopBrandParam.java new file mode 100644 index 0000000..047ec9f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopBrandParam.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 品牌查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopBrandParam对象", description = "品牌查询参数") +public class ShopBrandParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer brandId; + + @Schema(description = "品牌名称") + private String brandName; + + @Schema(description = "图标") + private String image; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCartParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCartParam.java new file mode 100644 index 0000000..05bd2be --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCartParam.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 购物车查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCartParam对象", description = "购物车查询参数") +public class ShopCartParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "购物车表ID") + @QueryField(type = QueryType.EQ) + private Long id; + + @Schema(description = "类型 0商城 1外卖") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "唯一标识") + private String code; + + @Schema(description = "商品ID") + private Long goodsId; + + @Schema(description = "商品SKU ID") + @QueryField(type = QueryType.EQ) + private Integer skuId; + + @Schema(description = "商品规格") + private String spec; + + @Schema(description = "规格信息,如:颜色:红色|尺寸:L") + private String specInfo; + + @Schema(description = "商品价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "商品数量") + @QueryField(type = QueryType.EQ) + private Integer cartNum; + + @Schema(description = "单商品合计") + @QueryField(type = QueryType.EQ) + private BigDecimal totalPrice; + + @Schema(description = "0 = 未购买 1 = 已购买") + @QueryField(type = QueryType.EQ) + private Boolean isPay; + + @Schema(description = "是否为立即购买") + @QueryField(type = QueryType.EQ) + private Boolean isNew; + + @Schema(description = "是否为立即购买") + @QueryField(type = QueryType.EQ) + private Boolean isShow; + + @Schema(description = "拼团id") + @QueryField(type = QueryType.EQ) + private Integer combinationId; + + @Schema(description = "秒杀产品ID") + @QueryField(type = QueryType.EQ) + private Integer seckillId; + + @Schema(description = "砍价id") + @QueryField(type = QueryType.EQ) + private Integer bargainId; + + @Schema(description = "是否选中") + @QueryField(type = QueryType.EQ) + private Boolean selected; + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "用户ID") + private Long userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCategoryParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCategoryParam.java new file mode 100644 index 0000000..2466e34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCategoryParam.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品分类查询参数 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCategoryParam对象", description = "商品分类查询参数") +public class ShopCategoryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "上级id, 0是顶级") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "菜单名称") + private String title; + + @Schema(description = "模型") + private String model; + + @Schema(description = "标识") + private String code; + + @Schema(description = "链接地址") + private String path; + + @Schema(description = "组件地址") + private String component; + + @Schema(description = "打开位置") + private String target; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "banner") + private String banner; + + @Schema(description = "图标颜色") + private String color; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "可见类型 0所有人 1登录可见 2密码可见") + @QueryField(type = QueryType.EQ) + private Integer permission; + + @Schema(description = "访问密码") + private String password; + + @Schema(description = "位置 0不限 1顶部 2底部") + @QueryField(type = QueryType.EQ) + private Integer position; + + @Schema(description = "仅在顶部显示") + @QueryField(type = QueryType.EQ) + private Integer top; + + @Schema(description = "仅在底部显示") + @QueryField(type = QueryType.EQ) + private Integer bottom; + + @Schema(description = "菜单选中的path") + private String active; + + @Schema(description = "其它路由元信息") + private String meta; + + @Schema(description = "css样式") + private String style; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "语言") + private String lang; + + @Schema(description = "设为首页") + @QueryField(type = QueryType.EQ) + private Integer home; + + @Schema(description = "推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopChatConversationParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopChatConversationParam.java new file mode 100644 index 0000000..7202265 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopChatConversationParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 聊天消息表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopChatConversationParam对象", description = "聊天消息表查询参数") +public class ShopChatConversationParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "好友ID") + @QueryField(type = QueryType.EQ) + private Integer friendId; + + @Schema(description = "消息类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "未读消息") + @QueryField(type = QueryType.EQ) + private Integer unRead; + + @Schema(description = "状态, 0未读, 1已读") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopChatMessageParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopChatMessageParam.java new file mode 100644 index 0000000..25c2fb5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopChatMessageParam.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 聊天消息表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopChatMessageParam对象", description = "聊天消息表查询参数") +public class ShopChatMessageParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "发送人ID") + @QueryField(type = QueryType.EQ) + private Integer formUserId; + + @Schema(description = "接收人ID") + @QueryField(type = QueryType.EQ) + private Integer toUserId; + + @Schema(description = "消息类型") + private String type; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "屏蔽接收方") + @QueryField(type = QueryType.EQ) + private Integer sideTo; + + @Schema(description = "屏蔽发送方") + @QueryField(type = QueryType.EQ) + private Integer sideFrom; + + @Schema(description = "是否撤回") + @QueryField(type = QueryType.EQ) + private Integer withdraw; + + @Schema(description = "文件信息") + private String fileInfo; + + @Schema(description = "存在联系方式") + @QueryField(type = QueryType.EQ) + private Integer hasContact; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0未读, 1已读") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCommissionRoleParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCommissionRoleParam.java new file mode 100644 index 0000000..9b4e15c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCommissionRoleParam.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分红角色查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 10:01:14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCommissionRoleParam对象", description = "分红角色查询参数") +public class ShopCommissionRoleParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + private String title; + + @QueryField(type = QueryType.EQ) + private Integer provinceId; + + @QueryField(type = QueryType.EQ) + private Integer cityId; + + @QueryField(type = QueryType.EQ) + private Integer regionId; + + @Schema(description = "状态, 0正常, 1异常") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "备注") + private String comments; + + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCountParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCountParam.java new file mode 100644 index 0000000..55f57a0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCountParam.java @@ -0,0 +1,64 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商城销售统计表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCountParam对象", description = "商城销售统计表查询参数") +public class ShopCountParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "统计日期") + private String dateTime; + + @Schema(description = "总销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal totalPrice; + + @Schema(description = "今日销售额") + @QueryField(type = QueryType.EQ) + private BigDecimal todayPrice; + + @Schema(description = "总会员数") + @QueryField(type = QueryType.EQ) + private BigDecimal totalUsers; + + @Schema(description = "今日新增") + @QueryField(type = QueryType.EQ) + private BigDecimal todayUsers; + + @Schema(description = "总订单笔数") + @QueryField(type = QueryType.EQ) + private BigDecimal totalOrders; + + @Schema(description = "今日订单笔数") + @QueryField(type = QueryType.EQ) + private BigDecimal todayOrders; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyCateParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyCateParam.java new file mode 100644 index 0000000..b80698a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyCateParam.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 优惠券可用分类查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:48 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCouponApplyCateParam对象", description = "优惠券可用分类查询参数") +public class ShopCouponApplyCateParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @QueryField(type = QueryType.EQ) + private Integer couponId; + + @QueryField(type = QueryType.EQ) + private Integer cateId; + + @Schema(description = "分类等级") + @QueryField(type = QueryType.EQ) + private Boolean cateLevel; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyItemParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyItemParam.java new file mode 100644 index 0000000..f27cc47 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCouponApplyItemParam.java @@ -0,0 +1,54 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 优惠券可用分类查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCouponApplyItemParam对象", description = "优惠券可用分类查询参数") +public class ShopCouponApplyItemParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @QueryField(type = QueryType.EQ) + private Integer couponId; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "0服务1需求2闲置") + @QueryField(type = QueryType.EQ) + private Integer pk; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopCouponParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopCouponParam.java new file mode 100644 index 0000000..58f71f5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopCouponParam.java @@ -0,0 +1,108 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 优惠券查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:23 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopCouponParam对象", description = "优惠券查询参数") +public class ShopCouponParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "优惠券名称") + private String name; + + @Schema(description = "优惠券描述") + private String description; + + @Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "满减券-减免金额") + @QueryField(type = QueryType.EQ) + private BigDecimal reducePrice; + + @Schema(description = "折扣券-折扣率(0-100)") + @QueryField(type = QueryType.EQ) + private Integer discount; + + @Schema(description = "最低消费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal minPrice; + + @Schema(description = "到期类型(10领取后生效 20固定时间)") + @QueryField(type = QueryType.EQ) + private Integer expireType; + + @Schema(description = "领取后生效-有效天数") + @QueryField(type = QueryType.EQ) + private Integer expireDay; + + @Schema(description = "有效期开始时间") + private String startTime; + + @Schema(description = "有效期结束时间") + private String endTime; + + @Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)") + @QueryField(type = QueryType.EQ) + private Integer applyRange; + + @Schema(description = "适用范围配置(json格式)") + private String applyRangeConfig; + + @Schema(description = "是否过期(0未过期 1已过期)") + @QueryField(type = QueryType.EQ) + private Integer isExpire; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1禁用") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "创建用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "发放总数量(-1表示无限制)") + @QueryField(type = QueryType.EQ) + private Integer totalCount; + + @Schema(description = "已发放数量") + @QueryField(type = QueryType.EQ) + private Integer issuedCount; + + @Schema(description = "每人限领数量(-1表示无限制)") + @QueryField(type = QueryType.EQ) + private Integer limitPerUser; + + @Schema(description = "是否启用(0禁用 1启用)") + @QueryField(type = QueryType.EQ) + private Boolean enabled; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyImportParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyImportParam.java new file mode 100644 index 0000000..50ff384 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyImportParam.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.shop.param; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 分销商申请记录导入参数 + * + * @author 科技小王子 + * @since 2025-09-05 + */ +@Data +public class ShopDealerApplyImportParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Excel(name = "类型", replace = {"经销商_0", "企业_1", "集团_2"}) + private Integer type; + + @Excel(name = "用户ID") + private Integer userId; + + @Excel(name = "姓名") + private String realName; + + @Excel(name = "分销商名称") + private String dealerName; + + @Excel(name = "分销商编码") + private String dealerCode; + + @Excel(name = "手机号") + private String mobile; + + @Excel(name = "合同金额") + private BigDecimal money; + + @Excel(name = "详细地址") + private String address; + + @Excel(name = "推荐人用户ID") + private Integer refereeId; + + @Excel(name = "申请方式", replace = {"需后台审核_10", "无需审核_20"}) + private Integer applyType; + + @Excel(name = "审核状态", replace = {"待审核_10", "审核通过_20", "驳回_30"}) + private Integer applyStatus; + + @Excel(name = "合同时间", format = "yyyy-MM-dd HH:mm:ss") + private String contractTime; + + @Excel(name = "驳回原因") + private String rejectReason; + + @Excel(name = "商城ID") + private Integer tenantId; +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyParam.java new file mode 100644 index 0000000..0826134 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerApplyParam.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商申请记录表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:50:17 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerApplyParam对象", description = "分销商申请记录表查询参数") +public class ShopDealerApplyParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer applyId; + + @Schema(description = "0经销商,1企业也,2集团)") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "姓名") + private String realName; + + @Schema(description = "手机号") + private String mobile; + + @Schema(description = "推荐人用户ID") + @QueryField(type = QueryType.EQ) + private Integer refereeId; + + @Schema(description = "申请方式(10需后台审核 20无需审核)") + @QueryField(type = QueryType.EQ) + private Integer applyType; + + @Schema(description = "申请时间") + @QueryField(type = QueryType.EQ) + private String applyTime; + + @Schema(description = "审核状态 (10待审核 20审核通过 30驳回)") + @QueryField(type = QueryType.EQ) + private Integer applyStatus; + + @Schema(description = "审核时间") + @QueryField(type = QueryType.EQ) + private String auditTime; + + @Schema(description = "驳回原因") + private String rejectReason; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerCapitalParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerCapitalParam.java new file mode 100644 index 0000000..72e959b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerCapitalParam.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商资金明细表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerCapitalParam对象", description = "分销商资金明细表查询参数") +public class ShopDealerCapitalParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "分销商用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)") + @QueryField(type = QueryType.EQ) + private Integer flowType; + + @Schema(description = "金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "描述") + private String describe; + + @Schema(description = "对方用户ID") + @QueryField(type = QueryType.EQ) + private Integer toUserId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerOrderParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerOrderParam.java new file mode 100644 index 0000000..d631622 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerOrderParam.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商订单记录表查询参数 + * + * @author 科技小王子 + * @since 2025-08-12 11:55:18 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerOrderParam对象", description = "分销商订单记录表查询参数") +public class ShopDealerOrderParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "买家用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "订单总金额(不含运费)") + @QueryField(type = QueryType.EQ) + private BigDecimal orderPrice; + + @Schema(description = "分销商用户id(一级)") + @QueryField(type = QueryType.EQ) + private Integer firstUserId; + + @Schema(description = "分销商用户id(二级)") + @QueryField(type = QueryType.EQ) + private Integer secondUserId; + + @Schema(description = "分销商用户id(三级)") + @QueryField(type = QueryType.EQ) + private Integer thirdUserId; + + @Schema(description = "分销佣金(一级)") + @QueryField(type = QueryType.EQ) + private BigDecimal firstMoney; + + @Schema(description = "分销佣金(二级)") + @QueryField(type = QueryType.EQ) + private BigDecimal secondMoney; + + @Schema(description = "分销佣金(三级)") + @QueryField(type = QueryType.EQ) + private BigDecimal thirdMoney; + + @Schema(description = "订单是否失效(0未失效 1已失效)") + @QueryField(type = QueryType.EQ) + private Integer isInvalid; + + @Schema(description = "佣金结算(0未结算 1已结算)") + @QueryField(type = QueryType.EQ) + private Integer isSettled; + + @Schema(description = "结算时间") + @QueryField(type = QueryType.EQ) + private String settleTime; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerRefereeParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerRefereeParam.java new file mode 100644 index 0000000..eed72d1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerRefereeParam.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商推荐关系表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerRefereeParam对象", description = "分销商推荐关系表查询参数") +public class ShopDealerRefereeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "分销商用户ID") + @QueryField(type = QueryType.EQ) + private Integer dealerId; + + @Schema(description = "用户id(被推荐人)") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "推荐关系层级(1,2,3)") + @QueryField(type = QueryType.EQ) + private Integer level; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerSettingParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerSettingParam.java new file mode 100644 index 0000000..e9f214e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerSettingParam.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商设置表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerSettingParam对象", description = "分销商设置表查询参数") +public class ShopDealerSettingParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "设置项标示") + @QueryField(type = QueryType.EQ) + private String key; + + @Schema(description = "设置项描述") + private String describe; + + @Schema(description = "设置内容(json格式)") + private String values; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerUserImportParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerUserImportParam.java new file mode 100644 index 0000000..064bcb9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerUserImportParam.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.shop.param; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 分销商用户导入参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +public class ShopDealerUserImportParam implements Serializable { + private static final long serialVersionUID = 1L; + + @Excel(name = "用户ID") + private Integer userId; + + @Excel(name = "姓名") + private String realName; + + @Excel(name = "手机号") + private String mobile; + + @Excel(name = "支付密码") + private String payPassword; + + @Excel(name = "当前可提现佣金") + private BigDecimal money; + + @Excel(name = "已冻结佣金") + private BigDecimal freezeMoney; + + @Excel(name = "累积提现佣金") + private BigDecimal totalMoney; + + @Excel(name = "推荐人用户ID") + private Integer refereeId; + + @Excel(name = "成员数量(一级)") + private Integer firstNum; + + @Excel(name = "成员数量(二级)") + private Integer secondNum; + + @Excel(name = "成员数量(三级)") + private Integer thirdNum; + + @Excel(name = "专属二维码") + private String qrcode; + + @Excel(name = "排序号") + private Integer sortNumber; + + @Excel(name = "是否删除") + private Integer isDelete; + + @Excel(name = "租户ID") + private Integer tenantId; + + @Excel(name = "创建时间") + private LocalDateTime createTime; + + @Excel(name = "修改时间") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerUserParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerUserParam.java new file mode 100644 index 0000000..67aaa12 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerUserParam.java @@ -0,0 +1,85 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商用户记录表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerUserParam对象", description = "分销商用户记录表查询参数") +public class ShopDealerUserParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "姓名") + private String realName; + + @Schema(description = "手机号") + private String mobile; + + @Schema(description = "支付密码") + private String payPassword; + + @Schema(description = "当前可提现佣金") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "已冻结佣金") + @QueryField(type = QueryType.EQ) + private BigDecimal freezeMoney; + + @Schema(description = "累积提现佣金") + @QueryField(type = QueryType.EQ) + private BigDecimal totalMoney; + + @Schema(description = "推荐人用户ID") + @QueryField(type = QueryType.EQ) + private Integer refereeId; + + @Schema(description = "成员数量(一级)") + @QueryField(type = QueryType.EQ) + private Integer firstNum; + + @Schema(description = "成员数量(二级)") + @QueryField(type = QueryType.EQ) + private Integer secondNum; + + @Schema(description = "成员数量(三级)") + @QueryField(type = QueryType.EQ) + private Integer thirdNum; + + @Schema(description = "专属二维码") + private String qrcode; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除") + @QueryField(type = QueryType.EQ) + private Integer isDelete; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopDealerWithdrawParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopDealerWithdrawParam.java new file mode 100644 index 0000000..c053078 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopDealerWithdrawParam.java @@ -0,0 +1,70 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分销商提现明细表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopDealerWithdrawParam对象", description = "分销商提现明细表查询参数") +public class ShopDealerWithdrawParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "分销商用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "提现金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "打款方式 (10微信 20支付宝 30银行卡)") + @QueryField(type = QueryType.EQ) + private Integer payType; + + @Schema(description = "支付宝姓名") + private String alipayName; + + @Schema(description = "支付宝账号") + private String alipayAccount; + + @Schema(description = "开户行名称") + private String bankName; + + @Schema(description = "银行开户名") + private String bankAccount; + + @Schema(description = "银行卡号") + private String bankCard; + + @Schema(description = "申请状态 (10待审核 20审核通过 30驳回 40已打款)") + @QueryField(type = QueryType.EQ) + private Integer applyStatus; + + @Schema(description = "审核时间") + @QueryField(type = QueryType.EQ) + private Integer auditTime; + + @Schema(description = "驳回原因") + private String rejectReason; + + @Schema(description = "来源客户端(APP、H5、小程序等)") + private String platform; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopExpressParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopExpressParam.java new file mode 100644 index 0000000..ce43740 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopExpressParam.java @@ -0,0 +1,49 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 物流公司查询参数 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopExpressParam对象", description = "物流公司查询参数") +public class ShopExpressParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "物流公司ID") + @QueryField(type = QueryType.EQ) + private Integer expressId; + + @Schema(description = "物流公司名称") + private String expressName; + + @Schema(description = "物流公司编码 (微信)") + private String wxCode; + + @Schema(description = "物流公司编码 (快递100)") + private String kuaidi100Code; + + @Schema(description = "物流公司编码 (快递鸟)") + private String kdniaoCode; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateDetailParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateDetailParam.java new file mode 100644 index 0000000..6a2f8c9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateDetailParam.java @@ -0,0 +1,68 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 运费模板查询参数 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopExpressTemplateDetailParam对象", description = "运费模板查询参数") +public class ShopExpressTemplateDetailParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @QueryField(type = QueryType.EQ) + private Integer templateId; + + @Schema(description = "0按件") + @QueryField(type = QueryType.EQ) + private Boolean type; + + @QueryField(type = QueryType.EQ) + private Integer provinceId; + + @QueryField(type = QueryType.EQ) + private Integer cityId; + + @Schema(description = "首件数量/重量") + @QueryField(type = QueryType.EQ) + private BigDecimal firstNum; + + @Schema(description = "收件价格") + @QueryField(type = QueryType.EQ) + private BigDecimal firstAmount; + + @Schema(description = "续件价格") + @QueryField(type = QueryType.EQ) + private BigDecimal extraAmount; + + @Schema(description = "续件数量/重量") + @QueryField(type = QueryType.EQ) + private BigDecimal extraNum; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateParam.java new file mode 100644 index 0000000..43c4ebd --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopExpressTemplateParam.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 运费模板查询参数 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopExpressTemplateParam对象", description = "运费模板查询参数") +public class ShopExpressTemplateParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @QueryField(type = QueryType.EQ) + private Boolean type; + + private String title; + + @Schema(description = "收件价格") + @QueryField(type = QueryType.EQ) + private BigDecimal firstAmount; + + @Schema(description = "续件价格") + @QueryField(type = QueryType.EQ) + private BigDecimal extraAmount; + + @Schema(description = "状态, 0已发布, 1待审核 2已驳回 3违规内容") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "首件数量/重量") + @QueryField(type = QueryType.EQ) + private BigDecimal firstNum; + + @Schema(description = "续件数量/重量") + @QueryField(type = QueryType.EQ) + private BigDecimal extraNum; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGiftParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGiftParam.java new file mode 100644 index 0000000..e5e710c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGiftParam.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 礼品卡查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 18:07:31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGiftParam对象", description = "礼品卡查询参数") +public class ShopGiftParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + private String name; + + @Schema(description = "秘钥") + private String code; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "使用地点") + private String useLocation; + + @Schema(description = "领取时间") + private String takeTime; + + @Schema(description = "操作人") + @QueryField(type = QueryType.EQ) + private Integer operatorUserId; + + @Schema(description = "核销时间") + private String verificationTime; + + @Schema(description = "是否展示") + @QueryField(type = QueryType.EQ) + private Boolean isShow; + + @Schema(description = "状态, 0未使用 1已使用 2失效") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "操作员备注") + private String operatorRemarks; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsCategoryParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsCategoryParam.java new file mode 100644 index 0000000..093afe7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsCategoryParam.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品分类查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 00:36:45 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsCategoryParam对象", description = "商品分类查询参数") +public class ShopGoodsCategoryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "商品分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "分类标识") + private String categoryCode; + + @Schema(description = "分类名称") + private String title; + + @Schema(description = "类型 0商城分类 1外卖分类") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "分类图片") + private String image; + + @Schema(description = "上级分类ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "路由/链接地址") + private String path; + + @Schema(description = "组件路径") + private String component; + + @Schema(description = "绑定的页面") + @QueryField(type = QueryType.EQ) + private Integer pageId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商品数量") + @QueryField(type = QueryType.EQ) + private Integer count; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)") + @QueryField(type = QueryType.EQ) + private Integer hide; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "是否显示在首页") + @QueryField(type = QueryType.EQ) + private Integer showIndex; + + @Schema(description = "商铺ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "状态, 0正常, 1禁用") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsCommentParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsCommentParam.java new file mode 100644 index 0000000..cfae581 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsCommentParam.java @@ -0,0 +1,98 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 评论表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsCommentParam对象", description = "评论表查询参数") +public class ShopGoodsCommentParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "评论ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer uid; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer oid; + + @Schema(description = "商品唯一id") + private String unique; + + @Schema(description = "商品id") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "某种商品类型(普通商品、秒杀商品)") + private String replyType; + + @Schema(description = "商品分数") + @QueryField(type = QueryType.EQ) + private Boolean goodsScore; + + @Schema(description = "服务分数") + @QueryField(type = QueryType.EQ) + private Boolean serviceScore; + + @Schema(description = "评论内容") + private String comment; + + @Schema(description = "评论图片") + private String pics; + + @Schema(description = "管理员回复内容") + private String merchantReplyContent; + + @Schema(description = "管理员回复时间") + @QueryField(type = QueryType.EQ) + private Integer merchantReplyTime; + + @Schema(description = "0未删除1已删除") + @QueryField(type = QueryType.EQ) + private Boolean isDel; + + @Schema(description = "0未回复1已回复") + @QueryField(type = QueryType.EQ) + private Boolean isReply; + + @Schema(description = "用户名称") + private String nickname; + + @Schema(description = "用户头像") + private String avatar; + + @Schema(description = "商品规格属性值,多个,号隔开") + private String sku; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsIncomeConfigParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsIncomeConfigParam.java new file mode 100644 index 0000000..e3989b9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsIncomeConfigParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分润配置查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsIncomeConfigParam对象", description = "分润配置查询参数") +public class ShopGoodsIncomeConfigParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "店铺类型") + private String merchantShopType; + + @QueryField(type = QueryType.EQ) + private Integer skuId; + + @Schema(description = "比例") + @QueryField(type = QueryType.EQ) + private BigDecimal rate; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsLogParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsLogParam.java new file mode 100644 index 0000000..2635a0b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsLogParam.java @@ -0,0 +1,89 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品日志表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsLogParam对象", description = "商品日志表查询参数") +public class ShopGoodsLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "统计ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "类型visit,cart,order,pay,collect,refund") + private String type; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "是否浏览") + @QueryField(type = QueryType.EQ) + private Boolean visitNum; + + @Schema(description = "加入购物车数量") + @QueryField(type = QueryType.EQ) + private Integer cartNum; + + @Schema(description = "下单数量") + @QueryField(type = QueryType.EQ) + private Integer orderNum; + + @Schema(description = "支付数量") + @QueryField(type = QueryType.EQ) + private Integer payNum; + + @Schema(description = "支付金额") + @QueryField(type = QueryType.EQ) + private BigDecimal payPrice; + + @Schema(description = "商品成本价") + @QueryField(type = QueryType.EQ) + private BigDecimal costPrice; + + @Schema(description = "支付用户ID") + @QueryField(type = QueryType.EQ) + private Integer payUid; + + @Schema(description = "退款数量") + @QueryField(type = QueryType.EQ) + private Integer refundNum; + + @Schema(description = "退款金额") + @QueryField(type = QueryType.EQ) + private BigDecimal refundPrice; + + @Schema(description = "收藏") + @QueryField(type = QueryType.EQ) + private Boolean collectNum; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsParam.java new file mode 100644 index 0000000..0138743 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsParam.java @@ -0,0 +1,153 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品查询参数 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsParam对象", description = "商品查询参数") +public class ShopGoodsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "商品名称") + private String name; + + @Schema(description = "产品编码") + private String code; + + @Schema(description = "类型 0软件产品 1实物商品 2虚拟商品") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "封面图") + private String image; + + @Schema(description = "父级分类ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "产品分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "路由地址") + private String path; + + @Schema(description = "标签") + private String tag; + + @Schema(description = "产品规格 0单规格 1多规格") + @QueryField(type = QueryType.EQ) + private Integer specs; + + @Schema(description = "货架") + private String position; + + @Schema(description = "单位名称 (个)") + private String unitName; + + @Schema(description = "商品价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "进货价格") + @QueryField(type = QueryType.EQ) + private BigDecimal buyingPrice; + + @Schema(description = "经销商价格") + @QueryField(type = QueryType.EQ) + private BigDecimal dealerPrice; + + @Schema(description = "库存计算方式(10下单减库存 20付款减库存)") + @QueryField(type = QueryType.EQ) + private Integer deductStockType; + + @Schema(description = "交付方式(0不启用)") + @QueryField(type = QueryType.EQ) + private Integer deliveryMethod; + + @Schema(description = "购买时长(0不启用,1 一次性,2 按时长)") + @QueryField(type = QueryType.EQ) + private Integer durationMethod; + + @Schema(description = "可购买数量") + @QueryField(type = QueryType.EQ) + private Integer canBuyNumber; + + @Schema(description = "轮播图") + private String files; + + @Schema(description = "销量") + @QueryField(type = QueryType.EQ) + private Integer sales; + + @Schema(description = "库存") + @QueryField(type = QueryType.EQ) + private Integer stock; + + @Schema(description = "安装次数") + @QueryField(type = QueryType.EQ) + private Integer install; + + @Schema(description = "评分") + @QueryField(type = QueryType.EQ) + private BigDecimal rate; + + @Schema(description = "消费赚取积分") + @QueryField(type = QueryType.EQ) + private BigDecimal gainIntegral; + + @Schema(description = "推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "是否官方") + @QueryField(type = QueryType.EQ) + private Integer official; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "是否展示") + @QueryField(type = QueryType.EQ) + private Boolean isShow; + + @Schema(description = "状态, 0上架 1待上架 2待审核 3审核不通过") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsRelationParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsRelationParam.java new file mode 100644 index 0000000..d752169 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsRelationParam.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品点赞和收藏表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsRelationParam对象", description = "商品点赞和收藏表查询参数") +public class ShopGoodsRelationParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "类型(收藏(collect)、点赞(like))") + private String type; + + @Schema(description = "某种类型的商品(普通商品、秒杀商品)") + private String category; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsRoleCommissionParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsRoleCommissionParam.java new file mode 100644 index 0000000..eef6e45 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsRoleCommissionParam.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品绑定角色的分润金额查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 09:53:38 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsRoleCommissionParam对象", description = "商品绑定角色的分润金额查询参数") +public class ShopGoodsRoleCommissionParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @QueryField(type = QueryType.EQ) + private Integer roleId; + + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + private String sku; + + @QueryField(type = QueryType.EQ) + private BigDecimal amount; + + @Schema(description = "状态, 0正常, 1异常") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "备注") + private String comments; + + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsSkuParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsSkuParam.java new file mode 100644 index 0000000..cba9b24 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsSkuParam.java @@ -0,0 +1,80 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品sku列表查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsSkuParam对象", description = "商品sku列表查询参数") +public class ShopGoodsSkuParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "商品属性索引值 (attr_value|attr_value[|....])") + private String sku; + + @Schema(description = "商品图片") + private String image; + + @Schema(description = "商品价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "市场价格") + @QueryField(type = QueryType.EQ) + private BigDecimal salePrice; + + @Schema(description = "成本价") + @QueryField(type = QueryType.EQ) + private BigDecimal cost; + + @Schema(description = "库存") + @QueryField(type = QueryType.EQ) + private Integer stock; + + @Schema(description = "sku编码") + private String skuNo; + + @Schema(description = "商品条码") + private String barCode; + + @Schema(description = "重量") + @QueryField(type = QueryType.EQ) + private BigDecimal weight; + + @Schema(description = "体积") + @QueryField(type = QueryType.EQ) + private BigDecimal volume; + + @Schema(description = "唯一值") + private String uuid; + + @Schema(description = "状态, 0正常, 1异常") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "备注") + private String comments; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopGoodsSpecParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsSpecParam.java new file mode 100644 index 0000000..34667e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopGoodsSpecParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品多规格查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopGoodsSpecParam对象", description = "商品多规格查询参数") +public class ShopGoodsSpecParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "规格ID") + @QueryField(type = QueryType.EQ) + private Integer specId; + + @Schema(description = "规格名称") + private String specName; + + @Schema(description = "规格值") + private String specValue; + + @Schema(description = "活动类型 0=商品,1=秒杀,2=砍价,3=拼团") + @QueryField(type = QueryType.EQ) + private Boolean type; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopMerchantAccountParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantAccountParam.java new file mode 100644 index 0000000..d5a31fe --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantAccountParam.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户账号查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopMerchantAccountParam对象", description = "商户账号查询参数") +public class ShopMerchantAccountParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "商户手机号") + private String phone; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "角色ID") + @QueryField(type = QueryType.EQ) + private Integer roleId; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopMerchantApplyParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantApplyParam.java new file mode 100644 index 0000000..0ad85d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantApplyParam.java @@ -0,0 +1,126 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户入驻申请查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopMerchantApplyParam对象", description = "商户入驻申请查询参数") +public class ShopMerchantApplyParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer applyId; + + @Schema(description = "类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "店铺类型") + private String shopType; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "门店图片") + private String image; + + @Schema(description = "商户手机号") + private String phone; + + @Schema(description = "商户姓名") + private String realName; + + @Schema(description = "商户行业分类ID") + @QueryField(type = QueryType.EQ) + private Integer categoryId; + + @Schema(description = "商户分类") + private String category; + + @Schema(description = "经纬度") + private String lngAndLat; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "地区ID") + private String regionId; + + @Schema(description = "手续费") + @QueryField(type = QueryType.EQ) + private BigDecimal commission; + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "营业执照") + private String yyzz; + + @Schema(description = "身份证") + private String sfz1; + + @Schema(description = "身份证") + private String sfz2; + + @Schema(description = "资质图片") + private String files; + + @Schema(description = "所有人") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否自营") + @QueryField(type = QueryType.EQ) + private Integer ownStore; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "是否需要审核") + @QueryField(type = QueryType.EQ) + private Integer goodsReview; + + @Schema(description = "工作负责人") + private String name2; + + @Schema(description = "驳回原因") + private String reason; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopMerchantParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantParam.java new file mode 100644 index 0000000..1c087b7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantParam.java @@ -0,0 +1,149 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户查询参数 + * + * @author 科技小王子 + * @since 2025-08-10 20:43:33 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopMerchantParam对象", description = "商户查询参数") +public class ShopMerchantParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商户编号") + private String merchantCode; + + @Schema(description = "商户类型") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "商户图标") + private String image; + + @Schema(description = "商户手机号") + private String phone; + + @Schema(description = "商户姓名") + private String realName; + + @Schema(description = "店铺类型") + private String shopType; + + @Schema(description = "项目分类") + private String itemType; + + @Schema(description = "商户分类") + private String category; + + @Schema(description = "商户经营分类") + @QueryField(type = QueryType.EQ) + private Integer merchantCategoryId; + + @Schema(description = "商户分类") + private String merchantCategoryTitle; + + @Schema(description = "经纬度") + private String lngAndLat; + + private String lng; + + private String lat; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "手续费") + @QueryField(type = QueryType.EQ) + private BigDecimal commission; + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "资质图片") + private String files; + + @Schema(description = "营业时间") + private String businessTime; + + @Schema(description = "文章内容") + private String content; + + @Schema(description = "每小时价格") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "是否自营") + @QueryField(type = QueryType.EQ) + private Integer ownStore; + + @Schema(description = "是否可以快递") + @QueryField(type = QueryType.EQ) + private Boolean canExpress; + + @Schema(description = "是否推荐") + @QueryField(type = QueryType.EQ) + private Integer recommend; + + @Schema(description = "是否营业") + @QueryField(type = QueryType.EQ) + private Integer isOn; + + private String startTime; + + private String endTime; + + @Schema(description = "是否需要审核") + @QueryField(type = QueryType.EQ) + private Integer goodsReview; + + @Schema(description = "管理入口") + private String adminUrl; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "所有人") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopMerchantTypeParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantTypeParam.java new file mode 100644 index 0000000..cca6e49 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopMerchantTypeParam.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商户类型查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopMerchantTypeParam对象", description = "商户类型查询参数") +public class ShopMerchantTypeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "店铺类型") + private String name; + + @Schema(description = "店铺入驻条件") + private String comments; + + @Schema(description = "状态") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryGoodsParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryGoodsParam.java new file mode 100644 index 0000000..48d4657 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryGoodsParam.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 发货单商品查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderDeliveryGoodsParam对象", description = "发货单商品查询参数") +public class ShopOrderDeliveryGoodsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "发货单ID") + @QueryField(type = QueryType.EQ) + private Integer deliveryId; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "订单商品ID") + @QueryField(type = QueryType.EQ) + private Integer orderGoodsId; + + @Schema(description = "商品ID") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "发货数量") + @QueryField(type = QueryType.EQ) + private Integer deliveryNum; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryParam.java new file mode 100644 index 0000000..9a3455f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderDeliveryParam.java @@ -0,0 +1,60 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 发货单查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderDeliveryParam对象", description = "发货单查询参数") +public class ShopOrderDeliveryParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "发货单ID") + @QueryField(type = QueryType.EQ) + private Integer deliveryId; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "发货方式(10手动录入 20无需物流 30电子面单)") + @QueryField(type = QueryType.EQ) + private Integer deliveryMethod; + + @Schema(description = "打包方式(废弃)") + @QueryField(type = QueryType.EQ) + private Integer packMethod; + + @Schema(description = "物流公司ID") + @QueryField(type = QueryType.EQ) + private Integer expressId; + + @Schema(description = "物流单号") + private String expressNo; + + @Schema(description = "电子面单模板内容") + private String eorderHtml; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderExtractParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderExtractParam.java new file mode 100644 index 0000000..1ba173b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderExtractParam.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 自提订单联系方式查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderExtractParam对象", description = "自提订单联系方式查询参数") +public class ShopOrderExtractParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "联系人姓名") + private String linkman; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderGoodsParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderGoodsParam.java new file mode 100644 index 0000000..e309ac1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderGoodsParam.java @@ -0,0 +1,109 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 商品信息查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderGoodsParam对象", description = "商品信息查询参数") +public class ShopOrderGoodsParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "关联订单表id") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "订单标识") + private String orderCode; + + @Schema(description = "关联商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商品封面图") + private String image; + + @Schema(description = "关联商品id") + @QueryField(type = QueryType.EQ) + private Integer goodsId; + + @Schema(description = "商品名称") + private String goodsName; + + @Schema(description = "商品规格") + private String spec; + + @QueryField(type = QueryType.EQ) + private Integer skuId; + + @Schema(description = "单价") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "购买数量") + @QueryField(type = QueryType.EQ) + private Integer totalNum; + + @Schema(description = "0 未付款 1已付款,2无需付款或占用状态") + @QueryField(type = QueryType.EQ) + private Integer payStatus; + + @Schema(description = "0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款") + @QueryField(type = QueryType.EQ) + private Integer orderStatus; + + @Schema(description = "是否免费:0收费、1免费") + @QueryField(type = QueryType.EQ) + private Boolean isFree; + + @Schema(description = "系统版本 0当前版本 其他版本") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "预约时间段") + private String timePeriod; + + @Schema(description = "预定日期") + private String dateTime; + + @Schema(description = "开场时间") + private String startTime; + + @Schema(description = "结束时间") + private String endTime; + + @Schema(description = "毫秒时间戳") + private Long timeFlag; + + @Schema(description = "过期时间") + private String expirationTime; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoLogParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoLogParam.java new file mode 100644 index 0000000..a8f2f44 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoLogParam.java @@ -0,0 +1,45 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 订单核销查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderInfoLogParam对象", description = "订单核销查询参数") +public class ShopOrderInfoLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "关联订单表id") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "关联商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "关联场地id") + @QueryField(type = QueryType.EQ) + private Integer fieldId; + + @Schema(description = "核销数量") + @QueryField(type = QueryType.EQ) + private Boolean useNum; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoParam.java new file mode 100644 index 0000000..6b85a03 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderInfoParam.java @@ -0,0 +1,124 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 场地查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderInfoParam对象", description = "场地查询参数") +public class ShopOrderInfoParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "自增ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "关联订单表id") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "组合数据:日期+时间段+场馆id+场地id") + private String orderCode; + + @Schema(description = "关联商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "关联场地id") + @QueryField(type = QueryType.EQ) + private Integer fieldId; + + @Schema(description = "场地名称") + private String fieldName; + + @Schema(description = "单价") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "儿童价") + @QueryField(type = QueryType.EQ) + private BigDecimal childrenPrice; + + @Schema(description = "成人人数") + @QueryField(type = QueryType.EQ) + private Integer adultNum; + + @Schema(description = "儿童人数") + @QueryField(type = QueryType.EQ) + private Integer childrenNum; + + @Schema(description = "已核销的成人票数") + @QueryField(type = QueryType.EQ) + private Integer adultNumUse; + + @Schema(description = "已核销的儿童票数") + @QueryField(type = QueryType.EQ) + private Integer childrenNumUse; + + @Schema(description = "0 未付款 1已付款,2无需付款或占用状态") + @QueryField(type = QueryType.EQ) + private Integer payStatus; + + @Schema(description = "0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款") + @QueryField(type = QueryType.EQ) + private Integer orderStatus; + + @Schema(description = "是否免费:0收费、1免费") + @QueryField(type = QueryType.EQ) + private Boolean isFree; + + @Schema(description = "是否支持儿童票:0不支持、1支持") + @QueryField(type = QueryType.EQ) + private Boolean isChildren; + + @Schema(description = "系统版本 0当前版本 其他版本") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "预订类型:0全场,1半场") + @QueryField(type = QueryType.EQ) + private Boolean isHalf; + + @Schema(description = "预约时间段") + private String timePeriod; + + @Schema(description = "预定日期") + private String dateTime; + + @Schema(description = "开场时间") + private String startTime; + + @Schema(description = "结束时间") + private String endTime; + + @Schema(description = "毫秒时间戳") + private Long timeFlag; + + @Schema(description = "过期时间") + private String expirationTime; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopOrderParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopOrderParam.java new file mode 100644 index 0000000..83178cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopOrderParam.java @@ -0,0 +1,262 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 订单查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopOrderParam对象", description = "订单查询参数") +public class ShopOrderParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "订单号") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "订单类型,0商城订单 1预定订单/外卖 2会员卡") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "快递/自提") + @QueryField(type = QueryType.EQ) + private Integer deliveryType; + + @Schema(description = "下单渠道,0小程序预定 1俱乐部训练场 3活动订场") + @QueryField(type = QueryType.EQ) + private Integer channel; + + @Schema(description = "微信支付订单号") + private String transactionId; + + @Schema(description = "微信退款订单号") + private String refundOrder; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "商户编号") + private String merchantCode; + + @Schema(description = "使用的优惠券id") + @QueryField(type = QueryType.EQ) + private Integer couponId; + + @Schema(description = "使用的会员卡id") + private String cardId; + + @Schema(description = "关联管理员id") + @QueryField(type = QueryType.EQ) + private Integer adminId; + + @Schema(description = "核销管理员id") + @QueryField(type = QueryType.EQ) + private Integer confirmId; + + @Schema(description = "IC卡号") + private String icCard; + + @Schema(description = "真实姓名") + private String realName; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "收货人id") + private Integer addressId; + + @Schema(description = "收货地址") + private String address; + + private String addressLat; + + private String addressLng; + + @Schema(description = "自提店铺id") + @QueryField(type = QueryType.EQ) + private Integer selfTakeMerchantId; + + @Schema(description = "自提店铺") + private String selfTakeMerchantName; + + @Schema(description = "配送开始时间") + private String sendStartTime; + + @Schema(description = "配送结束时间") + private String sendEndTime; + + @Schema(description = "发货店铺id") + @QueryField(type = QueryType.EQ) + private Integer expressMerchantId; + + @Schema(description = "发货店铺") + private String expressMerchantName; + + @Schema(description = "订单总额") + @QueryField(type = QueryType.EQ) + private BigDecimal totalPrice; + + @Schema(description = "减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格") + @QueryField(type = QueryType.EQ) + private BigDecimal reducePrice; + + @Schema(description = "实际付款") + @QueryField(type = QueryType.EQ) + private BigDecimal payPrice; + + @Schema(description = "用于统计") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "价钱,用于积分赠送") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "退款金额") + @QueryField(type = QueryType.EQ) + private BigDecimal refundMoney; + + @Schema(description = "教练价格") + @QueryField(type = QueryType.EQ) + private BigDecimal coachPrice; + + @Schema(description = "购买数量") + @QueryField(type = QueryType.EQ) + private Integer totalNum; + + @Schema(description = "教练id") + @QueryField(type = QueryType.EQ) + private Integer coachId; + + @Schema(description = "支付的用户id") + @QueryField(type = QueryType.EQ) + private Integer payUserId; + + @Schema(description = "支付方式:0余额支付,1微信支付,2支付宝支付,3银联支付,4现金支付,5POS机支付,6免费,7积分支付") + @QueryField(type = QueryType.EQ) + private Integer payType; + + @Schema(description = "代付支付方式:0余额支付,1微信支付,2支付宝支付,3银联支付,4现金支付,5POS机支付,6免费,7积分支付") + @QueryField(type = QueryType.EQ) + private Integer friendPayType; + + @Schema(description = "0未付款,1已付款") + @QueryField(type = QueryType.EQ) + private Boolean payStatus; + + @Schema(description = "0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款") + @QueryField(type = QueryType.EQ) + private Integer orderStatus; + + @Schema(description = "发货状态(10未发货 20已发货 30部分发货)") + @QueryField(type = QueryType.EQ) + private Integer deliveryStatus; + + @Schema(description = "发货备注") + private String deliveryNote; + + @Schema(description = "发货时间") + private String deliveryTime; + + @Schema(description = "优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡") + @QueryField(type = QueryType.EQ) + private Integer couponType; + + @Schema(description = "优惠说明") + private String couponDesc; + + @Schema(description = "二维码地址,保存订单号,支付成功后才生成") + private String qrcode; + + @Schema(description = "vip月卡年卡、ic月卡年卡回退次数") + @QueryField(type = QueryType.EQ) + private Integer returnNum; + + @Schema(description = "vip充值回退金额") + @QueryField(type = QueryType.EQ) + private BigDecimal returnMoney; + + @Schema(description = "预约详情开始时间数组") + private String startTime; + + @Schema(description = "是否已开具发票:0未开发票,1已开发票,2不能开具发票") + @QueryField(type = QueryType.EQ) + private Boolean isInvoice; + + @Schema(description = "发票流水号") + private String invoiceNo; + + @Schema(description = "支付时间") + private String payTime; + + @Schema(description = "退款时间") + private String refundTime; + + @Schema(description = "申请退款时间") + private String refundApplyTime; + + @Schema(description = "过期时间") + private String expirationTime; + + @Schema(description = "对账情况:0=未对账;1=已对账;3=已对账,金额对不上;4=未查询到该订单") + @QueryField(type = QueryType.EQ) + private Integer checkBill; + + @Schema(description = "订单是否已结算(0未结算 1已结算)") + @QueryField(type = QueryType.EQ) + private Integer isSettled; + + @Schema(description = "系统版本号 0当前版本 value=其他版本") + @QueryField(type = QueryType.EQ) + private Integer version; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "用户昵称") + @QueryField(type = QueryType.LIKE) + private String nickname; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "自提码") + private String selfTakeCode; + + @Schema(description = "是否已收到赠品") + @QueryField(type = QueryType.EQ) + private Boolean hasTakeGift; + + @Schema(description = "订单状态筛选:-1全部,0待支付,1待发货,2待核销,3待收货,4待评价,5已完成,6已退款,7已删除") + private Integer statusFilter; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopRechargeOrderParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopRechargeOrderParam.java new file mode 100644 index 0000000..bbd1c3f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopRechargeOrderParam.java @@ -0,0 +1,105 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 会员充值订单表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopRechargeOrderParam对象", description = "会员充值订单表查询参数") +public class ShopRechargeOrderParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "订单ID") + @QueryField(type = QueryType.EQ) + private Integer orderId; + + @Schema(description = "订单号") + private String orderNo; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "充值方式(10自定义金额 20套餐充值)") + @QueryField(type = QueryType.EQ) + private Integer rechargeType; + + @Schema(description = "机构id") + @QueryField(type = QueryType.EQ) + private Integer organizationId; + + @Schema(description = "充值套餐ID") + @QueryField(type = QueryType.EQ) + private Integer planId; + + @Schema(description = "用户支付金额") + @QueryField(type = QueryType.EQ) + private BigDecimal payPrice; + + @Schema(description = "赠送金额") + @QueryField(type = QueryType.EQ) + private BigDecimal giftMoney; + + @Schema(description = "实际到账金额") + @QueryField(type = QueryType.EQ) + private BigDecimal actualMoney; + + @Schema(description = "用户可用余额") + @QueryField(type = QueryType.EQ) + private BigDecimal balance; + + @Schema(description = "支付方式(微信/支付宝)") + private String payMethod; + + @Schema(description = "支付状态(10待支付 20已支付)") + @QueryField(type = QueryType.EQ) + private Integer payStatus; + + @Schema(description = "付款时间") + @QueryField(type = QueryType.EQ) + private Integer payTime; + + @Schema(description = "第三方交易记录ID") + @QueryField(type = QueryType.EQ) + private Integer tradeId; + + @Schema(description = "来源客户端 (APP、H5、小程序等)") + private String platform; + + @Schema(description = "所属门店ID") + @QueryField(type = QueryType.EQ) + private Integer shopId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "商户编码") + private String merchantCode; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopSpecParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopSpecParam.java new file mode 100644 index 0000000..c2d5070 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopSpecParam.java @@ -0,0 +1,59 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 规格查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopSpecParam对象", description = "规格查询参数") +public class ShopSpecParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "规格ID") + @QueryField(type = QueryType.EQ) + private Integer specId; + + @Schema(description = "规格名称") + private String specName; + + @Schema(description = "规格值") + private String specValue; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "创建用户") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "更新者") + @QueryField(type = QueryType.EQ) + private Integer updater; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1待修,2异常已修,3异常未修") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopSpecValueParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopSpecValueParam.java new file mode 100644 index 0000000..f88b7e6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopSpecValueParam.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 规格值查询参数 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopSpecValueParam对象", description = "规格值查询参数") +public class ShopSpecValueParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "规格值ID") + @QueryField(type = QueryType.EQ) + private Integer specValueId; + + @Schema(description = "规格组ID") + @QueryField(type = QueryType.EQ) + private Integer specId; + + @Schema(description = "规格值") + private String specValue; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopSplashParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopSplashParam.java new file mode 100644 index 0000000..be4b6d4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopSplashParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 开屏广告查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopSplashParam对象", description = "开屏广告查询参数") +public class ShopSplashParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "标题") + private String title; + + @Schema(description = "图片") + private String image; + + @Schema(description = "跳转类型") + private String jumpType; + + @Schema(description = "跳转主键") + @QueryField(type = QueryType.EQ) + private Integer jumpPk; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUserAddressParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUserAddressParam.java new file mode 100644 index 0000000..9120916 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUserAddressParam.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 收货地址查询参数 + * + * @author 科技小王子 + * @since 2025-07-22 23:06:40 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopUserAddressParam对象", description = "收货地址查询参数") +public class ShopUserAddressParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "所在国家") + private String country; + + @Schema(description = "所在省份") + private String province; + + @Schema(description = "所在城市") + private String city; + + @Schema(description = "所在辖区") + private String region; + + @Schema(description = "收货地址") + private String address; + + @Schema(description = "收货地址") + private String fullAddress; + + private String lat; + + private String lng; + + @Schema(description = "1先生 2女士") + @QueryField(type = QueryType.EQ) + private Integer gender; + + @Schema(description = "家、公司、学校") + private String type; + + @Schema(description = "默认收货地址") + @QueryField(type = QueryType.EQ) + private Boolean isDefault; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUserBalanceLogParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUserBalanceLogParam.java new file mode 100644 index 0000000..d33a9fb --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUserBalanceLogParam.java @@ -0,0 +1,78 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户余额变动明细表查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopUserBalanceLogParam对象", description = "用户余额变动明细表查询参数") +public class ShopUserBalanceLogParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer logId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "余额变动场景(0下级下单1供应商收入2差价收益 10用户充值 20用户消费 30管理员操作 40订单退款)") + @QueryField(type = QueryType.EQ) + private Integer scene; + + @Schema(description = "变动金额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "变动后余额") + @QueryField(type = QueryType.EQ) + private BigDecimal balance; + + @Schema(description = "管理员备注") + private String remark; + + @Schema(description = "订单编号") + private String orderNo; + + @Schema(description = "操作人ID") + @QueryField(type = QueryType.EQ) + private Integer adminId; + + @Schema(description = "排序(数字越小越靠前)") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "商户ID") + @QueryField(type = QueryType.EQ) + private Long merchantId; + + @Schema(description = "商户编码") + private String merchantCode; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUserCollectionParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUserCollectionParam.java new file mode 100644 index 0000000..e2a814b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUserCollectionParam.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 我的收藏查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopUserCollectionParam对象", description = "我的收藏查询参数") +public class ShopUserCollectionParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "0店铺,1商品") + @QueryField(type = QueryType.EQ) + private Boolean type; + + @Schema(description = "租户ID") + @QueryField(type = QueryType.EQ) + private Integer tid; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUserCouponParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUserCouponParam.java new file mode 100644 index 0000000..3051530 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUserCouponParam.java @@ -0,0 +1,96 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户优惠券查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopUserCouponParam对象", description = "用户优惠券查询参数") +public class ShopUserCouponParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "id") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "优惠券模板ID") + @QueryField(type = QueryType.EQ) + private Integer couponId; + + @Schema(description = "用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "优惠券名称") + private String name; + + @Schema(description = "优惠券描述") + private String description; + + @Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)") + @QueryField(type = QueryType.EQ) + private Integer type; + + @Schema(description = "满减券-减免金额") + @QueryField(type = QueryType.EQ) + private BigDecimal reducePrice; + + @Schema(description = "折扣券-折扣率(0-100)") + @QueryField(type = QueryType.EQ) + private Integer discount; + + @Schema(description = "最低消费金额") + @QueryField(type = QueryType.EQ) + private BigDecimal minPrice; + + @Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)") + @QueryField(type = QueryType.EQ) + private Integer applyRange; + + @Schema(description = "适用范围配置(json格式)") + private String applyRangeConfig; + + @Schema(description = "有效期开始时间") + private String startTime; + + @Schema(description = "有效期结束时间") + private String endTime; + + @Schema(description = "使用状态(0未使用 1已使用 2已过期)") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "使用时间") + private String useTime; + + @Schema(description = "使用订单ID") + private Long orderId; + + @Schema(description = "使用订单号") + private String orderNo; + + @Schema(description = "获取方式(10主动领取 20系统发放 30活动赠送)") + @QueryField(type = QueryType.EQ) + private Integer obtainType; + + @Schema(description = "获取来源描述") + private String obtainSource; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Boolean deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUserRefereeParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUserRefereeParam.java new file mode 100644 index 0000000..f0ea733 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUserRefereeParam.java @@ -0,0 +1,48 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户推荐关系表查询参数 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopUserRefereeParam对象", description = "用户推荐关系表查询参数") +public class ShopUserRefereeParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "推荐人ID") + @QueryField(type = QueryType.EQ) + private Integer dealerId; + + @Schema(description = "用户id(被推荐人)") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "推荐关系层级(1,2,3)") + @QueryField(type = QueryType.EQ) + private Integer level; + + @Schema(description = "备注") + private String comments; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUsersParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUsersParam.java new file mode 100644 index 0000000..d7211cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUsersParam.java @@ -0,0 +1,77 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopUsersParam对象", description = "查询参数") +public class ShopUsersParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "用户唯一小程序id") + private String openId; + + @Schema(description = "小程序用户秘钥") + private String sessionKey; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "头像地址") + private String avatarUrl; + + @Schema(description = "1男,2女") + @QueryField(type = QueryType.EQ) + private Boolean gender; + + @Schema(description = "国家") + private String country; + + @Schema(description = "省份") + private String province; + + @Schema(description = "城市") + private String city; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "积分") + @QueryField(type = QueryType.EQ) + private BigDecimal integral; + + @Schema(description = "余额") + @QueryField(type = QueryType.EQ) + private BigDecimal money; + + @Schema(description = "排序号") + @QueryField(type = QueryType.EQ) + private Integer sortNumber; + + private String idCard; + + private String realName; + + @Schema(description = "是否管理员:1是;2否") + @QueryField(type = QueryType.EQ) + private Boolean isAdmin; + +} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopWechatDepositParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopWechatDepositParam.java new file mode 100644 index 0000000..97370b7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/param/ShopWechatDepositParam.java @@ -0,0 +1,66 @@ +package com.gxwebsoft.shop.param; + +import java.math.BigDecimal; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 押金查询参数 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "ShopWechatDepositParam对象", description = "押金查询参数") +public class ShopWechatDepositParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "订单id") + @QueryField(type = QueryType.EQ) + private Integer oid; + + @Schema(description = "用户id") + @QueryField(type = QueryType.EQ) + private Integer uid; + + @Schema(description = "场地订单号") + private String orderNum; + + @Schema(description = "付款订单号") + private String wechatOrder; + + @Schema(description = "退款订单号 ") + private String wechatReturn; + + @Schema(description = "场馆名称") + private String siteName; + + @Schema(description = "微信昵称") + private String username; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "物品名称") + private String name; + + @Schema(description = "押金金额") + @QueryField(type = QueryType.EQ) + private BigDecimal price; + + @Schema(description = "押金状态,1已付款,2未付款,已退押金") + @QueryField(type = QueryType.EQ) + private Boolean status; + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/CouponStatusService.java b/src/main/java/com/gxwebsoft/shop/service/CouponStatusService.java new file mode 100644 index 0000000..0bfad4a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/CouponStatusService.java @@ -0,0 +1,154 @@ +package com.gxwebsoft.shop.service; + +import com.gxwebsoft.shop.entity.ShopUserCoupon; + +import java.util.List; + +/** + * 优惠券状态管理服务 + * + * @author WebSoft + * @since 2025-01-15 + */ +public interface CouponStatusService { + + /** + * 获取用户可用的优惠券列表 + * + * @param userId 用户ID + * @return 可用优惠券列表 + */ + List getAvailableCoupons(Integer userId); + + /** + * 获取用户已使用的优惠券列表 + * + * @param userId 用户ID + * @return 已使用优惠券列表 + */ + List getUsedCoupons(Integer userId); + + /** + * 获取用户已过期的优惠券列表 + * + * @param userId 用户ID + * @return 已过期优惠券列表 + */ + List getExpiredCoupons(Integer userId); + + /** + * 获取用户所有优惠券并按状态分类 + * + * @param userId 用户ID + * @return 分类后的优惠券列表 + */ + CouponStatusResult getUserCouponsGroupByStatus(Integer userId); + + /** + * 使用优惠券 + * + * @param userCouponId 用户优惠券ID + * @param orderId 订单ID + * @param orderNo 订单号 + * @return 是否成功 + */ + boolean useCoupon(Long userCouponId, Integer orderId, String orderNo); + + /** + * 退还优惠券(订单取消时) + * + * @param orderId 订单ID + * @return 是否成功 + */ + boolean returnCoupon(Integer orderId); + + /** + * 批量更新过期优惠券状态 + * + * @return 更新的数量 + */ + int updateExpiredCoupons(); + + /** + * 检查并更新单个优惠券状态 + * + * @param userCoupon 用户优惠券 + * @return 是否状态发生变化 + */ + boolean checkAndUpdateCouponStatus(ShopUserCoupon userCoupon); + + /** + * 验证优惠券是否可用于指定订单 + * + * @param userCouponId 用户优惠券ID + * @param totalAmount 订单总金额 + * @param goodsIds 商品ID列表 + * @return 验证结果 + */ + CouponValidationResult validateCouponForOrder(Long userCouponId, + java.math.BigDecimal totalAmount, + List goodsIds); + + /** + * 优惠券状态分类结果 + */ + class CouponStatusResult { + private List availableCoupons; // 可用优惠券 + private List usedCoupons; // 已使用优惠券 + private List expiredCoupons; // 已过期优惠券 + private int totalCount; // 总数量 + + // 构造函数 + public CouponStatusResult(List availableCoupons, + List usedCoupons, + List expiredCoupons) { + this.availableCoupons = availableCoupons; + this.usedCoupons = usedCoupons; + this.expiredCoupons = expiredCoupons; + this.totalCount = availableCoupons.size() + usedCoupons.size() + expiredCoupons.size(); + } + + // Getters and Setters + public List getAvailableCoupons() { return availableCoupons; } + public void setAvailableCoupons(List availableCoupons) { this.availableCoupons = availableCoupons; } + + public List getUsedCoupons() { return usedCoupons; } + public void setUsedCoupons(List usedCoupons) { this.usedCoupons = usedCoupons; } + + public List getExpiredCoupons() { return expiredCoupons; } + public void setExpiredCoupons(List expiredCoupons) { this.expiredCoupons = expiredCoupons; } + + public int getTotalCount() { return totalCount; } + public void setTotalCount(int totalCount) { this.totalCount = totalCount; } + } + + /** + * 优惠券验证结果 + */ + class CouponValidationResult { + private boolean valid; // 是否有效 + private String message; // 验证消息 + private java.math.BigDecimal discountAmount; // 优惠金额 + + public CouponValidationResult(boolean valid, String message) { + this.valid = valid; + this.message = message; + } + + public CouponValidationResult(boolean valid, String message, java.math.BigDecimal discountAmount) { + this.valid = valid; + this.message = message; + this.discountAmount = discountAmount; + } + + // Getters and Setters + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public java.math.BigDecimal getDiscountAmount() { return discountAmount; } + public void setDiscountAmount(java.math.BigDecimal discountAmount) { this.discountAmount = discountAmount; } + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java b/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java new file mode 100644 index 0000000..cf2fb7e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java @@ -0,0 +1,691 @@ +package com.gxwebsoft.shop.service; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.gxwebsoft.common.core.exception.BusinessException; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.config.OrderConfigProperties; +import com.gxwebsoft.shop.dto.OrderCreateRequest; +import com.gxwebsoft.shop.entity.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 订单业务服务类 + * 处理订单创建的核心业务逻辑 + * + * @author 科技小王子 + * @since 2025-01-26 + */ +@Slf4j +@Service +public class OrderBusinessService { + + @Resource + private ShopOrderService shopOrderService; + + @Resource + private ShopOrderGoodsService shopOrderGoodsService; + + @Resource + private ShopGoodsService shopGoodsService; + + @Resource + private ShopGoodsSkuService shopGoodsSkuService; + + @Resource + private OrderConfigProperties orderConfig; + + @Resource + private ShopUserAddressService shopUserAddressService; + @Resource + private ShopUserCouponService shopUserCouponService; + + /** + * 创建订单 + * + * @param request 订单创建请求 + * @param loginUser 当前登录用户 + * @return 微信支付订单信息 + */ + @Transactional(rollbackFor = Exception.class) + public Map createOrder(OrderCreateRequest request, User loginUser) { + + // 1. 参数校验 + validateOrderRequest(request, loginUser); + + // 2. 构建订单对象 + ShopOrder shopOrder = buildShopOrder(request, loginUser); + + // 3. 处理收货地址信息 + processDeliveryAddress(shopOrder, request, loginUser); + + // 4. 应用业务规则 + applyBusinessRules(shopOrder, loginUser); + + // 5. 保存订单 + boolean saved = shopOrderService.save(shopOrder); + if (!saved) { + throw new BusinessException("订单保存失败"); + } + + // 6. 保存订单商品 + saveOrderGoods(request, shopOrder); + + // 7. 标记优惠券为已使用 + if (shopOrder.getCouponId() != null && shopOrder.getCouponId() > 0) { + markCouponAsUsed(shopOrder.getCouponId(), shopOrder.getOrderId()); + } + + // 8. 创建微信支付订单 + try { + return shopOrderService.createWxOrder(shopOrder); + } catch (Exception e) { + log.error("创建微信支付订单失败,订单号:{}", shopOrder.getOrderNo(), e); + throw new BusinessException("创建支付订单失败:" + e.getMessage()); + } + } + + /** + * 校验订单请求参数 + */ + private void validateOrderRequest(OrderCreateRequest request, User loginUser) { + if (loginUser == null) { + throw new BusinessException("用户未登录"); + } + + // 检查是否为测试账号 + boolean isTestAccount = orderConfig.isTestAccount(loginUser.getPhone()); + + if (isTestAccount) { + // 测试账号:直接使用测试金额,跳过金额验证 + BigDecimal testAmount = orderConfig.getTestAccount().getTestPayAmount(); + request.setTotalPrice(testAmount); + log.info("测试账号订单,用户:{},使用测试金额:{}", loginUser.getPhone(), testAmount); + return; // 测试账号跳过后续验证 + } + + // 非测试账号:正常验证流程 + // 验证商品信息并计算总金额 + BigDecimal calculatedTotal = validateAndCalculateTotal(request); + + if (calculatedTotal.compareTo(BigDecimal.ZERO) <= 0) { + throw new BusinessException("商品金额不能为0"); + } + + // 检查前端传入的总金额是否正确(允许小的误差,比如0.01) + if (request.getTotalPrice() != null && + request.getTotalPrice().subtract(calculatedTotal).abs().compareTo(new BigDecimal("0.01")) > 0) { + log.warn("订单金额计算不一致,前端传入:{},后台计算:{}", request.getTotalPrice(), calculatedTotal); + throw new BusinessException("订单金额计算错误,请刷新重试"); + } + + // 使用后台计算的金额 + request.setTotalPrice(calculatedTotal); + + // 检查租户特殊规则 + OrderConfigProperties.TenantRule tenantRule = orderConfig.getTenantRule(request.getTenantId()); + if (tenantRule != null && tenantRule.getMinAmount() != null) { + if (calculatedTotal.compareTo(tenantRule.getMinAmount()) < 0) { + throw new BusinessException(tenantRule.getMinAmountMessage()); + } + } + } + + /** + * 验证商品信息并计算总金额 + */ + private BigDecimal validateAndCalculateTotal(OrderCreateRequest request) { + if (CollectionUtils.isEmpty(request.getGoodsItems())) { + throw new BusinessException("订单商品列表不能为空"); + } + + BigDecimal total = BigDecimal.ZERO; + + for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) { + // 验证商品ID + if (item.getGoodsId() == null) { + throw new BusinessException("商品ID不能为空"); + } + + // 验证购买数量 + if (item.getQuantity() == null || item.getQuantity() <= 0) { + throw new BusinessException("商品购买数量必须大于0"); + } + + // 获取商品信息 + ShopGoods goods = shopGoodsService.getById(item.getGoodsId()); + if (goods == null) { + throw new BusinessException("商品不存在,商品ID:" + item.getGoodsId()); + } + + // 验证商品状态 + if (goods.getStatus() == null || goods.getStatus() != 0) { + throw new BusinessException("商品已下架:" + goods.getName()); + } + + // 处理多规格商品价格和库存验证 + BigDecimal actualPrice = goods.getPrice(); // 默认使用商品价格 + Integer actualStock = goods.getStock(); // 默认使用商品库存 + String productName = goods.getName(); + + if (item.getSkuId() != null) { + // 多规格商品,获取SKU信息 + ShopGoodsSku sku = shopGoodsSkuService.getById(item.getSkuId()); + if (sku == null) { + throw new BusinessException("商品规格不存在,SKU ID:" + item.getSkuId()); + } + + // 验证SKU是否属于该商品 + if (!sku.getGoodsId().equals(item.getGoodsId())) { + throw new BusinessException("商品规格不匹配"); + } + + // 验证SKU状态 + if (sku.getStatus() == null || sku.getStatus() != 0) { + throw new BusinessException("商品规格已下架"); + } + + // 使用SKU的价格和库存 + actualPrice = sku.getPrice(); + actualStock = sku.getStock(); + productName = goods.getName() + "(" + (item.getSpecInfo() != null ? item.getSpecInfo() : sku.getSku()) + ")"; + } + + // 验证实际价格 + if (actualPrice == null || actualPrice.compareTo(BigDecimal.ZERO) <= 0) { + throw new BusinessException("商品价格异常:" + productName); + } + + // 验证库存 + if (actualStock != null && actualStock < item.getQuantity()) { + throw new BusinessException("商品库存不足:" + productName + ",当前库存:" + actualStock); + } + + // 验证购买数量限制(使用商品级别的限制) + if (goods.getCanBuyNumber() != null && goods.getCanBuyNumber() > 0 && + item.getQuantity() > goods.getCanBuyNumber()) { + throw new BusinessException("商品购买数量超过限制:" + productName + ",最大购买数量:" + goods.getCanBuyNumber()); + } + + // 计算商品小计(使用实际价格) + BigDecimal itemTotal = actualPrice.multiply(new BigDecimal(item.getQuantity())); + total = total.add(itemTotal); + + log.debug("商品验证通过 - ID:{},SKU ID:{},名称:{},单价:{},数量:{},小计:{}", + goods.getGoodsId(), item.getSkuId(), productName, actualPrice, item.getQuantity(), itemTotal); + } + + log.info("订单商品验证完成,总金额:{}", total); + return total; + } + + /** + * 构建订单对象 + */ + private ShopOrder buildShopOrder(OrderCreateRequest request, User loginUser) { + ShopOrder shopOrder = new ShopOrder(); + + // 复制请求参数到订单对象 + BeanUtils.copyProperties(request, shopOrder); + + // 确保租户ID正确设置(关键字段,影响微信支付证书路径) + shopOrder.setTenantId(loginUser.getTenantId()); + + // 验证关键字段 + if (shopOrder.getTenantId() == null) { + throw new BusinessException("租户ID不能为空,这会导致微信支付证书路径错误"); + } + + // 设置用户相关信息 + shopOrder.setUserId(loginUser.getUserId()); + shopOrder.setOpenid(loginUser.getOpenid()); + shopOrder.setPayUserId(loginUser.getUserId()); + + log.debug("构建订单对象 - 租户ID:{},用户ID:{}", shopOrder.getTenantId(), shopOrder.getUserId()); + + // 生成订单号(如果请求中没有提供) + if (shopOrder.getOrderNo() == null || shopOrder.getOrderNo().trim().isEmpty()) { + String generatedOrderNo = Long.toString(IdUtil.getSnowflakeNextId()); + shopOrder.setOrderNo(generatedOrderNo); + log.info("生成新订单号: {}", generatedOrderNo); + } else { + log.info("使用请求中的订单号: {}", shopOrder.getOrderNo()); + } + + // 设置默认备注 + if (shopOrder.getComments() == null) { + shopOrder.setComments(orderConfig.getDefaultConfig().getDefaultComments()); + } + + // 设置价格相关字段(解决数据库字段没有默认值的问题) + if (shopOrder.getPayPrice() == null) { + shopOrder.setPayPrice(shopOrder.getTotalPrice()); // 实际付款默认等于订单总额 + } + + if (shopOrder.getPrice() == null) { + shopOrder.setPrice(shopOrder.getTotalPrice()); // 用于统计的价格默认等于订单总额 + } + + if (shopOrder.getReducePrice() == null) { + shopOrder.setReducePrice(BigDecimal.ZERO); // 减少金额默认为0 + } + + if (shopOrder.getMoney() == null) { + shopOrder.setMoney(shopOrder.getTotalPrice()); // 用于积分赠送的价格默认等于订单总额 + } + + // 设置默认状态 + shopOrder.setPayStatus(false); // 未付款 + shopOrder.setOrderStatus(0); // 未使用 + shopOrder.setDeliveryStatus(10); // 未发货 + shopOrder.setIsInvoice(0); // 未开发票 + shopOrder.setIsSettled(0); // 未结算 + shopOrder.setCheckBill(0); // 未对账 + shopOrder.setVersion(0); // 当前版本 + + // 设置默认支付类型(如果没有指定) + if (shopOrder.getPayType() == null) { + shopOrder.setPayType(1); // 默认微信支付 + } + + // 优惠券处理 + if (shopOrder.getCouponId() != null && shopOrder.getCouponId() > 0) { + processCoupon(shopOrder, loginUser); + } + + return shopOrder; + } + + /** + * 处理优惠券 + */ + private void processCoupon(ShopOrder shopOrder, User loginUser) { + ShopUserCoupon coupon = shopUserCouponService.getById(shopOrder.getCouponId()); + if (coupon == null) { + throw new BusinessException("优惠券不存在"); + } + + // 验证优惠券是否属于当前用户 + if (!coupon.getUserId().equals(loginUser.getUserId())) { + throw new BusinessException("优惠券不属于当前用户"); + } + + // 验证优惠券是否已使用 + if (coupon.getIsUse() != null && coupon.getIsUse().equals(1)) { + throw new BusinessException("优惠券已使用"); + } + + // 验证优惠券是否过期 + if (coupon.getIsExpire() != null && coupon.getIsExpire().equals(1)) { + throw new BusinessException("优惠券已过期"); + } + + // 计算优惠金额 + BigDecimal reducePrice = BigDecimal.ZERO; + boolean canUse = true; + + if (coupon.getType().equals(10)) { + // 满减券 + reducePrice = coupon.getReducePrice() != null ? coupon.getReducePrice() : BigDecimal.ZERO; + BigDecimal minPrice = coupon.getMinPrice() != null ? coupon.getMinPrice() : BigDecimal.ZERO; + if (shopOrder.getTotalPrice().compareTo(minPrice) < 0) { + canUse = false; + throw new BusinessException("订单金额不满足优惠券使用条件,最低消费:" + minPrice + "元"); + } + } else if (coupon.getType().equals(20)) { + // 折扣券 - 计算减免金额(不是折扣后金额) + Integer discount = coupon.getDiscount() != null ? coupon.getDiscount() : 100; + if (discount < 0 || discount > 100) { + throw new BusinessException("优惠券折扣率异常"); + } + // 减免金额 = 原价 * (100 - 折扣率) / 100 + reducePrice = shopOrder.getTotalPrice() + .multiply(new BigDecimal(100 - discount)) + .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); + } else if (coupon.getType().equals(30)) { + // 免费券 + reducePrice = shopOrder.getTotalPrice(); + } else { + throw new BusinessException("不支持的优惠券类型"); + } + + if (canUse && reducePrice.compareTo(BigDecimal.ZERO) > 0) { + // 确保减免金额不超过订单总额 + if (reducePrice.compareTo(shopOrder.getTotalPrice()) > 0) { + reducePrice = shopOrder.getTotalPrice(); + } + + // 应用优惠 + shopOrder.setReducePrice(shopOrder.getReducePrice().add(reducePrice)); + shopOrder.setPayPrice(shopOrder.getPayPrice().subtract(reducePrice)); + + // 确保实付金额不为负数 + if (shopOrder.getPayPrice().compareTo(BigDecimal.ZERO) < 0) { + shopOrder.setPayPrice(BigDecimal.ZERO); + } + + log.info("应用优惠券成功 - 优惠券ID:{},类型:{},减免金额:{},实付金额:{}", + coupon.getId(), coupon.getType(), reducePrice, shopOrder.getPayPrice()); + } + } + + /** + * 处理收货地址信息 + * 优先级:前端传入地址 > 指定地址ID > 用户默认地址 + */ + private void processDeliveryAddress(ShopOrder shopOrder, OrderCreateRequest request, User loginUser) { + try { + // 1. 如果前端已经传入了完整的收货地址信息,直接使用 + if (isAddressInfoComplete(request)) { + log.info("使用前端传入的收货地址信息,用户ID:{}", loginUser.getUserId()); + return; + } + + // 2. 如果指定了地址ID,获取该地址信息 + if (request.getAddressId() != null) { + ShopUserAddress userAddress = shopUserAddressService.getById(request.getAddressId()); + if (userAddress != null && userAddress.getUserId().equals(loginUser.getUserId())) { + copyAddressToOrder(userAddress, shopOrder, request); + log.info("使用指定地址ID:{},用户ID:{}", request.getAddressId(), loginUser.getUserId()); + return; + } + log.warn("指定的地址ID不存在或不属于当前用户,地址ID:{},用户ID:{}", + request.getAddressId(), loginUser.getUserId()); + } + + // 3. 获取用户默认收货地址 + ShopUserAddress defaultAddress = shopUserAddressService.getDefaultAddress(loginUser.getUserId()); + if (defaultAddress != null) { + copyAddressToOrder(defaultAddress, shopOrder, request); + log.info("使用用户默认收货地址,地址ID:{},用户ID:{}", defaultAddress.getId(), loginUser.getUserId()); + return; + } + + // 4. 如果没有默认地址,获取用户的第一个地址 + List userAddresses = shopUserAddressService.getUserAddresses(loginUser.getUserId()); + if (!userAddresses.isEmpty()) { + ShopUserAddress firstAddress = userAddresses.get(0); + copyAddressToOrder(firstAddress, shopOrder, request); + log.info("使用用户第一个收货地址,地址ID:{},用户ID:{}", firstAddress.getId(), loginUser.getUserId()); + return; + } + // 5. 如果用户没有任何收货地址,抛出异常 + throw new BusinessException("请先添加收货地址"); + + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("处理收货地址信息失败,用户ID:{}", loginUser.getUserId(), e); + throw new BusinessException("处理收货地址信息失败:" + e.getMessage()); + } + } + + /** + * 检查前端传入的地址信息是否完整 + */ + private boolean isAddressInfoComplete(OrderCreateRequest request) { + return request.getAddress() != null && !request.getAddress().trim().isEmpty() && + request.getRealName() != null && !request.getRealName().trim().isEmpty(); + } + + /** + * 将用户地址信息复制到订单中(创建快照) + */ + private void copyAddressToOrder(ShopUserAddress userAddress, ShopOrder shopOrder, OrderCreateRequest request) { + // 保存地址ID引用关系 + shopOrder.setAddressId(userAddress.getId()); + request.setAddressId(userAddress.getId()); + + // 创建地址信息快照 + if (request.getAddress() == null || request.getAddress().trim().isEmpty()) { + // 构建完整地址 + StringBuilder fullAddress = new StringBuilder(); + if (userAddress.getProvince() != null) fullAddress.append(userAddress.getProvince()); + if (userAddress.getCity() != null) fullAddress.append(userAddress.getCity()); + if (userAddress.getRegion() != null) fullAddress.append(userAddress.getRegion()); + if (userAddress.getAddress() != null) fullAddress.append(userAddress.getAddress()); + + shopOrder.setAddress(fullAddress.toString()); + request.setAddress(fullAddress.toString()); + } + + // 复制收货人信息 + if (request.getRealName() == null || request.getRealName().trim().isEmpty()) { + shopOrder.setRealName(userAddress.getName()); + request.setRealName(userAddress.getName()); + } + + // 复制经纬度信息 + if (request.getAddressLat() == null && userAddress.getLat() != null) { + shopOrder.setAddressLat(userAddress.getLat()); + request.setAddressLat(userAddress.getLat()); + } + if (request.getAddressLng() == null && userAddress.getLng() != null) { + shopOrder.setAddressLng(userAddress.getLng()); + request.setAddressLng(userAddress.getLng()); + } + + log.debug("地址信息快照创建完成 - 地址ID:{},收货人:{},地址:{}", + userAddress.getId(), userAddress.getName(), shopOrder.getAddress()); + } + + /** + * 应用业务规则 + */ + private void applyBusinessRules(ShopOrder shopOrder, User loginUser) { + // 测试账号处理 + if (orderConfig.isTestAccount(loginUser.getPhone())) { + BigDecimal testAmount = orderConfig.getTestAccount().getTestPayAmount(); + shopOrder.setPrice(testAmount); + shopOrder.setTotalPrice(testAmount); + shopOrder.setPayPrice(testAmount); // 确保实际付款也设置为测试金额 + shopOrder.setMoney(testAmount); // 确保积分计算金额也设置为测试金额 + log.info("应用测试账号规则,用户:{},测试金额:{}", loginUser.getPhone(), testAmount); + } + + // 其他业务规则可以在这里添加 + // 例如:VIP折扣、优惠券处理等 + } + + /** + * 校验订单金额 + */ + public void validateOrderAmount(BigDecimal amount, Integer tenantId) { + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new BusinessException("订单金额必须大于0"); + } + + OrderConfigProperties.TenantRule tenantRule = orderConfig.getTenantRule(tenantId); + if (tenantRule != null && tenantRule.getMinAmount() != null) { + if (amount.compareTo(tenantRule.getMinAmount()) < 0) { + throw new BusinessException(tenantRule.getMinAmountMessage()); + } + } + } + + /** + * 保存订单商品 + */ + private void saveOrderGoods(OrderCreateRequest request, ShopOrder shopOrder) { + if (CollectionUtils.isEmpty(request.getGoodsItems())) { + log.warn("订单商品列表为空,订单号:{}", shopOrder.getOrderNo()); + return; + } + + List orderGoodsList = new ArrayList<>(); + for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) { + // 重新获取商品信息(确保数据一致性) + ShopGoods goods = shopGoodsService.getById(item.getGoodsId()); + if (goods == null) { + throw new BusinessException("商品不存在,商品ID:" + item.getGoodsId()); + } + + // 再次验证商品状态(防止并发问题) + if (goods.getStatus() == null || goods.getStatus() != 0) { + throw new BusinessException("商品已下架:" + goods.getName()); + } + + // 处理多规格商品 + ShopGoodsSku sku = null; + BigDecimal actualPrice = goods.getPrice(); // 默认使用商品价格 + Integer actualStock = goods.getStock(); // 默认使用商品库存 + String specInfo = item.getSpecInfo(); // 规格信息 + + if (item.getSkuId() != null) { + // 多规格商品,获取SKU信息 + sku = shopGoodsSkuService.getById(item.getSkuId()); + if (sku == null) { + throw new BusinessException("商品规格不存在,SKU ID:" + item.getSkuId()); + } + + // 验证SKU是否属于该商品 + if (!sku.getGoodsId().equals(item.getGoodsId())) { + throw new BusinessException("商品规格不匹配"); + } + + // 验证SKU状态 + if (sku.getStatus() == null || sku.getStatus() != 0) { + throw new BusinessException("商品规格已下架"); + } + + // 使用SKU的价格和库存 + actualPrice = sku.getPrice(); + actualStock = sku.getStock(); + + // 如果前端没有传规格信息,使用SKU的规格信息 + if (specInfo == null || specInfo.trim().isEmpty()) { + specInfo = sku.getSku(); // 使用SKU的规格描述 + } + } + + // 验证库存 + if (actualStock == null || actualStock < item.getQuantity()) { + String stockMsg = sku != null ? "商品规格库存不足" : "商品库存不足"; + throw new BusinessException(stockMsg + ",当前库存:" + (actualStock != null ? actualStock : 0)); + } + + ShopOrderGoods orderGoods = new ShopOrderGoods(); + + // 设置订单关联信息 + orderGoods.setOrderId(shopOrder.getOrderId()); + orderGoods.setOrderCode(shopOrder.getOrderNo()); + + // 设置商户信息 + orderGoods.setMerchantId(shopOrder.getMerchantId()); + orderGoods.setMerchantName(shopOrder.getMerchantName()); + + // 设置商品信息(使用后台查询的真实数据) + orderGoods.setGoodsId(item.getGoodsId()); + orderGoods.setSkuId(item.getSkuId()); // 设置SKU ID + orderGoods.setGoodsName(goods.getName()); + orderGoods.setImage(sku != null && sku.getImage() != null ? sku.getImage() : goods.getImage()); // 优先使用SKU图片 + orderGoods.setPrice(actualPrice); // 使用实际价格(SKU价格或商品价格) + orderGoods.setTotalNum(item.getQuantity()); + + // 计算商品小计(用于日志记录) + BigDecimal itemTotal = actualPrice.multiply(new BigDecimal(item.getQuantity())); + + // 设置商品规格信息 + orderGoods.setSpec(specInfo); + + // 设置支付相关信息 + orderGoods.setPayStatus(0); // 0 未付款 + orderGoods.setOrderStatus(0); // 0 未使用 + orderGoods.setIsFree(false); // 默认收费 + orderGoods.setVersion(0); // 当前版本 + + // 设置其他信息 + orderGoods.setComments(request.getComments()); + orderGoods.setUserId(shopOrder.getUserId()); + orderGoods.setTenantId(shopOrder.getTenantId()); + + orderGoodsList.add(orderGoods); + + log.debug("准备保存订单商品 - 商品ID:{},名称:{},单价:{},数量:{},小计:{}", + goods.getGoodsId(), goods.getName(), goods.getPrice(), item.getQuantity(), itemTotal); + } + + // 批量保存订单商品 + boolean saved = shopOrderGoodsService.saveBatch(orderGoodsList); + if (!saved) { + throw new BusinessException("保存订单商品失败"); + } + + // 扣减库存 + deductStock(request); + + log.info("成功保存订单商品,订单号:{},商品数量:{}", shopOrder.getOrderNo(), orderGoodsList.size()); + } + + /** + * 扣减库存 + */ + private void deductStock(OrderCreateRequest request) { + for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) { + if (item.getSkuId() != null) { + // 多规格商品,扣减SKU库存 + ShopGoodsSku sku = shopGoodsSkuService.getById(item.getSkuId()); + if (sku != null && sku.getStock() != null) { + int newStock = sku.getStock() - item.getQuantity(); + if (newStock < 0) { + throw new BusinessException("SKU库存不足,无法完成扣减"); + } + sku.setStock(newStock); + shopGoodsSkuService.updateById(sku); + log.debug("扣减SKU库存 - SKU ID:{},扣减数量:{},剩余库存:{}", + item.getSkuId(), item.getQuantity(), newStock); + } + } else { + // 单规格商品,扣减商品库存 + ShopGoods goods = shopGoodsService.getById(item.getGoodsId()); + if (goods != null && goods.getStock() != null) { + int newStock = goods.getStock() - item.getQuantity(); + if (newStock < 0) { + throw new BusinessException("商品库存不足,无法完成扣减"); + } + goods.setStock(newStock); + shopGoodsService.updateById(goods); + log.debug("扣减商品库存 - 商品ID:{},扣减数量:{},剩余库存:{}", + item.getGoodsId(), item.getQuantity(), newStock); + } + } + } + log.info("库存扣减完成"); + } + + /** + * 标记优惠券为已使用 + */ + private void markCouponAsUsed(Integer couponId, Integer orderId) { + try { + ShopUserCoupon coupon = shopUserCouponService.getById(couponId); + if (coupon != null) { + // 使用实体类提供的方法标记为已使用 + coupon.markAsUsed(orderId, null); // orderNo 在这里可以为null,因为已经有orderId了 + shopUserCouponService.updateById(coupon); + log.info("优惠券标记为已使用 - 优惠券ID:{},订单ID:{}", couponId, orderId); + } + } catch (Exception e) { + log.error("标记优惠券为已使用失败 - 优惠券ID:{},订单ID:{}", couponId, orderId, e); + // 不抛出异常,避免影响订单创建流程 + } + } + + /** + * 检查是否为测试账号11 + */ + public boolean isTestAccount(String phone) { + return orderConfig.isTestAccount(phone); + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/OrderCancelService.java b/src/main/java/com/gxwebsoft/shop/service/OrderCancelService.java new file mode 100644 index 0000000..da40112 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/OrderCancelService.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.shop.service; + +import com.gxwebsoft.shop.entity.ShopOrder; + +import java.util.List; + +/** + * 订单取消服务接口 + * + * @author WebSoft + * @since 2025-01-26 + */ +public interface OrderCancelService { + + /** + * 取消单个订单 + * + * @param order 订单对象 + * @return 是否取消成功 + */ + boolean cancelOrder(ShopOrder order); + + /** + * 批量取消订单 + * + * @param orders 订单列表 + * @return 成功取消的订单数量 + */ + int batchCancelOrders(List orders); + + /** + * 查找超时的待付款订单 + * + * @param timeoutMinutes 超时时间(分钟) + * @param batchSize 批量大小 + * @return 超时订单列表 + */ + List findExpiredUnpaidOrders(Integer timeoutMinutes, Integer batchSize); + + /** + * 查找指定租户的超时订单 + * + * @param tenantId 租户ID + * @param timeoutMinutes 超时时间(分钟) + * @param batchSize 批量大小 + * @return 超时订单列表 + */ + List findExpiredUnpaidOrdersByTenant(Integer tenantId, Integer timeoutMinutes, Integer batchSize); + + /** + * 回退订单库存 + * + * @param order 订单对象 + * @return 是否回退成功 + */ + boolean restoreOrderStock(ShopOrder order); + + /** + * 退还订单优惠券 + * + * @param order 订单对象 + * @return 是否退还成功 + */ + boolean returnOrderCoupon(ShopOrder order); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopArticleService.java b/src/main/java/com/gxwebsoft/shop/service/ShopArticleService.java new file mode 100644 index 0000000..da3c943 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopArticleService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopArticle; +import com.gxwebsoft.shop.param.ShopArticleParam; + +import java.util.List; + +/** + * 商品文章Service + * + * @author 科技小王子 + * @since 2025-08-13 05:14:53 + */ +public interface ShopArticleService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopArticleParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopArticleParam param); + + /** + * 根据id查询 + * + * @param articleId 文章ID + * @return ShopArticle + */ + ShopArticle getByIdRel(Integer articleId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopBrandService.java b/src/main/java/com/gxwebsoft/shop/service/ShopBrandService.java new file mode 100644 index 0000000..294a1f5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopBrandService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopBrand; +import com.gxwebsoft.shop.param.ShopBrandParam; + +import java.util.List; + +/** + * 品牌Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopBrandService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopBrandParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopBrandParam param); + + /** + * 根据id查询 + * + * @param brandId ID + * @return ShopBrand + */ + ShopBrand getByIdRel(Integer brandId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCartService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCartService.java new file mode 100644 index 0000000..18d4979 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCartService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCart; +import com.gxwebsoft.shop.param.ShopCartParam; + +import java.util.List; + +/** + * 购物车Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopCartService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCartParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCartParam param); + + /** + * 根据id查询 + * + * @param id 购物车表ID + * @return ShopCart + */ + ShopCart getByIdRel(Long id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCategoryService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCategoryService.java new file mode 100644 index 0000000..5ac828e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCategoryService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCategory; +import com.gxwebsoft.shop.param.ShopCategoryParam; + +import java.util.List; + +/** + * 商品分类Service + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +public interface ShopCategoryService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCategoryParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCategoryParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return ShopCategory + */ + ShopCategory getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopChatConversationService.java b/src/main/java/com/gxwebsoft/shop/service/ShopChatConversationService.java new file mode 100644 index 0000000..32eeac3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopChatConversationService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopChatConversation; +import com.gxwebsoft.shop.param.ShopChatConversationParam; + +import java.util.List; + +/** + * 聊天消息表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopChatConversationService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopChatConversationParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopChatConversationParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return ShopChatConversation + */ + ShopChatConversation getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopChatMessageService.java b/src/main/java/com/gxwebsoft/shop/service/ShopChatMessageService.java new file mode 100644 index 0000000..c1b40a8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopChatMessageService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopChatMessage; +import com.gxwebsoft.shop.param.ShopChatMessageParam; + +import java.util.List; + +/** + * 聊天消息表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopChatMessageService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopChatMessageParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopChatMessageParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return ShopChatMessage + */ + ShopChatMessage getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCommissionRoleService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCommissionRoleService.java new file mode 100644 index 0000000..e9ef11e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCommissionRoleService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCommissionRole; +import com.gxwebsoft.shop.param.ShopCommissionRoleParam; + +import java.util.List; + +/** + * 分红角色Service + * + * @author 科技小王子 + * @since 2025-05-01 10:01:15 + */ +public interface ShopCommissionRoleService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCommissionRoleParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCommissionRoleParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopCommissionRole + */ + ShopCommissionRole getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCountService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCountService.java new file mode 100644 index 0000000..65af394 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCountService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCount; +import com.gxwebsoft.shop.param.ShopCountParam; + +import java.util.List; + +/** + * 商城销售统计表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopCountService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCountParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCountParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return ShopCount + */ + ShopCount getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyCateService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyCateService.java new file mode 100644 index 0000000..f247bf7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyCateService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.param.ShopCouponApplyCateParam; + +import java.util.List; + +/** + * 优惠券可用分类Service + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +public interface ShopCouponApplyCateService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCouponApplyCateParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCouponApplyCateParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopCouponApplyCate + */ + ShopCouponApplyCate getByIdRel(Integer id); + + void removeByCouponId(Integer couponId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyItemService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyItemService.java new file mode 100644 index 0000000..bcca01d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCouponApplyItemService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.param.ShopCouponApplyItemParam; + +import java.util.List; + +/** + * 优惠券可用分类Service + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +public interface ShopCouponApplyItemService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCouponApplyItemParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCouponApplyItemParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopCouponApplyItem + */ + ShopCouponApplyItem getByIdRel(Integer id); + + void removeByCouponId(Integer couponId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopCouponService.java b/src/main/java/com/gxwebsoft/shop/service/ShopCouponService.java new file mode 100644 index 0000000..074b62c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopCouponService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.param.ShopCouponParam; + +import java.util.List; + +/** + * 优惠券Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:23 + */ +public interface ShopCouponService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopCouponParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopCouponParam param); + + /** + * 根据id查询 + * + * @param id id + * @return ShopCoupon + */ + ShopCoupon getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerApplyService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerApplyService.java new file mode 100644 index 0000000..dd982d6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerApplyService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerApply; +import com.gxwebsoft.shop.param.ShopDealerApplyParam; + +import java.util.List; + +/** + * 分销商申请记录表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:50:18 + */ +public interface ShopDealerApplyService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerApplyParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerApplyParam param); + + /** + * 根据id查询 + * + * @param applyId 主键ID + * @return ShopDealerApply + */ + ShopDealerApply getByIdRel(Integer applyId); + + ShopDealerApply getByUserIdRel(Integer userId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerCapitalService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerCapitalService.java new file mode 100644 index 0000000..67fb29c --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerCapitalService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerCapital; +import com.gxwebsoft.shop.param.ShopDealerCapitalParam; + +import java.util.List; + +/** + * 分销商资金明细表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerCapitalService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerCapitalParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerCapitalParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopDealerCapital + */ + ShopDealerCapital getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerOrderService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerOrderService.java new file mode 100644 index 0000000..8390e29 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerOrderService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerOrder; +import com.gxwebsoft.shop.param.ShopDealerOrderParam; + +import java.util.List; + +/** + * 分销商订单记录表Service + * + * @author 科技小王子 + * @since 2025-08-12 11:55:18 + */ +public interface ShopDealerOrderService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerOrderParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerOrderParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopDealerOrder + */ + ShopDealerOrder getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerRefereeService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerRefereeService.java new file mode 100644 index 0000000..f36b263 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerRefereeService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerReferee; +import com.gxwebsoft.shop.param.ShopDealerRefereeParam; + +import java.util.List; + +/** + * 分销商推荐关系表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerRefereeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerRefereeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerRefereeParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopDealerReferee + */ + ShopDealerReferee getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerSettingService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerSettingService.java new file mode 100644 index 0000000..b9b1c7b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerSettingService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerSetting; +import com.gxwebsoft.shop.param.ShopDealerSettingParam; + +import java.util.List; + +/** + * 分销商设置表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerSettingService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerSettingParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerSettingParam param); + + /** + * 根据id查询 + * + * @param key 设置项标示 + * @return ShopDealerSetting + */ + ShopDealerSetting getByIdRel(String key); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerUserService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerUserService.java new file mode 100644 index 0000000..c6d5701 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerUserService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.param.ShopDealerUserParam; + +import java.util.List; + +/** + * 分销商用户记录表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerUserService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerUserParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerUserParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopDealerUser + */ + ShopDealerUser getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerWithdrawService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerWithdrawService.java new file mode 100644 index 0000000..61731e3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerWithdrawService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopDealerWithdraw; +import com.gxwebsoft.shop.param.ShopDealerWithdrawParam; + +import java.util.List; + +/** + * 分销商提现明细表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopDealerWithdrawService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopDealerWithdrawParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopDealerWithdrawParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopDealerWithdraw + */ + ShopDealerWithdraw getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopExpressService.java b/src/main/java/com/gxwebsoft/shop/service/ShopExpressService.java new file mode 100644 index 0000000..92dae06 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopExpressService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopExpress; +import com.gxwebsoft.shop.param.ShopExpressParam; + +import java.util.List; + +/** + * 物流公司Service + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +public interface ShopExpressService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopExpressParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopExpressParam param); + + /** + * 根据id查询 + * + * @param expressId 物流公司ID + * @return ShopExpress + */ + ShopExpress getByIdRel(Integer expressId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateDetailService.java b/src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateDetailService.java new file mode 100644 index 0000000..b63be6f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateDetailService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopExpressTemplateDetail; +import com.gxwebsoft.shop.param.ShopExpressTemplateDetailParam; + +import java.util.List; + +/** + * 运费模板Service + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +public interface ShopExpressTemplateDetailService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopExpressTemplateDetailParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopExpressTemplateDetailParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopExpressTemplateDetail + */ + ShopExpressTemplateDetail getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateService.java b/src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateService.java new file mode 100644 index 0000000..0c8c003 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopExpressTemplateService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopExpressTemplate; +import com.gxwebsoft.shop.param.ShopExpressTemplateParam; + +import java.util.List; + +/** + * 运费模板Service + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +public interface ShopExpressTemplateService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopExpressTemplateParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopExpressTemplateParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopExpressTemplate + */ + ShopExpressTemplate getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGiftService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGiftService.java new file mode 100644 index 0000000..5ff156d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGiftService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGift; +import com.gxwebsoft.shop.param.ShopGiftParam; + +import java.util.List; + +/** + * 礼品卡Service + * + * @author 科技小王子 + * @since 2025-08-11 18:07:31 + */ +public interface ShopGiftService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGiftParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGiftParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopGift + */ + ShopGift getByIdRel(Integer id); + + ShopGift getByCode(String code); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsCategoryService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsCategoryService.java new file mode 100644 index 0000000..5dbed7e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsCategoryService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsCategory; +import com.gxwebsoft.shop.param.ShopGoodsCategoryParam; + +import java.util.List; + +/** + * 商品分类Service + * + * @author 科技小王子 + * @since 2025-05-01 00:36:45 + */ +public interface ShopGoodsCategoryService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsCategoryParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsCategoryParam param); + + /** + * 根据id查询 + * + * @param categoryId 商品分类ID + * @return ShopGoodsCategory + */ + ShopGoodsCategory getByIdRel(Integer categoryId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsCommentService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsCommentService.java new file mode 100644 index 0000000..6fc33d4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsCommentService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsComment; +import com.gxwebsoft.shop.param.ShopGoodsCommentParam; + +import java.util.List; + +/** + * 评论表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsCommentService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsCommentParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsCommentParam param); + + /** + * 根据id查询 + * + * @param id 评论ID + * @return ShopGoodsComment + */ + ShopGoodsComment getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsIncomeConfigService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsIncomeConfigService.java new file mode 100644 index 0000000..5784743 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsIncomeConfigService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsIncomeConfig; +import com.gxwebsoft.shop.param.ShopGoodsIncomeConfigParam; + +import java.util.List; + +/** + * 分润配置Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsIncomeConfigService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsIncomeConfigParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsIncomeConfigParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopGoodsIncomeConfig + */ + ShopGoodsIncomeConfig getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsLogService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsLogService.java new file mode 100644 index 0000000..0c8d53b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsLog; +import com.gxwebsoft.shop.param.ShopGoodsLogParam; + +import java.util.List; + +/** + * 商品日志表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsLogParam param); + + /** + * 根据id查询 + * + * @param id 统计ID + * @return ShopGoodsLog + */ + ShopGoodsLog getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsRelationService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsRelationService.java new file mode 100644 index 0000000..9c43aa9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsRelationService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsRelation; +import com.gxwebsoft.shop.param.ShopGoodsRelationParam; + +import java.util.List; + +/** + * 商品点赞和收藏表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopGoodsRelationService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsRelationParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsRelationParam param); + + /** + * 根据id查询 + * + * @param id id + * @return ShopGoodsRelation + */ + ShopGoodsRelation getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsRoleCommissionService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsRoleCommissionService.java new file mode 100644 index 0000000..6ff923f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsRoleCommissionService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsRoleCommission; +import com.gxwebsoft.shop.param.ShopGoodsRoleCommissionParam; + +import java.util.List; + +/** + * 商品绑定角色的分润金额Service + * + * @author 科技小王子 + * @since 2025-05-01 09:53:38 + */ +public interface ShopGoodsRoleCommissionService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsRoleCommissionParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsRoleCommissionParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopGoodsRoleCommission + */ + ShopGoodsRoleCommission getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java new file mode 100644 index 0000000..115cf42 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsService.java @@ -0,0 +1,52 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoods; +import com.gxwebsoft.shop.param.ShopGoodsParam; + +import java.util.List; + +/** + * 商品Service + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +public interface ShopGoodsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsParam param); + + /** + * 根据id查询 + * + * @param goodsId 自增ID + * @return ShopGoods + */ + ShopGoods getByIdRel(Integer goodsId); + + /** + * 累加商品销售数量 + * 忽略租户隔离,确保能更新成功 + * + * @param goodsId 商品ID + * @param saleCount 累加的销售数量 + * @return 是否更新成功 + */ + boolean addSaleCount(Integer goodsId, Integer saleCount); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsSkuService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsSkuService.java new file mode 100644 index 0000000..1431347 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsSkuService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsSku; +import com.gxwebsoft.shop.param.ShopGoodsSkuParam; + +import java.util.List; + +/** + * 商品sku列表Service + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +public interface ShopGoodsSkuService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsSkuParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsSkuParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopGoodsSku + */ + ShopGoodsSku getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopGoodsSpecService.java b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsSpecService.java new file mode 100644 index 0000000..32250e7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopGoodsSpecService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopGoodsSpec; +import com.gxwebsoft.shop.param.ShopGoodsSpecParam; + +import java.util.List; + +/** + * 商品多规格Service + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +public interface ShopGoodsSpecService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopGoodsSpecParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopGoodsSpecParam param); + + /** + * 根据id查询 + * + * @param id 主键 + * @return ShopGoodsSpec + */ + ShopGoodsSpec getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopMerchantAccountService.java b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantAccountService.java new file mode 100644 index 0000000..127420f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantAccountService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopMerchantAccount; +import com.gxwebsoft.shop.param.ShopMerchantAccountParam; + +import java.util.List; + +/** + * 商户账号Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopMerchantAccountService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopMerchantAccountParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopMerchantAccountParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return ShopMerchantAccount + */ + ShopMerchantAccount getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopMerchantApplyService.java b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantApplyService.java new file mode 100644 index 0000000..7eeaf21 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantApplyService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopMerchantApply; +import com.gxwebsoft.shop.param.ShopMerchantApplyParam; + +import java.util.List; + +/** + * 商户入驻申请Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopMerchantApplyService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopMerchantApplyParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopMerchantApplyParam param); + + /** + * 根据id查询 + * + * @param applyId ID + * @return ShopMerchantApply + */ + ShopMerchantApply getByIdRel(Integer applyId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopMerchantService.java b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantService.java new file mode 100644 index 0000000..f3fb956 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopMerchant; +import com.gxwebsoft.shop.param.ShopMerchantParam; + +import java.util.List; + +/** + * 商户Service + * + * @author 科技小王子 + * @since 2025-08-10 20:43:33 + */ +public interface ShopMerchantService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopMerchantParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopMerchantParam param); + + /** + * 根据id查询 + * + * @param merchantId ID + * @return ShopMerchant + */ + ShopMerchant getByIdRel(Long merchantId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopMerchantTypeService.java b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantTypeService.java new file mode 100644 index 0000000..e0922d2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopMerchantTypeService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopMerchantType; +import com.gxwebsoft.shop.param.ShopMerchantTypeParam; + +import java.util.List; + +/** + * 商户类型Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopMerchantTypeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopMerchantTypeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopMerchantTypeParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return ShopMerchantType + */ + ShopMerchantType getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryGoodsService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryGoodsService.java new file mode 100644 index 0000000..55699ac --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryGoodsService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrderDeliveryGoods; +import com.gxwebsoft.shop.param.ShopOrderDeliveryGoodsParam; + +import java.util.List; + +/** + * 发货单商品Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderDeliveryGoodsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderDeliveryGoodsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderDeliveryGoodsParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopOrderDeliveryGoods + */ + ShopOrderDeliveryGoods getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryService.java new file mode 100644 index 0000000..3fffbcb --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderDeliveryService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrderDelivery; +import com.gxwebsoft.shop.param.ShopOrderDeliveryParam; + +import java.util.List; + +/** + * 发货单Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderDeliveryService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderDeliveryParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderDeliveryParam param); + + /** + * 根据id查询 + * + * @param deliveryId 发货单ID + * @return ShopOrderDelivery + */ + ShopOrderDelivery getByIdRel(Integer deliveryId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderExtractService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderExtractService.java new file mode 100644 index 0000000..d0a3bd6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderExtractService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrderExtract; +import com.gxwebsoft.shop.param.ShopOrderExtractParam; + +import java.util.List; + +/** + * 自提订单联系方式Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderExtractService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderExtractParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderExtractParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopOrderExtract + */ + ShopOrderExtract getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderGoodsService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderGoodsService.java new file mode 100644 index 0000000..d9a7e9a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderGoodsService.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrderGoods; +import com.gxwebsoft.shop.param.ShopOrderGoodsParam; + +import java.util.List; + +/** + * 商品信息Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderGoodsService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderGoodsParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderGoodsParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return ShopOrderGoods + */ + ShopOrderGoods getByIdRel(Integer id); + + List getListByOrderId(Integer orderId); + + /** + * 根据订单ID查询订单商品列表(忽略租户隔离) + * @param orderId 订单ID + * @return List + */ + List getListByOrderIdIgnoreTenant(Integer orderId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoLogService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoLogService.java new file mode 100644 index 0000000..9ede6f7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrderInfoLog; +import com.gxwebsoft.shop.param.ShopOrderInfoLogParam; + +import java.util.List; + +/** + * 订单核销Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderInfoLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderInfoLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderInfoLogParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopOrderInfoLog + */ + ShopOrderInfoLog getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoService.java new file mode 100644 index 0000000..f5a53fa --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderInfoService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrderInfo; +import com.gxwebsoft.shop.param.ShopOrderInfoParam; + +import java.util.List; + +/** + * 场地Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderInfoService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderInfoParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderInfoParam param); + + /** + * 根据id查询 + * + * @param id 自增ID + * @return ShopOrderInfo + */ + ShopOrderInfo getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderService.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderService.java new file mode 100644 index 0000000..8644e62 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderService.java @@ -0,0 +1,79 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.param.ShopOrderParam; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; + +/** + * 订单Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopOrderParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopOrderParam param); + + /** + * 根据id查询 + * + * @param orderId 订单号 + * @return ShopOrder + */ + ShopOrder getByIdRel(Integer orderId); + + HashMap createWxOrder(ShopOrder shopOrder); + + ShopOrder getByOutTradeNo(String outTradeNo); + + Boolean queryOrderByOutTradeNo(ShopOrder shopOrder); + + void updateByOutTradeNo(ShopOrder order); + + /** + * 统计订单总金额 + * + * @return 订单总金额 + */ + BigDecimal total(); + + /** + * 根据订单号查询订单 + * + * @param orderNo 订单号 + * @param tenantId 租户ID + * @return ShopOrder + */ + ShopOrder getByOrderNo(String orderNo, Integer tenantId); + + /** + * 同步支付状态 + * + * @param orderNo 订单号 + * @param paymentStatus 支付状态:1=支付成功,0=支付失败 + * @param transactionId 微信交易号 + * @param payTime 支付时间 + * @param tenantId 租户ID + * @return 是否更新成功 + */ + boolean syncPaymentStatus(String orderNo, Integer paymentStatus, String transactionId, String payTime, Integer tenantId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopOrderUpdate10550Service.java b/src/main/java/com/gxwebsoft/shop/service/ShopOrderUpdate10550Service.java new file mode 100644 index 0000000..2399a6d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopOrderUpdate10550Service.java @@ -0,0 +1,21 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.entity.ShopOrderDelivery; +import com.gxwebsoft.shop.param.ShopOrderDeliveryParam; + +import java.util.List; + +/** + * 发货单Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopOrderUpdate10550Service { + + + void update(ShopOrder shopOrder); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopRechargeOrderService.java b/src/main/java/com/gxwebsoft/shop/service/ShopRechargeOrderService.java new file mode 100644 index 0000000..dcb936a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopRechargeOrderService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopRechargeOrder; +import com.gxwebsoft.shop.param.ShopRechargeOrderParam; + +import java.util.List; + +/** + * 会员充值订单表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +public interface ShopRechargeOrderService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopRechargeOrderParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopRechargeOrderParam param); + + /** + * 根据id查询 + * + * @param orderId 订单ID + * @return ShopRechargeOrder + */ + ShopRechargeOrder getByIdRel(Integer orderId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopSpecService.java b/src/main/java/com/gxwebsoft/shop/service/ShopSpecService.java new file mode 100644 index 0000000..2db6368 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopSpecService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopSpec; +import com.gxwebsoft.shop.param.ShopSpecParam; + +import java.util.List; + +/** + * 规格Service + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +public interface ShopSpecService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopSpecParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopSpecParam param); + + /** + * 根据id查询 + * + * @param specId 规格ID + * @return ShopSpec + */ + ShopSpec getByIdRel(Integer specId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopSpecValueService.java b/src/main/java/com/gxwebsoft/shop/service/ShopSpecValueService.java new file mode 100644 index 0000000..f16f347 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopSpecValueService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopSpecValue; +import com.gxwebsoft.shop.param.ShopSpecValueParam; + +import java.util.List; + +/** + * 规格值Service + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +public interface ShopSpecValueService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopSpecValueParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopSpecValueParam param); + + /** + * 根据id查询 + * + * @param specValueId 规格值ID + * @return ShopSpecValue + */ + ShopSpecValue getByIdRel(Integer specValueId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopSplashService.java b/src/main/java/com/gxwebsoft/shop/service/ShopSplashService.java new file mode 100644 index 0000000..0087a1e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopSplashService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopSplash; +import com.gxwebsoft.shop.param.ShopSplashParam; + +import java.util.List; + +/** + * 开屏广告Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopSplashService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopSplashParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopSplashParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopSplash + */ + ShopSplash getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUserAddressService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUserAddressService.java new file mode 100644 index 0000000..70880e4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUserAddressService.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopUserAddress; +import com.gxwebsoft.shop.param.ShopUserAddressParam; + +import java.util.List; + +/** + * 收货地址Service + * + * @author 科技小王子 + * @since 2025-07-22 23:06:40 + */ +public interface ShopUserAddressService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopUserAddressParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopUserAddressParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopUserAddress + */ + ShopUserAddress getByIdRel(Integer id); + + /** + * 获取用户默认收货地址 + * + * @param userId 用户ID + * @return ShopUserAddress + */ + ShopUserAddress getDefaultAddress(Integer userId); + + /** + * 获取用户所有收货地址 + * + * @param userId 用户ID + * @return List + */ + List getUserAddresses(Integer userId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUserBalanceLogService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUserBalanceLogService.java new file mode 100644 index 0000000..08e4087 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUserBalanceLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopUserBalanceLog; +import com.gxwebsoft.shop.param.ShopUserBalanceLogParam; + +import java.util.List; + +/** + * 用户余额变动明细表Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopUserBalanceLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopUserBalanceLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopUserBalanceLogParam param); + + /** + * 根据id查询 + * + * @param logId 主键ID + * @return ShopUserBalanceLog + */ + ShopUserBalanceLog getByIdRel(Integer logId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUserCollectionService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUserCollectionService.java new file mode 100644 index 0000000..bc74a59 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUserCollectionService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopUserCollection; +import com.gxwebsoft.shop.param.ShopUserCollectionParam; + +import java.util.List; + +/** + * 我的收藏Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopUserCollectionService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopUserCollectionParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopUserCollectionParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopUserCollection + */ + ShopUserCollection getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUserCouponService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUserCouponService.java new file mode 100644 index 0000000..e237cca --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUserCouponService.java @@ -0,0 +1,43 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.param.ShopUserCouponParam; + +import java.util.List; + +/** + * 用户优惠券Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopUserCouponService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopUserCouponParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopUserCouponParam param); + + /** + * 根据id查询 + * + * @param id id + * @return ShopUserCoupon + */ + ShopUserCoupon getByIdRel(Integer id); + + List userList(Integer userId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUserRefereeService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUserRefereeService.java new file mode 100644 index 0000000..3c727a6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUserRefereeService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopUserReferee; +import com.gxwebsoft.shop.param.ShopUserRefereeParam; + +import java.util.List; + +/** + * 用户推荐关系表Service + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +public interface ShopUserRefereeService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopUserRefereeParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopUserRefereeParam param); + + /** + * 根据id查询 + * + * @param id 主键ID + * @return ShopUserReferee + */ + ShopUserReferee getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUsersService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUsersService.java new file mode 100644 index 0000000..538451b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUsersService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopUsers; +import com.gxwebsoft.shop.param.ShopUsersParam; + +import java.util.List; + +/** + * Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopUsersService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopUsersParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopUsersParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopUsers + */ + ShopUsers getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopWebsiteService.java b/src/main/java/com/gxwebsoft/shop/service/ShopWebsiteService.java new file mode 100644 index 0000000..6b9bef6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopWebsiteService.java @@ -0,0 +1,27 @@ +package com.gxwebsoft.shop.service; + +import com.gxwebsoft.shop.vo.ShopVo; + +/** + * 商城网站服务接口 + * + * @author 科技小王子 + * @since 2025-08-13 + */ +public interface ShopWebsiteService { + + /** + * 获取商城基本信息(VO格式) + * + * @param tenantId 租户ID + * @return 商城信息VO + */ + ShopVo getShopInfo(Integer tenantId); + + /** + * 清除商城信息缓存 + * + * @param tenantId 租户ID + */ + void clearShopInfoCache(Integer tenantId); +} diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopWechatDepositService.java b/src/main/java/com/gxwebsoft/shop/service/ShopWechatDepositService.java new file mode 100644 index 0000000..9c4b0ad --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopWechatDepositService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.entity.ShopWechatDeposit; +import com.gxwebsoft.shop.param.ShopWechatDepositParam; + +import java.util.List; + +/** + * 押金Service + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +public interface ShopWechatDepositService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(ShopWechatDepositParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(ShopWechatDepositParam param); + + /** + * 根据id查询 + * + * @param id + * @return ShopWechatDeposit + */ + ShopWechatDeposit getByIdRel(Integer id); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/UserBalanceLogService.java b/src/main/java/com/gxwebsoft/shop/service/UserBalanceLogService.java new file mode 100644 index 0000000..af7a18a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/UserBalanceLogService.java @@ -0,0 +1,42 @@ +package com.gxwebsoft.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.UserBalanceLog; +import com.gxwebsoft.common.system.param.UserBalanceLogParam; + +import java.util.List; + +/** + * 用户余额变动明细表Service + * + * @author 科技小王子 + * @since 2023-04-21 15:59:09 + */ +public interface UserBalanceLogService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(UserBalanceLogParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(UserBalanceLogParam param); + + /** + * 根据id查询 + * + * @param logId 主键ID + * @return UserBalanceLog + */ + UserBalanceLog getByIdRel(Integer logId); + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/CouponStatusServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/CouponStatusServiceImpl.java new file mode 100644 index 0000000..1700188 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/CouponStatusServiceImpl.java @@ -0,0 +1,339 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.service.CouponStatusService; +import com.gxwebsoft.shop.service.ShopUserCouponService; +import com.gxwebsoft.shop.service.ShopCouponService; +import com.gxwebsoft.shop.service.ShopCouponApplyItemService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 优惠券状态管理服务实现 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Service +public class CouponStatusServiceImpl implements CouponStatusService { + + @Autowired + private ShopUserCouponService shopUserCouponService; + + @Autowired + private ShopCouponService shopCouponService; + + @Autowired + private ShopCouponApplyItemService shopCouponApplyItemService; + + @Override + public List getAvailableCoupons(Integer userId) { + List allCoupons = getUserCoupons(userId); + return allCoupons.stream() + .filter(ShopUserCoupon::isAvailable) + .collect(Collectors.toList()); + } + + @Override + public List getUsedCoupons(Integer userId) { + return shopUserCouponService.list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getUserId, userId) + .eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_USED) + .orderByDesc(ShopUserCoupon::getUseTime) + ); + } + + @Override + public List getExpiredCoupons(Integer userId) { + // 先更新过期状态 + updateExpiredCouponsForUser(userId); + + return shopUserCouponService.list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getUserId, userId) + .eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_EXPIRED) + .orderByDesc(ShopUserCoupon::getEndTime) + ); + } + + @Override + public CouponStatusResult getUserCouponsGroupByStatus(Integer userId) { + List availableCoupons = getAvailableCoupons(userId); + List usedCoupons = getUsedCoupons(userId); + List expiredCoupons = getExpiredCoupons(userId); + + return new CouponStatusResult(availableCoupons, usedCoupons, expiredCoupons); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean useCoupon(Long userCouponId, Integer orderId, String orderNo) { + try { + ShopUserCoupon userCoupon = shopUserCouponService.getById(userCouponId); + if (userCoupon == null) { + log.warn("优惠券不存在: {}", userCouponId); + return false; + } + + if (!userCoupon.isAvailable()) { + log.warn("优惠券不可用: {}, 状态: {}", userCouponId, userCoupon.getStatusDesc()); + return false; + } + + // 标记为已使用 + userCoupon.markAsUsed(orderId, orderNo); + + return shopUserCouponService.updateById(userCoupon); + } catch (Exception e) { + log.error("使用优惠券失败: {}", userCouponId, e); + return false; + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean returnCoupon(Integer orderId) { + try { + ShopUserCoupon userCoupon = shopUserCouponService.getOne( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getOrderId, orderId) + .eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_USED) + ); + + if (userCoupon == null) { + log.info("订单没有使用优惠券: {}", orderId); + return true; + } + + // 检查是否已过期 + if (userCoupon.isExpired()) { + log.warn("优惠券已过期,无法退还: {}", userCoupon.getId()); + return false; + } + + // 恢复为未使用状态 + userCoupon.setStatus(ShopUserCoupon.STATUS_UNUSED); + userCoupon.setIsUse(0); + userCoupon.setUseTime(null); + userCoupon.setOrderId(null); + userCoupon.setOrderNo(null); + + return shopUserCouponService.updateById(userCoupon); + } catch (Exception e) { + log.error("退还优惠券失败, 订单ID: {}", orderId, e); + return false; + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateExpiredCoupons() { + try { + // 查询所有未使用且已过期的优惠券 + List expiredCoupons = shopUserCouponService.list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_UNUSED) + .lt(ShopUserCoupon::getEndTime, LocalDateTime.now()) + ); + + if (expiredCoupons.isEmpty()) { + return 0; + } + + // 批量更新状态 + List expiredIds = expiredCoupons.stream() + .map(ShopUserCoupon::getId) + .collect(Collectors.toList()); + + boolean success = shopUserCouponService.update( + new LambdaUpdateWrapper() + .in(ShopUserCoupon::getId, expiredIds) + .set(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_EXPIRED) + .set(ShopUserCoupon::getIsExpire, 1) + ); + + int updatedCount = success ? expiredIds.size() : 0; + log.info("批量更新过期优惠券状态完成,更新数量: {}", updatedCount); + return updatedCount; + } catch (Exception e) { + log.error("批量更新过期优惠券状态失败", e); + return 0; + } + } + + @Override + public boolean checkAndUpdateCouponStatus(ShopUserCoupon userCoupon) { + if (userCoupon == null) { + return false; + } + + boolean statusChanged = false; + + // 检查是否过期 + if (userCoupon.getStatus() == ShopUserCoupon.STATUS_UNUSED && userCoupon.isExpired()) { + userCoupon.markAsExpired(); + statusChanged = true; + } + + // 如果状态发生变化,更新数据库 + if (statusChanged) { + try { + shopUserCouponService.updateById(userCoupon); + log.debug("更新优惠券状态: {} -> {}", userCoupon.getId(), userCoupon.getStatusDesc()); + } catch (Exception e) { + log.error("更新优惠券状态失败: {}", userCoupon.getId(), e); + return false; + } + } + + return statusChanged; + } + + @Override + public CouponValidationResult validateCouponForOrder(Long userCouponId, + BigDecimal totalAmount, + List goodsIds) { + try { + ShopUserCoupon userCoupon = shopUserCouponService.getById(userCouponId); + if (userCoupon == null) { + return new CouponValidationResult(false, "优惠券不存在"); + } + + // 检查优惠券状态 + if (!userCoupon.isAvailable()) { + return new CouponValidationResult(false, "优惠券" + userCoupon.getStatusDesc()); + } + + // 检查最低消费金额 + if (userCoupon.getMinPrice() != null && + totalAmount.compareTo(userCoupon.getMinPrice()) < 0) { + return new CouponValidationResult(false, + String.format("订单金额不满足最低消费要求,需满%s元", userCoupon.getMinPrice())); + } + + // 检查适用范围 + if (!validateApplyRange(userCoupon, goodsIds)) { + return new CouponValidationResult(false, "优惠券不适用于当前商品"); + } + + // 计算优惠金额 + BigDecimal discountAmount = calculateDiscountAmount(userCoupon, totalAmount); + + return new CouponValidationResult(true, "优惠券可用", discountAmount); + } catch (Exception e) { + log.error("验证优惠券失败: {}", userCouponId, e); + return new CouponValidationResult(false, "验证优惠券时发生错误"); + } + } + + /** + * 获取用户所有优惠券 + */ + private List getUserCoupons(Integer userId) { + List coupons = shopUserCouponService.list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getUserId, userId) + .orderByAsc(ShopUserCoupon::getEndTime) + ); + + // 检查并更新状态 + coupons.forEach(this::checkAndUpdateCouponStatus); + + return coupons; + } + + /** + * 更新指定用户的过期优惠券 + */ + private void updateExpiredCouponsForUser(Integer userId) { + try { + shopUserCouponService.update( + new LambdaUpdateWrapper() + .eq(ShopUserCoupon::getUserId, userId) + .eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_UNUSED) + .lt(ShopUserCoupon::getEndTime, LocalDateTime.now()) + .set(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_EXPIRED) + .set(ShopUserCoupon::getIsExpire, 1) + ); + } catch (Exception e) { + log.error("更新用户过期优惠券失败, userId: {}", userId, e); + } + } + + /** + * 验证优惠券适用范围 + */ + private boolean validateApplyRange(ShopUserCoupon userCoupon, List goodsIds) { + if (userCoupon.getApplyRange() == null || userCoupon.getApplyRange() == ShopUserCoupon.APPLY_ALL) { + return true; // 全部商品适用 + } + + if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_GOODS) { + // 指定商品适用 + try { + List applyItems = shopCouponApplyItemService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId()) + .eq(ShopCouponApplyItem::getType, 1) // 类型1表示商品 + ); + + // 如果数据库中还没有 goods_id 字段,暂时使用 pk 字段作为商品ID + List applicableGoodsIds = applyItems.stream() + .map(item -> { + if (item.getGoodsId() != null) { + return item.getGoodsId(); + } else if (item.getPk() != null) { + // 临时方案:使用 pk 字段作为商品ID + return item.getPk(); + } + return null; + }) + .filter(goodsId -> goodsId != null) + .collect(Collectors.toList()); + + return goodsIds.stream().anyMatch(applicableGoodsIds::contains); + } catch (Exception e) { + log.warn("查询优惠券适用商品失败,可能是数据库字段不存在: {}", e.getMessage()); + // 如果查询失败,默认返回true(允许使用) + return true; + } + } + + if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_CATEGORY) { + // 指定分类适用 - 这里需要根据商品ID查询分类ID,然后验证 + // 暂时返回true,实际项目中需要实现商品分类查询逻辑 + log.debug("分类适用范围验证暂未实现,默认通过"); + return true; + } + + return true; + } + + /** + * 计算优惠金额 + */ + private BigDecimal calculateDiscountAmount(ShopUserCoupon userCoupon, BigDecimal totalAmount) { + if (userCoupon.getType() == ShopUserCoupon.TYPE_REDUCE) { + // 满减券 + return userCoupon.getReducePrice(); + } else if (userCoupon.getType() == ShopUserCoupon.TYPE_DISCOUNT) { + // 折扣券 + BigDecimal discountRate = BigDecimal.valueOf(userCoupon.getDiscount()).divide(BigDecimal.valueOf(100)); + return totalAmount.multiply(BigDecimal.ONE.subtract(discountRate)); + } + return BigDecimal.ZERO; + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java new file mode 100644 index 0000000..603e267 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java @@ -0,0 +1,231 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.annotation.IgnoreTenant; +import com.gxwebsoft.shop.entity.*; +import com.gxwebsoft.shop.service.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 订单取消服务实现 + * + * @author WebSoft + * @since 2025-01-26 + */ +@Slf4j +@Service +public class OrderCancelServiceImpl implements OrderCancelService { + + @Autowired + private ShopOrderService shopOrderService; + + @Autowired + private ShopOrderGoodsService shopOrderGoodsService; + + @Autowired + private ShopGoodsService shopGoodsService; + + @Autowired + private ShopGoodsSkuService shopGoodsSkuService; + + @Autowired + private CouponStatusService couponStatusService; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean cancelOrder(ShopOrder order) { + try { + log.info("开始取消订单,订单号:{},订单ID:{}", order.getOrderNo(), order.getOrderId()); + + // 1. 检查订单状态 + if (order.getPayStatus() != null && order.getPayStatus()) { + log.warn("订单已支付,无法取消,订单号:{}", order.getOrderNo()); + return false; + } + + if (order.getOrderStatus() != null && order.getOrderStatus() != 0) { + log.warn("订单状态不是待支付,无法取消,订单号:{},当前状态:{}", order.getOrderNo(), order.getOrderStatus()); + return false; + } + + // 2. 更新订单状态为已取消 + order.setOrderStatus(2); // 2表示已取消 + order.setCancelTime(LocalDateTime.now()); + order.setCancelReason("系统自动取消(超时未支付)"); + + boolean updateSuccess = shopOrderService.updateById(order); + if (!updateSuccess) { + log.error("更新订单状态失败,订单号:{}", order.getOrderNo()); + return false; + } + + // 3. 回退库存 + boolean stockRestored = restoreOrderStock(order); + if (!stockRestored) { + log.error("回退库存失败,订单号:{}", order.getOrderNo()); + // 注意:这里不直接返回false,因为订单状态已经更新,需要记录错误但继续处理 + } + + // 4. 退还优惠券 + boolean couponReturned = returnOrderCoupon(order); + if (!couponReturned) { + log.error("退还优惠券失败,订单号:{}", order.getOrderNo()); + // 同样不直接返回false + } + + log.info("订单取消成功,订单号:{},库存回退:{},优惠券退还:{}", + order.getOrderNo(), stockRestored, couponReturned); + return true; + + } catch (Exception e) { + log.error("取消订单失败,订单号:{}", order.getOrderNo(), e); + throw e; // 重新抛出异常,触发事务回滚 + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int batchCancelOrders(List orders) { + if (orders == null || orders.isEmpty()) { + return 0; + } + + int successCount = 0; + for (ShopOrder order : orders) { + try { + if (cancelOrder(order)) { + successCount++; + } + } catch (Exception e) { + log.error("批量取消订单时发生错误,订单号:{}", order.getOrderNo(), e); + // 继续处理下一个订单 + } + } + + log.info("批量取消订单完成,总数:{},成功:{}", orders.size(), successCount); + return successCount; + } + + @Override + @IgnoreTenant("定时任务需要查询所有租户的超时订单") + public List findExpiredUnpaidOrders(Integer timeoutMinutes, Integer batchSize) { + LocalDateTime expireTime = LocalDateTime.now().minusMinutes(timeoutMinutes); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(ShopOrder::getPayStatus, false) // 未支付 + .eq(ShopOrder::getOrderStatus, 0) // 待支付状态 + .lt(ShopOrder::getCreateTime, expireTime) // 创建时间小于过期时间 + .orderByAsc(ShopOrder::getCreateTime) + .last("LIMIT " + batchSize); + + final List list = shopOrderService.list(queryWrapper); + System.out.println("list = " + list.size()); + return shopOrderService.list(queryWrapper); + } + + @Override + @IgnoreTenant("定时任务需要查询特定租户的超时订单") + public List findExpiredUnpaidOrdersByTenant(Integer tenantId, Integer timeoutMinutes, Integer batchSize) { + LocalDateTime expireTime = LocalDateTime.now().minusMinutes(timeoutMinutes); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getPayStatus, false) // 未支付 + .eq(ShopOrder::getOrderStatus, 0) // 待支付状态 + .lt(ShopOrder::getCreateTime, expireTime) // 创建时间小于过期时间 + .orderByAsc(ShopOrder::getCreateTime) + .last("LIMIT " + batchSize); + + return shopOrderService.list(queryWrapper); + } + + @Override + public boolean restoreOrderStock(ShopOrder order) { + try { + // 获取订单商品信息 + List orderGoods = shopOrderGoodsService.list( + new LambdaQueryWrapper() + .eq(ShopOrderGoods::getOrderId, order.getOrderId()) + ); + + if (orderGoods == null || orderGoods.isEmpty()) { + log.warn("订单没有商品信息,订单号:{}", order.getOrderNo()); + return true; // 没有商品信息也算成功 + } + + for (ShopOrderGoods orderGood : orderGoods) { + if (orderGood.getSkuId() != null && orderGood.getSkuId() > 0) { + // 多规格商品,恢复SKU库存 + restoreSkuStock(orderGood); + } else { + // 单规格商品,恢复商品库存 + restoreGoodsStock(orderGood); + } + } + + log.info("订单库存回退成功,订单号:{},商品数量:{}", order.getOrderNo(), orderGoods.size()); + return true; + + } catch (Exception e) { + log.error("回退订单库存失败,订单号:{}", order.getOrderNo(), e); + return false; + } + } + + @Override + public boolean returnOrderCoupon(ShopOrder order) { + try { + if (order.getCouponId() == null || order.getCouponId() <= 0) { + log.debug("订单未使用优惠券,订单号:{}", order.getOrderNo()); + return true; // 没有使用优惠券也算成功 + } + + boolean success = couponStatusService.returnCoupon(order.getOrderId()); + if (success) { + log.info("订单优惠券退还成功,订单号:{},优惠券ID:{}", order.getOrderNo(), order.getCouponId()); + } else { + log.warn("订单优惠券退还失败,订单号:{},优惠券ID:{}", order.getOrderNo(), order.getCouponId()); + } + return success; + + } catch (Exception e) { + log.error("退还订单优惠券失败,订单号:{}", order.getOrderNo(), e); + return false; + } + } + + /** + * 恢复SKU库存 + */ + private void restoreSkuStock(ShopOrderGoods orderGoods) { + ShopGoodsSku sku = shopGoodsSkuService.getById(orderGoods.getSkuId()); + if (sku != null) { + int newStock = (sku.getStock() != null ? sku.getStock() : 0) + orderGoods.getTotalNum(); + sku.setStock(newStock); + shopGoodsSkuService.updateById(sku); + log.debug("恢复SKU库存 - SKU ID:{},恢复数量:{},当前库存:{}", + orderGoods.getSkuId(), orderGoods.getTotalNum(), newStock); + } + } + + /** + * 恢复商品库存 + */ + private void restoreGoodsStock(ShopOrderGoods orderGoods) { + ShopGoods goods = shopGoodsService.getById(orderGoods.getGoodsId()); + if (goods != null) { + int newStock = (goods.getStock() != null ? goods.getStock() : 0) + orderGoods.getTotalNum(); + goods.setStock(newStock); + shopGoodsService.updateById(goods); + log.debug("恢复商品库存 - 商品ID:{},恢复数量:{},当前库存:{}", + orderGoods.getGoodsId(), orderGoods.getTotalNum(), newStock); + } + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopArticleServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopArticleServiceImpl.java new file mode 100644 index 0000000..9e49324 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopArticleServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopArticleMapper; +import com.gxwebsoft.shop.service.ShopArticleService; +import com.gxwebsoft.shop.entity.ShopArticle; +import com.gxwebsoft.shop.param.ShopArticleParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品文章Service实现 + * + * @author 科技小王子 + * @since 2025-08-13 05:14:53 + */ +@Service +public class ShopArticleServiceImpl extends ServiceImpl implements ShopArticleService { + + @Override + public PageResult pageRel(ShopArticleParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopArticleParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopArticle getByIdRel(Integer articleId) { + ShopArticleParam param = new ShopArticleParam(); + param.setArticleId(articleId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopBrandServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopBrandServiceImpl.java new file mode 100644 index 0000000..5037b55 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopBrandServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopBrandMapper; +import com.gxwebsoft.shop.service.ShopBrandService; +import com.gxwebsoft.shop.entity.ShopBrand; +import com.gxwebsoft.shop.param.ShopBrandParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 品牌Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopBrandServiceImpl extends ServiceImpl implements ShopBrandService { + + @Override + public PageResult pageRel(ShopBrandParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopBrandParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopBrand getByIdRel(Integer brandId) { + ShopBrandParam param = new ShopBrandParam(); + param.setBrandId(brandId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCartServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCartServiceImpl.java new file mode 100644 index 0000000..12d6b3a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCartServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopCartMapper; +import com.gxwebsoft.shop.service.ShopCartService; +import com.gxwebsoft.shop.entity.ShopCart; +import com.gxwebsoft.shop.param.ShopCartParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 购物车Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopCartServiceImpl extends ServiceImpl implements ShopCartService { + + @Override + public PageResult pageRel(ShopCartParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCartParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCart getByIdRel(Long id) { + ShopCartParam param = new ShopCartParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCategoryServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCategoryServiceImpl.java new file mode 100644 index 0000000..fbe60af --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCategoryServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopCategoryMapper; +import com.gxwebsoft.shop.service.ShopCategoryService; +import com.gxwebsoft.shop.entity.ShopCategory; +import com.gxwebsoft.shop.param.ShopCategoryParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品分类Service实现 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Service +public class ShopCategoryServiceImpl extends ServiceImpl implements ShopCategoryService { + + @Override + public PageResult pageRel(ShopCategoryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCategoryParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCategory getByIdRel(Integer id) { + ShopCategoryParam param = new ShopCategoryParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopChatConversationServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopChatConversationServiceImpl.java new file mode 100644 index 0000000..e482f7e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopChatConversationServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopChatConversationMapper; +import com.gxwebsoft.shop.service.ShopChatConversationService; +import com.gxwebsoft.shop.entity.ShopChatConversation; +import com.gxwebsoft.shop.param.ShopChatConversationParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 聊天消息表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopChatConversationServiceImpl extends ServiceImpl implements ShopChatConversationService { + + @Override + public PageResult pageRel(ShopChatConversationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopChatConversationParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopChatConversation getByIdRel(Integer id) { + ShopChatConversationParam param = new ShopChatConversationParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopChatMessageServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopChatMessageServiceImpl.java new file mode 100644 index 0000000..ca50663 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopChatMessageServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopChatMessageMapper; +import com.gxwebsoft.shop.service.ShopChatMessageService; +import com.gxwebsoft.shop.entity.ShopChatMessage; +import com.gxwebsoft.shop.param.ShopChatMessageParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 聊天消息表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopChatMessageServiceImpl extends ServiceImpl implements ShopChatMessageService { + + @Override + public PageResult pageRel(ShopChatMessageParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopChatMessageParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopChatMessage getByIdRel(Integer id) { + ShopChatMessageParam param = new ShopChatMessageParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCommissionRoleServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCommissionRoleServiceImpl.java new file mode 100644 index 0000000..26823a0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCommissionRoleServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopCommissionRoleMapper; +import com.gxwebsoft.shop.service.ShopCommissionRoleService; +import com.gxwebsoft.shop.entity.ShopCommissionRole; +import com.gxwebsoft.shop.param.ShopCommissionRoleParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分红角色Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 10:01:15 + */ +@Service +public class ShopCommissionRoleServiceImpl extends ServiceImpl implements ShopCommissionRoleService { + + @Override + public PageResult pageRel(ShopCommissionRoleParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCommissionRoleParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCommissionRole getByIdRel(Integer id) { + ShopCommissionRoleParam param = new ShopCommissionRoleParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCountServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCountServiceImpl.java new file mode 100644 index 0000000..51fa64d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCountServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopCountMapper; +import com.gxwebsoft.shop.service.ShopCountService; +import com.gxwebsoft.shop.entity.ShopCount; +import com.gxwebsoft.shop.param.ShopCountParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商城销售统计表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopCountServiceImpl extends ServiceImpl implements ShopCountService { + + @Override + public PageResult pageRel(ShopCountParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCountParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCount getByIdRel(Integer id) { + ShopCountParam param = new ShopCountParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyCateServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyCateServiceImpl.java new file mode 100644 index 0000000..c613194 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyCateServiceImpl.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopCouponApplyCateMapper; +import com.gxwebsoft.shop.service.ShopCouponApplyCateService; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.param.ShopCouponApplyCateParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 优惠券可用分类Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Service +public class ShopCouponApplyCateServiceImpl extends ServiceImpl implements ShopCouponApplyCateService { + + @Override + public PageResult pageRel(ShopCouponApplyCateParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCouponApplyCateParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCouponApplyCate getByIdRel(Integer id) { + ShopCouponApplyCateParam param = new ShopCouponApplyCateParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void removeByCouponId(Integer couponId) { + remove( + new LambdaQueryWrapper() + .eq(ShopCouponApplyCate::getCouponId, couponId) + ); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyItemServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyItemServiceImpl.java new file mode 100644 index 0000000..6baa5ea --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponApplyItemServiceImpl.java @@ -0,0 +1,56 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopCouponApplyItemMapper; +import com.gxwebsoft.shop.service.ShopCouponApplyItemService; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.param.ShopCouponApplyItemParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 优惠券可用分类Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 12:47:49 + */ +@Service +public class ShopCouponApplyItemServiceImpl extends ServiceImpl implements ShopCouponApplyItemService { + + @Override + public PageResult pageRel(ShopCouponApplyItemParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCouponApplyItemParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCouponApplyItem getByIdRel(Integer id) { + ShopCouponApplyItemParam param = new ShopCouponApplyItemParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public void removeByCouponId(Integer couponId) { + remove( + new LambdaQueryWrapper() + .eq(ShopCouponApplyItem::getCouponId, couponId) + ); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponServiceImpl.java new file mode 100644 index 0000000..29a17a3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopCouponServiceImpl.java @@ -0,0 +1,71 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.entity.ShopCouponApplyCate; +import com.gxwebsoft.shop.entity.ShopCouponApplyItem; +import com.gxwebsoft.shop.mapper.ShopCouponMapper; +import com.gxwebsoft.shop.service.ShopCouponApplyCateService; +import com.gxwebsoft.shop.service.ShopCouponApplyItemService; +import com.gxwebsoft.shop.service.ShopCouponService; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.param.ShopCouponParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 优惠券Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:23 + */ +@Service +public class ShopCouponServiceImpl extends ServiceImpl implements ShopCouponService { + @Resource + private ShopCouponApplyItemService couponApplyItemService; + @Resource + private ShopCouponApplyCateService couponApplyCateService; + + @Override + public PageResult pageRel(ShopCouponParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + for (ShopCoupon coupon : list) { + coupon.setCouponApplyCateList( + couponApplyCateService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyCate::getCouponId, coupon.getId()) + ) + ); + coupon.setCouponApplyItemList( + couponApplyItemService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyItem::getCouponId, coupon.getId()) + ) + ); + } + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopCouponParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopCoupon getByIdRel(Integer id) { + ShopCouponParam param = new ShopCouponParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerApplyServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerApplyServiceImpl.java new file mode 100644 index 0000000..690e624 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerApplyServiceImpl.java @@ -0,0 +1,54 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerApplyMapper; +import com.gxwebsoft.shop.service.ShopDealerApplyService; +import com.gxwebsoft.shop.entity.ShopDealerApply; +import com.gxwebsoft.shop.param.ShopDealerApplyParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商申请记录表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:50:18 + */ +@Service +public class ShopDealerApplyServiceImpl extends ServiceImpl implements ShopDealerApplyService { + + @Override + public PageResult pageRel(ShopDealerApplyParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerApplyParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerApply getByIdRel(Integer id) { + ShopDealerApplyParam param = new ShopDealerApplyParam(); + param.setApplyId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public ShopDealerApply getByUserIdRel(Integer userId) { + ShopDealerApplyParam param = new ShopDealerApplyParam(); + param.setUserId(userId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCapitalServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCapitalServiceImpl.java new file mode 100644 index 0000000..897fbed --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCapitalServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerCapitalMapper; +import com.gxwebsoft.shop.service.ShopDealerCapitalService; +import com.gxwebsoft.shop.entity.ShopDealerCapital; +import com.gxwebsoft.shop.param.ShopDealerCapitalParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商资金明细表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopDealerCapitalServiceImpl extends ServiceImpl implements ShopDealerCapitalService { + + @Override + public PageResult pageRel(ShopDealerCapitalParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerCapitalParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerCapital getByIdRel(Integer id) { + ShopDealerCapitalParam param = new ShopDealerCapitalParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerOrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerOrderServiceImpl.java new file mode 100644 index 0000000..0bab68f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerOrderServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerOrderMapper; +import com.gxwebsoft.shop.service.ShopDealerOrderService; +import com.gxwebsoft.shop.entity.ShopDealerOrder; +import com.gxwebsoft.shop.param.ShopDealerOrderParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商订单记录表Service实现 + * + * @author 科技小王子 + * @since 2025-08-12 11:55:18 + */ +@Service +public class ShopDealerOrderServiceImpl extends ServiceImpl implements ShopDealerOrderService { + + @Override + public PageResult pageRel(ShopDealerOrderParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerOrderParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerOrder getByIdRel(Integer id) { + ShopDealerOrderParam param = new ShopDealerOrderParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerRefereeServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerRefereeServiceImpl.java new file mode 100644 index 0000000..287d9a7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerRefereeServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper; +import com.gxwebsoft.shop.service.ShopDealerRefereeService; +import com.gxwebsoft.shop.entity.ShopDealerReferee; +import com.gxwebsoft.shop.param.ShopDealerRefereeParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商推荐关系表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopDealerRefereeServiceImpl extends ServiceImpl implements ShopDealerRefereeService { + + @Override + public PageResult pageRel(ShopDealerRefereeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerRefereeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerReferee getByIdRel(Integer id) { + ShopDealerRefereeParam param = new ShopDealerRefereeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerSettingServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerSettingServiceImpl.java new file mode 100644 index 0000000..336fd7a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerSettingServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerSettingMapper; +import com.gxwebsoft.shop.service.ShopDealerSettingService; +import com.gxwebsoft.shop.entity.ShopDealerSetting; +import com.gxwebsoft.shop.param.ShopDealerSettingParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商设置表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopDealerSettingServiceImpl extends ServiceImpl implements ShopDealerSettingService { + + @Override + public PageResult pageRel(ShopDealerSettingParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerSettingParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerSetting getByIdRel(String key) { + ShopDealerSettingParam param = new ShopDealerSettingParam(); + param.setKey(key); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerUserServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerUserServiceImpl.java new file mode 100644 index 0000000..73a8ed6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerUserServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerUserMapper; +import com.gxwebsoft.shop.service.ShopDealerUserService; +import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.param.ShopDealerUserParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商用户记录表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopDealerUserServiceImpl extends ServiceImpl implements ShopDealerUserService { + + @Override + public PageResult pageRel(ShopDealerUserParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerUserParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerUser getByIdRel(Integer id) { + ShopDealerUserParam param = new ShopDealerUserParam(); + param.setUserId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerWithdrawServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerWithdrawServiceImpl.java new file mode 100644 index 0000000..9748034 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerWithdrawServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopDealerWithdrawMapper; +import com.gxwebsoft.shop.service.ShopDealerWithdrawService; +import com.gxwebsoft.shop.entity.ShopDealerWithdraw; +import com.gxwebsoft.shop.param.ShopDealerWithdrawParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分销商提现明细表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopDealerWithdrawServiceImpl extends ServiceImpl implements ShopDealerWithdrawService { + + @Override + public PageResult pageRel(ShopDealerWithdrawParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopDealerWithdrawParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopDealerWithdraw getByIdRel(Integer id) { + ShopDealerWithdrawParam param = new ShopDealerWithdrawParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressServiceImpl.java new file mode 100644 index 0000000..1a7cdbc --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopExpressMapper; +import com.gxwebsoft.shop.service.ShopExpressService; +import com.gxwebsoft.shop.entity.ShopExpress; +import com.gxwebsoft.shop.param.ShopExpressParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 物流公司Service实现 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Service +public class ShopExpressServiceImpl extends ServiceImpl implements ShopExpressService { + + @Override + public PageResult pageRel(ShopExpressParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopExpressParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopExpress getByIdRel(Integer expressId) { + ShopExpressParam param = new ShopExpressParam(); + param.setExpressId(expressId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateDetailServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateDetailServiceImpl.java new file mode 100644 index 0000000..2c5ea8b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateDetailServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopExpressTemplateDetailMapper; +import com.gxwebsoft.shop.service.ShopExpressTemplateDetailService; +import com.gxwebsoft.shop.entity.ShopExpressTemplateDetail; +import com.gxwebsoft.shop.param.ShopExpressTemplateDetailParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 运费模板Service实现 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Service +public class ShopExpressTemplateDetailServiceImpl extends ServiceImpl implements ShopExpressTemplateDetailService { + + @Override + public PageResult pageRel(ShopExpressTemplateDetailParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopExpressTemplateDetailParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopExpressTemplateDetail getByIdRel(Integer id) { + ShopExpressTemplateDetailParam param = new ShopExpressTemplateDetailParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateServiceImpl.java new file mode 100644 index 0000000..1858d49 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopExpressTemplateServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopExpressTemplateMapper; +import com.gxwebsoft.shop.service.ShopExpressTemplateService; +import com.gxwebsoft.shop.entity.ShopExpressTemplate; +import com.gxwebsoft.shop.param.ShopExpressTemplateParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 运费模板Service实现 + * + * @author 科技小王子 + * @since 2025-08-12 12:07:03 + */ +@Service +public class ShopExpressTemplateServiceImpl extends ServiceImpl implements ShopExpressTemplateService { + + @Override + public PageResult pageRel(ShopExpressTemplateParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopExpressTemplateParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopExpressTemplate getByIdRel(Integer id) { + ShopExpressTemplateParam param = new ShopExpressTemplateParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGiftServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGiftServiceImpl.java new file mode 100644 index 0000000..55a067e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGiftServiceImpl.java @@ -0,0 +1,71 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.entity.ShopGoods; +import com.gxwebsoft.shop.mapper.ShopGiftMapper; +import com.gxwebsoft.shop.service.ShopGiftService; +import com.gxwebsoft.shop.entity.ShopGift; +import com.gxwebsoft.shop.param.ShopGiftParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.shop.service.ShopGoodsService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 礼品卡Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 18:07:31 + */ +@Service +public class ShopGiftServiceImpl extends ServiceImpl implements ShopGiftService { + @Resource + private ShopGoodsService shopGoodsService; + + @Override + public PageResult pageRel(ShopGiftParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + if (!list.isEmpty()) { + Set goodsIds = list.stream().map(ShopGift::getGoodsId).collect(Collectors.toSet()); + List goodsList = shopGoodsService.listByIds(goodsIds); + for (ShopGift shopGift : list) { + ShopGoods shopGoods = goodsList.stream().filter(sG -> sG.getGoodsId().equals(shopGift.getGoodsId())).findFirst().orElse(null); + if (shopGoods != null) { + shopGift.setGoods(shopGoods); + } + } + } + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGiftParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGift getByIdRel(Integer id) { + ShopGiftParam param = new ShopGiftParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public ShopGift getByCode(String code) { + ShopGiftParam param = new ShopGiftParam(); + param.setCode(code); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCategoryServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCategoryServiceImpl.java new file mode 100644 index 0000000..170ab88 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCategoryServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsCategoryMapper; +import com.gxwebsoft.shop.service.ShopGoodsCategoryService; +import com.gxwebsoft.shop.entity.ShopGoodsCategory; +import com.gxwebsoft.shop.param.ShopGoodsCategoryParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品分类Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 00:36:45 + */ +@Service +public class ShopGoodsCategoryServiceImpl extends ServiceImpl implements ShopGoodsCategoryService { + + @Override + public PageResult pageRel(ShopGoodsCategoryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsCategoryParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsCategory getByIdRel(Integer categoryId) { + ShopGoodsCategoryParam param = new ShopGoodsCategoryParam(); + param.setCategoryId(categoryId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCommentServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCommentServiceImpl.java new file mode 100644 index 0000000..7f203bd --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsCommentServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsCommentMapper; +import com.gxwebsoft.shop.service.ShopGoodsCommentService; +import com.gxwebsoft.shop.entity.ShopGoodsComment; +import com.gxwebsoft.shop.param.ShopGoodsCommentParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 评论表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopGoodsCommentServiceImpl extends ServiceImpl implements ShopGoodsCommentService { + + @Override + public PageResult pageRel(ShopGoodsCommentParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsCommentParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsComment getByIdRel(Integer id) { + ShopGoodsCommentParam param = new ShopGoodsCommentParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsIncomeConfigServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsIncomeConfigServiceImpl.java new file mode 100644 index 0000000..d4ca36e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsIncomeConfigServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsIncomeConfigMapper; +import com.gxwebsoft.shop.service.ShopGoodsIncomeConfigService; +import com.gxwebsoft.shop.entity.ShopGoodsIncomeConfig; +import com.gxwebsoft.shop.param.ShopGoodsIncomeConfigParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分润配置Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopGoodsIncomeConfigServiceImpl extends ServiceImpl implements ShopGoodsIncomeConfigService { + + @Override + public PageResult pageRel(ShopGoodsIncomeConfigParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsIncomeConfigParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsIncomeConfig getByIdRel(Integer id) { + ShopGoodsIncomeConfigParam param = new ShopGoodsIncomeConfigParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsLogServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsLogServiceImpl.java new file mode 100644 index 0000000..0e2e46f --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsLogMapper; +import com.gxwebsoft.shop.service.ShopGoodsLogService; +import com.gxwebsoft.shop.entity.ShopGoodsLog; +import com.gxwebsoft.shop.param.ShopGoodsLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品日志表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopGoodsLogServiceImpl extends ServiceImpl implements ShopGoodsLogService { + + @Override + public PageResult pageRel(ShopGoodsLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsLog getByIdRel(Integer id) { + ShopGoodsLogParam param = new ShopGoodsLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRelationServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRelationServiceImpl.java new file mode 100644 index 0000000..eaf1166 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRelationServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsRelationMapper; +import com.gxwebsoft.shop.service.ShopGoodsRelationService; +import com.gxwebsoft.shop.entity.ShopGoodsRelation; +import com.gxwebsoft.shop.param.ShopGoodsRelationParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品点赞和收藏表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopGoodsRelationServiceImpl extends ServiceImpl implements ShopGoodsRelationService { + + @Override + public PageResult pageRel(ShopGoodsRelationParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsRelationParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsRelation getByIdRel(Integer id) { + ShopGoodsRelationParam param = new ShopGoodsRelationParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRoleCommissionServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRoleCommissionServiceImpl.java new file mode 100644 index 0000000..95a72cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsRoleCommissionServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsRoleCommissionMapper; +import com.gxwebsoft.shop.service.ShopGoodsRoleCommissionService; +import com.gxwebsoft.shop.entity.ShopGoodsRoleCommission; +import com.gxwebsoft.shop.param.ShopGoodsRoleCommissionParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品绑定角色的分润金额Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 09:53:38 + */ +@Service +public class ShopGoodsRoleCommissionServiceImpl extends ServiceImpl implements ShopGoodsRoleCommissionService { + + @Override + public PageResult pageRel(ShopGoodsRoleCommissionParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsRoleCommissionParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsRoleCommission getByIdRel(Integer id) { + ShopGoodsRoleCommissionParam param = new ShopGoodsRoleCommissionParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java new file mode 100644 index 0000000..4984d72 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsServiceImpl.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsMapper; +import com.gxwebsoft.shop.service.ShopGoodsService; +import com.gxwebsoft.shop.entity.ShopGoods; +import com.gxwebsoft.shop.param.ShopGoodsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 商品Service实现 + * + * @author 科技小王子 + * @since 2025-04-24 20:52:13 + */ +@Slf4j +@Service +public class ShopGoodsServiceImpl extends ServiceImpl implements ShopGoodsService { + + @Override + public PageResult pageRel(ShopGoodsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoods getByIdRel(Integer goodsId) { + ShopGoodsParam param = new ShopGoodsParam(); + param.setGoodsId(goodsId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @InterceptorIgnore(tenantLine = "true") + @Override + public boolean addSaleCount(Integer goodsId, Integer saleCount) { + try { + if (goodsId == null || saleCount == null || saleCount <= 0) { + log.warn("累加商品销量参数无效 - 商品ID: {}, 销量: {}", goodsId, saleCount); + return false; + } + + int affectedRows = baseMapper.addSaleCount(goodsId, saleCount); + boolean success = affectedRows > 0; + + if (success) { + log.info("商品销量累加成功 - 商品ID: {}, 累加数量: {}, 影响行数: {}", goodsId, saleCount, affectedRows); + } else { + log.warn("商品销量累加失败 - 商品ID: {}, 累加数量: {}, 影响行数: {}", goodsId, saleCount, affectedRows); + } + + return success; + } catch (Exception e) { + log.error("累加商品销量异常 - 商品ID: {}, 累加数量: {}", goodsId, saleCount, e); + return false; + } + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSkuServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSkuServiceImpl.java new file mode 100644 index 0000000..cc1739b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSkuServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsSkuMapper; +import com.gxwebsoft.shop.service.ShopGoodsSkuService; +import com.gxwebsoft.shop.entity.ShopGoodsSku; +import com.gxwebsoft.shop.param.ShopGoodsSkuParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品sku列表Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Service +public class ShopGoodsSkuServiceImpl extends ServiceImpl implements ShopGoodsSkuService { + + @Override + public PageResult pageRel(ShopGoodsSkuParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsSkuParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsSku getByIdRel(Integer id) { + ShopGoodsSkuParam param = new ShopGoodsSkuParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSpecServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSpecServiceImpl.java new file mode 100644 index 0000000..812a5b3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopGoodsSpecServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopGoodsSpecMapper; +import com.gxwebsoft.shop.service.ShopGoodsSpecService; +import com.gxwebsoft.shop.entity.ShopGoodsSpec; +import com.gxwebsoft.shop.param.ShopGoodsSpecParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商品多规格Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 09:43:31 + */ +@Service +public class ShopGoodsSpecServiceImpl extends ServiceImpl implements ShopGoodsSpecService { + + @Override + public PageResult pageRel(ShopGoodsSpecParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopGoodsSpecParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopGoodsSpec getByIdRel(Integer id) { + ShopGoodsSpecParam param = new ShopGoodsSpecParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantAccountServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantAccountServiceImpl.java new file mode 100644 index 0000000..6d39658 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantAccountServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopMerchantAccountMapper; +import com.gxwebsoft.shop.service.ShopMerchantAccountService; +import com.gxwebsoft.shop.entity.ShopMerchantAccount; +import com.gxwebsoft.shop.param.ShopMerchantAccountParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商户账号Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopMerchantAccountServiceImpl extends ServiceImpl implements ShopMerchantAccountService { + + @Override + public PageResult pageRel(ShopMerchantAccountParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopMerchantAccountParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopMerchantAccount getByIdRel(Integer id) { + ShopMerchantAccountParam param = new ShopMerchantAccountParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantApplyServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantApplyServiceImpl.java new file mode 100644 index 0000000..becb4aa --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantApplyServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopMerchantApplyMapper; +import com.gxwebsoft.shop.service.ShopMerchantApplyService; +import com.gxwebsoft.shop.entity.ShopMerchantApply; +import com.gxwebsoft.shop.param.ShopMerchantApplyParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商户入驻申请Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopMerchantApplyServiceImpl extends ServiceImpl implements ShopMerchantApplyService { + + @Override + public PageResult pageRel(ShopMerchantApplyParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopMerchantApplyParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopMerchantApply getByIdRel(Integer applyId) { + ShopMerchantApplyParam param = new ShopMerchantApplyParam(); + param.setApplyId(applyId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantServiceImpl.java new file mode 100644 index 0000000..9fb3e73 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopMerchantMapper; +import com.gxwebsoft.shop.service.ShopMerchantService; +import com.gxwebsoft.shop.entity.ShopMerchant; +import com.gxwebsoft.shop.param.ShopMerchantParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商户Service实现 + * + * @author 科技小王子 + * @since 2025-08-10 20:43:33 + */ +@Service +public class ShopMerchantServiceImpl extends ServiceImpl implements ShopMerchantService { + + @Override + public PageResult pageRel(ShopMerchantParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopMerchantParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopMerchant getByIdRel(Long merchantId) { + ShopMerchantParam param = new ShopMerchantParam(); + param.setMerchantId(merchantId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantTypeServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantTypeServiceImpl.java new file mode 100644 index 0000000..38c87cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopMerchantTypeServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopMerchantTypeMapper; +import com.gxwebsoft.shop.service.ShopMerchantTypeService; +import com.gxwebsoft.shop.entity.ShopMerchantType; +import com.gxwebsoft.shop.param.ShopMerchantTypeParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商户类型Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopMerchantTypeServiceImpl extends ServiceImpl implements ShopMerchantTypeService { + + @Override + public PageResult pageRel(ShopMerchantTypeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopMerchantTypeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopMerchantType getByIdRel(Integer id) { + ShopMerchantTypeParam param = new ShopMerchantTypeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryGoodsServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryGoodsServiceImpl.java new file mode 100644 index 0000000..c48c23a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryGoodsServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopOrderDeliveryGoodsMapper; +import com.gxwebsoft.shop.service.ShopOrderDeliveryGoodsService; +import com.gxwebsoft.shop.entity.ShopOrderDeliveryGoods; +import com.gxwebsoft.shop.param.ShopOrderDeliveryGoodsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 发货单商品Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopOrderDeliveryGoodsServiceImpl extends ServiceImpl implements ShopOrderDeliveryGoodsService { + + @Override + public PageResult pageRel(ShopOrderDeliveryGoodsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderDeliveryGoodsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrderDeliveryGoods getByIdRel(Integer id) { + ShopOrderDeliveryGoodsParam param = new ShopOrderDeliveryGoodsParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryServiceImpl.java new file mode 100644 index 0000000..1a94ab3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderDeliveryServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopOrderDeliveryMapper; +import com.gxwebsoft.shop.service.ShopOrderDeliveryService; +import com.gxwebsoft.shop.entity.ShopOrderDelivery; +import com.gxwebsoft.shop.param.ShopOrderDeliveryParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 发货单Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopOrderDeliveryServiceImpl extends ServiceImpl implements ShopOrderDeliveryService { + + @Override + public PageResult pageRel(ShopOrderDeliveryParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderDeliveryParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrderDelivery getByIdRel(Integer deliveryId) { + ShopOrderDeliveryParam param = new ShopOrderDeliveryParam(); + param.setDeliveryId(deliveryId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderExtractServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderExtractServiceImpl.java new file mode 100644 index 0000000..91cfdab --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderExtractServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopOrderExtractMapper; +import com.gxwebsoft.shop.service.ShopOrderExtractService; +import com.gxwebsoft.shop.entity.ShopOrderExtract; +import com.gxwebsoft.shop.param.ShopOrderExtractParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 自提订单联系方式Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopOrderExtractServiceImpl extends ServiceImpl implements ShopOrderExtractService { + + @Override + public PageResult pageRel(ShopOrderExtractParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderExtractParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrderExtract getByIdRel(Integer id) { + ShopOrderExtractParam param = new ShopOrderExtractParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderGoodsServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderGoodsServiceImpl.java new file mode 100644 index 0000000..7019db0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderGoodsServiceImpl.java @@ -0,0 +1,78 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopOrderGoodsMapper; +import com.gxwebsoft.shop.service.ShopOrderGoodsService; +import com.gxwebsoft.shop.entity.ShopOrderGoods; +import com.gxwebsoft.shop.param.ShopOrderGoodsParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 商品信息Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Slf4j +@Service +public class ShopOrderGoodsServiceImpl extends ServiceImpl implements ShopOrderGoodsService { + + @Override + public PageResult pageRel(ShopOrderGoodsParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderGoodsParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrderGoods getByIdRel(Integer id) { + ShopOrderGoodsParam param = new ShopOrderGoodsParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public List getListByOrderId(Integer orderId) { + return list( + new LambdaQueryWrapper() + .eq(ShopOrderGoods::getOrderId, orderId) + ); + } + + @Override + public List getListByOrderIdIgnoreTenant(Integer orderId) { + try { + if (orderId == null) { + log.warn("查询订单商品列表参数无效 - 订单ID: {}", orderId); + return List.of(); + } + + List orderGoodsList = baseMapper.selectListByOrderIdIgnoreTenant(orderId); + + log.info("忽略租户隔离查询订单商品成功 - 订单ID: {}, 商品数量: {}", + orderId, orderGoodsList != null ? orderGoodsList.size() : 0); + + return orderGoodsList != null ? orderGoodsList : List.of(); + } catch (Exception e) { + log.error("忽略租户隔离查询订单商品异常 - 订单ID: {}", orderId, e); + return List.of(); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoLogServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoLogServiceImpl.java new file mode 100644 index 0000000..fdd7f54 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopOrderInfoLogMapper; +import com.gxwebsoft.shop.service.ShopOrderInfoLogService; +import com.gxwebsoft.shop.entity.ShopOrderInfoLog; +import com.gxwebsoft.shop.param.ShopOrderInfoLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 订单核销Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopOrderInfoLogServiceImpl extends ServiceImpl implements ShopOrderInfoLogService { + + @Override + public PageResult pageRel(ShopOrderInfoLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderInfoLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrderInfoLog getByIdRel(Integer id) { + ShopOrderInfoLogParam param = new ShopOrderInfoLogParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoServiceImpl.java new file mode 100644 index 0000000..6c2681b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderInfoServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopOrderInfoMapper; +import com.gxwebsoft.shop.service.ShopOrderInfoService; +import com.gxwebsoft.shop.entity.ShopOrderInfo; +import com.gxwebsoft.shop.param.ShopOrderInfoParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 场地Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopOrderInfoServiceImpl extends ServiceImpl implements ShopOrderInfoService { + + @Override + public PageResult pageRel(ShopOrderInfoParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderInfoParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrderInfo getByIdRel(Integer id) { + ShopOrderInfoParam param = new ShopOrderInfoParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java new file mode 100644 index 0000000..dd89b55 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java @@ -0,0 +1,1109 @@ +package com.gxwebsoft.shop.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.config.ConfigProperties; +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.utils.*; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.core.service.EnvironmentAwarePaymentService; +import com.gxwebsoft.common.core.config.SpringContextUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.entity.*; +import com.gxwebsoft.shop.service.*; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import lombok.extern.slf4j.Slf4j; +import com.gxwebsoft.common.system.service.PaymentService; +import com.gxwebsoft.common.system.service.SettingService; +import com.gxwebsoft.payment.constants.WechatPayType; +import com.gxwebsoft.shop.mapper.ShopOrderMapper; +import com.gxwebsoft.shop.param.ShopOrderParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAConfig; +import com.wechat.pay.java.core.RSAPublicKeyConfig; +import com.wechat.pay.java.core.exception.ServiceException; +import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; +import com.wechat.pay.java.service.payments.jsapi.model.*; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; +// Native支付的类将使用完全限定名避免冲突 +import com.wechat.pay.java.service.payments.model.Transaction; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.sql.Date; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static com.gxwebsoft.common.core.utils.DateTimeUtil.formatDateTime; + +/** + * 订单Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Slf4j +@Service +public class ShopOrderServiceImpl extends ServiceImpl implements ShopOrderService { + @Value("${spring.profiles.active}") + String active; + @Resource + private ConfigProperties config; + @Resource + private RedisUtil redisUtil; + @Resource + private ShopOrderGoodsService shopOrderGoodsService; + @Resource + private ShopGoodsService shopGoodsService; + @Resource + private PaymentService paymentService; + @Resource + private SettingService settingService; + @Resource + private CertificateProperties certConfig; + @Resource + private CertificateLoader certificateLoader; + @Resource + private PaymentCacheService paymentCacheService; + @Resource + private WechatCertAutoConfig wechatCertAutoConfig; + @Resource + private WechatPayDiagnostic wechatPayDiagnostic; + @Resource + private WechatPayCertificateDiagnostic certificateDiagnostic; + @Resource + private ShopOrderUpdate10550Service shopOrderUpdate10550Service; + @Resource + private ShopUserCouponService shopUserCouponService; + + + @Override + public PageResult pageRel(ShopOrderParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + + // 整理订单数据 + if (!CollectionUtils.isEmpty(list)) { + final Set orderIds = list.stream().map(ShopOrder::getOrderId).collect(Collectors.toSet()); + final List goodsList = shopOrderGoodsService.list(new LambdaQueryWrapper().in(ShopOrderGoods::getOrderId, orderIds)); + final Map> collect = goodsList.stream().collect(Collectors.groupingBy(ShopOrderGoods::getOrderId)); + list.forEach(d -> { + d.setOrderGoods(collect.get(d.getOrderId())); + + // 确保 realName 字段有值,优先使用关联查询的结果,如果为空则使用订单表中的 realName + if (StrUtil.isBlank(d.getRealName())) { + log.debug("订单 {} 的 realName 为空,尝试从其他字段获取", d.getOrderId()); + // 可以根据业务需求添加其他逻辑,比如从 nickname 或其他字段获取 + } + }); + } + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopOrderParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopOrder getByIdRel(Integer orderId) { + ShopOrderParam param = new ShopOrderParam(); + param.setOrderId(orderId); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public HashMap createWxOrder(ShopOrder order) { + Payment payment = null; // 声明在try块外面,这样catch块也能访问 + try { + System.out.println("=== 开始创建微信支付订单 ==="); + System.out.println("订单号: " + order.getOrderNo()); + System.out.println("租户ID: " + order.getTenantId()); + System.out.println("支付类型: " + order.getPayType()); + System.out.println("订单金额: " + order.getTotalPrice()); + System.out.println("用户OpenID: " + order.getOpenid()); + + // 检查订单基本信息 + if (order.getTenantId() == null) { + throw new RuntimeException("订单租户ID为null"); + } + if (order.getOrderNo() == null || order.getOrderNo().trim().isEmpty()) { + throw new RuntimeException("订单号为空"); + } + if (order.getTotalPrice() == null) { + throw new RuntimeException("订单金额为null"); + } + + // 根据支付类型检查OpenID + if (order.getPayType().equals(1)) { + // JSAPI支付需要OpenID + if (order.getOpenid() == null || order.getOpenid().trim().isEmpty()) { + throw new RuntimeException("JSAPI支付必须传openid"); + } + } + + // 后台微信支付配置信息 + payment = getPayment(order); + System.out.println("支付配置: " + (payment != null ? "已获取" : "未获取")); + + if (payment == null) { + throw new RuntimeException("支付配置为null,租户ID: " + order.getTenantId()); + } + + // 检查支付配置的关键字段 + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + throw new RuntimeException("支付配置中应用ID为null或空"); + } + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + throw new RuntimeException("支付配置中商户号为null或空"); + } + + // 返回的订单数据 + final HashMap orderInfo = new HashMap<>(); + + // 根据支付类型选择不同的处理逻辑 + if (order.getPayType().equals(102)) { + // Native支付处理(兼容旧的102类型) + System.out.println("⚠️ 检测到使用废弃的微信Native支付类型(102),建议升级到微信支付(1)"); + order.setWechatPayType(WechatPayType.NATIVE); + return createNativePayOrder(order, payment); + } else if (order.getPayType().equals(1)) { + // 微信支付处理 - 根据是否有openid判断使用JSAPI还是Native + String wechatType = WechatPayType.getAutoType(order.getOpenid()); + order.setWechatPayType(wechatType); + + if (WechatPayType.JSAPI.equals(wechatType)) { + // 有openid,使用JSAPI支付 + return createJsapiPayOrder(order, payment); + } else { + // 无openid,使用Native支付 + return createNativePayOrder(order, payment); + } + } else { + // 其他支付方式暂时使用JSAPI处理 + return createJsapiPayOrder(order, payment); + } + + } catch (Exception e) { + System.err.println("=== 创建微信支付订单失败 ==="); + System.err.println("错误信息: " + e.getMessage()); + System.err.println("错误类型: " + e.getClass().getName()); + + // 特殊处理签名验证失败的情况 + if (e.getMessage() != null && e.getMessage().contains("signature is incorrect")) { + System.err.println("🔍 签名验证失败诊断:"); + System.err.println("1. 检查商户证书序列号是否正确"); + System.err.println("2. 检查APIv3密钥是否正确"); + System.err.println("3. 检查私钥文件是否正确"); + System.err.println("4. 检查微信支付平台证书是否过期"); + System.err.println("5. 建议使用自动证书配置避免证书管理问题"); + System.err.println("当前支付配置:"); + System.err.println("租户ID: " + order.getTenantId()); + System.err.println("商户号: " + (payment != null ? payment.getMchId() : "null")); + System.err.println("应用ID: " + (payment != null ? payment.getAppId() : "null")); + } + + throw new RuntimeException("创建微信支付订单失败:" + e.getMessage(), e); + } + } + + /** + * 创建Native支付订单 + */ + private HashMap createNativePayOrder(ShopOrder order, Payment payment) throws Exception { + System.out.println("=== 使用Native支付模式 ==="); + + // 构建Native支付服务 + Config wxPayConfig = getWxPayConfig(order); + NativePayService nativeService = new NativePayService.Builder().config(wxPayConfig).build(); + + // 订单金额(转换为分) + BigDecimal decimal = order.getTotalPrice(); + final BigDecimal multiply = decimal.multiply(new BigDecimal(100)); + Integer money = multiply.intValue(); + + // 测试环境使用1分钱 + if (active.equals("dev")) { + money = 1; + } + + // 构建Native支付请求 + com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = + new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest(); + com.wechat.pay.java.service.payments.nativepay.model.Amount amount = + new com.wechat.pay.java.service.payments.nativepay.model.Amount(); + amount.setTotal(money); + amount.setCurrency("CNY"); + request.setAmount(amount); + + request.setAppid(payment.getAppId()); + request.setMchid(payment.getMchId()); + + // 微信支付description字段限制127字节,需要截断处理 + String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments()); + request.setDescription(description); + request.setOutTradeNo(order.getOrderNo()); + request.setAttach(order.getTenantId().toString()); + + System.out.println("=== 关键信息确认 ==="); + System.out.println("发送给微信的订单号(OutTradeNo): " + order.getOrderNo()); + System.out.println("商户号(MchId): " + payment.getMchId()); + System.out.println("应用ID(AppId): " + payment.getAppId()); + + // 设置回调地址 + String notifyUrl = config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId(); + if (active.equals("dev")) { + notifyUrl = "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId(); + } + if (StrUtil.isNotBlank(payment.getNotifyUrl())) { + notifyUrl = payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString()); + } + request.setNotifyUrl(notifyUrl); + + System.out.println("=== 发起Native支付请求 ==="); + System.out.println("请求参数: " + request); + + // 调用Native支付API + com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = nativeService.prepay(request); + + System.out.println("=== Native支付响应成功 ==="); + System.out.println("二维码URL: " + response.getCodeUrl()); + + // 构建返回数据 + final HashMap orderInfo = new HashMap<>(); + orderInfo.put("provider", "wxpay"); + orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL + orderInfo.put("orderNo", order.getOrderNo()); + orderInfo.put("payType", WechatPayType.NATIVE); + orderInfo.put("wechatPayType", WechatPayType.NATIVE); + + return orderInfo; + } + + /** + * 创建JSAPI支付订单(原有逻辑) + */ + private HashMap createJsapiPayOrder(ShopOrder order, Payment payment) throws Exception { + System.out.println("=== 使用JSAPI支付模式 ==="); + + // JSAPI支付必须有OpenID + if (order.getOpenid() == null || order.getOpenid().trim().isEmpty()) { + throw new RuntimeException("JSAPI支付必须传openid"); + } + + // 构建JSAPI支付服务 + JsapiServiceExtension service = getWxService(order); + System.out.println("微信支付服务构建完成"); + + // 订单金额 + BigDecimal decimal = order.getTotalPrice(); + final BigDecimal multiply = decimal.multiply(new BigDecimal(100)); + Integer money = multiply.intValue(); + + System.out.println("=== 构建支付请求参数 ==="); + + PrepayRequest request = new PrepayRequest(); + Amount amount = new Amount(); + amount.setTotal(money); + amount.setCurrency("CNY"); + request.setAmount(amount); + + System.out.println("设置应用ID: " + payment.getAppId()); + request.setAppid(payment.getAppId()); + + System.out.println("设置商户号: " + payment.getMchId()); + request.setMchid(payment.getMchId()); + + // 微信支付description字段限制127字节,需要截断处理 + String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments()); + System.out.println("设置描述: " + description); + request.setDescription(description); + + System.out.println("设置订单号: " + order.getOrderNo()); + request.setOutTradeNo(order.getOrderNo()); + + System.out.println("设置附加数据: " + order.getTenantId().toString()); + request.setAttach(order.getTenantId().toString()); + + final Payer payer = new Payer(); + System.out.println("设置用户OpenID: " + order.getOpenid()); + payer.setOpenid(order.getOpenid()); + request.setPayer(payer); + request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址 + // 测试环境 + if (active.equals("dev")) { + amount.setTotal(1); + request.setAmount(amount); + request.setNotifyUrl("http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId()); // 默认回调地址 + } + // 后台配置的回调地址 + if (StrUtil.isNotBlank(payment.getNotifyUrl())) { + request.setNotifyUrl(payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString())); + System.out.println("后台配置的回调地址 = " + request.getNotifyUrl()); + } + System.out.println("=== 发起微信支付请求 ==="); + System.out.println("请求参数: " + request); + + PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request); + + System.out.println("=== 微信支付响应成功 ==="); + System.out.println("预支付ID: " + response.getPackageVal()); + + final HashMap orderInfo = new HashMap<>(); + orderInfo.put("provider", "wxpay"); + orderInfo.put("timeStamp", response.getTimeStamp()); + orderInfo.put("nonceStr", response.getNonceStr()); + orderInfo.put("package", response.getPackageVal()); + orderInfo.put("signType", "RSA"); + orderInfo.put("paySign", response.getPaySign()); + orderInfo.put("orderNo", order.getOrderNo()); + orderInfo.put("payType", WechatPayType.JSAPI); + orderInfo.put("wechatPayType", WechatPayType.JSAPI); + return orderInfo; + } + + @Override + public ShopOrder getByOutTradeNo(String outTradeNo) { + return baseMapper.getByOutTradeNo(outTradeNo); + } + + /** + * 修复订单支付状态 + * + * @param shopOrder + */ + @Override + public Boolean queryOrderByOutTradeNo(ShopOrder shopOrder) { + // 后台微信支付配置信息 + final Payment payment = getPayment(shopOrder); + QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest(); + queryRequest.setMchid(payment.getMchId()); + queryRequest.setOutTradeNo(shopOrder.getOrderNo()); + // 构建service + JsapiServiceExtension service = getWxService(shopOrder); + try { + Transaction result = service.queryOrderByOutTradeNo(queryRequest); + if (result.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) { + shopOrder.setPayStatus(true); + shopOrder.setPayTime(LocalDateTime.now()); + shopOrder.setTransactionId(result.getTransactionId()); + updateById(shopOrder); + return true; + } + } catch (ServiceException e) { + // API返回失败, 例如ORDER_NOT_EXISTS + System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage()); + System.out.printf("reponse body=[%s]\n", e.getResponseBody()); + } + return false; + } + + @Override + public void updateByOutTradeNo(ShopOrder order) { + order.setExpirationTime(null); + baseMapper.updateByOutTradeNo(order); + + // 处理支付成功后的业务逻辑 + handlePaymentSuccess(order); + + if (order.getTenantId().equals(10550)) { + shopOrderUpdate10550Service.update(order); + } + } + + /** + * 处理支付成功后的业务逻辑 + */ + private void handlePaymentSuccess(ShopOrder order) { + try { + // 1. 使用优惠券 + if (order.getCouponId() != null && order.getCouponId() > 0) { + markCouponAsUsed(order); + } + + // 2. 累计商品销量 + updateGoodsSales(order); + + log.info("支付成功后业务逻辑处理完成 - 订单号:{}", order.getOrderNo()); + } catch (Exception e) { + log.error("处理支付成功后业务逻辑失败 - 订单号:{}", order.getOrderNo(), e); + // 不抛出异常,避免影响支付回调的成功响应 + } + } + + /** + * 标记优惠券为已使用 + */ + private void markCouponAsUsed(ShopOrder order) { + try { + // 注入 CouponStatusService 并使用其 useCoupon 方法 + // couponStatusService.useCoupon(order.getCouponId().longValue(), order.getOrderId(), order.getOrderNo()); + + // 临时保持原有逻辑,建议后续重构 + ShopUserCoupon coupon = shopUserCouponService.getById(order.getCouponId()); + if (coupon != null) { + coupon.markAsUsed(order.getOrderId(), order.getOrderNo()); + shopUserCouponService.updateById(coupon); + log.info("优惠券标记为已使用 - 优惠券ID:{},订单号:{}", order.getCouponId(), order.getOrderNo()); + } + } catch (Exception e) { + log.error("标记优惠券为已使用失败 - 优惠券ID:{},订单号:{}", order.getCouponId(), order.getOrderNo(), e); + } + } + + /** + * 累计商品销量 + */ + private void updateGoodsSales(ShopOrder order) { + try { + // 获取订单商品列表(忽略租户隔离) + final List orderGoodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId()); + + if (orderGoodsList.isEmpty()) { + log.warn("订单商品列表为空 - 订单号:{}", order.getOrderNo()); + return; + } + + // 累计每个商品的销量 + for (ShopOrderGoods orderGoods : orderGoodsList) { + updateSingleGoodsSales(orderGoods); + } + + log.info("商品销量累计完成 - 订单号:{},商品数量:{}", order.getOrderNo(), orderGoodsList.size()); + } catch (Exception e) { + log.error("累计商品销量失败 - 订单号:{}", order.getOrderNo(), e); + } + } + + /** + * 累计单个商品的销量 + * 使用新的addSaleCount方法,忽略租户隔离确保更新成功 + */ + private void updateSingleGoodsSales(ShopOrderGoods orderGoods) { + try { + if (orderGoods.getGoodsId() == null || orderGoods.getTotalNum() == null || orderGoods.getTotalNum() <= 0) { + log.warn("商品销量累计参数无效 - 商品ID:{},购买数量:{}", + orderGoods.getGoodsId(), orderGoods.getTotalNum()); + return; + } + + // 使用新的addSaleCount方法,忽略租户隔离 + boolean updated = shopGoodsService.addSaleCount(orderGoods.getGoodsId(), orderGoods.getTotalNum()); + + if (updated) { + log.info("商品销量累计成功 - 商品ID:{},商品名称:{},购买数量:{}", + orderGoods.getGoodsId(), orderGoods.getGoodsName(), orderGoods.getTotalNum()); + } else { + log.warn("商品销量累计失败 - 商品ID:{},商品名称:{},购买数量:{}", + orderGoods.getGoodsId(), orderGoods.getGoodsName(), orderGoods.getTotalNum()); + } + } catch (Exception e) { + log.error("累计单个商品销量异常 - 商品ID:{},商品名称:{},购买数量:{}", + orderGoods.getGoodsId(), orderGoods.getGoodsName(), orderGoods.getTotalNum(), e); + } + } + + /** + * 读取微信支付配置 + * 生产环境优先从缓存读取 Payment:1* 格式的商户信息 + * 开发环境自动使用本地回调地址 + * + * @param order + * @return + */ + public Payment getPayment(ShopOrder order) { + // 先清除可能的错误缓存 +// paymentCacheService.removePaymentConfig(order.getPayType().toString(), order.getTenantId()); + + // 使用环境感知的支付配置服务 + Payment payment; + try { + // 尝试使用环境感知服务 + EnvironmentAwarePaymentService envPaymentService = + SpringContextUtil.getBean(EnvironmentAwarePaymentService.class); + payment = envPaymentService.getEnvironmentAwarePaymentConfig(order.getPayType(), order.getTenantId()); + } catch (Exception e) { + // 如果环境感知服务不可用,回退到原有方式 + log.warn("环境感知支付服务不可用,使用原有配置方式: {}", e.getMessage()); + payment = paymentCacheService.getPaymentConfig(order.getPayType(), order.getTenantId()); + } + + // 添加详细的支付配置检查 + System.out.println("=== 支付配置检查 ==="); + System.out.println("订单支付类型: " + order.getPayType()); + System.out.println("租户ID: " + order.getTenantId()); + + if (payment == null) { + throw new RuntimeException("未找到支付配置,支付类型: " + order.getPayType() + ", 租户ID: " + order.getTenantId()); + } + + System.out.println("支付配置ID: " + payment.getId()); + System.out.println("支付方式名称: " + payment.getName()); + System.out.println("支付类型: " + payment.getType()); + System.out.println("支付代码: " + payment.getCode()); + System.out.println("应用ID: " + payment.getAppId()); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("API密钥: " + (payment.getApiKey() != null ? "已配置(长度:" + payment.getApiKey().length() + ")" : "未配置")); + System.out.println("商户证书序列号: " + payment.getMerchantSerialNumber()); + System.out.println("状态: " + payment.getStatus()); + + return payment; + } + + /** + * 构建微信支付 + * + * @param order + * @return + */ + public JsapiServiceExtension getWxService(ShopOrder order) { + try { + final Payment payment = getPayment(order); + + // 提前声明所有需要的变量,避免重复定义 + String privateKey; + String apiclientCert = null; + String pubKey = null; + String tenantCertPath = null; + String privateKeyPath = null; + String pubKeyPath = null; + String apiclientCertPath = null; + String apiclientCertFile = null; + String pubKeyFile = null; + + // 运行配置诊断 + System.out.println("=== 运行微信支付配置诊断 ==="); + wechatPayDiagnostic.diagnosePaymentConfig(payment, null, active); + + // 运行证书诊断 + System.out.println("=== 运行证书诊断 ==="); + WechatPayCertificateDiagnostic.DiagnosticResult diagnosticResult = + certificateDiagnostic.diagnoseCertificateConfig(payment, order.getTenantId(), active); + System.out.println(diagnosticResult.getFullReport()); + + + + // 构建微信支付配置 + Config config = null; + if (active.equals("dev")) { + // 开发环境使用自动证书配置 + // 首先初始化私钥路径 + tenantCertPath = "dev/wechat/" + order.getTenantId(); + privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + privateKey = certificateLoader.loadCertificatePath(privateKeyPath); + System.out.println("开发环境私钥路径: " + privateKeyPath); + System.out.println("开发环境私钥加载成功: " + privateKey); + + // 检查数据库配置是否完整 + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + throw new RuntimeException("数据库中商户号(mchId)未配置"); + } + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + throw new RuntimeException("数据库中商户证书序列号(merchantSerialNumber)未配置"); + } + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + throw new RuntimeException("数据库中API密钥(apiKey)未配置"); + } + + System.out.println("=== 使用数据库支付配置 ==="); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("序列号: " + payment.getMerchantSerialNumber()); + System.out.println("API密钥: 已配置(长度:" + payment.getApiKey().length() + ")"); + + // 临时使用RSA配置替代自动证书配置,避免404错误 + System.out.println("=== 注意:使用RSA配置替代自动证书配置 ==="); + System.out.println("原因:商户平台可能未开启API安全功能或未申请微信支付公钥"); + + // 首先检查是否配置了公钥,如果有则优先使用公钥模式 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + + try { + // 开发环境固定使用 wechatpay_public_key.pem + pubKeyPath = tenantCertPath + "/wechatpay_public_key.pem"; + + System.out.println("开发环境公钥文件路径: " + pubKeyPath); + + // 检查公钥文件是否存在 + if (certificateLoader.certificateExists(pubKeyPath)) { + System.out.println("=== 检测到公钥配置,使用RSA公钥模式 ==="); + System.out.println("公钥文件: " + payment.getPubKey()); + System.out.println("公钥ID: " + payment.getPubKeyId()); + + pubKeyFile = certificateLoader.loadCertificatePath(pubKeyPath); + System.out.println("✅ 开发环境公钥文件加载成功: " + pubKeyFile); + + config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .publicKeyFromPath(pubKeyFile) + .publicKeyId(payment.getPubKeyId()) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + System.out.println("✅ 开发环境RSA公钥配置成功"); + } else { + System.out.println("⚠️ 开发环境公钥文件不存在,跳过公钥模式: " + pubKeyPath); + // 跳过公钥配置,继续后续的自动证书配置 + } + } catch (Exception e) { + System.err.println("❌ 开发环境公钥配置检查失败: " + e.getMessage()); + // 跳过公钥配置,继续后续的自动证书配置 + } + } + + // 如果没有公钥配置或公钥文件不存在,尝试自动证书配置 + if (config == null) { + // 没有公钥配置,尝试自动证书配置 + try { + System.out.println("=== 尝试创建自动证书配置 ==="); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("私钥路径: " + privateKey); + System.out.println("序列号: " + payment.getMerchantSerialNumber()); + System.out.println("API密钥长度: " + (payment.getApiKey() != null ? payment.getApiKey().length() : 0)); + + config = wechatCertAutoConfig.createAutoConfig( + payment.getMchId(), + privateKey, + payment.getMerchantSerialNumber(), + payment.getApiKey() + ); + System.out.println("✅ 开发环境自动证书配置成功"); + } catch (Exception e) { + System.err.println("❌ 自动证书配置失败: " + e.getMessage()); + System.err.println("错误类型: " + e.getClass().getName()); + e.printStackTrace(); + + // 检查是否是证书相关的错误 + if (e.getMessage() != null && ( + e.getMessage().contains("certificate") || + e.getMessage().contains("X509Certificate") || + e.getMessage().contains("getSerialNumber") || + e.getMessage().contains("404") || + e.getMessage().contains("API安全"))) { + + System.err.println("🔍 证书问题诊断:"); + System.err.println("1. 检查商户平台是否已开启API安全功能"); + System.err.println("2. 检查是否已申请使用微信支付公钥"); + System.err.println("3. 检查网络连接是否正常"); + System.err.println("4. 检查商户证书序列号是否正确"); + System.err.println("5. 参考文档:https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); + + // 开发环境回退到基础RSA配置 + System.err.println("⚠️ 开发环境回退到基础RSA配置..."); + try { + // 方案1:尝试使用RSA证书配置(需要商户证书文件) + apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + if (certificateLoader.certificateExists(apiclientCertPath)) { + apiclientCertFile = certificateLoader.loadCertificatePath(apiclientCertPath); + System.out.println("方案1: 使用RSA证书配置作为回退方案"); + System.out.println("商户证书路径: " + apiclientCertFile); + + try { + config = new RSAConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .wechatPayCertificatesFromPath(apiclientCertFile) + .build(); + System.out.println("✅ 开发环境RSA证书配置成功"); + } catch (Exception rsaException) { + System.err.println("RSA证书配置失败: " + rsaException.getMessage()); + throw rsaException; + } + } else { + System.err.println("❌ 商户证书文件不存在: " + apiclientCertPath); + System.err.println("⚠️ 尝试方案2: 使用最小化配置..."); + + // 方案2:使用最小化的RSA配置(仅私钥和序列号) + try { + // 创建一个最基础的配置,不依赖平台证书 + config = new com.wechat.pay.java.core.RSAConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .build(); + System.out.println("✅ 开发环境最小化RSA配置成功"); + } catch (Exception minimalException) { + System.err.println("最小化配置也失败: " + minimalException.getMessage()); + throw new RuntimeException("所有配置方案都失败,请检查私钥文件和商户配置", e); + } + } + } catch (Exception fallbackException) { + System.err.println("❌ 手动证书配置失败: " + fallbackException.getMessage()); + fallbackException.printStackTrace(); + + // 最后的回退:抛出详细错误信息 + System.err.println("=== 最终错误诊断 ==="); + System.err.println("1. 自动证书配置失败原因: " + e.getMessage()); + System.err.println("2. 手动证书配置失败原因: " + fallbackException.getMessage()); + System.err.println("3. 建议解决方案:"); + System.err.println(" - 检查微信商户平台是否开启API安全功能"); + System.err.println(" - 确认已申请使用微信支付公钥"); + System.err.println(" - 验证商户证书序列号是否正确"); + System.err.println(" - 检查私钥文件是否完整且格式正确"); + + throw new RuntimeException("微信支付配置失败,请检查商户平台API安全设置和证书配置。原始错误: " + e.getMessage() + ", 回退错误: " + fallbackException.getMessage(), e); + } + } else { + throw new RuntimeException("微信支付配置失败:" + e.getMessage(), e); + } + } + } + } else { + // 生产环境 - 首先初始化私钥 + final String certRootPath = certConfig.getCertRootPath(); + System.out.println("生产环境证书根路径: " + certRootPath); + + String privateKeyRelativePath = payment.getApiclientKey(); + System.out.println("数据库中的私钥相对路径: " + privateKeyRelativePath); + + // 修复路径拼接逻辑:数据库中存储的路径如果已经包含 /file,则直接拼接 + String privateKeyFullPath; + if (privateKeyRelativePath.startsWith("/file/")) { + // 路径已经包含 /file/ 前缀,直接拼接到根路径 + privateKeyFullPath = certRootPath + privateKeyRelativePath; + } else if (privateKeyRelativePath.startsWith("file/")) { + // 路径包含 file/ 前缀,添加根路径和斜杠 + privateKeyFullPath = certRootPath + "/" + privateKeyRelativePath; + } else { + // 路径不包含 file 前缀,添加完整的 /file/ 前缀 + privateKeyFullPath = certRootPath + "/file/" + privateKeyRelativePath; + } + + System.out.println("生产环境私钥完整路径: " + privateKeyFullPath); + privateKey = certificateLoader.loadCertificatePath(privateKeyFullPath); + System.out.println("✅ 生产环境私钥加载完成: " + privateKey); + + // 生产环境也优先检查公钥配置 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + + System.out.println("=== 生产环境检测到公钥配置,使用RSA公钥模式 ==="); + System.out.println("公钥文件路径: " + payment.getPubKey()); + System.out.println("公钥ID: " + payment.getPubKeyId()); + + try { + // 生产环境处理公钥路径 + String pubKeyRelativePath = payment.getPubKey(); + System.out.println("数据库中的公钥相对路径: " + pubKeyRelativePath); + + // 修复公钥路径拼接逻辑,与私钥路径处理保持一致 + String pubKeyFullPath; + if (pubKeyRelativePath.startsWith("/file/")) { + // 路径已经包含 /file/ 前缀,直接拼接到根路径 + pubKeyFullPath = certRootPath + pubKeyRelativePath; + } else if (pubKeyRelativePath.startsWith("file/")) { + // 路径包含 file/ 前缀,添加根路径和斜杠 + pubKeyFullPath = certRootPath + "/" + pubKeyRelativePath; + } else { + // 路径不包含 file 前缀,添加完整的 /file/ 前缀 + pubKeyFullPath = certRootPath + "/file/" + pubKeyRelativePath; + } + + System.out.println("生产环境公钥完整路径: " + pubKeyFullPath); + pubKeyFile = certificateLoader.loadCertificatePath(pubKeyFullPath); + System.out.println("✅ 生产环境公钥文件加载成功: " + pubKeyFile); + + config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .publicKeyFromPath(pubKeyFile) + .publicKeyId(payment.getPubKeyId()) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + System.out.println("✅ 生产环境RSA公钥配置成功"); + } catch (Exception pubKeyException) { + System.err.println("❌ 生产环境RSA公钥配置失败: " + pubKeyException.getMessage()); + pubKeyException.printStackTrace(); + throw new RuntimeException("生产环境RSA公钥配置失败: " + pubKeyException.getMessage(), pubKeyException); + } + } else { + // 没有公钥配置,使用自动证书配置 + System.out.println("=== 生产环境使用自动证书配置 ==="); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("序列号: " + payment.getMerchantSerialNumber()); + System.out.println("API密钥: 已配置(长度:" + payment.getApiKey().length() + ")"); + + try { + // 优先使用自动证书配置,避免证书过期和序列号不匹配问题 + config = wechatCertAutoConfig.createAutoConfig( + payment.getMchId(), + privateKey, + payment.getMerchantSerialNumber(), + payment.getApiKey() + ); + System.out.println("✅ 生产环境自动证书配置成功"); + } catch (Exception autoConfigException) { + System.err.println("⚠️ 自动证书配置失败,回退到手动证书配置"); + System.err.println("自动配置错误: " + autoConfigException.getMessage()); + System.err.println("错误类型: " + autoConfigException.getClass().getName()); + autoConfigException.printStackTrace(); + + // 检查是否是证书相关的错误 + if (autoConfigException.getMessage() != null && ( + autoConfigException.getMessage().contains("certificate") || + autoConfigException.getMessage().contains("X509Certificate") || + autoConfigException.getMessage().contains("getSerialNumber") || + autoConfigException.getMessage().contains("404") || + autoConfigException.getMessage().contains("API安全"))) { + + System.err.println("🔍 生产环境证书问题诊断:"); + System.err.println("1. 检查商户平台是否已开启API安全功能"); + System.err.println("2. 检查是否已申请使用微信支付公钥"); + System.err.println("3. 检查网络连接是否正常"); + System.err.println("4. 检查商户证书序列号是否正确"); + } + + try { + // 回退到手动证书配置 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty()) { + System.out.println("使用RSA公钥配置作为回退方案"); + config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .publicKeyFromPath(pubKey) + .publicKeyId(payment.getPubKeyId()) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + System.out.println("✅ 生产环境RSA公钥配置成功"); + } else if (apiclientCert != null && !apiclientCert.isEmpty()) { + System.out.println("使用RSA证书配置作为回退方案"); + config = new RSAConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .wechatPayCertificatesFromPath(apiclientCert) + .build(); + System.out.println("✅ 生产环境RSA证书配置成功"); + } else { + throw new RuntimeException("生产环境缺少必要的证书文件,无法创建手动证书配置", autoConfigException); + } + } catch (Exception fallbackException) { + System.err.println("❌ 生产环境手动证书配置也失败: " + fallbackException.getMessage()); + throw new RuntimeException("生产环境微信支付配置失败,请检查商户平台API安全设置和证书配置", autoConfigException); + } + } + } + } + + // 构建service + return new JsapiServiceExtension.Builder().config(config).build(); + } catch (Exception e) { + System.err.println("=== 构建微信支付服务失败 ==="); + System.err.println("错误信息: " + e.getMessage()); + System.err.println("错误类型: " + e.getClass().getName()); + e.printStackTrace(); + throw new RuntimeException("构建微信支付服务失败:" + e.getMessage(), e); + } + } + + @Override + public BigDecimal total() { + try { + // 使用数据库聚合查询统计订单总金额,性能更高 + BigDecimal total = baseMapper.selectTotalAmount(); + + if (total == null) { + total = BigDecimal.ZERO; + } + + log.info("统计订单总金额完成,总金额:{}", total); + return total; + + } catch (Exception e) { + log.error("统计订单总金额失败", e); + return BigDecimal.ZERO; + } + } + + /** + * 获取Native支付的微信支付配置 + */ + private Config getWxPayConfig(ShopOrder order) throws Exception { + try { + final Payment payment = getPayment(order); + String privateKey; + String apiclientCert = null; + String pubKey = null; + + // 初始化证书路径 + if (active.equals("dev")) { + // 开发环境 - 构建包含租户号的证书路径 + String tenantCertPath = "dev/wechat/" + order.getTenantId(); + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + + System.out.println("开发环境证书路径 - 租户ID: " + order.getTenantId()); + System.out.println("开发环境证书路径 - 私钥: " + privateKeyPath); + + privateKey = certificateLoader.loadCertificatePath(privateKeyPath); + System.out.println("私钥完整路径: " + privateKey); + + // 尝试加载公钥(如果配置了) + if (StrUtil.isNotBlank(payment.getPubKey()) && StrUtil.isNotBlank(payment.getPubKeyId())) { + try { + String pubKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getWechatpayCertFile(); + pubKey = certificateLoader.loadCertificatePath(pubKeyPath); + System.out.println("✅ 开发环境公钥加载成功"); + } catch (Exception e) { + System.out.println("⚠️ 开发环境公钥加载失败,将使用自动证书配置: " + e.getMessage()); + } + } + } else { + // 生产环境 - 从数据库配置的路径加载 + final String certRootPath = certConfig.getCertRootPath(); + + System.out.println("生产环境证书路径 - 租户ID: " + order.getTenantId()); + System.out.println("生产环境证书根路径: " + certRootPath); + System.out.println("私钥文件名: " + payment.getApiclientKey()); + + String privateKeyRelativePath = payment.getApiclientKey(); + + // 修复路径拼接逻辑:数据库中存储的路径如果已经包含 /file,则直接拼接 + String privateKeyFullPath; + if (privateKeyRelativePath.startsWith("/file/")) { + // 路径已经包含 /file/ 前缀,直接拼接到根路径 + privateKeyFullPath = certRootPath + privateKeyRelativePath; + } else if (privateKeyRelativePath.startsWith("file/")) { + // 路径包含 file/ 前缀,添加根路径和斜杠 + privateKeyFullPath = certRootPath + "/" + privateKeyRelativePath; + } else { + // 路径不包含 file 前缀,添加完整的 /file/ 前缀 + privateKeyFullPath = certRootPath + "/file/" + privateKeyRelativePath; + } + + System.out.println("私钥完整路径: " + privateKeyFullPath); + privateKey = certificateLoader.loadCertificatePath(privateKeyFullPath); + System.out.println("✅ 生产环境私钥加载完成: " + privateKey); + + // 尝试加载公钥(如果配置了) + if (StrUtil.isNotBlank(payment.getPubKey()) && StrUtil.isNotBlank(payment.getPubKeyId())) { + try { + String pubKeyRelativePath = payment.getPubKey(); + System.out.println("数据库中的公钥相对路径: " + pubKeyRelativePath); + + // 修复公钥路径拼接逻辑,与私钥路径处理保持一致 + String pubKeyFullPath; + if (pubKeyRelativePath.startsWith("/file/")) { + // 路径已经包含 /file/ 前缀,直接拼接到根路径 + pubKeyFullPath = certRootPath + pubKeyRelativePath; + } else if (pubKeyRelativePath.startsWith("file/")) { + // 路径包含 file/ 前缀,添加根路径和斜杠 + pubKeyFullPath = certRootPath + "/" + pubKeyRelativePath; + } else { + // 路径不包含 file 前缀,添加完整的 /file/ 前缀 + pubKeyFullPath = certRootPath + "/file/" + pubKeyRelativePath; + } + + System.out.println("生产环境公钥完整路径: " + pubKeyFullPath); + pubKey = certificateLoader.loadCertificatePath(pubKeyFullPath); + System.out.println("✅ 生产环境公钥加载成功"); + } catch (Exception e) { + System.out.println("⚠️ 生产环境公钥加载失败,将使用自动证书配置: " + e.getMessage()); + } + } + } + + // 根据可用的证书类型选择配置方式 + Config config; + if (pubKey != null && StrUtil.isNotBlank(payment.getPubKeyId())) { + // 使用RSA公钥配置(推荐方式) + System.out.println("🔧 使用RSA公钥配置"); + config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .publicKeyFromPath(pubKey) + .publicKeyId(payment.getPubKeyId()) + .apiV3Key(payment.getApiKey()) + .build(); + } else { + // 使用自动证书配置 + System.out.println("🔧 使用RSA自动证书配置"); + config = new RSAAutoCertificateConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + } + + System.out.println("✅ 微信支付配置构建成功"); + return config; + + } catch (Exception e) { + System.err.println("❌ 构建微信支付服务失败: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("构建微信支付服务失败:" + e.getMessage(), e); + } + } + + @Override + public ShopOrder getByOrderNo(String orderNo, Integer tenantId) { + return this.lambdaQuery() + .eq(ShopOrder::getOrderNo, orderNo) + .eq(ShopOrder::getTenantId, tenantId) + .one(); + } + + @Override + public boolean syncPaymentStatus(String orderNo, Integer paymentStatus, String transactionId, String payTime, Integer tenantId) { + try { + // 查询订单 + ShopOrder order = getByOrderNo(orderNo, tenantId); + if (order == null) { + log.warn("同步支付状态失败:订单不存在, orderNo={}, tenantId={}", orderNo, tenantId); + return false; + } + + // 如果订单已经是支付成功状态,不需要更新 + if (order.getPayStatus() && paymentStatus == 1) { + log.info("订单已经是支付成功状态,无需更新: orderNo={}", orderNo); + return true; + } + + // 更新订单状态 + boolean updated = this.lambdaUpdate() + .eq(ShopOrder::getOrderNo, orderNo) + .eq(ShopOrder::getTenantId, tenantId) + .set(ShopOrder::getPayStatus, paymentStatus == 1) + .set(transactionId != null, ShopOrder::getTransactionId, transactionId) + .set(payTime != null, ShopOrder::getPayTime, payTime) + .set(ShopOrder::getUpdateTime, LocalDateTime.now()) + .update(); + + if (updated) { + log.info("订单支付状态同步成功: orderNo={}, paymentStatus={}, transactionId={}", + orderNo, paymentStatus, transactionId); + } else { + log.warn("订单支付状态同步失败: orderNo={}, paymentStatus={}", orderNo, paymentStatus); + } + + return updated; + + } catch (Exception e) { + log.error("同步订单支付状态异常: orderNo={}, error={}", orderNo, e.getMessage(), e); + return false; + } + } + + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderUpdate10550ServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderUpdate10550ServiceImpl.java new file mode 100644 index 0000000..0e42d02 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderUpdate10550ServiceImpl.java @@ -0,0 +1,298 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.system.entity.DictData; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.entity.UserReferee; +import com.gxwebsoft.common.system.param.DictDataParam; +import com.gxwebsoft.common.system.service.DictDataService; +import com.gxwebsoft.common.system.service.UserRefereeService; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.shop.entity.*; +import com.gxwebsoft.shop.service.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 订单更新业务处理Service实现 + * 处理特定租户(10550)的订单相关业务逻辑 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Slf4j +@Service +public class ShopOrderUpdate10550ServiceImpl implements ShopOrderUpdate10550Service { + + @Resource + private UserService userService; + @Resource + private DictDataService dictDataService; + @Resource + private UserRefereeService userRefereeService; + @Resource + private ShopDealerOrderService shopDealerOrderService; + @Resource + private ShopDealerCapitalService shopDealerCapitalService; + @Resource + private ShopOrderGoodsService shopOrderGoodsService; + @Resource + private ShopGoodsService shopGoodsService; + + @Override + public void update(ShopOrder order) { + try { + log.info("开始处理订单更新业务 - 订单ID: {}, 用户ID: {}, 租户ID: {}", + order.getOrderId(), order.getUserId(), order.getTenantId()); + + // 1. 获取合伙人条件配置 + BigDecimal partnerCondition = getPartnerCondition(); + if (partnerCondition == null) { + log.warn("未找到合伙人条件配置,跳过用户等级更新"); + return; + } + + // 2. 更新用户消费金额和等级 + updateUserGradeAndExpendMoney(order, partnerCondition); + + // 3. 处理分销业务(如果需要) + // processDistributionBusiness(order); + + log.info("订单更新业务处理完成 - 订单ID: {}", order.getOrderId()); + } catch (Exception e) { + log.error("处理订单更新业务异常 - 订单ID: {}", order.getOrderId(), e); + } + } + + /** + * 获取合伙人条件配置 + * @return 合伙人条件金额 + */ + private BigDecimal getPartnerCondition() { + try { + // 查询字典ID为1460的配置 + DictDataParam param = new DictDataParam(); + param.setDictId(1460); + List dictDataList = dictDataService.listRel(param); + + if (dictDataList != null && !dictDataList.isEmpty()) { + String dictDataCode = dictDataList.get(0).getDictDataCode(); + BigDecimal partnerCondition = new BigDecimal(dictDataCode); + log.info("获取合伙人条件配置成功 - 金额: {}", partnerCondition); + return partnerCondition; + } + } catch (Exception e) { + log.error("获取合伙人条件配置异常", e); + } + return null; + } + + /** + * 更新用户等级和消费金额 + * @param order 订单信息 + * @param partnerCondition 合伙人条件金额 + */ + private void updateUserGradeAndExpendMoney(ShopOrder order, BigDecimal partnerCondition) { + try { + // 查询用户信息(忽略租户隔离) + User user = userService.getByIdIgnoreTenant(order.getUserId()); + if (user == null) { + log.warn("用户不存在 - 用户ID: {}", order.getUserId()); + return; + } + + // 累加消费金额 + BigDecimal currentExpendMoney = user.getExpendMoney() != null ? user.getExpendMoney() : BigDecimal.ZERO; + BigDecimal newExpendMoney = currentExpendMoney.add(order.getPayPrice()); + user.setExpendMoney(newExpendMoney); + + // 检查是否达到合伙人条件 + boolean shouldUpgrade = newExpendMoney.compareTo(partnerCondition) >= 0 && !Integer.valueOf(3).equals(user.getGradeId()); + if (shouldUpgrade) { + user.setGradeId(3); + log.info("用户等级升级为合伙人 - 用户ID: {}, 消费金额: {}, 条件金额: {}", + user.getUserId(), newExpendMoney, partnerCondition); + } + + // 更新用户信息(使用忽略租户隔离的更新方法) + userService.updateByUserId(user); + + log.info("用户信息更新成功 - 用户ID: {}, 消费金额: {} -> {}, 等级: {}", + user.getUserId(), currentExpendMoney, newExpendMoney, user.getGradeId()); + + } catch (Exception e) { + log.error("更新用户等级和消费金额异常 - 用户ID: {}", order.getUserId(), e); + } + } + + /** + * 处理分销业务(暂时注释,如需启用请取消注释) + * @param order 订单信息 + */ + @SuppressWarnings("unused") + private void processDistributionBusiness(ShopOrder order) { + try { + // 获取推荐人信息 + User parent = getParentUser(order.getUserId()); + if (parent == null) { + log.info("用户无推荐人,跳过分销业务处理 - 用户ID: {}", order.getUserId()); + return; + } + + // 计算佣金 + BigDecimal commission = calculateCommission(order); + if (commission.compareTo(BigDecimal.ZERO) <= 0) { + log.info("佣金为0,跳过分销业务处理 - 订单ID: {}", order.getOrderId()); + return; + } + + // 更新推荐人余额 + updateParentBalance(parent, commission); + + // 创建分销订单记录 + createDealerOrder(parent, order, commission); + + // 创建分销资金明细 + createDealerCapital(parent, order); + + log.info("分销业务处理完成 - 订单ID: {}, 推荐人ID: {}, 佣金: {}", + order.getOrderId(), parent.getUserId(), commission); + + } catch (Exception e) { + log.error("处理分销业务异常 - 订单ID: {}", order.getOrderId(), e); + } + } + + /** + * 获取推荐人信息 + * @param userId 用户ID + * @return 推荐人信息 + */ + private User getParentUser(Integer userId) { + try { + UserReferee userReferee = userRefereeService.getByUserId(userId); + if (userReferee != null && userReferee.getDealerId() != null) { + return userService.getByIdIgnoreTenant(userReferee.getDealerId()); + } + } catch (Exception e) { + log.error("获取推荐人信息异常 - 用户ID: {}", userId, e); + } + return null; + } + + /** + * 计算佣金 + * @param order 订单信息 + * @return 总佣金 + */ + private BigDecimal calculateCommission(ShopOrder order) { + try { + // 获取订单商品列表(忽略租户隔离) + List orderGoodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId()); + if (orderGoodsList.isEmpty()) { + return BigDecimal.ZERO; + } + + // 获取商品信息 + List goodsIds = orderGoodsList.stream() + .map(ShopOrderGoods::getGoodsId) + .toList(); + List goodsList = shopGoodsService.listByIds(goodsIds); + + // 计算总佣金 + BigDecimal totalCommission = BigDecimal.ZERO; + for (ShopOrderGoods orderGoods : orderGoodsList) { + ShopGoods goods = goodsList.stream() + .filter(g -> g.getGoodsId().equals(orderGoods.getGoodsId())) + .findFirst() + .orElse(null); + + if (goods != null && goods.getCommission() != null) { + BigDecimal goodsCommission = goods.getCommission() + .multiply(BigDecimal.valueOf(orderGoods.getTotalNum())); + totalCommission = totalCommission.add(goodsCommission); + } + } + + log.info("佣金计算完成 - 订单ID: {}, 总佣金: {}", order.getOrderId(), totalCommission); + return totalCommission; + + } catch (Exception e) { + log.error("计算佣金异常 - 订单ID: {}", order.getOrderId(), e); + return BigDecimal.ZERO; + } + } + + /** + * 更新推荐人余额 + * @param parent 推荐人信息 + * @param commission 佣金金额 + */ + private void updateParentBalance(User parent, BigDecimal commission) { + try { + BigDecimal currentBalance = parent.getBalance() != null ? parent.getBalance() : BigDecimal.ZERO; + BigDecimal newBalance = currentBalance.add(commission); + parent.setBalance(newBalance); + + userService.updateByUserId(parent); + + log.info("推荐人余额更新成功 - 用户ID: {}, 余额: {} -> {}", + parent.getUserId(), currentBalance, newBalance); + + } catch (Exception e) { + log.error("更新推荐人余额异常 - 用户ID: {}", parent.getUserId(), e); + } + } + + /** + * 创建分销订单记录 + * @param parent 推荐人信息 + * @param order 订单信息 + * @param commission 佣金金额 + */ + private void createDealerOrder(User parent, ShopOrder order, BigDecimal commission) { + try { + ShopDealerOrder dealerOrder = new ShopDealerOrder(); + dealerOrder.setUserId(parent.getUserId()); + dealerOrder.setOrderId(order.getOrderId()); + dealerOrder.setOrderPrice(order.getTotalPrice()); + dealerOrder.setFirstUserId(order.getUserId()); + dealerOrder.setFirstMoney(commission); + dealerOrder.setIsSettled(1); + dealerOrder.setSettleTime(LocalDateTime.now()); + + shopDealerOrderService.save(dealerOrder); + + log.info("分销订单记录创建成功 - 推荐人ID: {}, 订单ID: {}", parent.getUserId(), order.getOrderId()); + + } catch (Exception e) { + log.error("创建分销订单记录异常 - 推荐人ID: {}, 订单ID: {}", parent.getUserId(), order.getOrderId(), e); + } + } + + /** + * 创建分销资金明细 + * @param parent 推荐人信息 + * @param order 订单信息 + */ + private void createDealerCapital(User parent, ShopOrder order) { + try { + ShopDealerCapital dealerCapital = new ShopDealerCapital(); + dealerCapital.setUserId(parent.getUserId()); + dealerCapital.setOrderId(order.getOrderId()); + dealerCapital.setFlowType(10); // 分销收入 + + shopDealerCapitalService.save(dealerCapital); + + log.info("分销资金明细创建成功 - 推荐人ID: {}, 订单ID: {}", parent.getUserId(), order.getOrderId()); + + } catch (Exception e) { + log.error("创建分销资金明细异常 - 推荐人ID: {}, 订单ID: {}", parent.getUserId(), order.getOrderId(), e); + } + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopRechargeOrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopRechargeOrderServiceImpl.java new file mode 100644 index 0000000..ee0d9c3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopRechargeOrderServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopRechargeOrderMapper; +import com.gxwebsoft.shop.service.ShopRechargeOrderService; +import com.gxwebsoft.shop.entity.ShopRechargeOrder; +import com.gxwebsoft.shop.param.ShopRechargeOrderParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 会员充值订单表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:12 + */ +@Service +public class ShopRechargeOrderServiceImpl extends ServiceImpl implements ShopRechargeOrderService { + + @Override + public PageResult pageRel(ShopRechargeOrderParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopRechargeOrderParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopRechargeOrder getByIdRel(Integer orderId) { + ShopRechargeOrderParam param = new ShopRechargeOrderParam(); + param.setOrderId(orderId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecServiceImpl.java new file mode 100644 index 0000000..708f1c9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopSpecMapper; +import com.gxwebsoft.shop.service.ShopSpecService; +import com.gxwebsoft.shop.entity.ShopSpec; +import com.gxwebsoft.shop.param.ShopSpecParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 规格Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Service +public class ShopSpecServiceImpl extends ServiceImpl implements ShopSpecService { + + @Override + public PageResult pageRel(ShopSpecParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopSpecParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopSpec getByIdRel(Integer specId) { + ShopSpecParam param = new ShopSpecParam(); + param.setSpecId(specId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecValueServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecValueServiceImpl.java new file mode 100644 index 0000000..9e39e23 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopSpecValueServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopSpecValueMapper; +import com.gxwebsoft.shop.service.ShopSpecValueService; +import com.gxwebsoft.shop.entity.ShopSpecValue; +import com.gxwebsoft.shop.param.ShopSpecValueParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 规格值Service实现 + * + * @author 科技小王子 + * @since 2025-05-01 09:44:00 + */ +@Service +public class ShopSpecValueServiceImpl extends ServiceImpl implements ShopSpecValueService { + + @Override + public PageResult pageRel(ShopSpecValueParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopSpecValueParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopSpecValue getByIdRel(Integer specValueId) { + ShopSpecValueParam param = new ShopSpecValueParam(); + param.setSpecValueId(specValueId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopSplashServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopSplashServiceImpl.java new file mode 100644 index 0000000..b6d3f8d --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopSplashServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopSplashMapper; +import com.gxwebsoft.shop.service.ShopSplashService; +import com.gxwebsoft.shop.entity.ShopSplash; +import com.gxwebsoft.shop.param.ShopSplashParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 开屏广告Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Service +public class ShopSplashServiceImpl extends ServiceImpl implements ShopSplashService { + + @Override + public PageResult pageRel(ShopSplashParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopSplashParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopSplash getByIdRel(Integer id) { + ShopSplashParam param = new ShopSplashParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserAddressServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserAddressServiceImpl.java new file mode 100644 index 0000000..cf599c6 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserAddressServiceImpl.java @@ -0,0 +1,68 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopUserAddressMapper; +import com.gxwebsoft.shop.service.ShopUserAddressService; +import com.gxwebsoft.shop.entity.ShopUserAddress; +import com.gxwebsoft.shop.param.ShopUserAddressParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 收货地址Service实现 + * + * @author 科技小王子 + * @since 2025-07-22 23:06:40 + */ +@Service +public class ShopUserAddressServiceImpl extends ServiceImpl implements ShopUserAddressService { + + @Override + public PageResult pageRel(ShopUserAddressParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopUserAddressParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopUserAddress getByIdRel(Integer id) { + ShopUserAddressParam param = new ShopUserAddressParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public ShopUserAddress getDefaultAddress(Integer userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ShopUserAddress::getUserId, userId) + .eq(ShopUserAddress::getIsDefault, true) + .orderByDesc(ShopUserAddress::getCreateTime) + .last("LIMIT 1"); + return getOne(wrapper); + } + + @Override + public List getUserAddresses(Integer userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ShopUserAddress::getUserId, userId) + .orderByDesc(ShopUserAddress::getIsDefault) + .orderByAsc(ShopUserAddress::getSortNumber) + .orderByDesc(ShopUserAddress::getCreateTime); + return list(wrapper); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserBalanceLogServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserBalanceLogServiceImpl.java new file mode 100644 index 0000000..483b7b3 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserBalanceLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopUserBalanceLogMapper; +import com.gxwebsoft.shop.service.ShopUserBalanceLogService; +import com.gxwebsoft.shop.entity.ShopUserBalanceLog; +import com.gxwebsoft.shop.param.ShopUserBalanceLogParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户余额变动明细表Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Service +public class ShopUserBalanceLogServiceImpl extends ServiceImpl implements ShopUserBalanceLogService { + + @Override + public PageResult pageRel(ShopUserBalanceLogParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopUserBalanceLogParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopUserBalanceLog getByIdRel(Integer logId) { + ShopUserBalanceLogParam param = new ShopUserBalanceLogParam(); + param.setLogId(logId); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCollectionServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCollectionServiceImpl.java new file mode 100644 index 0000000..5d6dd6b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCollectionServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopUserCollectionMapper; +import com.gxwebsoft.shop.service.ShopUserCollectionService; +import com.gxwebsoft.shop.entity.ShopUserCollection; +import com.gxwebsoft.shop.param.ShopUserCollectionParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 我的收藏Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Service +public class ShopUserCollectionServiceImpl extends ServiceImpl implements ShopUserCollectionService { + + @Override + public PageResult pageRel(ShopUserCollectionParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopUserCollectionParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopUserCollection getByIdRel(Integer id) { + ShopUserCollectionParam param = new ShopUserCollectionParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCouponServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCouponServiceImpl.java new file mode 100644 index 0000000..fb3d5ed --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserCouponServiceImpl.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopUserCouponMapper; +import com.gxwebsoft.shop.service.ShopUserCouponService; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.param.ShopUserCouponParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户优惠券Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopUserCouponServiceImpl extends ServiceImpl implements ShopUserCouponService { + + @Override + public PageResult pageRel(ShopUserCouponParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopUserCouponParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopUserCoupon getByIdRel(Integer id) { + ShopUserCouponParam param = new ShopUserCouponParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + + @Override + public List userList(Integer userId) { + return list( + new LambdaQueryWrapper() + .eq(ShopUserCoupon::getUserId, userId) + ); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserRefereeServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserRefereeServiceImpl.java new file mode 100644 index 0000000..910561e --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUserRefereeServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopUserRefereeMapper; +import com.gxwebsoft.shop.service.ShopUserRefereeService; +import com.gxwebsoft.shop.entity.ShopUserReferee; +import com.gxwebsoft.shop.param.ShopUserRefereeParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户推荐关系表Service实现 + * + * @author 科技小王子 + * @since 2025-08-11 23:51:41 + */ +@Service +public class ShopUserRefereeServiceImpl extends ServiceImpl implements ShopUserRefereeService { + + @Override + public PageResult pageRel(ShopUserRefereeParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopUserRefereeParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopUserReferee getByIdRel(Integer id) { + ShopUserRefereeParam param = new ShopUserRefereeParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopUsersServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUsersServiceImpl.java new file mode 100644 index 0000000..73092ee --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopUsersServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopUsersMapper; +import com.gxwebsoft.shop.service.ShopUsersService; +import com.gxwebsoft.shop.entity.ShopUsers; +import com.gxwebsoft.shop.param.ShopUsersParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Service +public class ShopUsersServiceImpl extends ServiceImpl implements ShopUsersService { + + @Override + public PageResult pageRel(ShopUsersParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopUsersParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopUsers getByIdRel(Integer id) { + ShopUsersParam param = new ShopUsersParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopWebsiteServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopWebsiteServiceImpl.java new file mode 100644 index 0000000..9eda00b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopWebsiteServiceImpl.java @@ -0,0 +1,83 @@ +package com.gxwebsoft.shop.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.gxwebsoft.cms.service.CmsWebsiteService; +import com.gxwebsoft.common.core.utils.JSONUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.shop.service.ShopWebsiteService; +import com.gxwebsoft.shop.vo.ShopVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +/** + * 商城网站服务实现类 + * + * @author 科技小王子 + * @since 2025-08-13 + */ +@Slf4j +@Service +public class ShopWebsiteServiceImpl implements ShopWebsiteService { + + @Autowired + private CmsWebsiteService cmsWebsiteService; + + @Autowired + private RedisUtil redisUtil; + + /** + * 商城信息缓存键前缀 + */ + private static final String SHOP_INFO_KEY_PREFIX = "ShopInfo:"; + + @Override + public ShopVo getShopInfo(Integer tenantId) { + // 参数验证 + if (ObjectUtil.isEmpty(tenantId)) { + throw new IllegalArgumentException("租户ID不能为空"); + } + + // 商城专用缓存键 + String cacheKey = SHOP_INFO_KEY_PREFIX + tenantId; + String shopInfo = redisUtil.get(cacheKey); + if (StrUtil.isNotBlank(shopInfo)) { + log.info("从缓存获取商城信息,租户ID: {}", tenantId); + try { + return JSONUtil.parseObject(shopInfo, ShopVo.class); + } catch (Exception e) { + log.warn("商城缓存解析失败,从数据库重新获取: {}", e.getMessage()); + } + } + + // 直接调用 CMS 服务获取站点信息,然后使用商城专用缓存 + ShopVo shopVO = cmsWebsiteService.getSiteInfo(tenantId); + if (shopVO == null) { + throw new RuntimeException("请先创建商城"); + } + + // 缓存结果(商城信息缓存时间设置为12小时) + try { + redisUtil.set(cacheKey, shopVO, 12L, TimeUnit.HOURS); + } catch (Exception e) { + log.warn("缓存商城信息失败: {}", e.getMessage()); + } + + log.info("获取商城信息成功,租户ID: {}", tenantId); + return shopVO; + } + + @Override + public void clearShopInfoCache(Integer tenantId) { + if (tenantId != null) { + String cacheKey = SHOP_INFO_KEY_PREFIX + tenantId; + redisUtil.delete(cacheKey); + log.info("清除商城信息缓存成功,租户ID: {}", tenantId); + } + } + + +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopWechatDepositServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopWechatDepositServiceImpl.java new file mode 100644 index 0000000..9bf85d2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopWechatDepositServiceImpl.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.shop.mapper.ShopWechatDepositMapper; +import com.gxwebsoft.shop.service.ShopWechatDepositService; +import com.gxwebsoft.shop.entity.ShopWechatDeposit; +import com.gxwebsoft.shop.param.ShopWechatDepositParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 押金Service实现 + * + * @author 科技小王子 + * @since 2025-01-11 10:45:13 + */ +@Service +public class ShopWechatDepositServiceImpl extends ServiceImpl implements ShopWechatDepositService { + + @Override + public PageResult pageRel(ShopWechatDepositParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(ShopWechatDepositParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public ShopWechatDeposit getByIdRel(Integer id) { + ShopWechatDepositParam param = new ShopWechatDepositParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + +} diff --git a/src/main/java/com/gxwebsoft/shop/task/CouponExpireTask.java b/src/main/java/com/gxwebsoft/shop/task/CouponExpireTask.java new file mode 100644 index 0000000..9781bd9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/task/CouponExpireTask.java @@ -0,0 +1,86 @@ +package com.gxwebsoft.shop.task; + +import com.gxwebsoft.shop.service.CouponStatusService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 优惠券过期处理定时任务 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Component +public class CouponExpireTask { + + @Autowired + private CouponStatusService couponStatusService; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + /** + * 每天凌晨2点执行过期优惠券处理 + * 生产环境:每天凌晨2点执行 + * 开发环境:每10分钟执行一次(用于测试) + */ + @Scheduled(cron = "${coupon.expire.cron:0 0 2 * * ?}") + public void processExpiredCoupons() { + log.info("开始执行过期优惠券处理任务..."); + + try { + long startTime = System.currentTimeMillis(); + + // 批量更新过期优惠券状态 + int updatedCount = couponStatusService.updateExpiredCoupons(); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + log.info("过期优惠券处理任务完成,更新数量: {},耗时: {}ms", updatedCount, duration); + + // 如果是开发环境,输出更详细的日志 + if ("dev".equals(activeProfile)) { + log.debug("开发环境 - 过期优惠券处理详情: 更新{}张优惠券", updatedCount); + } + + } catch (Exception e) { + log.error("过期优惠券处理任务执行失败", e); + } + } + + /** + * 每小时执行一次优惠券状态检查(可选) + * 用于及时发现和处理刚过期的优惠券 + */ + @Scheduled(cron = "0 0 * * * ?") + public void hourlyExpiredCouponsCheck() { + // 只在生产环境执行 + if (!"prod".equals(activeProfile)) { + return; + } + + log.debug("执行每小时优惠券状态检查..."); + + try { + int updatedCount = couponStatusService.updateExpiredCoupons(); + if (updatedCount > 0) { + log.info("每小时检查发现并更新了 {} 张过期优惠券", updatedCount); + } + } catch (Exception e) { + log.error("每小时优惠券状态检查失败", e); + } + } + + /** + * 手动触发过期优惠券处理(用于测试) + */ + public void manualProcessExpiredCoupons() { + log.info("手动触发过期优惠券处理任务..."); + processExpiredCoupons(); + } +} diff --git a/src/main/java/com/gxwebsoft/shop/task/OrderAutoCancelTask.java b/src/main/java/com/gxwebsoft/shop/task/OrderAutoCancelTask.java new file mode 100644 index 0000000..d1b71ff --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/task/OrderAutoCancelTask.java @@ -0,0 +1,196 @@ +package com.gxwebsoft.shop.task; + +import com.gxwebsoft.common.core.annotation.IgnoreTenant; +import com.gxwebsoft.shop.config.OrderConfigProperties; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.service.OrderCancelService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 订单自动取消定时任务 + * + * @author WebSoft + * @since 2025-01-26 + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "shop.order.auto-cancel", name = "enabled", havingValue = "true", matchIfMissing = true) +public class OrderAutoCancelTask { + + @Autowired + private OrderCancelService orderCancelService; + + @Autowired + private OrderConfigProperties orderConfig; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + /** + * 自动取消超时订单 + * 生产环境:每5分钟执行一次 + * 开发环境:每1分钟执行一次(便于测试) + */ + @Scheduled(cron = "${shop.order.auto-cancel.cron:0 */5 * * * ?}") + @IgnoreTenant("定时任务需要处理所有租户的超时订单") + public void cancelExpiredOrders() { + if (!orderConfig.getAutoCancel().isEnabled()) { + log.debug("订单自动取消功能已禁用"); + return; + } + + log.info("开始执行订单自动取消任务..."); + + try { + long startTime = System.currentTimeMillis(); + int totalCancelledCount = 0; + + // 处理默认配置的订单 + int defaultCancelledCount = processDefaultTimeoutOrders(); + totalCancelledCount += defaultCancelledCount; + + // 处理租户特殊配置的订单 + int tenantCancelledCount = processTenantSpecificOrders(); + totalCancelledCount += tenantCancelledCount; + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + log.info("订单自动取消任务完成,总取消数量: {},默认配置: {},租户配置: {},耗时: {}ms", + totalCancelledCount, defaultCancelledCount, tenantCancelledCount, duration); + + // 开发环境输出更详细的日志 + if ("dev".equals(activeProfile)) { + log.debug("开发环境 - 订单自动取消详情: 总共取消{}个订单", totalCancelledCount); + } + + } catch (Exception e) { + log.error("订单自动取消任务执行失败", e); + } + } + + /** + * 处理使用默认超时配置的订单 + */ + private int processDefaultTimeoutOrders() { + try { + Integer defaultTimeout = orderConfig.getAutoCancel().getDefaultTimeoutMinutes(); + Integer batchSize = orderConfig.getAutoCancel().getBatchSize(); + + log.debug("处理默认超时订单,超时时间: {}分钟,批量大小: {}", defaultTimeout, batchSize); + + List expiredOrders = orderCancelService.findExpiredUnpaidOrders(defaultTimeout, batchSize); + + if (expiredOrders.isEmpty()) { + log.debug("没有找到使用默认配置的超时订单"); + return 0; + } + + // 过滤掉有特殊租户配置的订单 + List ordersToCancel = filterOrdersWithoutTenantConfig(expiredOrders); + + if (ordersToCancel.isEmpty()) { + log.debug("过滤后没有需要使用默认配置取消的订单"); + return 0; + } + + int cancelledCount = orderCancelService.batchCancelOrders(ordersToCancel); + log.info("默认配置取消订单完成,找到: {}个,过滤后: {}个,成功取消: {}个", + expiredOrders.size(), ordersToCancel.size(), cancelledCount); + + return cancelledCount; + + } catch (Exception e) { + log.error("处理默认超时订单失败", e); + return 0; + } + } + + /** + * 处理租户特殊配置的订单 + */ + private int processTenantSpecificOrders() { + try { + List tenantConfigs = orderConfig.getAutoCancel().getTenantConfigs(); + if (tenantConfigs == null || tenantConfigs.isEmpty()) { + log.debug("没有配置租户特殊超时规则"); + return 0; + } + + int totalCancelledCount = 0; + Integer batchSize = orderConfig.getAutoCancel().getBatchSize(); + + for (OrderConfigProperties.TenantCancelConfig tenantConfig : tenantConfigs) { + if (!tenantConfig.isEnabled()) { + log.debug("租户{}的自动取消功能已禁用", tenantConfig.getTenantId()); + continue; + } + + log.debug("处理租户{}的超时订单,超时时间: {}分钟", + tenantConfig.getTenantId(), tenantConfig.getTimeoutMinutes()); + + List tenantExpiredOrders = orderCancelService.findExpiredUnpaidOrdersByTenant( + tenantConfig.getTenantId(), tenantConfig.getTimeoutMinutes(), batchSize); + + if (!tenantExpiredOrders.isEmpty()) { + int cancelledCount = orderCancelService.batchCancelOrders(tenantExpiredOrders); + totalCancelledCount += cancelledCount; + + log.info("租户{}取消订单完成,找到: {}个,成功取消: {}个", + tenantConfig.getTenantId(), tenantExpiredOrders.size(), cancelledCount); + } + } + + return totalCancelledCount; + + } catch (Exception e) { + log.error("处理租户特殊配置订单失败", e); + return 0; + } + } + + /** + * 过滤掉有租户特殊配置的订单 + */ + private List filterOrdersWithoutTenantConfig(List orders) { + List tenantConfigs = orderConfig.getAutoCancel().getTenantConfigs(); + if (tenantConfigs == null || tenantConfigs.isEmpty()) { + return orders; + } + + return orders.stream() + .filter(order -> { + // 检查该订单的租户是否有特殊配置 + return tenantConfigs.stream() + .noneMatch(config -> config.isEnabled() && config.getTenantId().equals(order.getTenantId())); + }) + .collect(java.util.stream.Collectors.toList()); + } + + /** + * 手动触发订单自动取消任务(用于测试) + */ + @IgnoreTenant("手动触发的定时任务需要处理所有租户的超时订单") + public void manualCancelExpiredOrders() { + log.info("手动触发订单自动取消任务..."); + cancelExpiredOrders(); + } + + /** + * 获取任务状态信息 + */ + public String getTaskStatus() { + return String.format("订单自动取消任务状态 - 启用: %s, 默认超时: %d分钟, 检查间隔: %d分钟, 批量大小: %d", + orderConfig.getAutoCancel().isEnabled(), + orderConfig.getAutoCancel().getDefaultTimeoutMinutes(), + orderConfig.getAutoCancel().getCheckIntervalMinutes(), + orderConfig.getAutoCancel().getBatchSize()); + } +} diff --git a/src/main/java/com/gxwebsoft/shop/vo/MenuVo.java b/src/main/java/com/gxwebsoft/shop/vo/MenuVo.java new file mode 100644 index 0000000..5e13bde --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/vo/MenuVo.java @@ -0,0 +1,58 @@ +package com.gxwebsoft.shop.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 导航信息视图对象 + * 专门用于前端展示,只包含前端需要的字段 + * + * @author WebSoft + * @since 2025-01-12 + */ +@Data +@Schema(description = "导航信息视图对象") +public class MenuVo implements Serializable { + + @Schema(description = "导航ID") + private Integer navigationId; + + @Schema(description = "导航名称") + private String title; + + @Schema(description = "导航类型") + private String model; + + @Schema(description = "唯一标识") + private String code; + + @Schema(description = "路由地址") + private String path; + + @Schema(description = "导航图标") + private String icon; + + @Schema(description = "导航颜色") + private String color; + + @Schema(description = "父级ID") + private Integer parentId; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "是否隐藏 0显示 1隐藏") + private Integer hide; + + @Schema(description = "位置 0顶部 1底部") + private Integer top; + + @Schema(description = "打开方式 0当前窗口 1新窗口") + private Integer target; + + @Schema(description = "子导航") + private List children; +} diff --git a/src/main/java/com/gxwebsoft/shop/vo/ShopVo.java b/src/main/java/com/gxwebsoft/shop/vo/ShopVo.java new file mode 100644 index 0000000..b95ca2b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/vo/ShopVo.java @@ -0,0 +1,92 @@ +package com.gxwebsoft.shop.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; + +/** + * 应用信息 + * 专门用于前端展示,只包含前端需要的字段 + * + * @author WebSoft + * @since 2025-01-12 + */ +@Data +@Schema(description = "应用信息视图对象") +public class ShopVo implements Serializable { + + @Schema(description = "应用ID") + private Integer appId; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "应用介绍") + private String description; + + @Schema(description = "网站关键词") + private String keywords; + + @Schema(description = "应用编号") + private String appCode; + + @Schema(description = "小程序二维码") + private String mpQrCode; + + @Schema(description = "标题") + private String title; + + @Schema(description = "LOGO") + private String logo; + + @Schema(description = "图标") + private String icon; + + @Schema(description = "域名") + private String domain; + + @Schema(description = "运行状态 0未开通 1正常 2维护中 3违规关停") + private Integer running; + + @Schema(description = "应用版本 10免费版 20授权版 30永久授权") + private Integer version; + + @Schema(description = "服务到期时间") + private String expirationTime; + + @Schema(description = "创建时间") + private String createTime; + + @Schema(description = "是否到期 -1已过期 1未过期") + private Integer expired; + + @Schema(description = "剩余天数") + private Long expiredDays; + + @Schema(description = "即将过期 0否 1是") + private Integer soon; + + @Schema(description = "状态图标") + private String statusIcon; + + @Schema(description = "状态文本") + private String statusText; + + @Schema(description = "网站配置") + private Object config; + + @Schema(description = "服务器时间信息") + private HashMap serverTime; + + @Schema(description = "顶部导航") + private List topNavs; + + @Schema(description = "底部导航") + private List bottomNavs; + + @Schema(description = "网站设置") + private Object setting; +} diff --git a/src/main/java/lib/commons-codec-1.9.jar b/src/main/java/lib/commons-codec-1.9.jar new file mode 100644 index 0000000000000000000000000000000000000000..ef35f1c50d7c41278bc31f4b9fcfc8fbd708d55d GIT binary patch literal 263965 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 literal 0 HcmV?d00001 diff --git a/src/main/java/lib/json-20200518.jar b/src/main/java/lib/json-20200518.jar new file mode 100644 index 0000000000000000000000000000000000000000..0b85b0ee713f85785239da640579469c15b16277 GIT binary patch literal 65966 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 z z8P3{&L~$)de%lS_)m;_NGQ(NaB>;QUvX|~>u70yXuf)Z$_odlu_?(g%P7=m&14oJ0 z@FE&OEzON1m#}3S4{Lh7`&Idr`@LlK{g3yNG@EbMeumX}g?$TopidOuqu9BoihAP6 zy85O95yt&@JVU)?gv_A+w@&r%f8U>f{SVb@Wx0Pk_@^B1-yN(%M*Dvg$9)cbE)4n` z3J=lM_%E5z=kR}uaQp_pc(IQ27ySQ2lH<9j=VA)KeHr5a<>?>N3eWM+3Fv?0ZHfNE z|DBHhIs7?k_iwm6>0j`_QFuQ`KPN~2jg}+-2l`*+n$OYC8D@W@C8+*(>3{LfK1V;N z^8Ag)qx}c^f7bn+H1Rk3jQ$@j{}@* z$G<#SI{s~L{v7|$X~%E$ix<`|FJAoDB;+~#pQFFO!%5x$2LEd;_#FMuUik0mMbE#X c&HmFTE6c$^NZt<8 literal 0 HcmV?d00001 diff --git a/src/main/resources/templates/audit_report_template.docx b/src/main/resources/templates/audit_report_template.docx new file mode 100644 index 0000000000000000000000000000000000000000..77259b899befd2e453d31ce30137a99e05733088 GIT binary patch literal 909510 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

AXt(V+#_mevxzV1`o- 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
submitSucceed(values)} + onFinishFailed={(errors) => submitFailed(errors)} + footer={ +
+ +
+ } + > + +<% for(field in table.fields){ %> +<% if(field.propertyName != 'id' && field.propertyName != 'createTime' && field.propertyName != 'updateTime'){ %> + +<% if(field.propertyType == 'String' && field.comment?? && (field.comment?contains('描述') || field.comment?contains('备注') || field.comment?contains('内容'))){ %> +

AbgHq}4XhcY*g`G{<>ReaA zX7D=u0T2a%F87&FKgHv8#YlTqCE2egNq(8>XZiEBSX2Di$^?*`CgMc6ktcpFts+KA zGWC`MwwZe#Z;oKEW3z5(j7jD_&EKHbLQr(1#0L2hg@?tfebswfvbYdl?N=11ECvkx z-#gz66ULtIJX&N~1-LO^Iu)=qyz)OMhLf;!%Pm|=Vet+1v9;;bkgyR<_DjzzAHNKU zoxBJSUY!ScAk6y!y>WF}@?&G|4BJcG(!Tl^iBIk@*|Ro!R-`=DSFSmg-gsTS|1LSw zl8eo`WLE9PEEaZv!{Br5GGR77jjZ-+?8HwiNsX|~SolsN98yzJW|XN}B;Rb)j`Ymj zG{R{qux_aV#)S5XC$!HlGG);q!?EFPnfv;<(mN=~!W!FidNjr-o(@u2^xb&NbLi+6 z-uSohte$L;NL&$)q!h?Usni^AH<1FKDw_A=>;Ht966KcAF5 zDs#M=GYT6Cj4@3@>q|n$5T3taX~L}!(~n-PT->8MjUR%E=xow0SvKBKq#`={aL$26 zeo>deqH9tOW^`(EL+=Bq#&4)3|=u(N|(v!RMybcqK_Yji2~mS`8^Y= zI{SjG4UDh87=9$skUb>OdRE=X^Rp3Fm zP}c#JZd=Wy)OGKWe}W&9SDo6=5*4Pu8?n6!}i?)A4v5FJj$L1zttp6h>@>`5D+-t!L1KR zNyF=sP}jRht`Cm6%a2%_7050gjE%&pB|aLkH0$H6z({U+*8|SZoVg)fGK(~oB5ts* zOf@B~4G|y35;G&!Ad3y?V5J-H*OHzD%%7HkE?hcJC3$(oN6%x|w=@S5#$qq7V}7&r z@Qyw!x<@!Dq;is}nW804@c5R1k8?V$ASSTb9+vQNB<2A3!=1x-JFkI^@kHc2FIxDt zGn%wC_=TK;rv88qRm<%;6Y2bkpU!mpH+7;67ZdP*^OuZEp7LVL`h1Vbs7FVX+z3`{ zYi;w_>4%-oUzo`#-oY08DY$R0tM5U%Y5aw&O6?H>0=t0KiVa0anEX1Zek^8_BM?pR|e(E{GXvFXN(1<)vo ze*xH($_vsjczNAnOvjl8n9Xyi^PbM9L7L0qDQM1TR*%gH@F@J~$wQL`{RuGzWx2@2 zoShfgV?}L$c*6MMSAPB;kL(K<5vS`or9DQE=6yG8s^Pdj4A{HOjxX&|%za|=;3?<* zBT2Ij394-pKy3L4APrS|+nRLsilfYPcPw1IgE^ky1+?M6WI1p@T>_ASUrQZ};)*x( zno$VhVzhQAVJVO1yItNrY4|h}j?clhhB56#A)|^Stz??g8l1vKOEO}Rt`er#1W7PF zJMO`gX21?C4b>}u9;#Pnz;Iqz_JTS=tqW2(hY}a1i6DwLsZ}bu48}{HoZ-9PqMj|j z!57|ww*K9{-4nKZ84yciCs?Hy zvOh!G3!ZNPQ-~*6TpGET!s2;n_$5H<5X;jJuZ4bLs zEnxW@jG9Z*1IWX~(~rVu(?dFBD zX4bQ)iz6Vkykx!IH|D1i3t+py9tY8q?a96%hRz)OV#O$5hmah@wJ4{%zL1L_Y>S$`!F8F#P1_Qmi4RTTrO zZchdO1hD*f0Y~2oe)Pu|_x+f9^*9jCpSb*Qg}!jE6E#~p+!iHP!4tP4eL_{w%liBa z=y_H1&+(}Au)fH4%VQJQS5QpUV5*;e$wUAwS3CeqpI%M5&#qnPKECk|@!H4Zk&by* z;=z>C*HsivBSrbUnRJ;0^4_DM%vv1?=O%bByie(_iW8QwXw3Iw?sqsq%Txe15&$3! z)@?@O2w)lzBLODy0w|^Sm{9=$|FVfU3oP5JHxRt;&%a4T?E$r`~p)2uyjt3+tZ=>DT?PDTMSAVxXF`_bE&%Fr6<*R z`76Kf;q$%x5DNp$d%59^>$|JZtHjnk{xaa`NO1CiYjBH0{``tIe%)xyAup4hcL&(j z!^&6LGna5O38?O6uHzZ35Q1=G9D5Uhl98oAu3~sf66znu26*WJ&?{eMJr}#g3^EZ% z%(|cLi{9@5VK7F^9F!D@=e{BtjqS&4S-m;M!3Wk#5-^oaW8yu-Q#$NZff-^y)cM)Y zuXX-u=PRAx?tHDqq;iIS@m$3K5*}1n^s|o}vNYAX5Owi1G=G0GGM!26F>W}O;ko5I z&>t-S!x^HO|DY%b`w`w5+;OdfbFP=p`S)Oflxj++k=D9OAT-y67=|h6Aa!OQw~Z)h8>9A}%XA6q{~VnOI6A}KhKzwc?H3HzcI+cCJBDiannkUVCy z$Hrh`xB)eQFP85CKikw;FUgUVNgZp}YzwdefEO4G5O_O>)ov8@3vI%SH;fh$AQo+e zf`GxOgcsDTP~gEFbrb$$F@S5FpMBMklEn^5FaO2#HTLXkl)d4(mewSmX$V&@L>yp| z3KQWluNJ$U;cWG`N))#PcVlq^13R;f0y3OagpNH67Z(D9Y)@kqmW?<4-edctW9hTIgh+zp7N3)@eui-Kp7H{n z4Xz~x&ZR1|D@=7E)V+)q_Zh|tS&cwIu_EWsx$ZI*|Zi;IfI*B0&J^R00`OPW6K_l7=HddM&ZK^omFENE0o zxV;YX^8|>#28!P>a&-;d(QL(=be-7^?D9EQQL@p9DCr-|DEgp@(`li6)EfXT8Q#~D zV$?K;9^JQL=Cyf`_m3C{$=(Dc6!YjZCdP5?0TV{Wry`y+a~Cfbw6W@SpG1XBsd`=K zJd`joP=B$N^7Qv&&k|u$h~q>i9IRL53Wqu3KnF$@Jjozd@a>{+XvUiOV&S|h`hcwm zpqqaj`d5Uy{ua>9kk_?dA%M8$RcyV;$vh1k)qVwZw>+Y0@QtXHO)Jn}wmI0?o&0r~ z@^M0#icbN(xh@%SzNKy2tRFbzHhFiKY`sib=58oimSC8hLzW)Wr{)#Vvz=eig(fw* z9C;b>3gEGE1`PTjkE22Wm_5uSL@}1GXyy5ql}4DpAaNSeyu6c>NyhA+p-p6TIzcwE z`u+bIP7luR8JDYTGfv;KyeA8^-&V!7bn1Z$FA?zRndsEnQ|%MKz(rirK4o>!*nYOM zrHnVtRTt$C`r!6fcePIr70bmw_|d}^;r!%985Qr2X(z?gZXFsmgzlVSy~ zCYTGXIfT5fHI{p8oOk-^WJO_Pg!m-U)@BXJa1#1Ye<$4cV^58sF?b*HSalZoe<|F+ zVOy!H&`-8?c9*L24EHR6ouYiUR>Ms|NL@yRC}W&SLTS0d*6SZ%Z&)*?On=Fc3QrbP zK>LtGV$Ri}ZRV1ozC9lEq~#3m4nfRMwnY5k;QC@7qvxsdU6vX#eMI z*5#Q^2eC*-;BQg;YQVJr6(ra1T<+Wqy{U1bOAmkcpq|g?3!%0?s&!OL%>)w*;`qb;qhok$kZVZ1$g5y<1a9M<@1>M4J-ku>C-3vdb%poe0(QbwW>5sh|~ zNJw&9(}L{x#0MtQk+n8I1FIl#rgVE_(g~k0sNM1-1~eB``Md{QzS@3YZWFk~)&Rb( zPa!0o{7xI5V!ww;_m>C7!d|m8}GGP4cX~O8W=Sr|%eLz?^J6 z`;Zc5p0eKFA(rO#Ck|NHwO6aMX8lJVC{@3&V0r61lIA2m<^`}>(sX-f%ap>C{v zRljQIKjQRs?Bp^DGa%c(c3AP$np{{H-cz)|eX$r|qiD8BS7Cjv>EiC9R9exrFa}Xf zcyTtLy#!lgXr3pR$Ps+rGGEuQd8_D^%KZ2Q@bmQplIz;^mDTkj*svz~CO>rrjmq05 zGTokvtZx$UyEBrcgClyWXRZC~v4hps-5hjPC!`tRf#-EiYdT(iv|$|GxTYr2+6osD zyQDQ~#_ZZYdG3c9uuALY3D-_fCw&}-%-EG-T{O8VEnAXq|^9 z@vIEny$_>3sVI%*JiB}|iqm_a!R$l8$m+zJhg~~QMp~`i0fLgO@ElN5o7;va++T}$ob1;DlK0F3G_k*&mVzpztVX(Rt+rGyMD59#uMK{JVL#1!eLDN zEtvk>EgyG%VJabD{VswX9;e+N-sx`*y1R6JRjDqEqXA{H@VenBBhZ8m8pkur4r#2e z2zBcMpqtUE)()5!^StLK3Dy%=-L2>uo+S64w-v3`)e;d=oUQB43(oB{CC^sVp@KiD zqnt5G<#^q&MD+)bfg>y-c!6#85}eY(E=V9c8&mLsbiWpjrNwxvH&?+{hd<#8?%Mp# zzv%q$&bK-_c!}HS577wDKr|A^20?g`N-G;|lz&b|miqJ-5C8j-B>;40&&{k|!4?+g z8o53Nm%c?-!8kXszIsJzn?)0@m5vkOGop&8O5wHC)E&v|aa`T`58iy0VZsI=n}3$2 zv^3W2a6IAP2}8QC$$LGKux=l=fV%Mn!OPBo`$WN#ZzSo?07yJm{nudc|0C1|jDeZm z>D&)l9=NvP?9Z~E=D5ViMIw)lzFppVo5yDx5D?bJg%y7WKG-W7pq3EKT?d9lj|*;8 zuTYZ9?VIEpa8S82uFXK-BrVBgQo--tVNVf$#Q(|wm~0KJufG3h zArhpWvU3=wJUm$RduS`KPv>X?4JO?!SNYfroJEMmK`}f!X;B{%0+hsml(FUJRgUfc z0DJkT`LpDLBEU!{$<^!5#HK$rz-vZ;nAnP(@dnJmh&Yc{AqBCD#tBzN zu;RAL40A)UCZ?Thw3tI1qL;)x`g2ZTxv99cWpsawj?ZwLPq8BpJh#~LdI|qeF?Zr? z${U}qMXKt0T9qr?Y_?)bkWH4vIBr{ERRY%UR_26U27z?_*Z{#vV-9Ry0_k~wm8zjt zu6I*J`HicqWCf6J5BPaSejNuWbjKV3A7i2{rw#9J*raReU~N-kZ=!rP(WIcmf%iO@ z2_1vtCB&|*DS!&DvP~>=&^)H=kAa6}&33^f0F3t&4(>Tr1!ut*AYwR#s2DoIyu|>P zZV*L;ea(Utf~RO@+o;D~-OoCdx3c;O;8zDRVg`WDjc|@w+H6Lk|Fo>!Z$=l^=Jas) z)JNqt8*5)qOBbz^{V0uoG>fZ!QW~FJi(kK|)8gSnnVMASJ)=@mTydBexvhY^SA4LV zjdilRk@LY_fWy+IECW_QV|TIp3fSabO01xV10B&`=o!%`UYYW^zO7-Dcqxdkt3Q2{Tz>8uo@Mu+GG6XDRq+(iqPH~ue1jU6Oz^zR zOaVnu4Z&oJX8~dbuj1@2N2=Zya~9$Q>Fs!tLlgxARTpG;0Ia%R&L;TNFgxXy&ZqDc zK4jHY3KLq6mpO2=a6#N)!vJ&v?N=mk=Pl}OVHI=2+YVL=rL+vB=e0{Nk<=gRxe+8S zDh^Rq5Ar@CwA8}6T{`K~lzab*tSR5E$34~?!LG0ftY1K!WmIhIB*JTSM=@(ONyhXS z!Mga&?URduMD%C_Cqs4#*Sv3T-{nbs?~#<;Z^lHfMO|!)Umj`&?shP&d2x_~CnJs? ztXbO66*q;0@FFRYPl!Q0DpEi@MWRitbL&z$rhrz8BOi-`0im2**z)p71JmBm9!@~e zDWa|vOBOIb-!d~f$6maCy47i0+dojaj&9e#W8q#hwiKZoR*hjV{MJ) z2P#UiWkJuaIZS&7!=SgUFK+KUmRK%6Naqf_vV%ma0@7v8kX4n`7ErNBk2BIO%Rx44f&9cDosF z;y&8>MCUV)E85?BWOHV&%O>us)wZ`QAm%i;cmOtHIa2`jIIy80?gd&tT^!8*BhWdy z&f{ov#JpKQC6WIT9%tL7h|S81@N(-pDY56$_<)>nXH0i0oQ->6;5_Mm8H=_7w-NFb zW-n20Zf?Xvt*+8>SF)X_SsFj{Nx#kJ_Uowuk7<%{dX3}mMFB?csTDZ$l!z1)XT`pb zH7T=wFERiKZVJIv)R(|9Hruh2M=`g`PV^-_LKJH;aA;LTWVROZbYUk20GbaVJ62gWwd8NHcuwcZC-KnNR&&#^%q4t<5jh;9-t}rc z`IhC}ayKbJ@T>YqLCDyh+Ojg7;^NH*J|m3hg`wSHLHe7Kg!sjxnTGf?TSM@@vMv+< z6_pKnqu*_WQ8qQ$6Hqbixrwapzb&%F5SwDgT$o&W^7g2ECb~M@8Y~iB7d;fiORaWZ zsumAgRmV3jwkbfCR?t#y8m09B0;QKP+5#(KL86nZKL?oWtl11&8E z;ouXJ90^lYDl~w_7&_q zBqb_};AHSoJPm5f;ey}HoChgKSiYI>wplLk$+XETD33`bLdi^c8Sgp|?1?eoNiL1~t z14Go(Ss=j1O<1rf^d09LjWl6o4g57!-Uh$?eqy>iG|NnKl^#VYXlqZqtK@dU^L!|r4N3dBV`Je4?pr6yD3>;+P~_&4}E6}_Hj zobPUv+Y>&y7G^~Qn}=`iKv7S;Tg6h~i|}{I>i7mW-Rb675y&_3GVE!Ztzmg&(`~et z%872~Nz8pipT>VJ=d8$zi>%j$sE@bD|E2SX;pQDH_B$sH>RM~4_2XdE>Mm;8Nl-{^ zvkbqaPC=~dxJ_^PNdp|J$j9t>upfFdFmAq}@HG2f8X{4C85Hd1CqH`mHW=)6I+gET zyHCg0lu=1j)O_=Z)~WaP#qd?IHuL5#+r0z;KV40SmNTw+wR;8E~^zy$sS%ByFHoHF*9A4{u|R0MlQR z*ca!W5hKqipFd0H@Uys}eE+ws^M{y+Wj4r_#23JM>6J&`4G*p=om{yBUoJlquhcU# zdx>q3)9L!(pq>NyqLId~*cz=*GN($eoO~~KRb0JEs1h~e))04Oa|z=K4k znE9&`@jjithxTbTVEr1tBO<~2o&VMO7I<&$>x%u-!aPd!Dq(-oqb!!+em!a%4L-5q z0lS3DmDW-aVz({{jO|QT2u;xYC_I$|bqIgB`VjK(!7iO{Fa}}$RS1laaFFgUn-Xrq z^pPJCm^k!M0j#h}GQo8BmU79%B)uY>0uV@^K9D*(JmreAdoS}44%Z8PzpjJpmdA+ch_8Fu;8Ry447x z-6d6jX+%e4#D-)@hgTv;co{l$;;r#_0NXvc$gOBSGAafvJQ&QA!d$qIAia6;*$e3f zgaTkXi0JE1o7}mYjeUGe8t~tAV~_PO_a8xodoY#}1ix^-QfOx|iwTVRSsisB`s$vI zi<|1kW)!>LL#IbuaaOaY+T+TKnusBI*?AwrnGORwlM<9nxRi2b3Q5XQskkHBXB?+l z*ZfwT6F!8MB96tGOxR_+U8zKWfTM7Jpa3^O$iLp4oHb!u*vv^NwLM!dX2(C)l7HnT z;f&MqIzi>=@6gGVb5-SUh$w$FW%5$Y_abFPVD}(&c6*cN9T8V*>Nj;hdAA~eQDhXScY5=9I5R#b^6U%abS7{?Mb;8 z);K|5xP>jzLapE>#-EdZ{U7=y{{WCN_Kv^f^yL?m3>!gwR(zlVU*cb4@2^F|qvzRY zGnOg){w$%j$SW%`%ij{Cf$ON&#=qD3Kb?Qy>241z=r#C`2khZD%Bz?X)g4t%uOHI=1|$#z68KBt1YN*eIK>ZnLFe?` z!cWjXafd<#K;OT#)I}rCC4PErpNk{82`qn(Orh<#)yn92L4rS5k+5)bSw_Afk&CY+ zRy$Jd%E5p(J<-*XpU~3yd~OxzF|4zZKA#e@XI(WdGCimYvi@QMUqDH6Evnqgd8H_K z@il!t0mqcD7T_r57eD}-Tl!g9&G$=I9jsyBgKmKOa97k`u~3Ujf^n-l?tD{Q^%`>QMLm2jI~@()sbu&n!?Bu5B@x<+Ro5g}lJiW@h4+!`*!e;|2j*1gPbLM3&BQU+hd?W zYb=Y&$`}B}6`96G1tHh@M5i<-bq(W-=Undy;(29`X#F3?RyzZRQ42S38@`7eaygufXp520Tqvc<|^6xiKre2uhq%Oo}e;&lVGx zkHek(*L6Lw;OXWEz&;_n&5LSci{B^qj}ewTdsX5{MI&l+1k z6Gp6pS?(>b=yzqLl^fDE4_Le8c|ku>vwliR`KBG?d*e6$g+@tG%(X3apC3ncy3pX8>!oQ?GwOdeoCiRA)H6DlefG zE~>tlJs8?-V^o{7vy%4Q^Pg07K*>vhsp4xVE;N)A0L$KiShMOij0-X;l2HPNK&XuC zl`iaXD#;x4AVN!93&3)vp9|*rgD2b=Me-R2(JB5e&iAjMBFSUjQ#~kMRcOhEMQacAF!qHRjN`2 zKN1p~76+HOY?MuVD%7Tmwc8sU)Dw-I&v$}GBYj?*NffIhp?gFEpy;u<0V18pR4}_H zoGf=6XY+ogw2WB{sM3MK@Kvk>-jLqy_J#MWv&>gNq|MJTbs)CpOl?paXRl;7EkJk6 z+k{=+r0aJ+2#;|z+*jENo~yB4fL2+TjgKu!c^MONT3D9J_z>Hg>@s4+acI9r!?f8k zXguOl)Rhl+K9A8h(BHRe{W)D}1qB*`S`suS+=wsw1YmcDWy6+58c-$icoH<~>}7fW z-l#omn9ZMW*g=unZdE3>0nlUxfP#JPFsV7c1K67MdpKbz-yq4%D2Q@YTDJ@OL~wv_ zHKd%48WNASrhq(kGB1dHIahX&a29btx(?AQqLNFS*y0f{fg#cVN7t7INtRvZ<=xk} zX1@1wUn;XQYpfLkC@||}YOl5xdoUsCz5KpSx_RTp3{Zu)=Ibq==FjY??H#HP#nyu9 zEdpk8Uu&XCPN#9F!!o3^04#hR(ABrWe*4G@c}69}VryE>kO*zy(d#M?TB-3{xf4>@ zj`G+UYweS;FeL@)Kl+aLBTqTU)Q28STz2!pv;%2M{Fxju9?NEuq(!N~*?wgra&eU? zfR48Mw3}(XlRi}pkZR|R{|y-Z--gMM>ymtE(Vd?;DaQa@V$hJGNQ7S^FkBY%=Zl{o zfUA`Do>UQX;GMdUJmaIu&qdMTVRBBy=>p4G8i(1*D1{g`ppV}{N0cbohd+D&N< zJT>>NQ06_t$^s;$0>|nS-fqH56XNzY&K5_0H%I4?=VT~V$opFdhU9$G zL*5AH*8?phlB9+GU07V>d1-=r*{VF93F=YeMCf4*IozY$>B)xOIK&}m{Y?HR`MA)%! zJtacg)KzR)6fDh|WOn!znUinGTRL(tkP8XVH0O7)cbj6`3i-bx(UbC#Ia}P%iDUb% zgr+3zXqu6Z!GT~uQP`wn$w#fb9qUEhH=?>N_Aj?s^YA`LV-Z~zmybQ@V1%R~g*EB} z1v0IwcSaxt+XAd|0y4x7@U5C(V(rVKi789geDECU{Uy@j2 zc)SxwGuU{#>B}VDnYQqEJwOtM`UoBJZJ z50U(rWiJBKNsLpi%SoQGymelEBH{U0lQ7~XDirfH^E@r^)FRAoCvrkYyz(qw^21_{ zVXLthdcuP6Yr_3t@IjB4XF76Opbh0$c=i<-3NNbZ$+B!7eqc@d_${lXeH-Rv4|6KCehcG`NcxB4Nj9;NqC#xhX6nRfQj3VDQwUkedsTi7#s;Zdh|ootds z>v3WRJ+XN%%$x8jo&s=>13ohF>z5DY!MAcGfwmd4NlekJa4$R_g|Qqtx}?i^U^w|M zAmu-AY&32_u8ub!5&z)P-q{1yz|gSN%dol2h*Ph6fp5odfO^$IB2^J$IsQ~I-Bzuo zE%g(XG#86;rSOJx(H;*W%DVG74p;=WS_>6>z7vT4|Df!Gg7de5eyMvH`A?YZ0MdKuB@e z`5o~``4jy#!g@XFYeggI>kqh|qm1Wqq{{FI|X;r<3)$BaU*$z$h>^KaVqyHx{) zwDf*eP@K>VeDa0oc#_-(6c4!(c@}c_+Y5ko8VmBK1OM+z??V^-PEdWx*@1}MWhc}Y zAqZKNKHhtoTr$qxM{XS`c=QXfe4f{rUTS*A zYEH1IgW2M#ION?rv${)A)kxv^OJq=6F%NzqRT7e|%&RO9%r?$67n3bqV7n1d6I=r}<+O zCEHEs+no&1xo{`!$acBcGX_s6+yNy@(L)iN%OGHEkSFIA^3GE!_W{dZbUA!=4|rjp zsVvH9KM~~?U6e#N2dtmV7R$YH^qCA)&A%N|*rTpxf{6Xyhh}W^zJRw%urk^O+vJw& zFv{As1dXV-JWV;?9E>>iRJ1@-%i|W&ZuWQ+Vr$+#=(KrnU|o{%G;;~F5x_c)BTAB* z&*#5|yB29<)OZ-Oi5Ea88|BhduNAU3$h8|s^(&W7c}YCEmhsw$g_9MGtV`sAHVQv@ zGhAfn`d!K!VP8WE8TEvSG&i@Idr0j8rZ99GCEXbbG*lz&%)4S%=gC03Pf?#Y9o>s2; z)E*#czQNdn8G26p{6c+eh=+}#`~co_$W<%n+P1dD@iy6NGVPk%9D8>OZ=$cn`G!2l zOQ;ZOxK+CC4EW3h3C$pc0|Ic6Z4;GXHmMSByxMr#SQg<)qh@%CGS)1XGGb9;&;>he zZsyKiOR&i#Vspthc|?ezsQbg^1dm4>KhXGEi$lBv-cb6Exvk zH)CN;bBwhKYR3y{lP#R%I&$#hSXs#^QU#ymP~2WF`h8IWBilge^~-nst7d% zHi{xw;E^Iun66dWPmhOLfH|n6Dr~!F@NAoC0(J=BMGc_L@W}J7HX?zY(n=cS;-Qj5 z#GK~_49F9=!9V8#?;DO2xVTLKr7#07&?%aPz$=p@`p7KKczh#*`_^#OHiSlla(89)Gnuro-+9>ASl>zhdJ3t{~L6oevA&_&~# zC-y-mI*<@Csm-qUCht43ZO?_ z)dhpF&$L-c+%5PClsyh=SaO7h--!R_iyyUoDx-Ltq^&A6ko#`gCOzZ*Cb>Y04$qsC zbA1W#Lgye{?fo&iArs%^$s}|8yPn4zJsTG}%Zg7YhKu}KAN*JRy5%4VVX2B7AvnF$ zq<9U0rjY=xbykU!hey1;cC)0Vm|#jmATk0VZ7CJ;KJ9&G;@D(P#vM8t#*sBedB7HA zH+i}ZpXXS?t~so}-FR?)k9T9qQ}rgnyy<9sDVWn&miM|^m0X}tLWM>d-mT$laJs0A zFt5i$<&p_KPpUR+<&dVBV~Q$xBz<=N(VB1i`52Wr+9$Ru1Xocz0)LSy0(oBXLy(^B zL6ipgR3#Z=9>9LjCyojeJz{tpE#uQ(!cPldykw*NC=o=PE*)?pt}Dnj!%guJ!KKLH(W=@K)-Q+38(vPRw|VKgexpZ6 zyb>SJz@fVH111q@Nc&71gi2ul@@WsaKjjyhloWf?E9M}AkONlI(8hrCf z4Tx9q0g?u6(?hA%3}t6Xi%yFG>t6wH3y5nGptTGxzrF&#QV#r!#U;caUu*jz>$?$m zIG?)(yAN_|cJPcw>b7($0;P6}%Q>W&13+Xmn#IKWi^7?y5!$p#9};@?VF<0@{L_N8 zr?3Om*oQP#SEn>3rctCZidEi`w4}&VNQ(vSY*bg2iyyoU4+zpD z5WvXcm<=n!zFr_LQ~b*@_XXez2!$B|rO+nL-|K0U#cywScyl_zGX!5%nq@rymrX#p z?Pu|r#CH(f) z$a_Jy=?+eaBW?n|aUYD*XHRSjXLk=3Mks}&9u8ukT-npsUVO&*k(O?I$TtmmpLwKQKjqnWS5l+f2=D4k{do>R9I_=)0aYR)m}~o{E>V})hT)dD|VR#|cF zVQ*Er_PzCoQh_ei{z#^BjhxvSam*@_eTuvDd4)At3ZSrdBDoADe zZE$z8b-hO?CpS})fo?jY=`cqD@(yVDg16 zpO}V$ME;Nuw&z#*Z)+LvKemRY^6FNjD0{fT(A-v1`?4nP{ z)sfo=MUR_wAa?0+*6YoN)J9peXNBtmWR61N-FnSqnkmzbJk8hu2LmDp@DMiLj4gyj zt2Otc+wyk5WH^ZXb5pvxHKkL|rfg?GhpQV7{{y7*?#9O(Kiv5HjW0vo`kBVhV&;0h zEDzN6t?!k?d=;%$FRbn70)m#6U=`R4)@EuAI6j#!e<>mv%vNhvLQ8~*wXPqaIf6vP zxyFjr`~=GiFGj_AhI2KE}lZ*qSUJH2P3m6UV(| zT((4>Y;4i3ey(Hcb?h!g9h!N^lu(SgsNjavyrRV(PU(>D;*K@$=sx&UQ)0*%{%v^H zsTkNay#Zg}OaLDnVo3}slcPBnAmK}vF-S8rBOH<7$-p**wz30%L% z?#cvoo@(I$WMwQ#AmPN{Vv;Dj}3XDP2Ymh3^n2L{tc;I&FA!)AXZ6Om04dgX)Q>r-E++Q5A&(z;NlqZIezncL zF}L;i6*^Yt=^R9b{azV7>)$s11U$-dIOBi^El*oA<|`VY+E=4)pl*dnt>o!JfC?HH zqICW4I^$j%^KWrd48Z0HW9gvJ#PlLPKLhb+lCsT6W08~62OttVj+W`Jz0+$0X%cgZ z>$h054ZFn3$cT#&e=a>Pqr)WH%-djrt&5TE9Cvx|p=UU2l79bF&b&8^W%jK4{y3KY zx!Cbs<+0rA<<8q?RV=`B&$>y67jb`#oVB)|{ED}s|Fp3W*zx_rub%NPSRYufO0F6i z?xsJg>dieJfpC}9=C#yTm*y}HT~+%&7FC24%Gn_x6G)bQi3HAoJ!0z$6=@^Uyc)?WK5%JURsx3-CKu2~j4 zZPq@V3b2O_NUOvmMgD3(=JXbKyAesI=>WWA>mZdIJ0-8Kz17g1&dZNQa&dmZye=M- z@3k~h>d_{=TY~38CUd-}d9LwF<5P_v0nhc5jh}A(JkDxEr;S2b>-n-r1+@qSvZ}k_ z`vQ^M>aNl9$Cg!>?`<-xd-uXAWvHVrK|s(<00$@GYuLEZ+=f2@zY$|!q3U1$v|(Bf z*-Y3bOPVF#JJJAV0CkH>IYpFCXu?ba-+0#qu+$;~{A;+v`@)R1akgH`BRkkovEM}= zbg8!#D+=0!-{6z08nic3@oOyxd8stwP{_6i6rDD0PH+lh`#2o5c!n2>X zdJ|mfJfU42T}bd=(_!L##7qkk%~b7BH5I)PrE)+Z+V@y*Na1<#MkAhg;%*`jLccL< z>;WEMp05NuuS3iw46`WtaCN=@zut+Z-K!Uw4MEmDjY9|hE03wS{-f4EkD|`6L__b~yf7AqWJ$fi^?=V=TWOzv1=`L6yjNx%iLec}Ki$!%n(B}&}tvdn<* zvf)O_cf%y9lg6FK7aBhX5%ZTCzt;E{OU6l6pKgRF4`DJq>j{QLd~K;G_|)*`@Bv|V zjYGogwacp!7JjxqKAHc&s=N4Bd}q+cpwj3Vm(Q_2Cte9^CBX0$3Oxq|+UlE-^{LxJ zGRnDra>yRgQTj73Za*cEQQDJS$USA!wrJy}YgJ6}*xaagi{&vWo8_5j+;bt45s!V# z2rFXe3et*s=lvt@n|-YPb_vCP9!eu?0lP|0X`2vzhd)J>lT3t0cX*HP-Ab=Su5($r z>R!mFjw{G_gcex7y~X-uWMyuofn=g`%;~FWBCux$C!}ekxk~2X-P;ALCYpG~kh*VA z3K#J>kGW0Q_?&^qNiwDdb|H}nF9;HV$GqAVsPf9*GfE{a3%mCK>UzC#5?RV|IG+lME%OFJQnnw&X$3JOQ%lXf%!b7c= zD;lwyZ)i1X8;cL~ItBV2k+$g&?2oV`NHD%nNBTGx*S4J&Q|9Ehip<_7k6W}IWxb@R zM9IsK+emO1GcUbzPwph}STBbL0vqX9Ha}v(yCs2tJb&Y_LZmDpSKm8x(=jOZ%E{xC zEyafFUpobufr3LeZRazPSYnte7a5O>nB%d|tA)CE~&3lnHa?;*%HI z#^vkR3{4l)`?suB@5CEs5Et#8$wzw(+q{;XW#|cd@JwMc^=ZTiK>>}uex7>L2nz1> z`Ie=vvVO{MHRV7i$rMsWJD_8bu)F$bh3zJuTk z2LXPlLyiT9ryf7bA@ zg5AKohAXI+mu`<2;Dc&VIEPdYOGclqaetPGl3F&n9A0NJTC$j1l ziMP{WBks&PF^@&`L>mhO`Ou|(np>=-;w>QDjX~p}aS8L(W&I73kxLb3Axm!=x+3OF zkZ@)>OH*5F%^wUF2mEl;bWKKm*|Ipu(3YjslV1MpwlH9UQ|i(own@+DF;BG+w@OxN zh?bWw_u2Sr^8~SeclX>CPfK>;cqhfiS9WbjJMLao6tY?G&7JWlXRPZXFYnPDYzDPG ze&c^N{;u&K0m;|1pMs&O5GU4Pe%j0cYB~PO}jZ`FdwoPVjCY z_-_t<6pLPhm*Iw4x5dz5N9|6JwpuJ1SAr#ivR-L>t?Wy%D^ogSUXVTmoSU`5WC(rV zjc)0sU6iRSNz~eG65=OWVKnJI7n^a(@++5+;)1Cyye>)T3=JMIfz|H{4q&g#W2}iF zgD$czqDPuHE;jB9Y;{5A>p99xbvt$GOCOJ>veZcozv~>Q_6F+rM8-N!aB;)ZQC(SX-OIE}HQq1Vu0vtH+izpr$(A-&*nGU${t3{q^03vF421(7PDk)$Z zmq40~RM4?_&Fifc0w|;HZ8n}}thqUhSk7MGhTrn8Fr^KaerNJQgVcV3eF>{1$hU!P zji*)O+PllMmtNO^?fmIdm;8)NU%%Yse;1i3^CcacXlP7+iH^r&!Cdi!Ch9%n7(B)1 ztqFh@IwH=F(PxhD7dB_D=pBIcSu^@rq+aP}6)pX3K;cjAvB{&CImJLq3_zho9Zw^# z?qI~XM&Isz(zdYLk*c!EQp6+a8~+D<<-dnD)p2Y2`193iGsdvsP1fgcVV!59*J}5^ zz6}Y@bHXI^zSYTyT#`2Xl(rsk8)b@&F3+f!WXx?mmY~Gr7IS^pbHC02NZ|;thjF?s zf0k3oCBZ_UX;~VR;t9HQL3?3e4D=E%Qg5GsP(%8hD%_F!9`YYSrhtC_p$4+_;B&4U z9oDC0ZyG?l3|q^UEH|a9_vKe&k#R7#M2>8n+#6_peEk)8U|+U7>qfqJC?GHYKu**QocqfG>9IT{>QQf|bWx zbwX4~+OyoW@Yt8s{0zzn+Z9&TQa^H@jxubyFHR^(?s9cY#pYZ$rR^}}fw+uB@#1A0 z#Zzq+8PlbKO}RiaSTZyzWwI#!^D&KJK9IpMX$UMC}!Sn1zJvHg)SW_x5;M$r+a%_$u}Kv}HH-b6k^?wQg} zsX;P55!tbJs^3F?;@&?acA(J-?nBn9j;$(F<NBd1><6`SIO*S|vwE{Oe0y#xoSEAB8 z+K5=$ZOB)xf{gs+o;FM8y^*AOyL2wnuTNtLnUl}DkuDiGl()r1z8K;hsQ0Y_oB4mN z;{nTM@T+*1kGs4)#b3G9bw6P3NKa2hSLcV}BS_-SUg=b|@sod*3KMOZC_S2TAI&c- z-#V$Mxb5($nD>SElR;$t1+TU%9+5VnZfZDhL*){anhLnu+f;vwmP29pFbX zLf#j36FfMJ&P%*nG(=j?A$VTZAG3ctiM+gno9!}r@_B>2OtX4NsuhbyoUN^=aZY|@F%gcAjx9LJC0Xn~|@KI}F ztc0_&fq(IiEg<@Pd&1ho;JmT){t+B2?P}WY&R|uGTh6;5*1p=V;H}-hbhz&=wW&?I zxJ{;j0qH<$KT?1uz;RZr0tvpTunbhD03P(77qQe2N??o1I`u-!M=j=mv6<@w{?1}2 z#uV>9gfVF>H;yG-#~fZ)Z;7!r>g6!G!Fu`9Pf9;vdFVNT-1&b)Y+D9hdEdrscuV12 zU7PK?O3KLq60gF+=Ep0i?yI^ed|UWcFqj}^v>S{J#ae0~lhI8J%rX^9-w?jI(SAU0 zw|Ar&Gp9(cv>r-t$n1C%+m<(ZqhVt6`3d?J;Le}2wsm+gXS0I-Qo+Ei*m1$eET&1$ z3wx|Yzo&r*v`ai{RBfbQp1p|$4rz+L>V358wFbX1Iqd_okw4Sn z7(srwpt05Ng;=E8Y&r;}S~UHN@Z95@8)Jx_uROc~d3yCE7DW?`O|1??7K>}`EE-{k zAJ<)Z7UA^qVH#*9?CKm8aOH@8r#rcMqwU&C0;r2OWw;OUr19}s#hnfz$pD-F2?M&= z-KF#Pz`6J)*^J4Z32|wU96;dR9<_?;_tm*EtgTJbl~%Fg(_-mIpKF7M@!5v!fwomb zrx~y}LJPqn55VQe*wFqaa8+GatJ}?g2uQI9@#zA*HCElD23|ekKt~l!Gw38h#w$G-k(n*e))oz3 zY_U40XzCh+=B^=hP+552%8&gguc?Y_(&zr3)g8EEg=-GQP+ymr&cUSay|GlQD zfQ_KtuDkCKf%m*?_`$yDbTW!B z%x1NSHt+Ip$d82xWlwf9at zY_x~R{@@W|=OlH~TJzpEA6qBXXCS_`_eCv-Bxcq1u7yBLIqrp(SWIACQY}rgx>Ua(MRO4af79Rbk0x&rE#4&qm#GRyV5k*}E zKMbB>%R6k-OkQXk0Ng*ta&Db$YhL0BaEis?1$sK<#VkkL#hG8cMM+Po$eFF)JT#>M@n#}D4L z{SRUG!0Tb!bG>enacS|YT7ma<*7(}f3gcZ(SH7)&6Ge?qBD|vTI9UC(z|p!SjmGU> zRgtPRQW@W&Bf;p&EpqFKvb|lh*r#f8KzHY%Lm36=(;D>w>Vnrl)Ltd=DexO8-d z{M6XTfS;bo!&@^YZ$6hKv8iyjaU=~N* z1n2~h`cp%4q77FOjU3ljbUtcd1D@N)Kmv?a~{ozET*2pt|T0K@Jlep~1*p!8k6 zXCTZF>=Z&iN;#lI>f5v!_X+3<=f3oNk8+;eIq>%f05a<7k0$_ss|;`48qWuoS*O3J z#}ir}s|Rf!i{#h=;o$k!F2pP%L>4OB{^!OY1LGNQoQK}~O=agQN;YwHjcCKeCHK2r zUGQm$Ue=3iDpG!1(Oi-N_gSCVETLsF1mqBvsKOGCGeM*)gyT~F1$t3Dr;Gu~w*ZE7oQ@|;toV-+Gr!J%04m2- zXHeB&LQGD&HPd`Ee@sX5McML`vSPb28PJ}EfS&*#EndLV55a-?qXqz3C1md|;G<}Z z)dPm@;tB7YUoQa8D1U3PX-Gx(GX;wxW^0mwiU8CiuhzVmc&T2-YiA8 ztw0;%PF6|+xY8XMqxWlRg-SJzx#`^S?lU@ODG6AM}7hBbwb;D78R9u6e#1pU;u8)FBK9e~D6s51{jGlqU!X zUMVubZ)X2FLS203DJ6GgoZKff#sUTiTog_1dEw7{x(22+j4!f>D7fWQOkNP#^CH2J6UjMj==aq zcPg;B^V(ql_EuMa5f?gt1~DI#jPLFqo9o8jX z*4>2vzG|zI=Pul;Xl}!fLNDcK*3~XoN5-pFqmw4EprTw0r_IZlIg?j>>`HC^0Z`3M z?g3Oi?1nP0rva5>(N}ebnZyuLZ(`FEDct_zOx~UKlP%ioXbp^;5CyUIA9W43M4dxq zF~^uGKDDZ5xwJ@N$1WLVlcu=zi{A}3z;+*p{ag`FJWM?gl?qKfs5;_yf3t zy2FOu9Mk)Vlhm`J^AO(KCGTNQfIW!6Ddk|#ZYuA#rP8G2T;JqzbY!O~`EV-y{t{?X ztdilFkk0Zjr@1HOhwPw}vfj9_NJ(P%Jp^2;m#3+SSUiz8THq#KKUP54!A}lL3sK2s zV_*jY5Q#7}X;pl95%H6rDUEty3T=TUD=TG@>x9|Aa`Sw_CRvyH_zB>QQDhR9 z=a_d#cS@f}Hv-1PYe%>++rnWBHdD?C^KLg?2>a;hMy_}wv^gDrjb=ymApg=HzbJWm z0%%5qe9kkU^0*7C^eE6prI+d?!Nxbe#Ifj^(%^KCo^@RAW|p;7#)+q?IIfldr>b+B z(1iE;JrU__$SbpHn7cqSsHwMuEu#5pq`JnNuq_tWi9OxP^hq{}Y- z5S_|d%x9w!eD)2^S>y#C$lNd_W?~}uvBFO+vs6?QDm)t);sHN!VhN};P7sQ4xPb=r zuaM8kwqgYkeYvm}uZ7n3wUlx7v~$UP zM?~3fIwIY3ki@f*@Hc@x>__am{E+VDU%+y|c#|!ccmdKIqThqNg8xIv^H?uT$-iW( zkEbHK7dj%~P~#q6r|U5#WX_5ELc;dPoQvU2`@Bg9ywD%_z(iX;K0Pu8aqgbQD`-w` zbpSb;i9SwvB=yMYFzE$*9^l0FIiSeP9!_Urv6mIsppF>stI(fk?c`Rc^FbvqrEz+K zoC5VP2(a=0GJtIKkImB$W~|C_RDcriP?4=zyr(&xwR-K0)U9+%cV)37Gz0@?o{=8d z1we87>?~&Se2Y$JiF^PmxW}jQ&yn;d-L5_Xp`W$g&*yA%z-gL*nR-59eQBwh;;?Nh z+8KPjzstTHao`|RACpLHFYbajFk{DzKd8cDp9 zSAP3XHvSCs>Ha0dR6&=T$IJS1boq^IyAt#u<~0YY>umRrHund&RMOfbkFzv?CKhBz zMd< zo}>a;*}Yaw+Z!pwPMlebwcgmDs+mprZvhQsm+8yMUadAd?DIPE)eZCi z?rqY;=T<(cdU%&%He0Nl{f3WzOmu{`!_Dntci3Y;9JRqzjo^5hyHB@y6yIno9}NK* zBs|wPuDim=vrk4wxLYJiDq4X!{rEFY&WJckwXbkwHN{tV|x zWZEA=ydp*qCErt{Ho^iNra*({ART@4Dx~9<>rAkiI_n;r&BX#WEd@E^$>e84Cz~rs2@rE| zFyKgcJ#F24B-4uM(#SKD_lBK6@Tqk5vDe@Kz;##f!-B zx@MqaIvZ$NXq^_Dx6)lQDAgBj$ty27iYl@L>7#imoC7A4tJvDO0GN%J04to&nhQD< z&d#V-3Mf4{z;bBYvjjZ(Q+ugtM_Iv&x-%8;=<=D>ir)k6Qembq8)p@RP^-j6Z^Y)e za7wG!l0U|{W9adDw*-uUN?^(vqZc+iiU?;ZBCz zo~XN6`EVa}_pQr&w3kYDj{FtC1*&j$HR<0xUhc*;)N_X2SQW`N4gq83I%|6A*5Hep zWA(H1-+A!BseM@H2%oEL8&Uv$K2_TGpO@3s{!X=-Ep z3V+n@2d>|o(tdqo6F6Mxcy@mIw^XaPa)1wU4_Ccbn?8g%wx|_;HnjoHkzleD#5*Nmuv=ETDof0GZbzfV?oFPsNGN@ieALF`EkX zWgf=zbaHU#wHgm2oOxE4m}`@|)d863v$C{xtKfM)SZv>bwM%soSTkPSxIuQ==DUQE zNo*%lYyuuKp!K`Cqlt(Y>Vp72oH>Afkj7vm<{4Hu@^Lj!Ihft-T+%GgsIX3T>N25J z0NTta80b9Thj;|;&2Hv|-P@PDKQmWTfbA;DE5IF?MZxX`|2I@s+y-v=46sA24nWBU zxn9+(>!;T%1a;o{rPb(~1EM&B^E%Aq(mA*R(J92^MnpMt~_i7vxnH>yrB~u?6|%dPlqL zNPVGSBRNmA@@Ga6^$51bO*zzCD(Q7J|01W@`8xiz~!obmSlB z5E-eJPv$mhvE*-LCQ{Sf*qDr1yET{dl!np)_7n90>$sCF84g(Gtp0V^D#PhF*{Hw$ z2OEC`T8lS?*L~csK(SR;w+WPLm9458yO*e@DC3_X44}>&7|Q0eELJaLy7qlCD&Bjf zt?Tc>Uvy?PgHW&ZizX^x9(**3HE563*ndqdftA>^N~h9KfUcRP5LO&>yiJf-hUPp< z18+G&e~A^Ocr$P()HbY&Yc7lJ5HbRDI1X)@X46wdQMlK#$*Qf>*tn2jFyB#}^Y+$6 zwfdAA;I`+I5;PXZi1P^n7;TEEWUQ-B*q+Y#94YWNX~hTNJd8;TfK(lTG1y8)OiTpk zpvMG55HKlQZISIC;j>N6ve^&}Q=eXVVr@U`0CdbR&iQMH?BG>SKVQgH+iAos^>ZmK z&z5)wodPZ|XbhmjQn8haA~x6)uodbdrcT67Kk@$>By|+CsR~BU=+J@S^!hLhvZlpp2su{$a;r zi3Q!34NRC({XYWcJlS|@)!FJZ>ps(G7_HW_y(Sm60z#|cRyWc_f4;b6zVLi{EUOi| zL@i!ib3>P2XxU`Ap2fPzINc+CZFJG*E^AAHZ9Wz6OP7qxK0_{tEj(>XCS*#O`&?=( zu=X?^?9llrF(StH;|^zmfq%a1-|Kw&oO&xuae{G?zS^Zda7GVu17Q|>8GCHIHW4DC z7ufMLuW)pG5xP$BJ7OqUL@U(Eu@>%;Ok`C{;5{7E?rc@OJm>)g0ymWu15R62{RzCS zMhtylJqABY`^BQo!DXpWXU2(Eg}IjG+y6b(h`)2=$2PvW@v|GhfH>pQ7cJ3*uO@}x zb~4qN?fGR%B5LjmU|jiv@8SaO$Bu2tz6QEp8 zvz*eJYIHbqMXI!Fw+joKeTFA{H(M-YaUWvGIdFPqUW+YHSUZfH?GzRdJ?k(6572R> zKUrH}(1iIbr35zyk*w$z&WJJp;gFm_)FqW4|XvEa-af35inFHnrgjnBX z9i}POBlhz~Xze51rwk&;w>Ex%qX$;;kx;$T)POgE7A4(CHv|t*>1&C}zt9C=3qu&$ zuOg=AZ4ZBm5crScoYcUr`pr$l$BkEA1;q1p`R0sUv0$m+A4nifv5TDPE6#bsrJQ2K zidBztywIScB%QweDA&M0xjDotm$QjQHHaaJ`Qt9L6;ot}CuM)k7y zNHaDVA(Y6~?B8Mf+X_7Hd~lfBc$gkkYMz*OO|B!(o6bgj}hiN)>&Tt_U~IE}L;X5Dt0 zx}mcc0)k7PswuWY#8yhmwm`y)6U;R83&q`itVT^{hC6De5J|`_LLDtrMH6BRZ1-`& zQ*S7Q*U;hkoX}-~`}!`A_qL6ZuVglI9RBD1v|zx(3wm#Hrm%hFJcWOZGCde{O9<;4 zAcBdb*CCWYSxB65^IYaZA!F&8-UL00wSb)q&cBJxu%lHdFZ@o& zw5oay5hcWe^A$4vrMD8DwH=Amxx%wK0Gc_Nf~UtC$9T**{AJimdtZ2aD(~!cCcC7r zSj~B-OJ5Q;<9Q^8h#-nGpJLy38;N;dwP1BycRASqbfjnuBof@+y*;ap5<XGvywCcL zjnT#<8;|2z=Gmf&*)_VTV2NSnRmgQ<)M#i#NZ}rzH5+?|6r)C#0TvrReIB;7s{hXU ztY@5Q13uPnCVaG|z0MreUo5K1#nrYp-pSBLY%Pz!c zu(M$e&>7ScyG&u(Nl5QnCuULFiCaL!sQDo}&7`)euq=*Fu!}r)$(6u0(uw&z2@Nmd zpGbzR3TrRE{pa9?eh=1uXXAUYI_j*+)2xIhd6iih%uXkbnARk0j;Vnv+-A=8=6Th6Aq;yojE&d@5scyuEm@#JYip} z!C|WE`zb&_=k#WjR<;Fts?$Uf?kO$fc>V;Dk-^IZucazy(X*QazJFIT4+Q_MIqTU- z{xrZ4opwWo;I{(r9EY2{??zh<8Vn0VxcksvA8s@%_iA@x%?q^Gb~z9yC%G= zGCP-z7NNeZ9pyDGTUp8PIb~v5(zcd|%n8t()}-dfbvoSmF2+Hz$DF${X2y?i{s^;o z&ht4LyS?)ntRsb3?8ef&9UxI@(E)+z_UEm-QThskz+65EeH8XW{-(z3QWYa$J*^yz z8+-;HL{)fywN^Vxl_m`Iizk`EB0kVj>xSSl6oC2B5413?G+5UMp+?Pv6}@;_Yqvfj z3Uu?TtinShh?N_;syAy#vr?T^i1-Q?+U!7t z$Kk~1uPK@25s*z$rY)O?{u&mDX_4k-n$cgj0005Q4_KF>GDv0ZjRy~AzO~_n{r2%ih0&ZIn-)9}$7Lobh*ljzU zJVjeyj{1Tqu9w`!OJlVYhU()-wV4m1vnTrMG}1T+G4Y@7uv+tE)aA!+E028^qtNEfXrTHm62|S}%lh%|`MfYbnZPO?_!Ymcy-FRi7w5*z`iZ@B z#%`Z}ss~~v>^VFS9{{nSnU0@JBU+H6XGyDS!Qv&*^iL)l#~p(QGO%(GwFmFq%A=GE zDPPxJ`G)SPSzn|oUe#iLE9BT)8^;^>Ru+SsxolFMs|Bwd#nnY$55&t$h6D=^_a;^n zTX~Wn-KRH0!*)siJ8-6me&z?~iSFH+mzqay17H@=2Y8BD6%mWiFPxj+xWB7>a^r40 zBFO_ftHFIpZ@&oqMw)C{vw@EIkGC?CK~U58jQo@p%H)axf?;LhCt(Pdvw9l0YG3f* zlAukRi4KsQNG2z&>L`+0`=yRHs_cY1^FI&Wc=6_Gs0#WEmt8l8Qk}1#aK-T6Acyd@ z!qC-P4_2~RIgvk}CAxby1K*l^=dp}1@*Qj9 zC1EGb76*QBm=CJS?toPlqhF~d6*hUX6;K$}LYtN(HdeH3B_lySb}u-?Dj}A+-)!F0j6uqu)0`exIBzs9%^X`-CwY}vijRSt##F#jcDN{#_bgR z%m3*7ac370>FYxkCSuBP;LRB)U+K;{n{M6e%DKw>oy%Iv_{CF1t3hh-C6^bJdD+c1 zP`s-$@;B%JE2s2hSZW65R8m0b=@$L4wtg2Jt(A_LeQ+*2v*_@_X(pq5gex1Ed_TWV z_J{=^V_p^!u)zeRMDhB<>P_rm-7zu&%e?AYN;k3pQZ8}dFqoD|lO(H53Pz+-FK|N# zt+Pn!^v0gv{$U^YMIzCjQea%@Po|(nSlNtI70;e^4N=Y?buA36alRJ5aUG(<=G66M z#bqdkZ_zx!A{3{r$`ytOx75Dp2?*E5a)0R|VV41RXGLi!(cKp#4z4y$ZArkL$s`BR zq6JNIn-rn{`JItWub93jCCwf83_xCe#X-2U75o6!{Aj8EX|!xg_!G*}kpjoOw|kO) zY5<7a%hU;{bsbpi{|+5;kFRcC*46T8SgmdagpbRL&nw1H&tu!UqTxT*u6qTj|5m5H zQn;@m3M50}lQFrhTy)t2y^k&B1Y$~_b_u~!guKT$WXYpsNm5)Z%VUiRPA+;Byty_- zVTFzsK=hZ&$s#8Ho z_rVr3o}g@kV1nCf5x^21)!KnID>t=*mVso1>%v!!dZ^l~s+*vTpPA9Y$Yy=I)oh8# zz0<|}U}L^8ag60*z?%lx*EXa$mX-|!C(~Vexlbj#v1qi(YEPngCR{#|kp}OcT)93U z_T3i}kQ$dS-&PUdnU?sQC(u?iKYwc@cDoCi% zXxbnUOc*T^WwT0jUBf%m5Do4XAR7KfLbp=Df0RjODy+1}<+N2tyOYj(#`+_eQ0+{j-Cv<{fL^^nN<{&Ob=*7xRh_s2R!goqL3dGTHl ztr}$>%o!yPFJNIG=-w-+-Lnuu%cAr#^C=PWb6M%FV|Gx5`< zwl)bHF0cmae*&849pA}1-Oi-*aOcOca}%V1#)&mCGT_a7Sc*jGabJey9>)jDeW8hS zEmsu!>L3C$2O8`x6$zCl^mPi)?#p_g^zh zj4Q$9jT%i2?@2DKRB5xQbzz^jBn$N{Vb{R{8SmCyQM;M6XJjW$6OowUsE z-5G2&#kG;=N+*#}Di2N=L@>bVZz`ejUgmn0*T+S6m4c32V^Ws)OO7M1Hb6u|=QO1v z=MeBA0KTWoYCzVa!HPQu5y%vP)(MV?O})zjV=|B}D8l0+r4-IQ!HptIjm^i$QG~?N z%EvN`yGlOcv4V=3gMQ}l3XVw=v246hG@|iR;>o(arM(_IFWtv;Sover{QI;Sm3{4(TSPHOHUy`+)h1mo z_OAl^0HTRyW!0%_Z#q{8@}mqJH5ryLU_a^@5x?5MDkA?wI?fL!9~~w~w`Q?+b>VSv z(gq;49pNBadf%`6F%B}qIl;ZF%MXX~)%u3UichRjy2U4xyVKEl=4m{6d&4TQgU?b= z$Kb4)OMxn3Ak#*cO5~srE_Bj=Ny;?l6es;RvG;gr27+&t#YRJniw`yNbrQiV^3c%; zP93P1fv_MiB|IkGRNZ1Z(>!DEFO=13_kAOl0-x=eP7+SwzW`%+q4Oi)*+1R+eCG@2 zdk}&^SBbkbzuely8ez0Yfm(lzIJkA{ZJU00?FxY3#r*$oX=rNUigOh*gB=0o08|5f z4CBj5dp6kn|BC>3v0(HPu1$pJfxtM>3|K}4$DPxRZATPHQWreZP>*IQwF)2v_Cj%uH#sA;5eDsWxa4$1DYA zW|HLBvP_YL#CUtAG6sP1*g%Np;p%gm8k^w$F~pJD04%Zw9IRlGG>ZfxHm*!C{MR$! zhLRPzi>tt4AYzoU&ngdeSaA6n;v(8al^s5nYiqLJv!Ia*=@Do!_|Ss&Uz@W?OVy_t zL3_%=of~G9-rV_k=f&XXzIdKh3gvVco6%RV%XO|E!)+^hl=a!spS!lXMDIKOpBS^bg^)dDQaY+x+`sBer1q`DGLHfCT)@$t~J0OW9HgR?TqxxCuf`8^5HH4an`3meH;}Z z(pY^ho@3VYnHISC4KK-uL($#5ol>lI&=X*oswimUCCIuba15iuoVw)&nsz&1>rx8N z%nopZBkmwE^n)qxVdJDOZQ&s*67UN=DoxfGm1MxwewR*jp+!G>qfnEisK7Axl4 zo}2#*th3+Q1@w2k~9#?=~IQMCM zjE&h;sEGa2%N2zn_T@AIkPb?Gymvh-$6o@CwpS3CdS^D?QG*M!;ORh4WrQpW z6RJ%@eJT`Xm*hfxOBJ9{R!U~cLcNk|JAUZQ922m><<3>bYT9%R`f`AJjN*X8akgr(YtUQiFfL~Piu2fkr z%4DbPHR4+p&r~(VQB_?E5#>}}&J#i>VdDVio&NyP(3Q@cfCW8=GpJ6{=vQuD!vD5Z zy+ouN0bgp{(d#EUh1m_%+m_{)UW8uj%`vLdjIB1l&pZD7rj6C0E0Fe{g(e2y`+(BisTa67`kIX_ytLp z=|n41e_s*->%ES*|KLL&RH{2`=xbgFi1J-nH$;I0;hgxt47*$QJJ&mRL4I!!JGYRr zf1>lL&@phb>fb?ytnZO3n5{Yq@(WHi#MfpuBjf~gNLATJK2XdiXyr zM=k$ z#f&X%B7rP6>Bljl-PJwa^? ztC*%lTfOyEk?=>D+aU+tZOU|S@yS>uzg6-~#NSM@A$c%3er(DIkNto%--ef&e=)N( zVM(zKLH1;Shuo^{WSKrPCDTDcbMWb;&S@SWz-dt0(Qq373*sG~#(O$H4Dqhr;`9$! zr?7R7LR!djrrYzD=dYCXUcP_E`Y#5%a41`Nlouw0pkp~XowwlSw2hg-q|iv9cMsG_Xb&;Z3eIIpHPYmcDIU8yuV9Nb{b#jH)R)VK+`8+>mUPlCE>ZxG`ge15*6+ z74ABn6JjtuKoZnulV*pU&OU&<(qvi(?2FXgC%6AbsN$k0Q=qa)wKS8+Ww7vWKPUo*7 z;w{78Ox#S?S&20y3f3Hb5Q#D8Np(9 zCy9oD6yiy*BvwCtlRWUK+DLI4HZSriJT%bUBkVt7s(oW+M5d8mGVcOqkzVKqSI+!@qF!* zw8|MCrw!lXwip3+`;K!xe3c$T#8u%d%jJb>- zi#Hr&ttB@0SlkC5jVg%C+3PVcL^hnuw`=Z_nn*CsuC`@;S@`5=HXC-?5Q4i+fu=-$ zU%T=E;$4zuXZ?K|f+e12_(N8X)Z|05s!NQveeMbnmaoxDs~S(aAja@w?269K^0jy zyS(X@S*DI!R)$-%uHZryIw4B*q=;3;tX0^>W%YCvM$9(~S6y<1z#Wh3 zX)7=E`*)irJ&ug;ZYf)AMzI0Q^AW(7?29H&AKg+uzSL7uxkBh|_A`2q@)4osZ$PNBnEU!zhIdHP#o$%U};HSFr|h@Nwm4@d3}~Lxy*Uhi~7$ za@;N8URCP0z-0wdo?MZeP6; z%9u6>MX$`|VzztpH)WJOnnRh~wT}CXg020n2U=z>A8Sp)+2K|HSPa!8SQdVD8Bm+#kH&8K+ z_e!j(&5t7ZRcajJ*u-Snu+#vR!yd=)gl_l4Fj4JXKh9)!kNoiiy6W}E)Ar?7nyr?# zVmx*Q&-*`4e-;Tmh}EFi9x@@{3EnMN9i z_@%uXwLq&)>dLu^j*W}U6a+FL6E;&??8P2)@xqJv7C2y+#xXa`SCZbPkHr4QppB>KUAVoHb4KUuX zHhIIk<4-0rrE!WI~4LTaal}f?L8g# zm;M>}t&eqntn>FfpTB=9H|8I=R<}oIGZxoi|GqYNl=6k4-S|o?yI1q$_u1*`g?^Y5 z)WTBz@kSxF>6X~E1zs#wCKwqAbJp|DY~j4AOWyT%7l7}DDQNXlL!Ij_*ityv1GhKR zT6ut)y`X_wR@P=iYxEo^ef^!O7{qXL-JOz_z;dGZu>p81UgDZtdJNQGL<_>ywTgps zzdZv$kE^~7$2@$(9YQ3^`*2w5y(8-j7QZ3PiMiHi3*&@70x`=ep|}RQ+}}n=fcxt# zm01L?)ZjSY0g?kb;6IUrN#N`=(J8^NeFyy70oZ!m7Z&CyhTFRJP00QCC3r6D`w|GN zukd>-7k5E@W3>m> z(^w%or_(THGPf>mh%vesWjPLEfS)UDxsg@>4lpW~;Ig+>mT{f`X(FRz4u1TFu?K&f zu<{4>AYjWzQe%!wo(lIitdU|@3;*`e`iom5_>G1Q9RxH8S&q-(P^)HfRg)~kP8iB( z!ynFR)u+9Y^rh1}8?<$jxYg}X0F!+PIO{KTeyQ_K%)$h)f0oR;@8i9*j83khEH6UU zB?=v0JPG-DESx}{Jvmip{WADI{Lq$8EJKCCg)=!1Me8G3pINX4{Qv2%3QSY^It8kb zTJRL5ja^h5+^8DH8dd_E#f&V7Tr|Sk;RbVVi}34BLH4Fa!}>n41c%9RygyAMZ4LI$ zq>(UEn8AM0yc`>qk(AjeCAekAfHZ}RalEmv;uPHx@UpJ3X+Oz-r>?4k*4&0Nf(#ca z(wyMOf>2H2-+=n!7=EDuv(rWu6!&}-nuj!ehV^J-0PbE0-mm5r9gjSKQiwubDm*jKj`q}s`TKLd08IB>x!wqvsE73(KXJ`}nu!JnDDKRpISLewyHtWLU*vYfht7VSnU zoVfZYovJg02=L=)-F_FMBLe9FnrgLt6({v7K5G5G26Gb^2FJ7r?Cf}h#^68K{8_3` zFSU_JTiP_UU18k*b@}k!1)WBO{v^1n2B3G}OKdLf;*)`=DwNF|1;`WPXI?QA7ehw1 z;T%Zs*jl|uamghobWix~62r0&QEF2?s?D;b*_>zU!#ME0?dFXTf5=?CMSXh23oBhu zTPd#r?GqD9g-hAwNk=dYGyHasGqAztCX8stPKR8RH)MeQX&0Ggf=G;*C=`Hn@e1wx zF)6P+omy_EKps0JY+n8=kUwrwJdd(65W$sYt%K-=oWorG&f^8yr#luf<`tl-4}h8# zl5A}|DyBfz|FGGxU&f{ZWX`IeQJP$tq+y!JFwp;Pl>3xgTBy@$>Q_D0cu)^v|t6u ziJ4zh6tB7T%RQf!JTnaqs;Md{r z4cY>~eWiZ*2eldVE!CXL5O3G=YX7JgfZ$+yfjEwT9l*gWq;=~}*H6@vmDRYR)wBFIjc|1~ z{<-}z8w>zGXY+OI-3If1YwF#MlBcb!pHLB?W@LIc-4O>|3i=v1`f5Sqm)l+?tAC|T;$|4DSVo`8ae`~IH=?=0rxl$Y@Ed>L z`QuL7nS<`Od)O|hMmVi=rD@$o4PJkZ99Q4l!lG5O?Bz!Qw=f*1dW^~Vok(goV)$4x zNxtXO@sL?NIedGV4~WAfAx1J)U6&xR?!*#hs`CZJ>~(KY2VDrtpTt~oFZ?9ExUIgC@e=2Z8Ps@CbR zlHqG(x&MAKh!-{F>zwll5SJ81>`FR{;w7==b-KAQo7^QI@KO21^Q28F0(#n1u_>2x z19UkXCJzILa-4sXrZFa!dr#DPm-hNk2%_#@VzZ|Kg*p$OT2FryZ|uvjrU@yfw`TX! zc2u!qw6X0fRz<+6S!V$ngHLzVxs8?{!r96(2b^>Em#>-gg?qjxj3ivEK0;E&M{Di) zImZc`CAOF6YIis!;3q5EjR)Ccl|zOH+YpgbX(F1+vpP(<7G@`5ln&-(*yr+3Q=nG| z8IWY2+`2j<-M$0RJy~2<-%dr&{m)b>Zfh_mou>JCd!hc!zX7Z#k@g3{_q`ai(fFX? z#pUZ^`r;^T*7?Iu51#gV+j-cu4|Qhf=a1W9FmKK)G1_5sC#%djJR~Xtp85(z@3l@1 zONcg=*VWd{C%jIF>Bhf~Zi6q7@sKVkAKD~+UVLd#QLgB?{+zq1xJ=(fPO$hm@trY_~R{@3j0~A7= zrM(c}&np^Kz1UiyIGBM$)>SbbffC2Wd2F(Spw7xJr=BU1Y)Y@osGBDlDHBpR^BIYF z1FF(X7ia^2j}YY48ayB4nm`sQBZVuM3z@U37$vixeq@U|zjLFv z>3~UmqmFSaf-#S3v7c75CN9nTio<*T(GhviXeZ&Rj=okVv{uII!`&TQv2-%OER7t7 zyMb}2^PR#I$jH*66TWDSR=NdH+)f;0&<^f6SE%gG?~4 zRfPExjb{UTL&cGc-_3otts!J&d5W#`Oy_lv(B4~D1_&%?0+FVejYL7KxQQm;^5ABe zc{?@IG5$8?u2Wwes2)x;%j8|Mzw~VS7{Q7#cuFnY<;0KkO7rGgB-wL`$fW;rQji?0 zzIHZtP08Zkd`nu#bI-aqjuSIV?LJWiZ?7&1j~ka!J4RrGI6LR{t9odB%KD7_tuuz%&?J3^? z%irS8?B~*AV~LId+!pa{1i zFB9bLC@=I`eLyd_!)`>JiGNSv2@+h3qCWFoXLLfHT_-HSGvU`(su|?7!e_g!(SWbs z(vgm$v3@9&XIkmdZ;z66a?# z@h)(gd=lN5Bub_`t@eDU^Or#y*E;XVS=PZfhiAXfZLeNMcpO2Q4>hn*@SOY2?(s3-GRt7Y_+r_+qw@a%^@Xd9yXqANv@H>ev3#Rr9 zz_Z?A%1&Jp*7Y~wvz$uj(u{w`0 zE0JfUtDK#Z$8e?4P#R!0jq)DDpoXybCc*)MQUXLeb?muzOX=S_eiUR>2W zu8Gh+8Oh+aw$``lXQp={pQ0}Mu6smTFPHePa9_4M1o_JTqp$CP_9(EklJ0huih3j9 z3|+jL5)sP*PgpsT61*N0a*vpLA@m39sEns1?DzpJpk{!}%GubYFKy0BWujE`CK^04T3!+~&1;`Sl&O@>PSb~zKVMhWpVY9L~|Km!wv;gf7{C;#9V#V;KVd_6%dToEZx|Y zB%`(Xm!8Ek{&@)UC3gEB?(VvZro+w4;BbA$KS;I>U(Csa^oPKs3VI4AVT$@#Psy%- zI#NzXHAIw2;5T?Lbo;{0$AHXMH)bfY)kdCc?I&%bZ|ffMtu9`}SLy&wu%K}s#1@ox zEy6HM>huI@!AYyT|G`P||Jb_|_^7IL0pRDJbN6-jnM@{=$)1o9LP8Q&fq-lxC;}={ zWJ!P^gfv7HYAv;Bty=3=OD$5h)KY3KQcJB5t)u9Wr&uua!Z(+9N zrF}btW((Rd2~uV*1S!k0uErE17mAu^y{TndSmw$F)iw5fey#--K$*(BctIn&{3%7w zlX;K5s(EgZ(2Etc1@>SAYH2M9VXrPe6MK;G%wa9`}{hMdVh&Z?73?eqbuxswKe=t40~{{ zAKAs)&Z5~Z)rDwh+tv}L0h(KBmy-6YqypM#IX~>{Lv*mZoiPul@v5AeE+|^GSW-Rb z)0^TUy|C;fI?bFAlb8!7-JP{Ub?1_(FHF)ErC8LpazU5i+d@k-FG4T#M>8NL)o4Ig z^@!jTq0X@C>B&<%^#msr2vCqEETwD2L$s|B|9Fb41kR&oLjVb{vA|W3OL9sJaLk{_ z(+SQMO|CHgx-3EcAeZvjLdq42D#3C-$KM)MB*hc4=RbZ6qQPH6OygYRaa!dHno$1u zD7rI~P>Nq_vxf*c!QvT<9opxm;io-{Bz}4-`QIw1`kZYAh(8Y#IygrHbL7s`3{~hHI`j>dws4#A^GxTge1cF=~=*dgpRs33?#(#()gLFtXD{!;IyK zS`~DVM8e8Ki9|m zbD;b}4Jl$QczI~uWZp$M`yT~zHx@HoyrD`0jStfy-@S!iMB~wNR10a?OlR*7I)%37 znpnz;9#u~39#2ZA?raI6Ab;73SYd}e@dvf8mK$GQuH}S&LHuXS?=8LmJL!qOFfG>h zB4p6q=I}gMqiF>@JZ9ixk(b5Uf-dr^Vi71@`GHxSCK;SCD@QXzm&&?p%sknE=!RG4 z*;)BQIaAR*Y0?5EQ;=n#`k{FUXqRhVO?G>E(GWDj$i-C#ys93^Sait{_?tR}CF4w97Y&u3fx|qV ztA&nDr3;z)rH(TY*|mYo}Z>PUP@J7L8}u zZ(lqeYrct7BrQ~JujnT5+CtHgI92hJP>IM}WP)sXCKu|uT6HmmiQlwL|AK5?_Y{tS z)Eq;!MUt1U(k^)fs1$`fuV9JBOepobq$=`~G}+}L=2JR7QLIRIRq3HCS9oY7>7w`I z+E>m!G^C{8HO-E{>0D$}oZ9V-?`gNWEh|wwneL3j*E+^zSD~W0O5p3IRbu+hFkNk8 ztfIOr=y@YyJd~#?!Jw{rqK@+BPrbvy)#j#Q7=7Ge?Y&2nAe$8(P{MB8;_g#KK7rTU)p?=DkmzAH;6&! zbWftDPr8og6d`*+U$yiQA{JXeVC&bUGQ^0^a~V0xyVE4ttjvy$)H2M=mSMd#0JsCbP+2TXf%Jw_7s+o zHDu1i$*PBN^a7`mNtX-6d^Alb8Uu?ho4)Q5fR5Nbas6y&j;@=33z`Q&1Hpv&v;kkqUWD==k zg=isNSuUvNxXJcyMfQ~8%e^waSO#^t7Mx=5#sW5Ze>5hG*d&CH#8y~M9!9m*0%7uTYLU>|l9H#R>XrsIzTk7U}D zqRyl>4mR&M$JD?>gg~s!ofhNMEZLdQ*ts&2kIJEsIMa>jW2IiRpc+zRY{Bn+Tz)o0e)1P%g(~O6v8+kiJhZ90J?9-w zji1@88aQV~6B)zFFD-YGrQ|9*BhjJ~|ARSJ_oM^6bnxGay7)DoYgRC@T%xFnBDj~fxp|bSyhUCW@lpjS)vsaGgHFGo{#@;Jk z1WVCUt|%&so?K{S@+yLYOE9awbPQ(L1*H;FOVs!rrS_tWq zz=Xq7;|&up&j=HLXpEp)Wzfgf{IWzy7B7d>vxz4y$P=BH!dj^dD%rdkWD)hMjph^6j>( z+TOMcYS=ehl3hX0t;j+Kmu7e&62lhMZGNvu@%eJHRBq1shL`ud=xzd8FHea;#ZOnQ zg%Fe%tLNIc^2jbRq-f?mQS)c$8MKO|YuIyKIp!w{ve#S4m)w?)!#ZBgsdVgKh@PF{ z^AutgR7hAC{GlRK!~!7@4Di9U>;TT3MsL*DbalEwLYygSS#E=K35p_(Q-pDnG764{ ziS3!(WeB0Oq4HE_d1YO6j}fPO_^V(q4)7I%?}l{Zm1TN=x62-L`YWjaW}=0VgIA#q z=#%JL^qF|sZ|Q^3^mC3#YuSeik*-WedlmK{S~G6q(UAWW?Wc5R6-g`5O1nR8R~3O^ zyoYSJEBOUqrL7{*D{jL1W!;*o>YgI#gF#~`&>I8@SDgbPV~E#W)45za!>vG+T$vv$ z7?DcocUUz#k+ZUKB&LuXM@OMbCB->2c-|Mq={~M3BTRS_$@ng(<>PE~KfT>h(ENsg zp;Q#o!RP9DOXn=?rX4v>rGuP#Aw)<2Epi#MLOe(8s=d||fp?Qt!K2Yr>6&h+0uNpP zY)P;>2!Sgg1F+{7(G%%DUU7|M?LBNcm(CG?+!@a~!48fc(-0Lcw zqd*`rtJt_$i1B6`37M*NL?@am$k5ZGZRZK-*M}g6hNBhC&#$IufLqE0P1^uHBiB-2 zIq7L1oq`hr866PnICmxqn?&T5Iyw)bD^tEb=Az3z@~X=N?U&{cXOYabXmN!*ZRHYu z_R<`ZweV6e=ZDUuI`>L<>8eqYG<$}#jId9YgPBDUK3Y;nds6OyoO1p8VAGLzEJ1ysFXkK!Td7_f6s&6_h;&Q$jC6x^4h=&$3^hXx-8sNe zUq0*k7oN5DulHJKoqO+loqL~aUoS?kY)v)GkiW&Gcuxb`3n+Ruu69VbsffZCU+(r~ zO8mw|f6Q)JFWC)pi!W#V>7a9(ANSR?X?HEiy5cF(#|cXrXu;nmZzcAeLTnM2;(fNW zuuRww#Q{~sM(dEH(l!YDTu+YgTUX72cc!XBjSrH|`cFmo;BF)6nlvt>QV(s@~r|eUD>+7qfGmn3ug8^wi1B zw}za1ue=5j@yHQoyyEoMO+K~oi_Ss)9sM4B{(1mX?&=5F_E1ZED$ytxwUg#s&l|Wt zxA)Tfub-erY;;BtyCjRM=z=-?8hurAC2*aLYja{Cx#uEWswMw(SA>a8LP_D?M$S@r zXTNnO-9*`aO=Pf1oS=kG_*H8_N;XoHYdbo1KIgNksUk060Dc^zNfGznDZ}xydOtYT z_I;zEib5cJvhl}c#v!!5ITh7cjV zowYn=uMw)&q`Ip+UdEco@~@>Q!>d~Jlt(?Ln8rUy#SH!m`8t%4?ZVF=oYNDu9h;j# z_$uOq7hY!72ix>pZ!s(k@xt`ENFc{x2-4Q0s#^V6a(P+7@s(uZXoBgo&6P2_B(xXJ zx)9#kykvC}{{^eZY%c8qE$ z05AOPCtaXNhlRvToWoxrBSG0QK8s_WSFD*N-A^==_nn9QYqwMerFpFhdWcSdAnVPl zvv7@-znV-=ZJAl$^%lWb^MzhCc4>X1>>fNv<3~*t%?f=w)+DT0r_h}@zdz*_n|;?@ zvl;mnp6Vy}Z9)Xs+mJGgjZu$Ao7u(|`nN^K*+_F{YD@8r(%3pK^wWEydrqH(z_R|K zZP2!Ys7tEX6a&s5_WLR>t2Y2nV+jVTd^4sVd7x?s)l~{ok00CYd;jI>YYGEe`VZgh zw|EN9WG3Ed;xGG24Ani`x)ExE6iB(m|LC2P$l)#LR%8P!j!TcPXUqK*e?lWu=`9F< z_xHJqO<0O-@0(|YZmPVUKbyhjOfR5)*8L)e(_;#N-0oncNyj_+RwAL*FtYbF&IzNX zdLL#llE&Rx;olDRr3JO~#9xOEW;;3dDg4dXu=b2UNL^1!mxRG+H6Y$5Oxx#bn94^#{3-OeTGOCT*lq7~onJn_wtS;M z%yFdXo`iOQQ9JlycgDXwYy_tz_;ZCPK-&^Relw%g=tlSUER}7+HWRx-A(Q0>DsdG@ z2BmY6%m#+=f8KKWjfY?z#S3?vF_4I04t~u@Bza>YZ2&nkmxX-6vna8eJOcAOth`nEoPH77#UFzmSg`cuE@trOQsyq%KjsnM-kGKwUVn0OyyJ_$7UmP-;zRv?SsDSyYV zcvO5o$h$7+`K&~q-H?Mv@*mL;uJ5E;t+lmz5)LO;jBFCy{Mu5upa4U&am)uR zz2ra(?LXljx{B9qjPo0R>uT3YpJmtMb2uR2O0rELWS4H)Xc#F_C<>Nq zvE>zk`Z2`@HU@`jxD!6RR`Bj_Fy_IEq<3Xa``~02wuv@)oSNHWQ_HeXpDQm-cgu%u z7%`CRkSa~BY=206sx75&s!Ot;@Po7HZK#~rp1wnFN{5{jM@rt~a(e->2PXCk#DAr}=DpvHm{Oqp{ z&zItfm*?+-dCHbY;_zt8iOc`nI%CPqNRSFr4y4W9Tb3niic)U2!%V%4?thiMoh}yf zLE^!XWQSVzlPrMcqnY{Wn-?p0thiToV#)ffwQ z{xKT7la{LT+swB5oBadL_vOnG>MUoE&QANw+qT@LzdQYnczTz!O{F_tC*6>C$tn zYB?1zc9bYpU$B8I-cr4aOlQDuW8=Z>owd513d9gdy=qGOyUZB#M>X%;j8tztyVaCt z`V_(~M7KuOU7y@-v(A~~lXSH44Dol;5<@r9A9RK0wJ*aHH*&Zy($%=;rBaS~M9}FJ z?=3QB>31POV6Os9toI zdP*wKMZ5nWt-u#UvA5{eEE7Tj0>gE+l_vsj8z#cPK$2`;&(h1nR|ZDqj>X=zCZDS@ zUt6!8Z=M-&=6nZ zMLCBGYhQakl-hnP^5K+3U2s#mD|-Dkd#g=AolvotHo$*5l1LqM#)nr{eCaV(yQQwQ zJX4iUcW2diR&05A)O4nT?_UE7v>FwGE$JfokLpJaof>a8{pUcqhdZ>m%ZE$7;B3GI zTQR#}=a)OX6KD+mMohj-JpV%Tx%HT#NAnFkBxSuj$Rg!Q7^=crwyu@uFe^yqYgSE8 zlV`=W@RJMlASuvtW;ST1=EZ2b)6&3Uspw2w-R=ANIoCa{%Oy-#5a!lV;P-e4W%E&q zvb{l;?NnI|@35VnYg=v=HDuVqc*+9HYDl_3+epFM(anHb!n(BdEiIkfNFA@Asz_QKfQp(6YBh_<{EQd0JN1mC}s|kLP|3;9tXe^bve z@C0^W7jyKj<%p>1ZQ(cx!$+15%(tM3+n8-hxCjw9+DVRMg$)_|}Vs`<*B^%c^lo-O+qs#oZ^LkOYT2 zH$74U0WgOhj1ot@g<&=LG<$W6o5;s`;yo}I=y#&5&q4jO`4qt6cCbOGXiTj;^eBJB zYeiQ1w-aV-QvkQLS+FN=sDbX{k`K;?U3No0Tkr{P-#^EcVb5brVwTYpRjDY?xFEQh z=;civ)=_lq3m7d}+3Rfc(A&-!jwI+2b@G6|-3`o*5kK4!hZ*lL;53=}w>4NHHA4&9 zkN>wHv8>ofAKhH6VD~r8+?l&j4LIGoc>ao;{t>mXFmmjG*56P_l1g$r@OlLcwv2Y~ zmI0luPWbMe?5?n;KFIl<`NBF-J{_ik4Xrt0n2VYcDrK>ty)nxu}2HwGsu2g#|xS9 zDU}#}!t5<{swpsww5u?PR3nyxqNg`>s=eRMG`WVaM!}>vjkB0U$mfe!kvMq-!4+;7=+7P-$Z31XfH{JUGpru2y87J1<~`zUGcfKp zFAdvJK_iLVz`A~m$92E0MtQZx{Lp31Hpk@y79l|?LRvTGl8D3lS+R&l_t;v%U0e`8 zxWZ5qh_;{9;N4fU-p^Hu*`rk&!oZV(Z_~xM7iNUj9Y8g%@QA?excd6yOWkd>i%T# zg49vPeJK0hIb#XyhRQfIX}#{S1!N3wbJMs)Osq7zY8@dqoR_FYyMb0?DZMm4HCBfp z4=3M^=v6y7u2i?SvHS(^*l{)G&#@crkB(y@L6t{)Y1n?;UNpWp`&ZK2OaDBr#z1E{ zA*N)T_SdsdLrkR~FQN8N$YJK$mH;^JjbZNO!2eD=n`1{TVAlYz&@8wID$0+rX{nAUcgVX?$7%D&#erv-PH`Y!&ntQmzXKN$-Aa=x17>vLl z6TEi|MD?)3Aau|(M~4T@(1EDyY0t6Cu4Ra6Nb`f>WZk8a$--GXhv)vC=K^L=4z?`+ zR%~e^R=MHmXe=MIZSrspOSs!ZVJTKm&3BU;GMI&RFCkZ-9BoqxPPbnL-pR~~w}x@x ztGJMNe?dqO;i)=06-AD*I{+`Y8wd^i!dH9KXOl~*O!;@o$I_H9V=lifUmFH8s3yN& z67$U;aSnordgjwA6VD^H`rKo-Jqq-dFCnR8am1zz3D6vnAQ1DJ@Y3M^H<)EkxR#j4 zGb)|eDT2^VWk4>3+}CV4E}wOx zkiGFczGejx;F_Ij+xatOCnI=Ekey;V+*77LQpMviz6;If=Vq=u>FcQHqp)m%>6rHv z6l-A3fPL9&yIR^G=S7_s2|$kp?~FOnCpMfF<$Q<(82IGRxDs!wi*Y+ z4%oHx{4^d*wRJ$T_DSb6Rv78#2VVuAL!GYjP^yLl#jCYya1%&ktBt3;T(~d4xu(|4 zGs$fX!J>hv5uQm|mc2+))ox7M~Pf+G3*Dp9AA7Q3?lM#66+#l zbJ(Mdj)5*NVmAANLXV`b^i}9?4+gRp+qqVS8}K<|S&!yCE03~dAb`D&Z$Y#zp4P%T z+^xR;7Rw@j*6RZAOvpmWKY%@NkAIZVDp3ij9J*4q6 z^~TzpA$?=yN`LFK+;KCeW(AudY}uN7Vp(*9IJ-N#R4d>>>Uf}USFawg*QB<<4FGvB z8KtY-Ljj}RtAze9Qr+PPk16&TptG$ytL2tUF42n=EA_j}9CX%#un4rlZUss!1UoPb zX>Q;2Tp9U6OXYm);JFO)vc0;#87da@slL3gs&sv@`kX0!hl0@a{Z4M^b=Vg4eNE)@ z(*&n3EG!lsHB5*<*fUxTkH%ZDDG=Xg@adb|`ZwGNe>@FiAbXnMM3_G!RpGv_RQJLd z8C50Xg+N)a6TuZ|n}UCq=?mp`x8?5{K!&rAk98ieBi`TaV6tK0?>KC;Xi?@f<^%X>$m*W0*h4ftNd1!bT}i?fP}m|-^u!4Zf3#}dOraz+g5*e_Z>LwfuFWouHtEq8rmaksAU%*mg1MvKw*R*#}+SlJWWDcZI+$* zy(17<3%z@fNcNV6aP0t390*P(k5G%{J6MaKY{Vh8h;*@kY@ZKO#w{lG(H`pQ{c;dv zm4!;!)Yf8EdNT(90)Ox4&@8}F$I~0Me;e&?m!84LKV3QhCZnpU_7k5+SJ2?ZhJ{5E zebo*ne_#48yc~dhV^^}M4zI1N@R%(LaZl;sCY>p7@j;aoc1FhSy+~?1B$fWukA{+_icebl?fK5A5(oM!P5ip5z5<`CJqj;b>RW?4`dJ-# zZE;%@xsl@qpO>GZz1dxQ^9zjLaVT5Wh!Pv6-HveLIOJR{Ja%fXwh6t{?vU&ulBDZHjIA-t5873HN$zL*hw%7)%odyzlgGqk#`%2n7+ry>*%OA^Mmk6ujM*DBsxQzzMUx# z$;?o^o4yU7#{)cnFhVSOxx1Ft$M`8>QZL*oDK#`|dHA5F`nRj+zdCRwF=ds;hJ=u& zcWhrX)ZQ)xX=O(jJ#h$J*2;Cr4A>oiadoHi{^U|YmHXEIYuz#6Z3+El9IRhs9kwE~ zE_eD8!xxcF;bkc*(m+GgK;UN_GTY5EelYZi+sq=Y)CisJVBwv=*)s*=?n9omC*{1E zqlo<6@sMQA!z^QQ?4J+y6cPc@-QN1oj^cu9n9b5nt9x zK?QR%hy62Gh$)w?HkJRlA;I@tF6t+C+8>LjpI*Lk_@#f z2MZqYcD-@T#ep`uW%>q5aCftJcuC^8tP5*^iyr%1CSh&elwNR`#x~5}S$UM*dx6@a zu@`CXq^ai`4ueR&hk%E^lDoSL2(rBijwSY*G0uOijEm7`5s)e2joQatP@_FPZ0}Y} zF7EDV%G=sd5)Ug+W|!ymgHzb7qo*@D0&-%WD7F&J1{Pt(0LuNAkxYsiUJmV&*^o0j z6Z0{9@wuJz=!=I=o}12j+2Xrs-t-1f@~cfO801NE$U_}TCakg3zduWXNWyg*(|w-^E89PN9qN0?#E!+_?^1@BYq!2H`L zO;igmu9NRwY5R3B2lONha67SB3EJt~dN@C;Z1ui4tGqpw0z8~+`(NPtp9lj$cM#Ct z3_$d>+xV^y;J?GsezM$lOe=QIFZPgIc|Qa(iEOLBTSEuWEN>;--%p}V7F}ya3hV7+ zc`w^(@5YGr#jcJ3j^9B14G_#kY?!dt0OVnNY#GONd8B;`vv)e`H?~J;&tI?IP&m6a za}a)al^?tbadZ*2ke_mj!XhA4)SYC390(*qAxi%+dOePr}l2J7MJz6j16Ho4a<6Pj*2mO z{jveP{gGeaT5)Bddto#$PQ}4GS6O!Abus975uiC%s=Ztc>%3Ojdw^?QlWG&T*LB|y zV(!m>4_##3TrMxSwcbDU<=|Tw>$zWTUJW*%)qRhowfC>S%e`o-_j0y-xa|}I8^G%5 zv5pXkD+I__r94jb%Ikjrs@!wb4|tPxfVmF{;V4fVE3CgPLp?&NI4awWbzs5I?>2L| z9dG+eu1|$lFexcg*TZYQxQmtL3o1usOS(|;_QvzulCuHgv}OM#F~JH*3mB{EYVP;_ zMDHC$cNt<}MQeO@cWxA7?(~p5^u^Z-9*GU2757JoFF2jw!S8^NOB=vkJ;30?{+F@v z&SjMYnE&$q5Sq5ZzZq|4j6+Q1wgUx?L^hC&fDL^cYX}-o&U4DK`PTA#JM9!g0p*XI z7pd@+y+SWVJ0nt|nWN$qxaVanzIk)FhN-{XI#7X&TpyO)O#cpfwj@#`qGGQNKWT&B zV*LD^lMIpK@Y(Cg(pVVW8E~`Tbr4NY3(7Y8lS6``-ctCd3$w*pJg<;jjso_%E1=IsLtQR zFkr~Fbie6tWw-YN1grzPB>DM7AM?YnIV#R7S)1Fc?B`S9m$-I89OXV76>-p3>8mEq zqokSLgwngx(hV31zIZj`@mk4c7X>0?Z+8K+geXT1DHMvg{%x&)JYG}K&hF0y-z4A; zE*m*5H!L7@HjE9vO!TO*d15Bxp~i;EhZ?2py}`X4_Wqs{ciX>1L~p0NR_!8VD=X33 z+RNJY?R9?*48MT&rbCs-*u)U}iO}Zb_piMzFPvQG*`%F7GhTjm654UES-H86!?AbQ zW`ei&A`1ZQ`Zr6CH`xr+UZxjk_lQZ+_Pz@QbS$=9tRhnmXQ{n*!Pp3ve|KAT@ousyW4;cD+mw7>P6_&68vkdZy6$K#SK8WXEVES(&Pu*iK9oa$MUj~ z`n#h8e}@ONozctW^4r1cJ&680ZwKf1kgM?T4P)$V7}Q0lv%k+JcYEs@x;OaX4EN@; z+!t5 z+%=%~e7o_NduxzieYnfuNU0=X)QQUy; ze*bqf8@qU2-sLS3vyMsxQD^q zRI~{9ZlS~=O3*~IuBny}j$`&C2eM#Po>e|vY=^`C`hDzL0PD** z*Y`SNI`CMor{GmK+Jm&Xr;)Lseg`0)6)&+t&l|GDoU`e_00$qCLU9~3C-tjwX?A_e zYq$LlwGta9r95``jXxe(hpXHaoTz{|KXLN^{E7oGjte9?_$p6Ed4v(_C}vctIpO%l zxG@HDBfY%bXMXsEzKFS5&Dzq#`a&hEmjkO09c|pm>H~0e>jRU`bm|58)9noR2j2pf zwv!Uogo}SU+I)o$axYloq9B)Lm6|!8dGpPLM{^$u*EU4ksT+P26uh>lXUoIAs0tc8 zf+BU3dDt{;r|a#kQVkLR8f!K5ty$a?pSM94P?0huc{0ho!AGa4I79`Dv$|kebP?^M z==1t4qpP+y*q92dNX5~qhj&A9N(y#s{pv{N(VPZq@bF1R1)w4`M+jLzrZE zi7IQ7gLu6%v86t?Uucen2}TZ*;5_6fkp8u0s?|@O6C~pa2<0fuYR=gRfY2`FW7Z-{ z^19x((ZJhvHNIL-vk?|*+PTyJdb}R}z;H z7&P!CbAFU9VpP<7)klR%?3~_f8i=;pS=0S6I^PJ?Jy&Zlqo*l+_v9>ev?%8XVbC#N zEOx9|gssH-)>m!HGS56mmqSr~9@~SC6|VYS@) zSXSu`zh17}PmS!U&=itvj*jMP_$QAkNJPp+{|4gYHa@BnEoG~}S0t{sf{DScva80i zbm7wXH4FW9%RQ(Mq~5CSdA0kR-kO{9al~n94rS;Dt`d+Oo#2(f{z4%Z(-Edq{xopQ zlbHa%z0+@3;dn@)B#QY%D(#!1g;$dKlc|VMvFU2z9$zWEn_Ljp2$ks?n9agp?7bzh z%fScQ-nE0Io3{9J^*}iF^bZ%?NNygD(Gy_TKLVGpZi;>Hqdm>#UnlE4M9ySG>6)wi zuv3!WfSzi>JuW@Ge=ePyC4T?meXU4Q8!;!cU$M~ta==)u+RWGDk~S&?Tv;jfZGT+p zVazc16641HACI05qA1pi>>x3BSnp2_(8>(z!>4!1)g3+7M?5dj8(y@#Nf1ts2i|*$ zATyGOLDp0U>vksR=3?Rm-mbCNkkkU4#$~wV?>Okzb6*5p_h`!J#s$>l3%-zQ6rK$x z=`YOJbGgf3a^H7qa%Lfy+>#PC$Jy;j5y=75h-6KD5~dy`BIzROmoZWhj@Ob1ud0_b zi_72We<#4d6?fX(*|)M1X7B-O+SR0%QWaHoKqO%-E}eAXlEk$!{8SQ%uo zklhU+kGE4fZPyun8If-Nhvx|gYu6R&znPrPstJm_UwYp~eq{Q2Po&=G>7VdfeU*Ni z_;~~CUV_}rXM3QbP4jLXW5yAFfQmlD+S}4+<1PqwG!dMfl8Bu=n(UIU_)5O+>Kjx; z=AFy1s#w&sKcBez!;2A7%M6Ynl>D)o9?m43nX7BbdsUE}?q^q+ry6xoE79vOV`t|3 z>G{CUYAo3Bw!_insDErFG)f|xs*=@?5HRibmj!V=bxkEUFG4Mv8=Qi^(p5Y+8SzNp zBG(XZnJ##TV8Y&3_eo-RHOk}7m+rur$Y_;*OdVjxOQ}<9@HBAXJ?o3B{Q2BHFieJu z#s^!kHM&yWh$?7l$?Km(lowBm)VumgPImrWD=-6*z46IUheE3nJI1l z0N*^lb6mX)zQEx7W}V7fO4=`I2J#;t<8`y`9MaiJaliIZq-y?Qjs=fc2_+u+yeEim z9(j`WVJ9iZLl9v%cu&JmbTHDTMX69k=_=s`7IN%%am7FokT*E5~C$# z>9BI#V?81TAZ~ZC>hZ5_u4nDGR=Ivs-b7A!o-O#sK;2_dF9u9SXENHCvIv(AaPL*! z+wNy^zU^x}-x3|73T{nW);+oY0Ta z$P)IG{~2ZvT6EKL%N5YSHMRKs zXcZ=THp+)pwsKK*Ys`d2S>TJT+Lp9HPEB1~`@OF`{!Gcb(`8XCUuJ_DRG{6RXFAJE zsq`dwlr_rd17&@Rae^dg-wdp>reClX@2Oj!%?P-*Rn%^i!YV=_TQNID^QYgkSYsCv ztdL{-Ea?V5PEgQD55dH%)Qy}1KTd15$hpu*;~ZCPg#G=vx3HD&FpN!cDnk8 zv37&W9&+N-c%QP~Px5iZ%~07Y6msS4UV$WK4dVA zMNIr6xZi)rz4xU}teus2SQd}CW&~~dA~%%`vWld;$Y?ufTg%mIT>M}phl?WKvKxqQ zNu#2_`;$@#@3V{=*2#lh?}w9SW|+i61Rv+1gxGERLR)$3bBoGf!lP`E%GcE~O>Zw; zeim0<)4>kj#Z93+d&Y~thBI2F(=^h3Kmb0M&?I9!jB4d^9we|HY@8@_D?Iko&2Kjt zvA+Im18Z`J_-N4W_dINaH}2X zGQa3$yD7A{FwJo~rp(prKKnCizRBvmvE2#7!d7cV>3X6<)OqshE?t`U2haZ=B|>sX z%ysgXTE-3;gzqmSv=YJxBD__Wx(lhlkJwy^d$Ve%cQZ88U(!{M@EjuxcEqc-OR zEGwmTW61eyRn)_4Gu0$6(KgX43GEAsB06@cQ79SCy)>l|kIL_uNfDCO{_IVaq#sfU z<|)o*t~iO>s_$YOUhfFaGftnM{L`J~EKLP85W@T-=8p%qnT3;)wDb~{nrR0$0VWba zvw4)r$0eCb2mRW7MFPM1^?xz)13`S8_XBn0psSdC8$Y8&XWOIRJVQed++&?d<7WZ# zWhEICE}utLH*raA-cnNebuvJAwDsTXUgx}V-~odObsVQPTV5fVIsfo~10Yu~I@iFs zTb2gC(;}s*dKtAH(>=48cjV5>6?pQT^dIIS*ZHDMq$6oDV$-vu1A|YPc`-L9LTK9Z zUcCw&8C)Bv3w}5Mjt+_GI?z|nH)U_lXQ|~AVvwdUWp%~7SZk9_yaHh7eo#q~6w9M{ zbET;a-cGdh7IgW8#s#h%8&V0e8<>@VcD8Q zo@{ByiF~bkI>m2MbTe-~N?)F2BlaX1cqwsajEGelROCwS*wx#H51%?`wJPfHomz8c z$~TAR#M*oEgIPYP__!Lst>?d7{nxUIY>VCLYA%IH4-OCrFl&1bAmjY$P%rMkC6bP! ziJAHP^M&iE!i7r!`y0{Ey=o%vdc;)R*ewk7MGi_TUl-fNBoF^ZS@_etQf`%cH7^R| zZRw5ThF^qR4{H3wRYDHm{^ytGIzW-49o2V8lnZx*+1XHQNpovmPL*a< zOEfCtDJ^!=+%owY5^qSVvVmOcnIk%6wAxfW$mLyg894;t;LB3Mge2nKM%KDq@0l(1 z7$7M&>{^L@R5rEf{MI2|S9b4QM(0|O&Iw5-w&(%QeyFX=QXxHG426C={56D?5nZ;W zdCK5yw;31BNb48ncp!nJP~Sq@5LD#2)thgH)FLOh%L!EtjCk4erN53GLerkm$y)Jd z4sS#=Gffy@c^6}y_d`~=MnXMLplI&%{tc}=_HL~fLDQn%*;VVlx$3p-*hdu@j{6e) z9eRz$5OK+)rRp`Sd77hxme+shI4V5cIB;jZ)|M%q8%%7K&(pRb4PuGm<1R`5l(q`- zBo|IPu#71^abP^(h=engILW_NX|Hw$~m<@9Yu zE_6zNArA5QgLF5qnV4@aqpD^8&wfLZW-YsqBZ^uHQ|)yq=V~u`zQ{YC7Vd4ee>wc9 zHWsH5)|&nZn9SySz4ea{Z-rB!g(v!G~864$*3cGevEOK$j^?`#*0&E2*${jK_fc#DW zvWR|}eU~iNV@jWfV5S zKAt4eUeyRI74X~^-nV)T9QWql&*W3-ARAogiNG4(_v@fKuhU2t_7b5UM^ibw!!SlYA$*GSk`!Ol!)rQAk z&vQ9=GML@N=ND%>34Yj&P?Z^;t4fqik*0FLRkO<8hk_hpb;A~VZxxik1pXHRTtz%r zEPL`0QzO=w&Fk*iKQjYDZz}3KN(GzqQ)VAp=%1&|tO-(D`JapVO(zlBRu%T7xjQe8 zM2nt`IxGpYA9cRe{o2qWTfyWn5@)K_4BcGPzE3YEnTnFZ3O!jEiJlbp7!kH(Y`~KK z`P&VF9Lg)=f|SwOC4U~En{bix!>YuSyBW7GWK}(M{V}PS&ec%(Ei#T+i*E94^QG1smE$<`vW4o0Z+ewp=8UQva$S{tp( z{;BtcdC^0Xo&5g1h{}>A`9tLvKw%v;8)YED8H(DAemX8HH>|%0tO22;2_)ZI;)rl= zF|NJkaeq-zA){f=PvvXNts=vAIo>!{wZ)aF_eGmy(3w2Nys|fJc0_3*N3B`*Lf{1}?Atmb%Y*bu zw?V{-vV+0FnKyqv{+)F&{oP5Rhhi=B3!@!+)?C7qJ;B}0_Gu30FN@+|YW=_xy7}6S zE6&5yF|}L9GpF_eUWzoFByJE|=^H4q6RrO@jWO+5l{@w0%ul)ZkEN9ptt*N>(6C69MMuO!r z+{1L`=S|)OK+n*)X};=+hj}MweE%J_0?8Je*$#|TiIqhk79XXS7U)o&O$(FF;%v@i zmiY~NlE+zs!_7ai+z^{h9cif*kvld1Q^Wy8PDQU=T8M0VLr2BiSeTvX?_6-Ac#=RR zRB=Ih$0%w81it-@C83YX6!d1_3xZXZE=|PtZY}{P;QN;=9Lkml=0<<>QRFRGe zs0^`@^!V2DP>vdDa6SPb!x<1{umfm_*)xSLO6|)_xq&C!`w5HkyNJ88xv#grEy-25 zxz?j}Zad~8L$Ao_R4Jo>rh0Wwb&^AJV9>pTo=xtb!viN`Ib7j zTNQ96^q2p1)Y-lK>NY0s^7r{uqx#oJqHQ?yf7+tBls*wmYuQuKyi$z83^|NnHOn2`=H)0`7+7%g*5~Zh}de}QJL7!=JHH9`cBjvZ| z0lA-TjFc)su6R@-+&!g(J*br(6gXbI0m0NT^&9SoHUx%|xrB$?05M0u*It!_5P|nuT zI`sA1O=cz!i^YY%WmxnnoAQc}SobgTNaWz;#o*3*C4TIuJ8upj*5 z%;QOmBOvZvP4!GVlo|Z=cHnoY;IarJm_D>dEFqDUQtoaaTPw=I3}p-G|5l_MFCC{% zQ#q@cmYWnn%IDhM=RVCz>l&1s|05)jC}zT_^do>$82zi^_fNU}tTc7VC7YJZMFy0v ziyL`@sF-`PA4Fy1M|>WBc=lnpeeWZN5YyfW96=s%9zlMAcPysIjnGJP$ZHYlX?57; z+(6nDad6bN?_$OnuIf;xLruR6laIAGEvbx8A}7<~St75Mo9rv# z-fsn}YWnQuDTeQBU z#$WM%;kVyrbk`yu22bW7Blg$OVo9GES!f2rQJUph-n8e7V#<+$nAwO}ZmbmwO$?6~ zI>I-E`@NYcePch=o$~L$Wn25hxvyrly#87j=OhR5jx)~Yv)0IoMFnmp)Yq-D@13ZL zEy3?*3H#-GS;H56g3w@YPP%7y7skc|H&9DsMNXUh7Wv^b4@bV4DpJ(4TX4wX@AVBQ{e$Xz);p zVX{yi9F9Qk7u()W^;1ks35^?X2*>6>x>-~h%h@gVbHK**OLltgYf72DZP%0giF|~* zRl$35a@^{}KY2X+L##tDLUv8~&^+?1&7yD2K*9=>Nr!`Qz0d1@b^E?hFI-Z3VJ*$m7>&&%Ns(DC@0z43 zu!ay$*%lqdJGVK|O%&Mp&J&v><3|q?9Z@aSO?#CP0F2n^KdP#&hn={4tVhp!|J~F7 z{0cuNjvN&`RCwq7iI@;N)!lOkGZGbkSXOz6UDg2j@X;afZnCL{dVS67L6b0*YcyRR zBX=$t73EQfK>qK8#@*|Gx#EK2e$GOY;`#m4Vl+N5o~)Y%fIHt9snLNBUyeA&BIe&D za7nBbHDxT-z4m>@HelO7=PfiV^#r1xt9Jn_o7TyoTM|)yx7AV?sZ}(hls~g8o0#4t ze=<-UoFF|PC>WE@Nl^%9cyjQxx;67gHRb4V0q#sNb(eigR3rEq-1aa&NUxUk@p(c( z+GK7b&C#}mrLw2J;2gEhPxkwltBF1q3%jI8p1zaWVWnVugrb*k85#fEqI$9{v~|`* zgjRKo&Zj3R>KAl%+*H`;3Go*j)*Pc}z8)RLxMow>4bpk*7P_%4yneAS#+;}B$gg`^ z(6I$hIiXv7@%f=k&i=cyku0^n|Lq!;;*CzHCp1-T`+Vny@Z>d7ehDj{q{$v`6X11h zZDD&!umt% z)k`M{?sl=SKp!1sqgq_4*c^Q9{!K>|3`zs_7kjf~Rri7r84>wk;@S;d#uPEO;ANxC zsg}_4vd_n<^vz?R<<3P*zj|oqirT7s`0BUX2P?=ga>YmiWJW~FlX8n>DcuJ6e|>db zKKlmNP*&@{6dH%OLRcu@@reSsRBX&=Iy(iM&SVy<#lH?V$Noxsd8up@3wkKOCfk~| z_yl|Up;%Yzpe>Hf!GP2L!L&J1$$gV85j-p;9jTsA)qiz$(sCeW^L@;~nq}Y8A%yF^ zpw@e{8T>lZdg}SIQ$_hvlQUd1GmfRk%wm$Cn8|z>h%<5NwE-QWMurbb2bWT)*M&6Bk*BHq`N_D`1#QE#Lj)5vT7lDJ70k zd8rFqc_+S3n(%B@sA4+>P_8({m5VQRNH`K=rFxdyo-JZKvv_O>5UWC7_n%B_Q_*$W za4yl3A!x;@^Q(vmiKQ)77DKV%h3Z7&A+A!U`F|;B)E+5LT2wLfDR~xs}*JAd-2w4%~9y=Ln!(oG0s8cVB`&fe{4jJCuF9| zJr}j2d-B(-60l5YE?iM4bDYX|(UrCw%};(3#n<|MfLJ=$M^G-gyo^kJ_FfPO@q%G` zgO@nO+>uBy$E<3Ui``z+GLSSCkNf;Gd%sY?5RCtZm(<`6jwNtYmxLCjC=bB`_X}TyM z*EU2+tI+yw4k+@iD0$T-LHy5+K1-ki75p21O(;Y7>t$p#_qjM)mE5z2{B5MolOZye z8dxK}SEp+FXW;hyzU6DzieEggzmFY_19aX=kwbYu-fem5iG~9<2R*;#cSg?`GjjU< z9{_JakiQ}s&z6=&8axNXk;2h2<{(Cj#R<_+dP`rSPvjvFCsrs2l{N?6nrf(fBYi4_ zTE6uToX}!tFiN`_jpW_PT)9%>#zT3>vysVoZcZYrFfh`ER!@_K>4836GwnGM%d{NU zawm#7sG(;3TH(?HwCdjYTFr56%~5S{X}njL#9Y5m56YtUO$m1gJCG?gTXD=r z1vN?R2^H-jeP<1&jdGx!aWu}}T{Bc;snd=OGjnDTuogW}#(($4L_wv}IlL4wR{?$8 zbFAj77iI!&>3;S*8^82Nw$NX?VV&Dwe`LR^?@2Zn59#UzVpcEc9-7__{Wo;N?BTyY(pf@j@ED~Hq8#_ba*&G3dE>P22^{G}Qq-5a^K z=QzqEZ^S4sqaIK<|9Z5u;e4b=N1icRh)QMr#@ZZ5hCR-!93uiswLrBPs!7!uhrX#^ zm5F0dNl!lU)c()tw4hly$5cMVx|7jr1nP6%DBF~8J=X3@T+~&c#K`mHwX{fCx}kWz z+fuGhFUid@2Fj~g!`j#dDcnbj4JAfqCf*K9Yxr~pd(5#?NcNMP@A)bcqG#W1uRgKs zB^yY6e=m{DJ^D^-EK%{PHs6&A^ZeRtrAy1|v&GtLX-*E%2*8XT`+u~HdLruF-23l$ z`1C72gQtH=Zl$kSuy?<_UfSaOf6P{v+g2C#4*cS&nB^;}i>~_%eMxA3jVBoH?PY2~ zQELY~p4_d@PVF1}&KB~uNQl4PDEHm7Shty-5IyX4tg9{iKBPV^CD#r?RaI&``vBfpey+r%f`1Dl`vADrLc@rpHeBZ(~^!a zDaM%i$g#=e@kjI2RU?_vj3xBC=)Q5zkyY@D*=zD~y3f2;?L~mjtkFOj-x2OUSl1~0 z70Ol?w2bMUP<*nPTsC*E?R#{IThevTwy%%8%1+!pfM$>$ArO8BI zXzT*#mF8v3JGP@en_J9(lrgV`=4xuxm(xAncK%Gxs3k>@-Zc`F=TmDSXhSG*6W-MELlRswtRV(0JmAkI~`}UNcI>v5d@7#s#4hLZA-HlR9)*eQd^`w{~mF>azJupATrJ&;Oe{J=iCqEIL-DnH7KD z=1T13n_BZ{{OY{F{>u}0TB7nzVCiqJwX&w%YRPEplcr#HbLY2vW3)QpK~LZ;Jjz%+<(J;>7>T{3|_OK-nrrWw$xU3X-8!D^o|41;Q2iV%o0*Cga&4o@3F!6#1pg*n73pyZ8t-TwPA)M_Q2{;xQ$yXDxv4 zyJ_0lfkyUwz`ZSG=%%!|f52IRdy=yG$%K|Ddo|DQOl&e|&6~;+H#ar6lI6W+R{_N+_0;zdZY78x{gsT+Gdq4IvGn2tH zs!^|)R=+pj@%uV5Obv+apAN{Yg*C^9xdlr{^N~Wn@v@As5tQ5BCelddb z&vlSl3mobRFM8-9i)W6OBY)oCxMrK>h3KIoIX8oxF5va$AeY9-_AK(Ix4X}QD9Pjc=ZMqNBz>`V=Pwwlp+ ziul#N@O1`|EeIcNojo@S?}k^lSh*16Y|2q8qhRrW9$ac@0eU-%cbqY0EsJW^DD8?p zVV9hBH~yKU#zkO4gKVhwp89I#NJ&;XNJ-gwPBeFzaamU!D-pw?pnYlCXGHSKK9U{p z`P*yYNQCY4gU&L{HyESpzx+;y>M^L!W!ewztY><1H?3Ki$=MK@8TT=^esK@1jK!R# z`z#wZeCMV9)`;JCN%_t-cW9pOUH0k9q9Xbon?khJyX=6{Y{aNPOO|gYPM=+sJ$bI@ zABmI)V5~HIZBD{SOt|lCZkU|Nd0nn)@{xX zY%t*Mr{tl@4>2DjOG2i|JQ~z==d$0_<-EPJ+;Jww74pMfaAe-eePUo?kwG$pOg+wR zshm>F^&@%!V}J92S)y}f=CnTHY35t&W0r?@?y=V6G?SNpd9g}w@3R1ASmjX^6Ztfj^H^J1(;gmYsB-h@IlWf~>}Z|C;aD>%nLApvxt?gV8Ht!D zqwOK6buqA4YK#~wFG#YyawP+uhMUOoyloNgGDG#b=sW=#F(EYH;S6qQslbXl^hVCl zsN6ZC8l*igr{GR;-VD^+mg&b?HS<)xMA)SNX6y9&J-OWTQ?$Li>j68Z{T)l>#kUS4 znd0p`%6z+kYw`1LEbgKOXcd$Mf_*3-H+Q*bwo5JFuDLs|9HlF1Hbt-1%4B6xpWJ9$p_#zR#;rKh+Y zVI48cI}c=gt4-hO_+Tz=j+md8H@7IND4GkO&XqkQ_!&=i@+acgSo4V5oV>C3tlLbn zQZ1yJIsSJ}-}<&9dOe9NcJ2MAE_T;9Oc-lNBYltYtIwd+$|&B$oo(Y&kUntK0?pfy zqafT?o{NmB<6Zjh*#)U@48*w^J}tX%)1|a|lJUBCd*Wkybk7Ut`eCJhq9(`Vd$)|$ zk`!rK?xTF2dy(3=(Occ|e?Wt}^9C9{g_bQ3)jcwC_O3kmu8}xB@vN3b3i!5J$Ll@@ zf3anvf}X=|UN@OCr_?=d+xaQaPj$renE}Vwnd!vQ(X=>x^bMSqjjL8` ziRJy`hm<*=Ij!F#?MAceCAs44L2K3Y+S?AwG57YXBX&cBFy-8L9Ja4>+8n7l6cvxt z-9+JjnQg*O6o6$lHC$c-@eLBXt zQM9c6Yu+YiquUQU8%myzB6=om;W5%RnUvNWsAF_)j(z4DN%7FdSzh=bGYIV*l)eLF zsK8t9o?Dc^wLZ`8k@@xQ>5Y|Re!=qMiYl4AU0=1BXA?K_+bB0To{M)sG4nmwn$4#0 z)I95GQ^3 z4@yDJd;8m)FK_SPVZFG@d2GTJ++89-eVTRgT*myUrQ)vwNSH&ZHAaw>M~abHQcLg0jUsu%#oJ z!e_S^*73xu$-COjsYqAk708VFJv(;Xk-v9$N#@Sedp}Pso>FcZy(nK`cYDY3`hr2d z>kE6N#a8MKzk3T^crPoz19fAK^~R1NzvM(Me#hm?P9wR=8!eC(-`POM3XEy(QHGR5 z8Rg9r-c#=y3!26mdV6zGNy)r-22DCCQN#_`(R$ z2>Jp;wOARVN3%iDg*KGR5A(a?$*)_PE#sPuS5^Ya^*3ga^y~ZkqRA6@RaVg5$?gL7 zT@IGf8vL1Q`<#T-RmQ0akCJB2_!VNx(~tCLV5^6WLD=UB~ZR zHmU1A6my-8L->|@B@DGvDqBbGs75$xGm;TyXymCo{(4n?#J%X4REv+47#L$^?Kw;7 zJ7CM+Q;zJwJ;!SA%F5WUQ>&zn)5ad_*|YXH>lL5GkM{*L_TzZNC2XS<8mZUnK(9RG zE^-L3J&Td6W4mhSZHfHE@^?pYp0c9 zT4A%~Gg?kR_Njbp`}NVjy(c>YEJ~9ive`hTwK}5S1<6_*V1<6lm1j4!2K&cvXhnzf z96RcIvNwEHk6q2N&vMA#am7&jC|hC2f9R-R(i-HS{whb>9~DloE%U=ZvpZ+Fh$Gj$ zuh?-w7wDVau3XC9SQO%$-v>ZGu5Ye0=)ILnB^xWC{756cPV~>Zj9eG1sY_)!r64>? zbNO65`^7Tl@5q~5`+xcv|E?wtABXmG%=6p(UAddAD!=kV-!f9mJ?%{plT6C+5kfxI zed}6g334hoB$70M1 zMqr^|boBm0h_dJ}gA$=-mHDsNqrV~v`k+;^PF$4z_At(|y(MW#$Jkn*U^~5go@am6 z1AjXn=Ev7_|NHsFD2jH>j4T^ZWV=(yoej47H4o03h}Xc9(6?2Qn$p=|<6Woa)J zeCq*%e2s7D4aWm~Avj4$yR3l2@GHj9GmZ;+2AB>s0Mu4v;Awm~se_*$MNOhCz`DeW zq6UJeHF_vQ{|+$=#(n@rqt%8N{C+P`1RQ}DQNM`BZGbB;r|oeL!R{k=hq@pka0gxtA~xVX z%pK0C&{iOKARp^NF8T~q8VrpxCB~BQy?24vmlFr zU~uM8E5iDLHqpKSCm=F_vyXZM%aEw zKP5u-&yr~5bOo6-c=@oewWf0ba#Ep}wY|2bd#hgJ>~arO=Lg4+WZn`G)upJcYmRLt-YV z3t%na%tC({$>X3qUW}C0w;A*SLZ}JEG*n6X;tb+C_^3CCFbG z58!FEF31Js3343b8VWQwK*mwifXxJL5AA}@M!M8{o#SXRDIzJK<_UDZ+QuRo{D>-(L$8+B8IaFfL`mJ{-Y=IHzd4!Sf^02(l|C zn#dl{pG$;^tW068?={u`jlLtgM>PFB_vh)a+W7BDf2NnLX0$1YjS&wZZYOdB$Pm#z z?x~PC8rf?DX+%5+D~z;+-;IDduxkRVl|+A`1=L-*SAf`xAX&7SkcapUdLuhAsGD)$ z4_7^UB|sZSsD*wRYHsv603pI1pcF(ZNH-Y~+FmHZ-pD=bAJiAnD(?6KKhax9{vp;z zO^ahk3ji7zbOT}wc!w9!&Daidj94e!qXA2eXaTSZ+~Mvl=s&q zaOM1Yl|kG1&H9LjQ1)RT8`c?=z;6x`Bmw)gprZk|;JPB;4UQ{Yh`fM$^wlAWda>x| z;@s#8^f>5gz(UXt_=rF){D*p=gV688Xargh*i}Pc3=kIV1(Ll%^vIwtPztT0p9Vcc z8L|54%K)87H)uDshPWai4|ERni@$z~k!Yj}=Mm}wZD{wQR~#|MfH2C65`ZxV^qDX= zfGv<{24Y;a$D+CddO%x@D+MSfoPk<^sZqwh+xN`c8TjMzBaJatmcblvYv;cht$9Xj}zl7srL`6Gw=> zd`|=RF3JTGX@gSO?Gz^BOA|{@tQGOHiAP7cMB0U1^f1U85Y`suB}yMYjn5F9gEd8Y zjA)B!6U@g}iN3-0Kw86EL_;GICXpc2r$Vj^y?^0+h>i;cC^na00I8sCvNFDAFAdQ%h6p+5KS29~dxy5`yqGSeyGD`R(I!nkW zkTVje70wUZ8RR@X1&A566_z25$RE_)q7gjg9kzy?z!#1U`b3O~Hi*nW&Xlkg*^3cH zCc-l*Aa$6BTtW>&YC!u0(+g1z(u3bd0GJs(bHs=6%?y}FG#bh$iH3tFgx{4Gjs#F6 zU@x>5=v5NFlmGacH=)d+v=QVftckKh%7mq&iL@y!Lp@9Ke)c5FrSOi(5F{dPLb)N& zl6&MZev2ispJ;`MB%^MDx;Q4(M}$Wxakx`~zf(%)3NZ#)B%}ahf;b{V7fKIjQyiJz1Z^c_3E=Es1g+$3fNr(ne$$R~5E_J{$T@s8Nt|^w?mg5D!8wo-9Kw ziJy8y4l4EF1Jp(scLYA6WTAHs_yXffs0maB@g0l~6Z}K;4A4lTm^eeY^MnWx_p*tc z;BTsbpYS_YFbORIN%-kSNcfpCh)&@<`JLqNOYuyaXdc==)OcwBu_rufiT}}(<5&@0 zz#bcn4&rU#Z36lnz<@JlP=cNxa0IQV4~$XXL3aT1$DsYTp_C)Nm`NRMczkrtv$gb`Pg zPzMPu2;z8@JoLCw8X=+#c2SsMjsIN&JBkU$0*S;rVLWIr;q#|3GKHQvE$C$uB_jw8 zufEr;WKM+V<8XD7nIe%b%oXZN;T)i(pdG`x!dyf;pt(Tjd@tLgG7SkMQbblUf(>oK zH$V{_2ER3qJ5&%GgOv#XK|`tw${}8cQEEKt3tYk&8pPG{EEq}(Kkac5onG2D^4(FCg9}Jl%}vk^w8h9v`kA%tfmVngnznv;ejl zePe=e(FVbo(U&FhD1!KaGiaO0`92ao!84QS*CB@q21Y4IjfK$%a?%0k62Dgr=K!Sy zH5*VS+z%x30U8=!c+wW^8tQ+PcbEl?un}|yW5Lt3kOw@$Q*J~T;7L4k){fkh9Yw^b z&<4yV?jecB3UK@w6#ywl{~mQS+6?3#t_73~ydryg*cw_~;0>N0!$>#b2|;bb*bVUr z(CP}2DzUvtJ0_vOD&z~$tst0MZz9LHI(oUr32~07zfU)kZU+2s8{d~;i!Hq)#MD|a5)1xS{!YT4o8=x#~H-Y z=M3f;aE5RUIYt~~jtPh2@HnO%GmbgOg6_kJ$zpPtVoY(S1hW@YifM@Lv+P**tWhjS zmJ`dFHJUYsHI_Aw<-&4hO<=jP+*uwhPxfB+5%y8`arOyzIr}{O0=t&o!G6qs!ja(g z;z)9&II=zCfj@ov#R`dv3G2D$B;d6n{jwbs7?a=-8y9QH9hDp(Wyl>JA3D>JOXo%i z{wL7?BjN+;|M4>u4Y}ju0%HuhE`c$0j%#3IjG%qwm!Lo0H2PfuyVadd@pa?HB@MF1 zWWDRnlwcL|b%W_Fe-@L;>qGSxmkh`n!wh9}SWGbnHA`G-h&as*z7@@q!eAR9e&KM?0Zj2yC0wb0Y#Rz32(l2{R6z_)yIozjf`Q2;nZ*T+7J*eE;Ja@l^zr)I^ zd>3a2IoH{_e&aS5@};Lxe9i-k&#oSTcN6EuYuYOV!0~5j{V!{$*V|a3uEM!Xblkf};iH?m4;b~Ip zkR#PkaePE@d~8DOj709J*!Z~E_`t*n`U!nX56ap728fo2gmS$i!eZ!Fx$dJza!u($ zQku%WO-(6F%8F-YZcg$1=vy=Tme+GjCH_a~>nUgdMLET1YKbz6=0BUy?8Bh*rC59> zlhN{gPjdic+sHxl^sG1R*F3&VcdCJH*{Sy{Gbc8W*ePqXbN+2T+04F-W4niM&VJa3 zJH7Mvi!ZAlKDqhpTVKayle6>U&R6YMSvJC%QRm9zJnBz#YfDguo#%iB#nbdc3J%S9-EOnswg{@E3x$-k^Ke2eTT4(X4^4WW{-zG)+a6|KF#5BCUsj~Q(f?=Hd zvxNpVaUD^Ux28Sii@l!rc5Cy>W6x^mTTi6q54`tHdG+MVlJNBMv?uzCXCDrGGW6^G zy>BuG-m1%({AOx2i%l<)ZG2`B&5`L;Kbn^sx*QowO1u{>;$mVFY&N9!m)*;vz)>c z6XR@6OoHR1jH5|k#=)`CCUG+(AlD==J~kvNI5EM5CWRJST4ZTy^#^G+qO6Q4OJlkc zd%Bo2XU@;Mp!M_LwVg=G0L}fF%#rR>tvO)X;##rAt zWSm-PmA^1^RM73a@M?f8^>Kdx>F}gH&D>t2U;5O0_Us zH=rQ*z_XlkheOr{HlChGTgNx(O&i;Bz5U`o*b1MhSm(-eVT`5ls3L| zAI5V$lhpQ!y&%VQ4&!`Ed!z|#mwV2-=T-L%>)aI|PdX6scEVC8&z!)Se!F|^s6Ou$ z#=i71O}bla+(IXA#ai2~DoWALV^6KF`0V1_RWap--JaiuHx6?PZmzw4Jr$zeyU9&}4GTsN=U z^UgQC%dzE3^(42_?X%}cJC!%u{BopN^TUTw{zK)4yl%&hy*fklFB|$Ss!Z+mz<9{c zkwa9DZFPwm>pD|W;%J=JQK0y=X--n&h<(l7}wqtw2 z^xgXDbxnshKVlyZZakz=bEamgp48+85>LO%K1=Uw;o5h{Y0oKdcHC@RCOu}(^~}*t zawpDr?s~o~(nxG-!1*HvQ#NX=`#U{QQ_zgHs#P^hkIWvwEpkTi-25i$O2Gi4@@V_Y zY^CJHC3}pyB9lW)Awxvp{ZtRBetSTH#gWmJVt6rF_MyoC;M=qnJlW$x3w=u zvFW$(G+fhL-`c#+ty6=->+M#pn9_WCy-L;ptA?ZHmX=ytSbMaPrID%Gf0u^)i?$P~ zj7>l^my=OIW&A>Ah#QdFXG zcw#g);@jga%3RZwtHE%kaa9;2kj7a440i$6ieLmYlIc4y8gm5-a91?_ilMKJxf*}c zx%339CBB&GHaMk9DRb^|_eQPHlAHGM*St6XYGAmgpzouHl~*eBYaW=F-pV+BTa$6R z!6J75qXo$Ya~`p-{r;q-Zh~h2z^!NfwAEfN-x=iL6xQp(@L`(y@2O1X8k-S2nq?0U z9Qw#x0?=Rflr)}njg6paJ9tX0=c@(dYsyQ}boSJMzY4hzfY4u~{c9rct z89zE$`Qwi3TX$qV-6`j|A-L>NM7H<^=d5`u!#k@r1~#R9U}+mwkF2fKaIH~#ad6{; z&sr{Hm&PbQ*s);tlzOk^m6@BG8gAJoSiBmx&-;+)=*VAp^($z|mA|?zI1ST%r1?GnKwt91?3fATxsS%OLN5jwV@Za zIvob;E>$-QxM7t>T6@Pjld~Rp9}iFRqVXSC~HHxnjl~`;Bd0Dr)W;)&(0hXPEUH zWTQNHvE}6n=ll=7u^a7G*7oR@bfBF<^Dl;%t^DjqIPi)G$n`qwyRlMdng?rBWO9RY zZEEOUp0y1tInd)K1GY|zpSqR65ETo422m7K<)Gh zvLu@)BkGgTlnC#nd9svfsUl?}e2~M_;&7EKG>Sf4?eke}jO_I2s@B%b=<3y7&aciy zt?*G!dAZ+-auj)7hG$8c7b>LpWsDarw0L^Sfr~SPkyxajt_CYQ0hja-Yx)NZ#Njg; zN>$xYN|+7y23Ly*#8})K98{9-{?ux}Zu~a+xVs8#H#CXMwx%`T6T471!Tp)h_~f9D z6V>GkyAGQBo=pCuSHbMPzsS~jbJqBjTRGXxFDWrsTPyBgHQ(}notUaimnEB5rLH%; zVrPT6HRpvvfX``@2epUh9Cp^}K4P}yRR`zJJ0t_g~bULjL!FD!M*EdBM^Vu^~kzh)l4__^kFKS?Mrt={r?VPZO!l9hQ9jc{T$)U$9PN+t znImZ44xd@5bmG1&d-FBx8 zzpTs>57rLfzr{qQpD9KAayDiZMFS*S)0^Aa_)(m67V~vs#?|7CIx6GRe`M9@QrhS| zs0+(SMu*1JIE?ES7mBqKczTo$R@3kf4~z;;;Cg#`ah<#-*qWMJnj1NpnK>C*SyQ7( zb8OM(yd$DRjl2>AqvN<j3mNw|; zuNCa^bs79h{`i5zgBB>?KbBae{rvp33yp5iCsxQjXLOv}pD)kw>^C92`hu;r;)nHB zC$IQUUdSJDzoC4O?UfO0TAI!*%JlYsImzg>#Od5iHHVys*_*%rz+S9p+OKP1V~FE{ zMW3W&v_G>V_kK0{qEpD*m$;Y{s3NngxYPPkuZfFK%3tq)*KKCNtCVpU;|?73EV&oE z;kA10FT2vlF85z_tZ-wBM$3vtJ1$)_UYDl#<;C79>yNn^*>6s|5mqZ1^;~W@cUfkc zSbtCZr8-$**Uq;D*UV&GPA&W6@!W{aDYLfCmD4+=K4MF`o$;oUOO9{OHJBf6o_2O+ z*zzN9hwXc>V!o%!N439?k%>Xj7w4%9n>KHJsk+_8P4U$U)0QnSSD(sBs^xxJ`r))o zw%Gi_iw6PB0u zUD#rArM`e0(lx2wN_*77)V3r02S2`iK{9lpa=W&v#LZ2T%X6F!W2_tx7%qFv(9hX4 zpvz^t)ndi2tm-q>oYQ)y88hxC6@Tu$xB2x!*{V-Pmzl9Q*7x_Vko-e6Npa%T5Z_jX z*0$Na8(-$IWw+M(N$8tq3_K^(`P5xDX6yRum&w0zBLnr{w}XJ=IV4jKL>fdG0-S__(r!1tJyi{f*3)L5;T8g%$j{iBS=x_egN zq3|swiqja=gQhKf{iq?dq`#Mi>f?CIA5r)u%9rvk^elAGn(%iJIW{gq0K~ruu&ya3 z6k+;?Ub6ImShV`Q!2&oQB?89-sOi*H5jgJtk9`qD{$K0u#}LF%0zAEbhLLqXN)*kx zYx4P>MI*m>m`UMUYJRG?-eMd71NoaS+8*cEwJ*zAB7dn?GAp8E(92C4`yz9y4pgW` z`>h^p=Xv6=$^(lxaqt&6+!I#&3&J^zx8I}@Z9lSG> zHOL_?Vf(aWnURCT@2Wp&Ij<9ch55PSWaYXsA;#yXvkt~KJ06tQu$!;FSE=ECuGl`e z4WU6>`{`Gle{3;gk-XZHp-p+`=W-=On$hQB$T)-`Wvgox0S~g>{FOHgDU<;Ez&I=L9`rjJi5nf9~6(`?Wu=o8KAj z7U;Wy7uU&;4Lfky&Fi#c9hXauFWqhUNW6W;W2s;h@8`2dbd|mf-LiadzM-!5r-gC@ z8ntzN_oz%>6>_h<R+I1V+F6m~gb{)!_d?J<|u<@y_mBra?FSFEB&t{LyO){VV^5h7eV2_+9*K;J2 zrw?&H(b=~oV!58AQ=PibrYg0Q?qd(=e{CvkxxY|r=mq`FW7khoYe|{rE@@)-ZST12m>QKrcLNQ&Uqgjx>-F z+?v7dzihhy$(lX9x5KsLNc=$Q{BXP5j30=Nly?8cL%kB-LE9nD~~tAZlFO z{fZOz$^m9yr@CF3@vgvYmf!ufaeiNpTZUyiYs(zX>33z0>92AsodKzlo(mn1?Z|bW zDWUMHdiI+v_uFS2Yi3RL;8_()S;yo&Ywpvc=QZCZkTtJrS9g5GevRFG-ddiv&7bBImHiF<{h#z^w{_~ zE~57(OQX14UX6u*#e*Y@bW~MPwkV1P94rM zmrR>}&7!8d8Qu>%RCnTj&XR|#_b|tuJUy#ac}iHo-S@w&)YP!+=3Yri9l72)sjkZ3 z{7&EO>)qSAscD~YY<6lbi#NHI>YrYIYgd}(Q%l#kLt=LJ_2{e=V{fZZ*fHKJah19` zzt2m#KaP!7c~;W7ZvJrBTl=SnkEv`~sN? zm%_GAZ`Pm9x^Jcu-(13ddG=#pspjBVnTwVo%cd0WSU7N+Kb5$0)vA^g?jNMH17BH+ zTc|7=ed~7r`tC#5)T?K{o$Bc^t?w<}^8M~&^P;NdHR507eDJKwRL=i(nBpxps|&pp zRff;q?Ns(gbMf?>d7G8uBhK$jId;rG^1kt2V$JT**6aq=S2R2&CMcsp)3-bp*g1m| z&o3cW>reAn4s*5`d`0f73Fo>Su$!_Y4PwF?l*u?rn~D`CJ;? zHC3U^e!}RGxd+Ab*VysQj@mSzon<-vW=Pcht4TJ|C;RXop3VCdRbRA9MeF(F8DX|w zy@N4@x+}iuX63qzY1>+eOv6i>s>*azR#emIu#=dD^<(-_LZMzH>hpIYU@4kJ`B!2 zcS`wycc#PP&oY~rsb+V)l#a|eo1yBU>Y23E@~pD4JGF)JYIRIbV}>k zZHY3co~}%9uxlS@KVzud$*hm2YByHD&U?F(`S$Yp#O}AQ@v(Bdlc;L*1M^zkzer8G zGh)~U@90gziDL{8P8e-;Mu9V{GUS8K>4fM@xsnx!nG?r)hulbLUZ6R}k(ukKx^iak zEyen`!&LQk%B@}2$9x*-Ju}itest;Y$M)59W>0t9)Ns{x_R)#nk_RK0j%o9Y0;3YX zNS%B-a)w<*ckP*xW7E>|PuIR5`tX=y<&n$w+j$;~PF1qb@VBNYYba*t#C_>FdR1R7 zTRPA3X1VK!4PT`mo4?nw2piJY`*{2Eb=U7{b-f(qV5hsPJuz`gWc_CQXK95z+Kkse zPN}qxbF5$Sd(Cd!rMA6-7Tkyn_8On_oFBbiOzzAq@kqHN8a`%|4j0AE_@ju=s-?|< zxgaq0zUY6Sr@d8MiXNJTxY!U`HcO4I$arO_`DI|T*@YUf8Tn6itWGC9o|s>lv5?A0 zFHZZH&9un+6cb~x#2D>uQ+82WDt$ylBRt9vGk9=~8B`)=D~!W%m^?$ufRgHI0QMJX zt;%4r*sPul`JGsLZ?6HAT8cc&f-vH@a2`Z;ScOtlFL;bCQN%4G*UYTKALwOgWH1~Y z_?sUsFp_&)*ZtLHq{rxjjH=$(0^0gs?!LJ8p2uj1<6X~2tXqH4Tsmlmgr#^?ZkUR}ks*BR@{=nfrL;$!TN3}uuz9|JYUGYC7wH=d4JYcKI?QkX zmGfIalM}8NkB?TLSe(_}TZjL~w_~BC=7!I$Z9G1wH^t{j3dA}8-<{6C4C#nQS^3Oy zl$vOmwfA?iwm(HuwLayIyyYX^&Ink2d(nop1v}XbJwGJYi?3m1A644hzKY8DDAEAS zgqP7pWpq#(52=g_4!3g0WpNvuukYALE#-fEBXPIEk9qfE$T<(EyOkRjNBdS$8EgLs ztb?C3jvl;B$!dS)y+!WI8p+pFwFXBt6oKG{o;iBX(wZCqr{V@48GAg?$EX25B7}A%*}hz zIcwG93ZIEVpEqg03|o0GSw()MKYM-)@7uUVzC@7Bh8%QA;6>-_gsPHJ^^ejBY*)VN{D zG`~r&osy0mGC)5{EYA zwYi^5)haWMdboH~+B0VPJ}2qBI;%cxZfP^<|8nW_$!B6`R=rtWXZbKQ@bRhECa;V& z`;F)RVcQbE$TN*`qB>hSt-5&7q5FM3UYDOQ7+LHuHfNbbE$_w#8?)}KF$0c2h@bG= z!&}l~NxYh*>!pcH56DmD)*ja9{Zh%bwNJ?FR_RhC z^?J&l?&VuL4`x~o8hpJ(r|-wpnc)TwN~@!-AN}$Cdf$O}SoaOGcC_3p@)c5mfVqbQNx^TNRi^2Fm00030{{sL}O9KQH00saE0000X0KxtG2)OnD0DM6M02TlM z0C#V4WG`lKZgg`mW^ZnEHZE^ubaZCSo$JqU$931|XUDnPJ{O<+^?9E2oU70A-LaE6 ziPPrNIHXA^B_&Cd+PPVxj@>kbB!Xf=QIVjct*WZ15&}x$MG8Vug(_N+e*h>d0tu8V zZ-_u@L9NsZ2~f>^=Ci+N&HkN}l1RMZZ0+Z__gvPjS+i!%T5Hyx^A{ff?LYfF-}=>` z{rJ-#d+gauzx?k%e#dkF`FH;4pMUw|@BH%5Ja_5q|MoY2>(~EByYtjfw$}dl2Y%qe zNB-T&w`N=PSVdphh@BIDNe($l??)aCV zdFit+JoEQ|?!UJ7yD#4{4}SJXzxBsgrAM53Rv6tU@;nQDu_HW(w*MF_udHUu4{+Z7` z`@*Mx>BWEayX}sLo^S2-f9ItafBXxt{I_;@$LCd)f9dm|e(A1neBfVod@296FZT8~ z-)i6N`H#{57km3}_}6#;mHz%iy}fH~(^K!d>-@L=({KLmPaJsakJ=kZXze#Y`1L>F z_nRMm_d8$t<5&K}X4AgWUwbyf75#YD?Q(eH~-!FVNzcFx%QRznwvEq zY*+ikv+ZjZX)kYnrrp(E+g|Qhr{?nea&PAyokE*ezwXr={=MAp`1+H-^_Tu<&pSrN zkNmlJeQ$fZ?Y#0WacJDHS}^#r_SOD(r*)LtnUNw)?uYt)so%6nYj-U71O0wuzhA%P z@q4k~uj%)pCG8FUJ~ME8x!t>@@k{!Fr4ILgzb`Cl)TbTlysqDye&4yIzpCG_?)P2G zFX_koeb16c8On3hR?@g>Cv~_f6IhgexZj7Dw$C>Eeq^af8Sv%?N8012{;7W7x3qDh zNgJN&Bl+}$cVJOB?Eyb@qs_`u#$`ulM_@em~vsxAyz-<@ter zzq#K(-0w%0XK>^BdzN?l_GG``xV%5u@2ma3*6&M8+V}SR?cmYx5BB?`8)-uyez4zv zzTeOE`#t@B@5X+-e<}NYOWk+%`_C+CZ(GvA`H7|dhnIBP19$MCAEy_X;B)^{p8j53 z-ruwlU+||L-svBCr1Q&t_i~eVW+?{_$Cvhl8@My}^yLRO;y_-;jCA?}oxwMB_)|-H z-pK<``ti=C4zvVk>Hrh^Fpl6&8OHTTmfzR*dv_zx{c682^!tk&d6D|irkAG5$_&WYkzfHuAEFry<*jGd`P|rQyUZ)*H!_AY{HERchEMsW?z!nk zu8;>!f;;m98S&6YoPT8_9)EKqum9bp+%GM^Ke2JX0}q+@k2lJ8XiUAox)CqN47m(k z`b0kuZj=Mmqd(yI=8bX#`AQk)Cvu(slg^rw_P8k*`Ol5q;YPM!+ekBR);M5?Cg8>m zOm66ywTwHo<_6!a!N5jN()W`aWpLzOZtBo4^5-#*yyT{DxoIDqfC=r8^E^Xu^1z3+ zjAwp7xUqlGmG%NV__O9Qw$S63H?EWPpK(olj63P9M=1~AG3LOaPxP6-(ni(~#)-Dm z{{u@paKro5g%5$7^_HN+$2u}95Q$6i$x6#hF(>?!mw`WfFvZuREaG;kX|6or$)@>>ve7=3D zz4nzq?v?}B>&ErkZeQ@1`~I$@bD!)G_;;#5wO;F^{r+6PKi2Q(+b7$n+H*barS`t| zeeL_ZUHG2%-uB^cX@0O@H`+VfQ|%KS&iC|`z4pcSv)!_LyzO)wZMS3l{`R5vgZ=L_ z?H&E^`+ME@br}0S_v|Qfp|^jvJ<+YhC&qQXzkRel)-A~=dx`y#_GG``*6*wRI@?R! z=`}*(U zUh+u4PYoPtgY=^#|4@Iw&;VmVoWCixGoS#2Q8?Lg+U=Oq*708RP*1ts(?~fpT0Amv z+v(TAf#?L;BRU$4@f<*q!fMU5%8@j zUl;9oRnIHk-qu(FqqsX%O&vKXXKJXs!sLpz|Ci7NOIP# z)c8_*-E&JZ%Eh-lIkU(s+HAwCr(k+JUQLT@jBbZV>ZCk!a zR32p&i+peeMMq%OW{q)VT!oOJwnBecz0VEGDbM)}Ug!vv*KlCTe#MLFAr|J)_)H#xtB(bYyB_u<7Pzxo6Ki& zz}Yl%&kn80Rp8SD=biS}F^Z4(>%k#2F18=)I`h%q0(0YRe`jpz=lO*P;VR(6Z5dnm zIg;C4S2{VM);^xVGWr1&TFdH^i1U*(`TTf3)#oq#?;K&~(Z}NpPbwwgcc=|sP~pUw zMa&jB?Lxa}j0x?-FQ?nv$0)0_Gds_WzB9^K`*<){&U9M@*>thj*zFg&=*XvrE{ztv z+iSw7r#mgrc4~qN@E#d-KhfUYOJdEP>6dygl7O_mA(`QxlS3Crhd$f$p!%`?etPtY z*?6kI(K5M6-ZQ<-$u61TVC)LU8h)e>ymn&90<`Fzf!T?UKUNU3*!IpeG;OR9o}eMn zv1llPO)AFJ05kA}|Ian_e%i-=VTCw1N~yCm-^33Kf?TnO4wM1{whc3d=QE?{U?Dul zWM{yEcGRO?+Gi~Jy*hAUY#A4z**-S&=`nhgCzW*Y06I4qK><>!cYWkRL1cMawCA*C zf1wtoc@q;XAh;R2(L?aT$`dYSvFVQw2=?SeW4#mqy}=t$fSTOep*&k(+UfxS6D}Z$ z6RRe)qtw~KMbv>i>9svO*yRIuBclONP)}>nHYg$f^hRpY!xMdm92x!5I)y{_dMfhu z{4##wWJ*zwGEzavi~_A39rQjscyVW7C->2A#v6@br{7mbA83yf(pgURnOS%LGT$71 zW86D9xzT6L^5p06*x7+-8<@A>PPC_{Dz z?1u+r=ZBi>p~25`AXg7=ddsY$ z4|M7LV3!*Yby;|Sf5!)Mqd#38Z&!QX^W| zd%eSYbo|~J7dqmVv1)Vioa=#pQ~!F$bGeJVDd`r&~VO)J`TS%9vy_=(5^(t(CnYAOAc z(a2^USDlbwy&_;iDdwCy3wUVx#{Q{`vE>&nU40V{(9?EmDXXueJQ2}t&5K?U9Wm68 zb(!|chKLp|6&MAz2=oFyVNk)IVPza>SUkuWDN(S!Ojln*4n=FOORXw4b0AAIsi(i8 zAnp$Q^psf#3aR@e&rGv#jJ~Cq_tCecN~~^X=~^!f4nWqPkVhCdEOaCYX~34d z_0-BzD?+NP-H6+T0VlpbHF3C*F#ry9Iy$6~zQp^+>Hc31~qDh^?-;#n({837bqt$JE-bGz~avlD9Fb2D$2o538J zq7={yt-h8&Y~3R`p7-(>YqeG{t!bV3S%O3PcbuhO=}l-=EozO^i`1h9=f9TfTyw@# zdWACNS9|kiE#)x%a1|yeGJM9cYTXVB7kgE=EAjdPWji$&@b9_Vn{_+Z2_?2+AGkRV zg`+9fP1$&Y?4|v^9Xv-+3A7%I^%Xf{38m^Rt8T4zYwvEoTouHjr0yElTK;=6bB>L_ ztVk!jmUF%7u|t|8U6dGDpp0-l8%Q?k0msmel9$;{I`TgLHda3DaP+VvU86+up(*YR z*@0|vrw4ru4IEjG?u*_B)T3ROrOe5$2eEqv1gv3jz$eJ6g9OIQA|-&VPJk>`9;)Lc z)!nD#9oE4P)fp}SUTW%p;C63AZ${3cY1(`Hrv`%jgKBuvun2$*mDuy5Wu>ArLSLSq zp3FgYD(IqsyJQ@xn5mdK2l(qeK5c-t$-i z-H+8)r<8U!rQD}Pmbe#+1Gz1W3?~f%#*zzP>{I=HBJw25q1>q zFf-V_rENR|$NMu>4jxdK*0uh%S)dA7b3HO@iW``6(G$4`4}!iw{Kxnp&==$@sI7O5 zIe?v_PZA!{h{{r~@xSHTlRLcjlU!qkYxGiiDsKl#mb5gw%d^2mhQO zqa=?q$1W~hM^E)&KR9}HaO_-MU3%mG!=rs1-qdgB8?t~Az%Tmb=nIxB{E41OECw>} z#-M?AZ+ymjGLXTi`pm*|MJv2M`VMwVN$}IZq<+4*eDX#Nj@R^(H90lo;6~R!+obQVK;5 zXCER~R_$TuZ}w`bFAQZ{{KT^*q4bX|OMBeepXUwTFTUu=LABIb7UnsU^j1k3iCY@D zaS>|f?!m=_E1vP`0psC551>097-R%v=xPk_!-e{>m|^fK7n0KvpjF=8BZ}m84P&I8 zd3gM3&%FozR!2KuAM35cW%5NV6k24KBjwY_Xzv+O`;QHdlnXu77NQ$#Z|L7IYH1x? zN(;uZNxXyORBKZp%K>6g`cvS^WwBkg+O!g+j?yr84RZ*Nb+?8xP>N^QP$L^CgN39- z16Sq~m_YZ8x^jnkZ>)-|1M?bx3jTTJ5A8?`wFq}(GOoPg1!k<7`;%`v#Zg;dScG{cgM%l<3FiWTki1QM zw1c&-#snVdI6RXl?#N-vyPF9lR&Cy;x89l~L+=y1@jjQwTw=8c4trws`|`l%!k91W zkno~%h4RMuumXd*aYaZx*A3cMHi5HoG|-<^=Bzz+Zn)D-EB1_;OdZNF_K{Zhg&vTH zx5ph%N-(mdSQ1#2;n#g5aMik2h5*N?E_V~_JA3M^{c0Y}1!j=77!cq;!v_F3fnilzFwE zZp1=K!+Qqyu8aDzgn;#1`akkX`K4768-QQ;?e%&c9=3L^xV9A8EhdqILQo@)jo~V= zy;6!KP+Eb1;bgK9*pVl@Lwd2K2nx2MWW*PWJN=4sbEpvvKrczWnK6*@F?p;!&!Q|Om zvGBw;dP-KYj7Q>095ZB4@~Lf%Re0p?1%B`>zi`HI_R44csiB=GHusLSP_)LnxJAxd zbDg>2T-%IyQ_RI=#zxLaYBUq`{nc+?bF0RVF{qeOVVPgPzjZI9s2DpNGBQdGN6Ym& zTBq2Bd%cQFDjddIhqVVqClt#+eGNF!a#xH# zpq1&(jdZQ{ZBOoyU*>+WJ8Vw&C8TX}AX9#9#XjZ^Uz67Z~jikJfwoeSdpwms0WLx_wa#tXgdp<(Y~l^e^ChColzPO%G& z=P}BH_KYWBK3yBSkkSrIj@SpVr>tuOP}w=czC(B8Vs{?zCK`v@(CKz%v;h{0dI1ww zA$KRy_l@4cvqz7OG0^)!Tn5@Fr7jJ6(F1WdT11bUaSPOdr*)3mI_SO0MK zr-o7?yYS42_(U=vbzUs}}FY3pGE)FRK@;tut;L@4yUA$^GxrjZ8&7jS!W<=F8iyrgr zoFUDyV`nCYAgRSeGZwREu!fV%J-u7=!l%xvdzzMIXehr+oxJC&IY$}f!aNT$tD?cz zvo!EI8d#0AvsT^@H2133{lPagr^RmA=GyE$hH^D)(sGrImOe*lSiA5peknN*4QroN z-|e<9KDjgPOQTjQ7VfhqxZQJWhO;tPa@}s%ytMlG2z*W}YmT+`c?8z*DKp0--FnGW z#^wU!LL+*MH2u}?cuEao#D&C~UJ{?y-CN7vy(@a!MQ@`H{1+`hd9nY2qub3+8f7L$Iz0! z9=xpXQD(2mT^Rl2`i7-0){!1#W8~phquunFf}MZ$2Inga_lVdv&CZlwU%i@o;^TJ* zU$KE6@x!`DsCQjV<7ajM92m5s4X1PfrtxKH+qC2A2;U{Tz!eV-pMDxKA!Pm5#*Ad| z3rO+4yJw{@*PZ`(#f_Q;G8ED>0Uy772A;d9V1>=$>IY?{o%Gl93rA!pkH69%+{MA( z@+*zy^z1)CalM4}NonZpbtKHuGvW6{q~Wji8zkci9VNf@pcl}}eGqGBhuobBN0GXY ztucuD=9L0+h2Q#8LOu_cw(E|Sxkf$6kK!ps6aSOF6E68eUKC}u|sCT;Z!MP}JEsT#jaYt*Aj2j55rchN$9agK)K{0N?|4DRdt^)=~Qzm#^Z1s1#rx7uUlYmBL} zCsHZ$NLa?sNXMKvV5|vvAq65SvV-NCnHdUZTE0g0%Gdhjq?+`AkE~(bjccL(u*c-9 z$vC$&SDn@I%{eC>hj}*2^#{gZBV-(fP(J#C`h$DEwC_sfHw@NwpD@(T2&HZHVRu~3 zQU2hkiIj1iD5Wiu-N-NLJb z#a)93mW+DJ#R)c)=POp>g?8wBQrlTX3Y3<=#Wb+9l7*Jax@e5ffCG&PFfEttV*59< zgSpJO*dKQKqnBqsMj|5}Y{}J9NUou1E_hxoi!a>o9K=ey#y2HTjlOzDR^UWg)R!Vz z5&WJ(Ziysvok{OqbHXu+!kszXJu1H*M!HZhUf1BFyofGQmpB6-*#{i=QApWc`eq-5 z7V2n!i8XmCGgH8Cs}Hv;%~3I$&seUD3x;a>e(&PXGfCEQQKxr~I3R&l+~TRokiC*E|dL05W!o4R)T2TPRVUq0H@?BcUXmW1Z6WorNi_ zm)rEhB@`)Y$h(0#kgC2w7&N*?4_E;o0^B@k0OPuuMj7Qt?o9NW3U zW{pFx(zXt^CmM0NPc^h$>;=BA&$$A4$*zt_0(05-$KK-m1^kV>3;%f@gSY2k>@`nm zVQ0Ves-m@f7>Q8RI(7!5$7!jko$IC43USSVQ-K?AGILFs=M1xevoBYuyEFW*xijFUjB82RCuGRAdqB8PD$ zVD1?to@8P)g?1&fkQ^x#i3IJ=bPJ5WBKwTHFk;#-i#$7m_>#1hGiHG0mnTXXFa{d& z^OOZ^%BUH83Q10u(GMA(^28G8171=ZUSL+`8(1Ttt?RMPey6%uTrpcG>m8HB^~3f?&J;o%PMqZ3>2#G~>f|7$brr z9YfKzl;ixo_u0k;RK(` zdJ$s%P2Gx;pXb0#7fY3_QJ>A6qLuQX)ENX^728~%B5Q;aj7Ie%^JC`q85`*ANkFWI z(A{V}S1!IK^;~15JYQ_=k9<#h;)_Uo;8}-`KaxKu@3CI$r9me~w$r8%qMftSBIA+F zo~=RDqnaaUuX$#NUr`wqr>+-$!1X<2Lkai#Ok*`vTTqW6O^=qgW$Z0|((aG=oeV3D z-{rEhxWcens*R|nvii94`%N0X@mm{U6Md@Y;ob%oDLwNXI8Sqd=Dc~DT6SpsV$+p7 z=Y8rO@)T&lER|02D*Me0XK;!msqQPftIIC6UyA9~_BJmZR|e5ntn)PqC7 z(+E@dKGlV?V^lkd&=(w`18>%(HCiT(XkTfST_o_gU+@Y2q*cy{vM+w4=U=~O<6@-; z4)8sz$8!R9MoYAX=8$i(knTp1>m%pXEqABT0^teI2hUyt(2QqxZ(F+uJ`eTX?{O#B zuae%NL1g*HAkuT7=+=(F#?_G8ZJfnq+@3Q#z8kk0KxND+rOiKK{_K_U- zocX0k%H`}3F^h~6mUq;&l|9bL$k)t`tUB@Nlq@RiE~`}5itN>q?zuX=X${X-pl{B` z>;})810_nX$IqQrEMA1Ho33Q>UGW_GmsaOBf)#o0Va}v;wEa%ZU0T;dccMJQ$F!XF zu5T>g%lmybGH`EjGUZ5*6auPW+qu7I{IlN~JAUrY^6NL+wP(%fpov2#@btXWe5FsX z)a(5Q-MwW}GJCZ1(5c~Cq&l_q#7A4EFGj1wNBOqRIB#RUJ$X`(LT0XQc-nz614bKp z_MG&Ay2c;oD^`3xK=rG4^!cn7(o{Y-hK3$Nm(0YR7XpVF2|+)MqqnAV$#ycH`6~r- zrmj{FEqD^6-xTVz_RTXEo#{v=df;iy;Gh-eyv?~I+1X((uuQcnONp*6Z`8qR<*i!ogK>MoVei_Ci4- zOD#pMT;8-r<07E@^%mVgk9%g!!S>{!|63RQYXvtNJ@#q&F5y0M)^W|$x-?*3y@~Im z?TL|-5{!B6ggVl(Sk*=VztjwM<2mp(@_?B3Re9ufx%9rJtGuiOTPzPM@S@6+r z<lMhz1<{Q|&4~#)_oR2uEVfb1$4*Tr!XT{Fe9G z-!OY_stj*4KU(hYfzeZARynivL%56q{8&<0&&OfixuG9=m< zvOz1ySau_bwQ$s?wYzxc#B|q`tPEO>p4XhHJX|PV~|Tr7h!`)Sjph zRn_|`p@+)Tt>Vkj_J;?cwz?Z9vEx57Ry%F*C9E++@+j*ha)5Jn_=;CbI~QWzdwz|# zr`=02BlENaJSAC=1O6{+?hfCMXA>ENtEP?2ximZ%?x-*}_8V;Jqo=XNigXt@G1%H) z)b`t4cf_<;GK<|OcD5h~!~)DZ3)tR@R4t$N|5NW+xdu{ht%G{Gd2;)H7G~ zUTEVwXMn}|Y@VIx$!B0m4?WF4dbnraoNO0H3H2tw2Zkzg5HteH?#T1}A8mZO&F_Ze z@j^dfXUP3EMzzlE01sCfSAF`oKVTpu7=5|eQ`!99**!V`rvh;SQ@`MeK1?fehvx}- zwlO{^B>-dS$?2Ra45m3(8m4I?iqR)YJOGLO&@S&FJh7 zn&~6To+2>$${Gs0Hu_zMUI1mTJn8r2#yJTAY8oA&g!X?Ra%|!6_#=QS&nh)sIX$Zk zz9ibB&QP-F`O-rlgq~nXl9NPeR)jGL|RG-R4_f7+3HkSMR^uFr{iV;o6N zQ_=>`|3Tx4rFgCK zH8d){)855;^9wTl+kuF5GtGCoZ2r2P)H(8L{$6e!5e3((9f}Qq6VZoN8>+8kwQ`xZ zwquKGdKcC@W0jYCW_qc?msi9qd)fM0ytco+jKwc(&(m-$LX(;Q$T8+iPly(qo@3Xh zV4uF+`fX?WnAkSo&M$V+e1(haQaetpfd98a&Y3n+AiX)?w13uiSx(n49%aO!r|HNM zqR5o(!It?YB<6l>QgZd_Sb!E_J>(?`xkz(U|_k)~Pl!t0EVaPI6xTFMOQFm8gm6)xyy8<&2xZ3)i9w^mz{R zx8lk&`tvN*hZk$f^4ZI%sOVh2!y}Qi&Xq_w-(#0@9<}XglL}jV%a&v51}^8n1#`>E zBQpgndSN?yV=kB8&v=K{nd|c|u`~e#ezE;Le@4wjk9quI%!c6S&Ytosd|azdje=k1 zh8HM@{1h+G%wYyQ=iOf|4+(N2djz$1)a;BG!>Etlo2}-5u2@Gs*h8L~YkD;j`#;A} zGsgNVz_P}L`B!w;A`{O8Bf7t?H80v%d?gv3P%fowo<;8nchSRm6pEAAYftk04Nuo7 zZ^r^Ny!R7!^pK>+hz>hH>tGH92r~voO>Xw%nK>yQ%ao}&(0tI26v&A&Xw^j zzg))9XF1li{8Agf3&cPw3Krh_5{iyqdbagASYzhKnZND3Gs3+a*MRxU37(Mg;^xYf zeHHoMRWuUSRbcjxE7Ma`54dBZN5Gb{PZB)Dlzs`*`6HCfw!|-_w4Q%$2eGI%()gs< zFs@GAtk&s=@Wjd96|3~R7@Ku0o>s6{3tm|L&T0w%PYP&X(nqZGm2qZ(78ZR) zqJkYeCTeQPZ@)v%I;B0E86y6qc?LvALmqOajK+pl^(^SG0}ftCO0xEx8zYk4RJ|ffVp(doN7H1q1H@o-e5HAlhtNPtw_--W4?T>2f#kK(v*ZK z?v3}XFbyW@uVuv~k|y+;Ypfw8rCM^FBM+;NdxW!Gy%nnKoBQ8$PL-~)SX13M_u+YR zR&Ceuaz@p-#qSo{`c(FM)py*sM>E`$oRm!*Ls5CASNl&bTRo=*k!F3SU`{Tg#IA?s8i%|67c_t@l*< zQma$eVB6=-l}U>QIqN=7ut=*F!mbx9F_IbNa-$j%d??}F+V&T3u=Ul zeCY0+`hjOYgqzeL9Mhy4gL2>apMLPCKDc<~#L3;X%#Gq*_tzYG_Lto6Q}2UfyW?-{ zDQnr&l6b_;@qB5dq7Oxiu~#iU=*P}7re*V6-h5ZNoG2w`Bz;aR8TrTBtf>7)0$T1iPMkg=eq z?ML3$dY84w)WpH8=d&7D-fhDj2m4Z9q>N1@Tk?S?ZwJ0qRmRL;04QUPh^**0VbnO! zYmm9y>)X6;E4PZ$X(drJfwjKYYyP^F{aUYN>(bGaD<`_jx@7w2+x!hcL7+Xq9I4f& zAeIfcyJp!S?5~GrABOnnGPBGwf0qC3(c@H zlelLZ6<24yFs*XKV>Q=>Z><@pZ7oZ@*Er;=7^2jlJqA)xcuHw!_V)jExE{NFS+c%> zKKn$Af-`-yGFdi#-mWpXm{cW8m564Pzky+FOyz0y-O7K(b>f$iEZckCerl03%k@a6 zDzny~Yxd+sjoXdie9ifhcDI`&!7(E$99QysD~)Z5O)~D{^tlTW&(_&!N3Rs#&RxRNMXZ60qrf6LK zcb%K*QDaeBl%pliMxmCbuB@tc)zUuY3Qp1OE0$QPY=5lB|7p(uXTH-lm+Sq$JpNB~ z{>QfL&)ofy!V~S0b7lPHvhLo%j5JDI9yRzM+xcJLulDDwM0k;|MX5v!I&rBfkzs(dE4*hh+2XMXZlSf5V02f?ZSvP*&R^%mTkXrLIlry zjW4AQcFw%+Wp)@zWNPqZJaQEi_ zu0WrNf}w={I^XdyA_yJQ8E8D-EDyXBr^ybDvL@b1H8^c1wacAme3KC<#+xK=4$a&z zHt?wWojLw7zjhrtdhd2R!BL8RLbE)&N^xt z{|~YwBMxhhcA3{&@9e>l=DO}0m~ZcAjUhWrIYpYk^g^vHc75R6Rz^fi=aR##D-EBoYs(k>*|tkYKe!I-@fTXN^Z z8u~ESnoa3UuCeNQ+d8|KRFnJjp6k{cRWs$2mhbp)#e$O29guRbr$mWc+U97M%o$AHCoYIs_p&dDc-KE2ByZJl9K`T z8bu$QTizSL(LU2&=G-=(H5YH!hk2%M=WmaCzIVyYtV`W%cjVTX2g`WTz1F?S_e!^K zIU{2%Q_6)>JeOt8IJnm(^`cRyJ!F>aah*Y6&*zz=-dM6Oy91t%<%pV>QNFP7O(>}) zA`X_b^_t6Rd*<+bWmCI5N%v$^zfo|WdroiG0N2ud8R!b;H;nn(K-*P*a((65cmd() z;SII)c6yRG&-wqz{jR_$c3`64qG6(qsg-f67p3{XsaeZ+n&*$R^Ova7a5;MiGo9xV zkz%2qr@LFrGQ~4h-lxQw$)y=zOGNgEKU{-~j-=0?8rP|^yK3d2#O0AEmu2m+RD3~M z;a!uGE0j5xIAq%_PE7yGA93qE2{gIS=aDu!^)^e7XYqJE-Ja=j#_bt)olA!9@$qBtfHAYe;Tf&?an*a~(aQ3!#AC4_*PC=n<@q4WLj?|pq= zwcARQNaxunhQy;smrG@J(IC!)LVzlR6ghm--Af9n81Td^ujg@(Fp&X{)&^tPRGM zUZ`v{wXr$d87*Z5K}x()RXSH)lR`*^qgjt|oCjkGtSuv5l1CD1-0zJ&+MdbNc}c|c zS}UNIhMMCZ#)Ia0y-EYV$rYWb5ipdPj+)S?18B0|>YKv(s5)|nGv+QJB3i9?DE8?3 zW3(9aL8wckW;#>wsPB`x!G_v#xT}NqpT=UO&WF;G)8=e`F58arOxUfuh6(&1lnAR= zBQhfou6@QA8odILQVF6ps>ceExc&xkfnGhF%}mg-Nk-$>na?PnyBsT>ab*Z4j#8s) z?E$|0^c(RJpElPF7Vw0Y{| zI3sagc@X1h>RXh&b=#EFkc-wZQ<*XUrp{(z8+CS!>YrFYp|Q2`J#A;wHEE`vpT*q} z=_{aACoCyl30c)#;Ke~JFe=JuE+_?2|EL>rg&Wt?ksDx54%d`ZV})M<@i}2%!cRh5 zog>t==IM`MWD5LiTnPSCS5|(eo=8UMU|bI63ECgzcF7EC^At|1C9ddQay~-3ra}G_ z#yV=JeL{?5FDnLc)+~F4{tVSWSXwFxGjd4>*c~0|c%}cqvj*P^VwN)c3H?GR9L$%( z8Y-@ggw_ZT9nW-q5ui!$6+A9nYop`w8rgwk_RteKuYl|4sPAzv6WyJM2{W-YmII@Y zF;>fb(*Byi)+jC5IE{G%-xg}Y2t??emV+^P^3BZ(-(e*} zg9Ud3EV+V`b?3|j_$Rqj$hrgRg>`X=tci8ASck`mTku?Q4^PCLLQV1Zj?^AybLIy; zrC7(yc)9TB?o}8?&nH4ul4lo7yn3G)fq6JURrPw`hxKu{HM?YaK?F(h{1t3>8FL3JE~d^pE379 z#kc51B%Z;`iuU2SD+wz#VPPPtzn0!-yo)mV4c~wcvOQNLyiX4=e7Ddv;15O%H>7U# zA)^iO*`ls{l{XijZOCax07Z*fBlHu?hEEthtO*ch?a+3UnWS?jfcbcT z7uPZ~ZX?f`7o!O?>Nl=- zD&E8xfC~Z{oRP0HA7Ck3yH`D7^kS5ziI8BvjN8jx8SxRc0g27;06j(;gEyX$gE5zk z`V?akGe$BJS0r;?H|mHUVx$d3bH?Zx+D@Lz1S*5>wJOlzzS7ih8Qqdcu4(4Z)r{M$ z@zZd|Rk~d5tvjeODkfwX-rBFI0;%Q-|1;^!y*-c7n#ji}3o#oV+=7w=JwlkU7qPcE zA+M3_0A=(7ci(1vzRDvSMv zen+okEJS+{39=RR1MQ?vXzjS-dF%s@t--%X{SArI0sVpX{%a2OL(WFj7!&voSd6!T z$?QXDj)U11V_E~x;F+U71LqmiU#xvwIi0HB|t79Z^nuMWZ?sxRPE6n z4jKJ}c3+JX$7fQ5F)@%6xXfF_%N%dy7j^zNCZpT)ggFLTO^9oW4{V=Z`5-QB`D7@$~xKlm&3%*HBLv>)z z;^k^b$_DrmRHo@KM>B9mdCS;CDsx#6^^IuVVFN%brFKo((sdBo0HfB7`hhPfgVj5M zlvn9uHIh%A4tg9gd|XL7)i!bGtOE@g(i?Yi2e?K}6Ph5rPLO|CH@YIc2wa(*m>MT! zBIO6a1AU+zjjsWI&S)EYhgu@^IrJqXVSwKGHS?hqqnDmK8hc9l*KP%mS_sVSP!nF=4=N>USSL)@bG0Ps-AK-K1x+D z8t1=tDoaRV&AAP#Q!}zEN3qy$9;uzt&I0EKrIU**H?I?Sf}n&3CkOAw89BcuaD4i$ zR31`FaxFHaxoE-@P4oBl91!9esE$&FF{&7gl|D!&MvY?ZBt|~bc(C;0(0ikaUJdRu zLal;FUISY7j2YEs0C#E!d{-qZzol1zQM4GDMI&2rg#_gl`$pvvS6k{ev4eJ@&MV|Q z#8~nl_ga1-rsy>D#5=!EsK; z4MHLx<~hpcdyHsKdLvc0o6P^2T!{MwzO;WzhYUuRwQIYQzyU z&fu*g1(T<1%zl-HglF41a5xvMmdEtZQdXizC^31g6LgjOT)7G9nB%p$*4mQ~(7RWA z*}+}w`7FF5_fkKqzQGYY^`EPsm8IjCzK1&1JeOYUqw}*U>nYnv^VEUqGiGcfwuLp; z(G_hUTdH_p^LFHlUZG6U^2iy%b;uvp_fbPeJ*1Qhc%QhhbtNxm$qG^8jed`3S`PIZ z+@aF%E5RdJXMs%@R5Mu0lJie@Dego2lD00oKaN@w4AN# z8@7uxthfRi(I4>~-ZyC56X$eQ8|WLHj8Ihs1owDQM67M+~IfiVv`ixD~(HL^8F zB2g=()(9;R5g*|(rSA{gCwRd~EbEa#A5xQd@IE}8I)g&Jt@K*b!-<6FxCXyLLfzn> zJUP3Dx0O~I^QA{sYsNi=SUdHIa%QW_R(%3}R9mAST7FOers}zvqpNSACaQJl=o&|Q zaffpoIr74ldZ18x^Y5H6(gzJvby(G!bR_4_Np&P0Pef1a`yA`laUXC@j%RcAJKh2g z)Vb@snipq2>G&XHLb8?QjFcket+iiIXW%f;qq9z_Z}UD!LQ!9iyw$j?37n4_ z%1S6hnyH3KXesAq&(yq_iQJ^tjOX8|&udQ|6W``+gSu{N`%yn+nfRygANY&8z?dnH zBj@|#9|=z|QbJn52n^j=AFa$6b2q@DzSf{(>Lk4b+l%N7Y-LV-7DIM~Og%9N}PII-&yI zk0TF^hKcszTE{Q=HgnskWCF^A7X*k&V{BAm0CRgSmPPj^(6Xm>YT575aR>TF1Vhd-qWMjhBVFd8|`8A z%UZ=H$q`XIT_MKjSXHGf^)c&<9tm&<)#Q=$JRh0YBx~*s_%W(5aE7s52pM7)&gm1> zh!E(v3$uFZ%u#Z5euu9VH+8*1txw>6%*EvTY2~K)nm&P~!hYN(r&rCG@Mjr1=Zabb zVS;Gi`Ru&*!>A2_`2@(3k?j;}x^o)hqN8Y30cbzFt} z2vP#;E@(O%qwYyU3-M!ce_rm@b1>=_uvVOpUPoY~OoP4Iu6Qe`0+`DYb86YZa7G3N z)xZM{=|%4ZEC7zWu&;nCvnwBV6?L35 zU>ALypbEUjnEkr$fNTE{cMUngKZfW;)IzeVT+aAsiO>kpX53@hc#fKbM%HJvU0ZM9 z2;@HG%(IT{iH78YL#+|lW zX^-ErJi@Ntfl=Q=Y5aDv+DifWtoko_jz(+)258>otDr`}Tj!JWE@y^gmK14&`~fu} zf6%(A&rta&&$NVE9;lUtyNC>hcPT&A2T{knJ31q{=2j{()WV=nO$;UE@pe7SpzdA$ zy1v7s%8dFA>df^$;#%!>pj=Q-I`jhcB4Bot?s~`B4R31wxO$^3r-Os^+X%g+yL~#CaB!GLYDm()85KCrXle zOOjUf?eEMdLWLe=w2Cu{SSPi#SxekoaF$e+7k5kKcKL2-=NfDuQg zDJAHi$9+g7jR{y&2BC)t&iI0RfKn1tD+WGmJU>#I(jD?6)v8ocyi~qIOy-Q(FPE_9 zeE#=K6IZbxO?#3sLxgjf=&R%I+x0vhJW*SZ_8M1k!@tQfRh&@}BzQ>)qIcJJ~ zP@RijN3Z9&8+ivue%K$}S&rj_u*i|%CsFSlbT*!GCvx7ym^A99Z{dB^jpJmv#ySP{ zu|X>Yzb&d2ADkPaHivqq=Oet5Q)yC5uExIk#2rTO{Oo8t>Q7Z}QD0MYsPAZ*^*y`+ zszpo5!Rl3~q+fi4Ge}U{!{8~f7IDVAgp02CW&3peP-o#oyHo!?{Z!;s^W%)b{Tfcn zcX-A5zs#%Vdv{J>PKtfGXK1UgE@CW8^aANluWJY*sSRD{P_Kj66%}`SPxrI9y)JsD zd;tmbIbnAIZp0yuA=j4d?df(O0lv}l$npLnN#zX_u^dvqh6!s!oaNkp)niyE)yT9C zwazn77nB3U4@8oHwgvc;A1G(IQ-bzC{|+o%&fkmo1Q{HF+>8>tH}evyv}V63b^z9_Hco<><8)styssjaLt z^*AyPU7mM1%UsuaFsU|zF;%WIg^Y!qR(^o!sU^{`86b zi8nm?U$2MP^XrrA-+KKEPkrT6-}ThTp8DaZKK1kuyy@`3+~0rhPrmBpSN-7g>(8Hi{?gli`fb1Z!b30o&TW z_09*r@r&=e_gx=;@xqI*ygPl*kG|(0z2^(x)_d=3U;5#f{^m=ceCg9K{oG4``rYP( z8y~#?L%;aF3*YU|e(~j(f9N}JF5J8+Eo941|Akxj zU;a0@?4R7ZWjB9&K7Ibu-#h$2KiR)s;pYF9e?Pu`{E^4s@WB0x>+9F|g<7MKu!GTT9vUPqmkE_c4{b9v)`Sk%qR%KRlE6zG87%|CEn z@9aD0oS&2Y?scZlT|zmRoPTxmH=HMhv!|kQ;r}D@{f&eDaHG5bTR;Ev(NF(${<-iU${S(_SzFU&|Kj-|Iw8)uuJH7q=uGId_ z;laV#@kVtveRUEi564OD`>i-D?T@`2ML`fnFIr!Yl0zmryufD2XSot&;(MGRxfNfi*6W2*7yR1vOO%yt{*J` z2O@#PH%g75c}bn-K)G=+-S5iPB-}rEDhlKK(kOh@!^z@e^ZbRjz(%06?tjDA2hYCo zjkj9%3Bed=TGjQ=jT;5w1ON>W5ASaeh6}AF$;k%ow9zcfv=PSsiX}^7Wu(;MO zgE%rlXrpLh&>2R#`&4;f5lCr}jNc-q?cHAIaAUG@2ZZSFOIrZaclg%s`o8r>!K&w5 zvtoR}Zh6^TU6Yf7NcJ5c{+{>p>_hVN{`UvKI#1eo(|MQklY%=Zq}=hWkANbfHnv}5 z;H(JR!wvAMjouo{Mk z0jlsZ-v-9C<5XHLMB{ve^G;_+%0HX|R{%{64)06}`)87VmZZM?xt<^OW8d~((?PS_ zQP}p)V~u7U2wwQcc3OvQ_BA0B;{jtgZ@wa2WdnFJo%VXYPB0#CnpP)C;$qGCX;B8P zrod+N=AStaInM|suYM^lAKZ5Rkncw!DilUI*5}#W-SczjsL98jZ*qRq`5C~Vs!1;x zf86a4Q&YBn%J^m0$#$h4pO)JGYv-r#T#wtYe`CIEcmCq}xx&xPSHyKXH-FFhL+4k7 z=Q>kC=BZ$GuR5NY9!{qHaTtV$(|!=*I1a<9oD1Q~jj=qP-U#D3xhu+=_r;?i%Xjj~ zG^}U*!!&JOil2+~&Rf%mf;?JDv(od{Pnc8d7yR6;`JOPEk!KQnBJ$c^9Crk1+;9|| z%uPm7oY~`@Ac~`fB|E$%aA*o||6{4;Xof4OnP3QN#tP$3(3k3=T0vhJI_miH($F`V zU0S`AmAxNrmF>%}8<|$%y6diR@mA4q`+~d`fDR`I z6)5XtZ+ZYBb6A0IcKa;q+(m0fvur}iuRr^r$QM6%&*>lwy`?zLHof$6m<_yT>t3*F zdExazUS2)l=bhX?w)@VY=|@RW z8ZYsSIM`^VWz&>t+6}xUXhvQVHGDUAgQktVJQ&Cg+lst2EQ`b|ZIa@RmLF$+!)^MF zy@p?Q8@BnNZCJsluE_IOoL?3W>>T!b{q9KMAgw&C`b;|O<#A)Q)CeC~xzDVf?)jz^ zG`w=DC0wic#%K5Z{_EZv*`7D3xOqe1{EG8Ak&7bN4kzg9Sw10zN3SpN8qeiTwEV^0 zczNA!FQkEK`XZs%^JM4_@(-N$)|O-6G(+nz=B{aW!fQMGLo<5lYh%;0gTcl<-P8}f za;&ZoaIJXz z)um~c))XRp$F3WiCJ@punj2@LqCD@~M6RcPUR)gd@kyZ(w_G;EePa?+W-m0&(m^0p z)+)@nlbfd38Tlr4{arZ*ZzxSWEG~v#=7zhj4L;DctswZ;!Gc-7lDb{5*8*IDzkewB zyDoZ{bE{&(jG)+zIkc@Cqz!m^xam$Vx~36!TGJ>iFYSg|@l2dHj-3wk#=+I91--sm zIc25N?daoaYKslu%>{Gri-g9*E7nV6FZCY{yfk=w;kU!|Tb4U!^=ja@z2zKK*1GxI zRqtaI_n27u=jQ4v)gxFBEB3_NR!`(PaRKz#69TLl!R2zrTix+&VzW%(>wjC39E5rI zYon}lm}Y_~U)g=y=H)Zddgzz79VP;|$oTIYEc>I^q;99(`FI(&Z2ru}ku@18V*9Sd zYYjz1Ox@UAjh6PjW98S6ynYrZ0^!ySU(Pu93%-meGup1IT=RlM^4W_1$20l|T6brS zo9~*>EiMd#BwZGCXk=NZv$$)H4JS4!j|0t5MOm@mA2d3n517<<%b{7D2uHPzaBFYx zQ4soJVH$q4;hD_5`ksm1yVrV2xRWJb;kMR&n?_rkP2-zMTXE8S|!tZ`Xc<)7Uy181-M8k{^cY6>Lw@{vRPmCcBeW+1%ZGR&)g^jZ(!mM?V8*hba zx)nv?=_pN4MzQP1{%bmYyLeInEnG530(lxFfWuXM4SA zH$0piWm}&}SKLj3|Bic4-u&hy>zsEN!?aj+m;JPTJ{)zHc1*jobL_l3+}<)K^F8yd z-INp0KVfX{MsDKT6V^@5ru2pgq0wf;^+UHeIQiPK?VY$>2;SRDGdLx<^UsAkPl^@O z1Jo+~m>yO?Bk-JlNb!4!zUcKKYAS3B?{-D?5~!qiiM7(S#=9rVLX+of-X!u~<=Mf| z%QqW4-pS;m-wzso@z!?3ZTaIg^G#7~OZ&p_>9v-;!RiYlRho@7ZmjPt*v$*R=qVkk z*DKCHb&dj!AZQR)n2+aj12^R=K=b6`j8@6u z|ISjFWb1oT)_5$5T-Si3wA;2h=vtEt3BDmpvg5rhDi_bV*~&0nTN0VJx>GFnlBauK zZ^v%>R>XJg+OKPxp%>f-A{ZOtkzTlz*+vw8>q^fIj-3g;raNfIu}%GTVX>~=JZ`o; z?+N2Y+pY~aT(j(%>CiT=dv7jZ?0M}diFTIlFpkC2g!c4`P{u$k1<|0;A;co$RUemC zPmOWxK+o$#OCW6U^skmD(w%K@v=a^dqAvOQqHA3*JaIg8ecNiDI2pMeuRq-EI#z1> zyP|J?-Z>=_?0)Al>9;=gluDG&SCr&Uia0!&5psoq{febRrJ2dRzI;cHRw*$3?@U4) zo=#mch{r)125D3@%rNt}oBQT?bJZ5*rY%JtNad30o7^Z{@v)X!)*TDf=HJwnLA0e$84reFO#QtR# zMe#*fjAAt5^tUy|eqW2yC~st4vz#Uyoqg{_bL{4gv2WfTo&IndUx|aYP_)Mf+lANl zA8mMxBb%o2H}|_8(_ekFedd%o`()r{{-n_ef8>gP1-^Vp59pU;=N{()!KxXB-?3g5 ziJ{)%Gh0^Ws zdcvc4Pn! zUOm=!DMy)xsz8T_l=k29M3|OOy2nMtixpzS%W*FHzZZn*W|)Oha@oCZ^6nF^jeYm> z;j&Gd3n#u}Zy2P-(usAO=3YBJ9tx)pmuym)N32(djWvOfmn4FTX?QLUmMU>`SK+s8 z_5WmtrRAHZvXA+S0 zMn6npAchs81U=OHQ%T%xujKjaQkhIP56qdZw3l|zn%#Aw?7a1+IE(ws8%fl=5ZTD4 zX4ohii+@G<=WtErlBoQ<#}@74iF`S~GWNFLCZJAJu`e#zb`qWoQoGu4?OLldw!j)t z#VgJ~5FG?K^XPn?1uGy?K+ZF?uMlH~CKdXO$Kk<#RqLQH%@MIQJs4M*bTF3xu=|-z zaHpN-ogKR}SoNSKx~>>C#X`d?il^eV=&ZOR@B2yL6mFj9@d>vq_nuwZNmmxdD)NJ! zA4|f6@kTQ?!iCQtUo>JKHG^!cYenF{$CI|%%)B%4)4NtrH20)O7yE0cLX*Ug_}l4d z$Bc}LcOnrx{u;PIUu4UF5DaQKyU+!u6}K8w8K|g26qzFoOU?H~VP@0m&%D|7r3u!H zpAZ8)xYU1J`e>NNs|`V*jb!*}9EHhQTXg{&$6mpSy!JE!;8`o;%)&mclp- zU(*tGV5PVe1|o{2d)&fY8V3{LPwVEtbADAg>e)&&uh2wIuv>T>Kt-(T7*yf{v|JJK z0bYX9+_$Piy&)1t?20X^_pdph ztwv&x2?x4sP8yu4xKCA8sdS~U3t;;dVo?f)!f%yMd*C^}@b3!ejGh(6Rd|kxAAiif z;-_M)n^=U~bFTD)a8fbL-*T>DZMHjh*Y@)8fm7yG*L6ReL>p0U^bgob&_5v6PCKuqK1QiFnN%&Q zC&?yF2AU30slhYn`ae*cnx-*RFy$;NM(Jd8$p z;g!B%!Ljd4#dh^GFV8!|y4|e0+A)4niYeZAy{;8ud8uJ{{Mc*V>kCHu2a7Sx;7{1& z{ztlQQ8Yk3&6{5o8S@3YnfUrV+Ty(AW>dviYI_c)Bd0Sp9g=YKiqDjL5>)zbwX^zamB1AUg0n zlgut}`7Rz90Hoc710dT2y#|H6YfR~14xSS+otZqY1Tthr;)!{(B1Va81((`F7l7C~ zf!HIWAqp-5lMW^oIfH=;B2)|%g#IN2F2I}um_Z-_o!-*YAu^&I~#Yk?Lnmu;Fcai=N1wWudP>a{if6*F~YPCvV9d`I(8wT1-+` zp5xCTIRTD(Ri@*L{=f=nzzZpl0M1XQQM-9;W#I0(OY1xLxR=*jncrXb7LM86z^xZv z3A4zL9}z*C9M7(v&BD(9!xz%tMw+hfBt$VzNig;SwZyqn)R5~E-XQ`J6uaCxw4>Ge4hJsyURG0oUc^1R5vA$@VYbO)8{Xwq` z8ci{Y!er(%{v*K@5mUVx6qKoezmUz$sO!_qD}$@{;lX8_7WW56u-*uleB8~_$CrDp z&9mA0yRRR&d&16BH`+KUx{KRfT8rc=KEK@+TehNQ_yIrfY*u*bK-nYcZ!0*gXdD&j z_3;2&OLakNp9#uJSLmVNz}L`m;WxQ)e{tK}?xgJ?Trhbu3Ib~i69%guf0Iu1>Wfx5 zT$l&Vf%Q%YS>v=oD_ii^eNk_NJUQ(}Vp~Sng2b$4gCuQj_x#0C6!+JCFL&*VkYnkt zulq^tHQe=;g)I1<*h`Znx2?524@?@JoVJ>M+sn?b!J`+onxZ4cugxbd|Db}xA2@$3 zP~Q;Be40A*Oq~p7Er(#L1@N@uVd_@C#mQ>V9vN4Kjk8R|vI{`5;j|m1<|F9xP*i=KCEHU3~SIAKo;r7j7M{CmNXkx=S=&cm?9frB%Xl|7CQC&E-< z=o3u)VU0fo&vY__A{`Q+4YT$GL6I-I8=-$C%$rxe*o6MV!=i#;4Re33@Ln&9cVVsj zRhcw6_sYh(Ff+~EmN&{=h_P7cm`3RZ55RUfTr4}LwYCHtk21*5Ax@L9=w{K~&8&>q zTk&8c@Y-H)$xrfT(;ME~Y{k*SK=kTnGm4-Mg9iUt+PmSL5srP2;KPgz#Ec;Q0T+7R zBcyVO(>|JsR*GJO&Ysy$oyMi4w4$n7TYPn#xAxQa$%ZK|PVRN@U1_gG@!4P@4WxPA zOHmmQP4V4>L8qCX+X|xG6!{xGTdw+U>4!t#pmbWt7UQM#B<(b2xm8`p+ zFfF{1w-`R1ME#+8CKgLLFPuQE!rze|{G5YT!tgPjuYB!n32NW12z}O5x4Mo^sd}ng zwL^q^WiQW~_3Lh)KDqjs`@s4W`R3(7M1$9~QFK+bGHHwZ!LTR>wv`;1jnV6yo^+DE zxF)8{o)-*lC%F&m$GL$Wp7C+k+3fnJbaxk@=;Y0Gvm2**vT9a7wrN`J-mkPwlEle< zjVNi9MVyHG^WWL}(iltA>b(2z`?qg*->UAdwX3UZ>uq|O8M>LCW_qTFS(yc9n1KPa zLjXf?Km&G&tT+TKjtH^A#8{aqMow%?c4Em+ei$1E@Q)}`q{MOtu!S+U|3t~3$$7u; z-s^GH zgy$JYK1cX`dQ3QAzjCqcBbALyf1W9J0yHhC4oc-NzQQFpf}&p#p)9F=FyQDENB{wl zwq9}@bbG+5`JO7dR)drHjCMG15%{Zc0|L^K$<4qa#|oi%1_^pKwMvs7&V@mtW=gBe zDRX2SDMY*gwg;@3RG@DVDd^!(#tE^yt`{!3ssBIoPPZ<%-so3<BP_lFJbo;4f%G_XC!Oe$@QYQ45J?8*#|O9#q;j9=5Qp{zlS-~B20V+YzBOnyob4+V(T&jg z_WJ6|WGf2hZ1&|$M|z4ftt}bT>Vaa!P=bchT1i7^^ur2rWnCZK1yuU45T)(bSzzZa zzkdf!8=$G8d0W)2!UlrXZfYl7Cn!{|fL%giZtVj_Bb)5nEIf+^=SI4R#s0g>^C#=`-g25sq2#C_EV$ zY56-#8--shAa{`WFTLp1rR6W%z<(lP2^?1N=dkiM;t0x8ei^e6}We{QPM9_fz=P(Nu#_fTU` zp)C@nTi3J;9r;&xbVRx4$lhH&vl}iS;7? zyHyp7?s5!EIl!>_7^GKpS+>EA&j}k`Wr3_LfZYB?(!sgG_yTk03y2kMQp6naLA{{a z5O=s_@-VTsAhEI=p@20>38J&-%U?=anmYo2_y+G%kY@_^z-{dwc1i!#>Xo{F#9Qsx z{<8JM*0&(G&$ga!y{+{^>m|PX3m+oB~d0Q;W2M?!l1o6~a-*1jbn1B`l-j zO)rkE^tC|$vYgOO>hifTlFL(cPOUJJ?R<@1zCzd46nzGpkouCQq+7g$i(DubrM~74 zz9Jp6uC&OJ%6egjf>LYjm@#~!_y?=OX$Aqa3eN1vIz^fS(O#Eq%9Rz@hf(`8V?KOz zLVclZ(sVn0b?aMzmSgWDS(T{4qRA3`4oRbI3|hAhZwsa+&uhHP<7PceAnLSI zg9u;LPmdq|uHj5vBVN@*-ngjIfl`tyaERkN3E`M{H5pZRkA{l)&A}I+!)*W*c}WojlPbqDsr&U9aX?QLx&svRfIpt$a-Tft2b;Y!d|zDfE4BoTE9V}=v3!7ew& z2DWhsyM&W89pR%mz$6Ig+iA2P$23dl>mtj!(&A#3hd_t6`K1*+Id|2fRJ6wmIn?U{ zitv?{tWu~TMY$1deZ^|2ve^57{@M>}{=NbJe*q%ke((Ry74O});s!D6>{u^u)+|me zyE*ks;PwEu9&te+H7w!xEJPY)E*s@;{4D?1tYB!9P` zPk10^QpCcVvrQhmsWg|^so#iI}8W?BFJ5$xDutwx)UKm*#%*QQqH zBaklnJTI>v+3uW3hsyE^rriaRmi>9gVPYBZTYmFD;D7Q5W}b5S)bl5_a4-e+^p+ zHAH{Zhis0-geoPb-J**G_U}OOhLRRA7Y*h;m>8J%NEf&3qSV640cn zK)%}g8;Dx((QmD5X~n@Jw|Q2-j9J*#+ibJ%Z36~6IJPI40W#x(r#ef2Xbe>P0ft3? z!=wJE{NzWCdIC5Q;ZOqX?{^7y5jgb1Grfooox^nLZSK-E8^2$EwTinG{9N2k>S~>C6 zSdKbK>COmGzLI{FS@ZJm8u@m`69Po^so0`ioF&8|w#}XwKVP{NFic1165x>WI`!&s zRKfy51=kCVSo$#F0r;Eug#divuYk3#0FlD==9P6_;$q5ra)`Ml?6Z%e?HAA?>V*VF8`vKwEB{$_7W z-_h_4xrTQxek%}VsM~m?l1*;PtLS=i4ZGyu1?gKQW}=#&0GiuWCA30{LR+>Jpn@2w$$J0nG-+@0{*wGS`PvP(zLweU+Nzl;da!ghZ$UMFK1b zzFnYXGouW1ot)~!CR$@*t0MmbPTD$28!EI`Ujaf`mU$lbw zf6wqD#|iFK)=Mvg;?e;lJe~Kb$;R#+yoKqR-Y*e~=fPBRJlF>ch-DB+tf;nn7(b85V{w0|Va2!GGPZyQlq%~@tZ{5>$92NslV2I4u zJ`N7+fM1l+8~;D>ky6}-1-0Ar#uav%Vi50Q+YexKyw=AWZ+5u~dVe1rd<|F&Z1otg z;sY5ZXOLVq#9G2Ir0VF2EC<4(lzMJQ#7u*a4vV|_2|_KR9}Q`GlEF{xxuFXxnp^WW zL995)^=B&_#X>D6|Na^x-Pf?{$-m<$U1Y~Q&}z|vsu%M6!$I9E+&oXtt)G=a?`+d7 zdjJLbJ!@&$+eM&0@z@>ieqhK_9zAvMIz4eOq#wRfDH5Q3!(Ywiu0)J{QN}54(DhZw z(tlW=skytJwW!fGKb)7#68&sG&-oo!>5EJLx(^!s8ts4trxmTd3XNa;U-hKrNlk+3ch}1t-prX+A^ac5-m_C3W_HvgRs3li60M>NAR00>7}$v z>7#(2WaRdk2uWapw!|cSZev8o=YXXkKF$RNfb9{mL(I97)_&sTYQfs82J{xCaaaJu ziE!TOg^-8o*ClllcT>EQlHQcAsYoQf&y>WKEf9gyWOr zHKnvJ0Ggus1!CO25(J_!KbQ^{*^T8FkFhJwXTi)(?*Ddj|k` z5CdN<{lr05HgxZy?T)lkYMOxK0Pu5lk_9r1*KIjPRhgCIEO&~Vt98W};V@JI&^{{= z$_8YZcd?7QNKnZ41u~{gHNL{nAe%u5A+1jgMoP25;o%U7N`!(8{+8$#6NZ>1WtzvD z1F_qbA_BF>*MTa_)8<13%TW60>Xfb@a8LpBM1j`vTnSr8u|mEcu-KJncW?1#-9?^+)0;o%J#;u(`+NhbQ;j~qT(?!W5@#H`6NVEMJf&X!1Mkd#v-4^ zu|_ck9lRw8VTNHE{9InG6tr43DP$!Q>AV9pNC?e`)i^+m)^&|PYyA-NVAVR~>l#Om zEsi6zSaQUs8h2Pt3fr}~5EG7--0qUGa#&ms0PAt!`yrjz{x`(ce`@*ri8LH@I;68*xD8^g;S&?t}w@_lFV zc=7eOdSAHsBfYhzj5DJ-3k$6>Tx~)^PdA1DTpNw_*dR`x8o?9v&5FAqd{dhY6GDG6 zBXI#%7BUyF~tf2CRdcz{g}U;BRRPXUMg{|~j^+G(ca z4(H3d+Ut~dlk9x*Di&_x5xQJFm^Y5{7zGgYBdV(noxRLma`%=nip%F8dmKGD$$hnv z{mV=iiaca!B$+ttD3ye-f-_Ir22%CCi69Y*G7h*qrqYNF&CkkpzSqH>KpCCJdKGos zqzVK-*$EhLS1s1+)^pU~ZcSScwVr`I^wQ!)#iIARV7E*3tb^SPjw8=#yf72>L`D6R zho)u1xwGavKb~`fZ{EK=cFoJ9`#}Qn3WPMpK!H`a8SU~zJdhajkJDf@C#zG@*F+D1 zM*_WtP~F`YF5HuW-n#l0I790a9>gNCi!{a+048Fty|o#~KiWUsl?R_m*i&^4=rhc^wGF z_<(jGL6Qy`VXP(ua`}bV{-X6w_?NU+muFI%hQz{R&Dy79>&PWY_of%}-s`i9$*xIx-EdWF%D^)Y3B><2`0e?lOh z9Z{5BQ`R3j3g~3WwLfMP(9vBdfZq}z5_BW#N*7#`HtY$`A~X_K5kAJ0P<90^E6)TM|kDw`SU3n zM*~Kb6T5qUptU7HbN*?qqHW9H9m?Ok{NC2z^Q_B5v|l2xgY@XAKGIS6r32dU&*%Pm z;WzM3oA`qAF`0$W9Eq%cPje2bP7O_Ngy>(?rDwO;%D1MVjRK-{jM?O8P(`*(f(C68 zFojZsk&T8I{K*=Ebcsih`E-1Ss$eK7L`H`@0qn_!Dh8lKK$HT1b~~g(oMcXNal-N~ ztK;29R&0x}5Wy?~_#@6Sxj{Dj#2K>TFyVdNRubhgl@KbSuW{mO)6=3{1| z@)lLc;{;e!@8bhnK7}1FYEp}9J*s)+5nm9={J6W=MB8$7=mp5#7RcY2jH;2)VCXIy zt|4r7ng9zc;{dG*AuK37f4)aaw=bMYh&zD)W6(@Ut(C=`83Wm+>V}dA1HW2 z-CMSMw0GWjjzEe~1O9={rcY=-W$AN7Bn4Ty2U0AWwM7o^}-H&-VO^I)a0|*>17Z7e+PZI zjb}jIiJ$c|oQ$f|(kiOna3L#Ee;rU3RskQ9y#)LhWL#Z_Z4XJWJ7FrAO_TemF>3<| zi18k`(L0oLr#jpO{ho|ZvC}_+1F>h2@q_y7iXTeBOvNL__@*BLVa^Jr!sufuO-SHec5Rn$I3GgeaUOAt*OsBUv&9<*tXEI+`U1hdc_- zpF|0;gO1R$*tL{Pl&sMslVX=$Qo#S)!f9KE)>bnVM%{h9*VbBpCNTaOXq9Mig%Fip z!9#am;sTF`*H@LntLMbTjR%xDvL_`fN95cNy7nCLhfpj(1ezgNCY{J)Mo7OiAJEBK z7Dd34n&|hlhIiK!sdXjgw$*`~951hSqeZy*;IGRix0�c^1%Hc3A^x3|I50MI|K@ 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℘yL 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 u`p;Z>Ogi804|JItPhy1pF^*2Q6~| literal 0 HcmV?d00001 diff --git a/src/test/java/com/gxwebsoft/RedisTest.java b/src/test/java/com/gxwebsoft/RedisTest.java new file mode 100644 index 0000000..e3fe105 --- /dev/null +++ b/src/test/java/com/gxwebsoft/RedisTest.java @@ -0,0 +1,30 @@ +package com.gxwebsoft; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.StringRedisTemplate; + +import javax.annotation.Resource; + +@SpringBootTest +public class RedisTest { + @Resource + private StringRedisTemplate stringRedisTemplate; +// +// @Test +// public void test(){ +//// stringRedisTemplate.opsForValue().set("test:add:2",Long.toString(1L)); +//// stringRedisTemplate.opsForValue().increment("test:add:2",10L); +//// stringRedisTemplate.opsForValue().decrement("test:add:2",2L); +//// stringRedisTemplate.opsForValue().append("test:add:2","ssss"); +// HashMap map = new HashMap<>(); +// map.put("name","李四"); +// map.put("phone","13800138001"); +// HashMap map2 = new HashMap<>(); +// map2.put("name","赵六"); +// map2.put("phone","13800138001"); +// HashMap map3 = new HashMap<>(); +// map3.put("name","张三"); +// map3.put("phone","13800138001"); +// stringRedisTemplate.opsForSet().add("test:set:2", JSONUtil.toJSONString(map),JSONUtil.toJSONString(map2),JSONUtil.toJSONString(map3)); +// } +} diff --git a/src/test/java/com/gxwebsoft/TestMain.java b/src/test/java/com/gxwebsoft/TestMain.java new file mode 100644 index 0000000..5efb394 --- /dev/null +++ b/src/test/java/com/gxwebsoft/TestMain.java @@ -0,0 +1,95 @@ +package com.gxwebsoft; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import com.gxwebsoft.hjm.controller.PushCallback; +import com.gxwebsoft.hjm.entity.HjmCar; +import com.gxwebsoft.hjm.service.HjmCarService; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.text.ParseException; + +/** + * Created by WebSoft on 2020-03-23 23:37 + */ +@SpringBootTest +public class TestMain { + private static final Logger logger = LoggerFactory.getLogger(PushCallback.class); + + @Resource + private HjmCarService hjmCarService; + + /** + * 生成唯一的key用于jwt工具类 + */ + @Test + public void testGenJwtKey() { + + BigDecimal bigDecimal = new BigDecimal(NumberUtil.decimalFormat("0.00", 1 * 0.01)); + System.out.println("实际付款金额111111111 = " + bigDecimal); + + final HjmCar byGpsNo = hjmCarService.getByGpsNo("gps1"); + System.out.println("byGpsNo = " + byGpsNo.getSpeed()); + if(StrUtil.isBlank(byGpsNo.getSpeed())){ + System.out.println("空的 = "); + } + if(byGpsNo.getSpeed() == null){ + System.out.println("getSpeed = "); + } +// System.out.println(JwtUtil.encodeKey(JwtUtil.randomKey())); + +// final String encrypt = CommonUtil.encrypt("123456"); +// System.out.println("encrypt = " + encrypt); +// +// final String decrypt = CommonUtil.decrypt(encrypt); +// System.out.println("decrypt = " + decrypt); + } + +// @Test +// public void mqtt() throws MqttException { +////tcp://MQTT安装的服务器地址:MQTT定义的端口号 +// String HOST = "tcp://1.14.159.185:1883"; +// +// //定义MQTT的ID,可以在MQTT服务配置中指定 +// String clientid = "mqttx_aec633ca2"; +// +// MqttClient client; +// String userName = "swdev"; +// String passWord = "Sw20250523"; +// +// client = new MqttClient(HOST, clientid, new MemoryPersistence()); +// +// final MqttConnectOptions mqttConnectOptions = new MqttConnectOptions(); +// mqttConnectOptions.setUserName(userName); +// mqttConnectOptions.setPassword(passWord.toCharArray()); +// mqttConnectOptions.setCleanSession(true); +// +// client.setCallback(new MqttCallback() { +// @Override +// public void connectionLost(Throwable throwable) { +// logger.info("连接丢失............."); +// } +// +// @Override +// public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { +// logger.info("接收消息主题 : " + topic); +// logger.info("接收消息Qos : " + mqttMessage.getQos()); +// logger.info("接收消息内容 : " + new String(mqttMessage.getPayload())); +// } +// +// @Override +// public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { +// logger.info("deliveryComplete............."); +// } +// }); +// client.connect(mqttConnectOptions); +// client.subscribe("/SW_GSP/#", 2); +// while (true); +// } +} diff --git a/src/test/java/com/gxwebsoft/WebSoftApplicationTests.java b/src/test/java/com/gxwebsoft/WebSoftApplicationTests.java new file mode 100644 index 0000000..5a024f5 --- /dev/null +++ b/src/test/java/com/gxwebsoft/WebSoftApplicationTests.java @@ -0,0 +1,13 @@ +package com.gxwebsoft; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class WebSoftApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/src/test/java/com/gxwebsoft/WxDev.java b/src/test/java/com/gxwebsoft/WxDev.java new file mode 100644 index 0000000..110a008 --- /dev/null +++ b/src/test/java/com/gxwebsoft/WxDev.java @@ -0,0 +1,110 @@ +package com.gxwebsoft; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicReference; + +@Service +public class WxDev { + + @Value("${wechat.appid}") + private String appId; + @Value("${wechat.secret}") + private String secret; + + private final StringRedisTemplate redisTemplate; + + private final OkHttpClient http = new OkHttpClient(); + private final ObjectMapper om = new ObjectMapper(); + + /** 简单本地缓存 access_token(生产建议放 Redis) */ + private final AtomicReference cachedToken = new AtomicReference<>(); + private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒 + + public WxDev(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + /** 获取/刷新 access_token */ + public String getAccessToken() throws IOException { + long now = Instant.now().getEpochSecond(); + System.out.println("cachedToken.get = " + cachedToken.get()); + if (cachedToken.get() != null && now < tokenExpireEpoch - 60) { + return cachedToken.get(); + } + HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token") + .newBuilder() + .addQueryParameter("grant_type", "client_credential") + .addQueryParameter("appid", "wx51962d6ac21f2ed2") + .addQueryParameter("secret", "d821f98de8a6c1ba7bc7e0ee84bcbc8e") + .build(); + Request req = new Request.Builder().url(url).get().build(); + try (Response resp = http.newCall(req).execute()) { + String body = resp.body().string(); + JsonNode json = om.readTree(body); + if (json.has("access_token")) { + String token = json.get("access_token").asText(); + int expiresIn = json.get("expires_in").asInt(7200); + System.out.println("token1 = " + token); + cachedToken.set(token); + tokenExpireEpoch = now + expiresIn; + return token; + } else { + throw new IOException("Get access_token failed: " + body); + } + } + } + + /** 调用 getwxacodeunlimit,返回图片二进制 */ + public byte[] getUnlimitedCode(String scene, String page, Integer width, + Boolean isHyaline, String envVersion) throws IOException { + String accessToken = getAccessToken(); + System.out.println("accessToken = " + accessToken); + HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/wxa/getwxacodeunlimit") + .newBuilder() + .addQueryParameter("access_token", accessToken) + .build(); + + // 构造请求 JSON + // 注意:scene 仅支持可见字符,长度上限 32,尽量 URL-safe(字母数字下划线等) + // page 必须是已发布小程序内的路径(不带开头斜杠也可) + var root = om.createObjectNode(); + root.put("scene", scene); + if (page != null) root.put("page", page); + if (width != null) root.put("width", width); // 默认 430,建议 280~1280 + if (isHyaline != null) root.put("is_hyaline", isHyaline); + if (envVersion != null) root.put("env_version", envVersion); // release/trial/develop + + RequestBody reqBody = RequestBody.create( + root.toString(), MediaType.parse("application/json; charset=utf-8")); + Request req = new Request.Builder().url(url).post(reqBody).build(); + + try (Response resp = http.newCall(req).execute()) { + if (!resp.isSuccessful()) { + throw new IOException("HTTP " + resp.code() + " calling getwxacodeunlimit"); + } + MediaType ct = resp.body().contentType(); + byte[] bytes = resp.body().bytes(); + // 微信出错时返回 JSON,需要识别一下 + if (ct != null && ct.subtype() != null && ct.subtype().contains("json")) { + String err = new String(bytes); + throw new IOException("WeChat error: " + err); + } + return bytes; // 成功就是图片二进制(PNG) + } + } + + @Test + public void getQrCode() throws IOException { + final byte[] test = getUnlimitedCode("register", "pages/index/index",180,false,"develop"); + System.out.println("test = " + test); + } +} diff --git a/src/test/java/com/gxwebsoft/common/core/controller/QrCodeControllerTest.java b/src/test/java/com/gxwebsoft/common/core/controller/QrCodeControllerTest.java new file mode 100644 index 0000000..9677d80 --- /dev/null +++ b/src/test/java/com/gxwebsoft/common/core/controller/QrCodeControllerTest.java @@ -0,0 +1,102 @@ +package com.gxwebsoft.common.core.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gxwebsoft.common.core.dto.qr.CreateEncryptedQrCodeRequest; +import com.gxwebsoft.common.core.utils.EncryptedQrCodeUtil; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * QR码控制器测试 + * + * @author WebSoft + * @since 2025-08-18 + */ +@WebMvcTest(QrCodeController.class) +public class QrCodeControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private EncryptedQrCodeUtil encryptedQrCodeUtil; + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void testCreateEncryptedQrCodeWithValidRequest() throws Exception { + // 准备测试数据 + CreateEncryptedQrCodeRequest request = new CreateEncryptedQrCodeRequest(); + request.setData("test data"); + request.setWidth(200); + request.setHeight(200); + request.setExpireMinutes(30L); + request.setBusinessType("TEST"); + + Map mockResult = new HashMap<>(); + mockResult.put("qrCodeBase64", "base64_encoded_image"); + mockResult.put("token", "test_token"); + + when(encryptedQrCodeUtil.generateEncryptedQrCode(anyString(), anyInt(), anyInt(), anyLong(), anyString())) + .thenReturn(mockResult); + + // 执行测试 + mockMvc.perform(post("/api/qr-code/create-encrypted-qr-code") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("生成加密二维码成功")) + .andExpect(jsonPath("$.data.qrCodeBase64").value("base64_encoded_image")) + .andExpect(jsonPath("$.data.token").value("test_token")); + } + + @Test + public void testCreateEncryptedQrCodeWithInvalidRequest() throws Exception { + // 准备无效的测试数据(data为空) + CreateEncryptedQrCodeRequest request = new CreateEncryptedQrCodeRequest(); + request.setData(""); // 空字符串,应该触发验证失败 + request.setWidth(200); + request.setHeight(200); + request.setExpireMinutes(30L); + + // 执行测试 + mockMvc.perform(post("/api/qr-code/create-encrypted-qr-code") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(500)) + .andExpect(jsonPath("$.message").value("数据不能为空")); + } + + @Test + public void testCreateEncryptedQrCodeWithInvalidSize() throws Exception { + // 准备无效的测试数据(尺寸超出范围) + CreateEncryptedQrCodeRequest request = new CreateEncryptedQrCodeRequest(); + request.setData("test data"); + request.setWidth(2000); // 超出最大值1000 + request.setHeight(200); + request.setExpireMinutes(30L); + + // 执行测试 + mockMvc.perform(post("/api/qr-code/create-encrypted-qr-code") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(500)) + .andExpect(jsonPath("$.message").value("宽度不能大于1000像素")); + } +} diff --git a/src/test/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtilTest.java b/src/test/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtilTest.java new file mode 100644 index 0000000..bb0ebcd --- /dev/null +++ b/src/test/java/com/gxwebsoft/common/core/utils/EncryptedQrCodeUtilTest.java @@ -0,0 +1,136 @@ +package com.gxwebsoft.common.core.utils; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.annotation.Resource; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 加密二维码工具类测试 + * + * @author WebSoft + * @since 2025-08-18 + */ +@SpringBootTest +@ActiveProfiles("dev") +public class EncryptedQrCodeUtilTest { + + @Resource + private EncryptedQrCodeUtil encryptedQrCodeUtil; + + @Test + public void testGenerateAndDecryptData() { + // 测试数据 + String originalData = "https://www.example.com/user/123"; + Long expireMinutes = 30L; + + // 生成加密数据 + Map encryptedInfo = encryptedQrCodeUtil.generateEncryptedData(originalData, expireMinutes); + + assertNotNull(encryptedInfo); + assertNotNull(encryptedInfo.get("token")); + assertNotNull(encryptedInfo.get("encryptedData")); + assertEquals(expireMinutes.toString(), encryptedInfo.get("expireMinutes")); + + // 解密数据 + String decryptedData = encryptedQrCodeUtil.decryptData( + encryptedInfo.get("token"), + encryptedInfo.get("encryptedData") + ); + + assertEquals(originalData, decryptedData); + } + + @Test + public void testGenerateEncryptedQrCode() { + // 测试数据 + String originalData = "测试二维码数据"; + int width = 300; + int height = 300; + Long expireMinutes = 60L; + + // 生成加密二维码 + Map result = encryptedQrCodeUtil.generateEncryptedQrCode( + originalData, width, height, expireMinutes + ); + + assertNotNull(result); + assertNotNull(result.get("qrCodeBase64")); + assertNotNull(result.get("token")); + assertEquals(originalData, result.get("originalData")); + assertEquals(expireMinutes.toString(), result.get("expireMinutes")); + } + + @Test + public void testTokenValidation() { + // 生成测试数据 + String originalData = "token验证测试"; + Map encryptedInfo = encryptedQrCodeUtil.generateEncryptedData(originalData, 30L); + String token = encryptedInfo.get("token"); + + // 验证token有效性 + assertTrue(encryptedQrCodeUtil.isTokenValid(token)); + + // 使token失效 + encryptedQrCodeUtil.invalidateToken(token); + + // 再次验证token应该无效 + assertFalse(encryptedQrCodeUtil.isTokenValid(token)); + } + + @Test + public void testInvalidToken() { + // 测试无效token + assertFalse(encryptedQrCodeUtil.isTokenValid("invalid_token")); + assertFalse(encryptedQrCodeUtil.isTokenValid("")); + assertFalse(encryptedQrCodeUtil.isTokenValid(null)); + } + + @Test + public void testDecryptWithInvalidToken() { + // 测试用无效token解密 + assertThrows(RuntimeException.class, () -> { + encryptedQrCodeUtil.decryptData("invalid_token", "encrypted_data"); + }); + } + + @Test + public void testGenerateEncryptedQrCodeWithBusinessType() { + // 测试数据 + String originalData = "用户登录数据"; + int width = 200; + int height = 200; + Long expireMinutes = 30L; + String businessType = "LOGIN"; + + // 生成带业务类型的加密二维码 + Map result = encryptedQrCodeUtil.generateEncryptedQrCode( + originalData, width, height, expireMinutes, businessType + ); + + assertNotNull(result); + assertNotNull(result.get("qrCodeBase64")); + assertNotNull(result.get("token")); + assertEquals(originalData, result.get("originalData")); + assertEquals(expireMinutes.toString(), result.get("expireMinutes")); + assertEquals(businessType, result.get("businessType")); + + System.out.println("=== 带BusinessType的二维码生成测试 ==="); + System.out.println("原始数据: " + originalData); + System.out.println("业务类型: " + businessType); + System.out.println("Token: " + result.get("token")); + System.out.println("二维码Base64长度: " + ((String)result.get("qrCodeBase64")).length()); + + // 验证不传businessType的情况 + Map resultWithoutType = encryptedQrCodeUtil.generateEncryptedQrCode( + originalData, width, height, expireMinutes + ); + + assertNull(resultWithoutType.get("businessType")); + System.out.println("不传BusinessType时的结果: " + resultWithoutType.get("businessType")); + } +} diff --git a/src/test/java/com/gxwebsoft/common/system/controller/WxLoginControllerTest.java b/src/test/java/com/gxwebsoft/common/system/controller/WxLoginControllerTest.java new file mode 100644 index 0000000..e879925 --- /dev/null +++ b/src/test/java/com/gxwebsoft/common/system/controller/WxLoginControllerTest.java @@ -0,0 +1,136 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.annotation.Resource; + +/** + * 微信登录控制器测试 + * + * @author WebSoft + * @since 2025-08-23 + */ +@Slf4j +@SpringBootTest +@ActiveProfiles("dev") +public class WxLoginControllerTest { + + @Resource + private UserService userService; + + /** + * 测试从scene参数解析租户ID的逻辑 + */ + @Test + public void testExtractTenantIdFromScene() { + log.info("=== 开始测试scene参数解析 ==="); + + // 测试用户ID 33103 + Integer testUserId = 33103; + + // 查询用户信息 + User user = userService.getByIdIgnoreTenant(testUserId); + if (user != null) { + log.info("用户ID {} 对应的租户ID: {}", testUserId, user.getTenantId()); + log.info("用户信息 - 用户名: {}, 手机: {}", user.getUsername(), user.getPhone()); + } else { + log.warn("未找到用户ID: {}", testUserId); + } + + // 测试不同的scene格式 + String[] testScenes = { + "uid_33103", + "uid_1", + "uid_999999", + "invalid_scene", + null + }; + + for (String scene : testScenes) { + log.info("测试scene: {} -> 预期解析结果", scene); + // 这里模拟解析逻辑 + if (scene != null && scene.startsWith("uid_")) { + try { + String userIdStr = scene.substring(4); + Integer userId = Integer.parseInt(userIdStr); + User testUser = userService.getByIdIgnoreTenant(userId); + if (testUser != null) { + log.info(" 解析成功: 用户ID {} -> 租户ID {}", userId, testUser.getTenantId()); + } else { + log.info(" 用户不存在: 用户ID {} -> 默认租户ID 10550", userId); + } + } catch (Exception e) { + log.info(" 解析异常: {} -> 默认租户ID 10550", e.getMessage()); + } + } else { + log.info(" 无效格式 -> 默认租户ID 10550"); + } + } + + log.info("=== scene参数解析测试完成 ==="); + } + + /** + * 测试查找特定用户 + */ + @Test + public void testFindSpecificUsers() { + log.info("=== 开始查找特定用户 ==="); + + // 查找租户10550的用户 + Integer[] testUserIds = {1, 2, 3, 33103, 10001, 10002}; + + for (Integer userId : testUserIds) { + User user = userService.getByIdIgnoreTenant(userId); + if (user != null) { + log.info("用户ID: {}, 租户ID: {}, 用户名: {}, 手机: {}", + userId, user.getTenantId(), user.getUsername(), user.getPhone()); + } else { + log.info("用户ID: {} - 不存在", userId); + } + } + + log.info("=== 特定用户查找完成 ==="); + } + + /** + * 测试URL解析 + */ + @Test + public void testUrlParsing() { + log.info("=== 开始测试URL解析 ==="); + + String testUrl = "127.0.0.1:9200/api/wx-login/getOrderQRCodeUnlimited/uid_33103"; + log.info("测试URL: {}", testUrl); + + // 提取scene部分 + String[] parts = testUrl.split("/"); + String scene = parts[parts.length - 1]; // 最后一部分 + log.info("提取的scene: {}", scene); + + // 解析用户ID + if (scene.startsWith("uid_")) { + String userIdStr = scene.substring(4); + try { + Integer userId = Integer.parseInt(userIdStr); + log.info("解析的用户ID: {}", userId); + + User user = userService.getByIdIgnoreTenant(userId); + if (user != null) { + log.info("对应的租户ID: {}", user.getTenantId()); + } else { + log.warn("用户不存在"); + } + } catch (NumberFormatException e) { + log.error("用户ID格式错误: {}", userIdStr); + } + } + + log.info("=== URL解析测试完成 ==="); + } +} diff --git a/src/test/java/com/gxwebsoft/common/system/service/UserIgnoreTenantTest.java b/src/test/java/com/gxwebsoft/common/system/service/UserIgnoreTenantTest.java new file mode 100644 index 0000000..d24bc1d --- /dev/null +++ b/src/test/java/com/gxwebsoft/common/system/service/UserIgnoreTenantTest.java @@ -0,0 +1,100 @@ +package com.gxwebsoft.common.system.service; + +import com.gxwebsoft.common.system.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.annotation.Resource; + +/** + * 用户忽略租户隔离功能测试 + * + * @author WebSoft + * @since 2025-08-23 + */ +@Slf4j +@SpringBootTest +@ActiveProfiles("dev") +public class UserIgnoreTenantTest { + + @Resource + private UserService userService; + + /** + * 测试忽略租户隔离查询用户 + */ + @Test + public void testGetByIdIgnoreTenant() { + // 测试用户ID(请根据实际数据库中的用户ID进行调整) + Integer testUserId = 1; + + log.info("=== 开始测试忽略租户隔离查询用户功能 ==="); + + // 1. 使用普通方法查询用户(受租户隔离影响) + User userNormal = userService.getById(testUserId); + log.info("普通查询结果 - 用户ID: {}, 用户信息: {}", testUserId, + userNormal != null ? userNormal.getUsername() : "null"); + + // 2. 使用忽略租户隔离方法查询用户 + User userIgnoreTenant = userService.getByIdIgnoreTenant(testUserId); + log.info("忽略租户隔离查询结果 - 用户ID: {}, 用户信息: {}", testUserId, + userIgnoreTenant != null ? userIgnoreTenant.getUsername() : "null"); + + // 3. 验证结果 + if (userIgnoreTenant != null) { + log.info("✅ 忽略租户隔离查询成功!"); + log.info("用户详情 - ID: {}, 用户名: {}, 昵称: {}, 租户ID: {}", + userIgnoreTenant.getUserId(), + userIgnoreTenant.getUsername(), + userIgnoreTenant.getNickname(), + userIgnoreTenant.getTenantId()); + } else { + log.error("❌ 忽略租户隔离查询失败!"); + } + + log.info("=== 忽略租户隔离查询用户功能测试完成 ==="); + } + + /** + * 测试参数验证 + */ + @Test + public void testGetByIdIgnoreTenantValidation() { + log.info("=== 开始测试参数验证 ==="); + + // 测试null用户ID + User result1 = userService.getByIdIgnoreTenant(null); + log.info("null用户ID测试结果: {}", result1 == null ? "成功(返回null)" : "失败"); + + // 测试不存在的用户ID + User result2 = userService.getByIdIgnoreTenant(999999); + log.info("不存在用户ID测试结果: {}", result2 == null ? "成功(返回null)" : "失败"); + + log.info("=== 参数验证测试完成 ==="); + } + + /** + * 测试跨租户查询 + */ + @Test + public void testCrossTenantQuery() { + log.info("=== 开始测试跨租户查询 ==="); + + // 查询不同租户的用户(请根据实际数据调整) + Integer[] testUserIds = {1, 2, 3, 4, 5}; + + for (Integer userId : testUserIds) { + User user = userService.getByIdIgnoreTenant(userId); + if (user != null) { + log.info("用户ID: {}, 用户名: {}, 租户ID: {}", + user.getUserId(), user.getUsername(), user.getTenantId()); + } else { + log.info("用户ID: {} - 不存在", userId); + } + } + + log.info("=== 跨租户查询测试完成 ==="); + } +} diff --git a/src/test/java/com/gxwebsoft/common/system/service/WeixinConfigTest.java b/src/test/java/com/gxwebsoft/common/system/service/WeixinConfigTest.java new file mode 100644 index 0000000..1b915d0 --- /dev/null +++ b/src/test/java/com/gxwebsoft/common/system/service/WeixinConfigTest.java @@ -0,0 +1,174 @@ +package com.gxwebsoft.common.system.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.cms.entity.CmsWebsiteField; +import com.gxwebsoft.cms.service.CmsWebsiteFieldService; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 微信小程序配置测试 + * + * @author WebSoft + * @since 2025-08-23 + */ +@Slf4j +@SpringBootTest +@ActiveProfiles("dev") +public class WeixinConfigTest { + + @Resource + private SettingService settingService; + + @Resource + private CmsWebsiteFieldService cmsWebsiteFieldService; + + /** + * 测试从cms_website_field表获取微信小程序配置 + */ + @Test + public void testGetWeixinConfigFromWebsiteField() { + Integer tenantId = 10550; + + log.info("=== 开始测试从cms_website_field表获取微信小程序配置 ==="); + + // 1. 查看cms_website_field表中的所有配置 + List allFields = cmsWebsiteFieldService.list( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getTenantId, tenantId) + .eq(CmsWebsiteField::getDeleted, 0) + ); + + log.info("租户{}的所有cms_website_field配置:", tenantId); + for (CmsWebsiteField field : allFields) { + log.info(" - ID: {}, Name: {}, Value: {}", field.getId(), field.getName(), field.getValue()); + } + + // 2. 查找AppID配置 + CmsWebsiteField appIdField = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, "AppID") + .eq(CmsWebsiteField::getTenantId, tenantId) + .eq(CmsWebsiteField::getDeleted, 0) + ); + + log.info("AppID配置: {}", appIdField); + + // 3. 查找AppSecret配置 + CmsWebsiteField appSecretField = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, "AppSecret") + .eq(CmsWebsiteField::getTenantId, tenantId) + .eq(CmsWebsiteField::getDeleted, 0) + ); + + log.info("AppSecret配置: {}", appSecretField); + + // 4. 测试获取微信小程序配置 + try { + JSONObject config = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId); + log.info("✅ 成功获取微信小程序配置: {}", config); + } catch (Exception e) { + log.error("❌ 获取微信小程序配置失败: {}", e.getMessage()); + } + + log.info("=== 微信小程序配置测试完成 ==="); + } + + /** + * 测试不同name的查询 + */ + @Test + public void testDifferentNameQueries() { + Integer tenantId = 10550; + + log.info("=== 开始测试不同name的查询 ==="); + + String[] nameVariations = {"AppID", "appId", "APPID", "app_id", "AppSecret", "appSecret", "APPSECRET", "app_secret"}; + + for (String name : nameVariations) { + CmsWebsiteField field = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, name) + .eq(CmsWebsiteField::getTenantId, tenantId) + .eq(CmsWebsiteField::getDeleted, 0) + ); + + if (field != null) { + log.info("找到配置 - Name: {}, Value: {}", name, field.getValue()); + } else { + log.info("未找到配置 - Name: {}", name); + } + } + + log.info("=== 不同name查询测试完成 ==="); + } + + /** + * 测试创建测试配置 + */ + @Test + public void testCreateTestConfig() { + Integer tenantId = 10550; + + log.info("=== 开始创建测试配置 ==="); + + // 创建AppID配置 + CmsWebsiteField appIdField = new CmsWebsiteField(); + appIdField.setName("AppID"); + appIdField.setValue("wx1234567890abcdef"); // 测试AppID + appIdField.setTenantId(tenantId); + appIdField.setType(0); // 文本类型 + appIdField.setComments("微信小程序AppID"); + appIdField.setDeleted(0); + + // 创建AppSecret配置 + CmsWebsiteField appSecretField = new CmsWebsiteField(); + appSecretField.setName("AppSecret"); + appSecretField.setValue("abcdef1234567890abcdef1234567890"); // 测试AppSecret + appSecretField.setTenantId(tenantId); + appSecretField.setType(0); // 文本类型 + appSecretField.setComments("微信小程序AppSecret"); + appSecretField.setDeleted(0); + + try { + // 检查是否已存在 + CmsWebsiteField existingAppId = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, "AppID") + .eq(CmsWebsiteField::getTenantId, tenantId) + ); + + if (existingAppId == null) { + cmsWebsiteFieldService.save(appIdField); + log.info("✅ 创建AppID配置成功"); + } else { + log.info("AppID配置已存在,跳过创建"); + } + + CmsWebsiteField existingAppSecret = cmsWebsiteFieldService.getOne( + new LambdaQueryWrapper() + .eq(CmsWebsiteField::getName, "AppSecret") + .eq(CmsWebsiteField::getTenantId, tenantId) + ); + + if (existingAppSecret == null) { + cmsWebsiteFieldService.save(appSecretField); + log.info("✅ 创建AppSecret配置成功"); + } else { + log.info("AppSecret配置已存在,跳过创建"); + } + + } catch (Exception e) { + log.error("❌ 创建测试配置失败: {}", e.getMessage()); + } + + log.info("=== 创建测试配置完成 ==="); + } +} diff --git a/src/test/java/com/gxwebsoft/config/MqttPropertiesTest.java b/src/test/java/com/gxwebsoft/config/MqttPropertiesTest.java new file mode 100644 index 0000000..7570f9a --- /dev/null +++ b/src/test/java/com/gxwebsoft/config/MqttPropertiesTest.java @@ -0,0 +1,44 @@ +package com.gxwebsoft.config; + +import com.gxwebsoft.common.core.config.MqttProperties; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.annotation.Resource; + +/** + * MQTT配置属性测试类 + * + * @author 科技小王子 + * @since 2025-07-02 + */ +@SpringBootTest +@ActiveProfiles("dev") +public class MqttPropertiesTest { + + @Resource + private MqttProperties mqttProperties; + + @Test + public void testMqttPropertiesLoading() { + System.out.println("=== MQTT配置属性测试 ==="); + System.out.println("Host: " + mqttProperties.getHost()); + System.out.println("Username: " + mqttProperties.getUsername()); + System.out.println("Password: " + (mqttProperties.getPassword() != null ? "***" : "null")); + System.out.println("ClientIdPrefix: " + mqttProperties.getClientIdPrefix()); + System.out.println("Topic: " + mqttProperties.getTopic()); + System.out.println("QoS: " + mqttProperties.getQos()); + System.out.println("ConnectionTimeout: " + mqttProperties.getConnectionTimeout()); + System.out.println("KeepAliveInterval: " + mqttProperties.getKeepAliveInterval()); + System.out.println("AutoReconnect: " + mqttProperties.isAutoReconnect()); + System.out.println("CleanSession: " + mqttProperties.isCleanSession()); + + // 验证关键配置不为空 + assert mqttProperties.getHost() != null : "Host不能为空"; + assert mqttProperties.getClientIdPrefix() != null : "ClientIdPrefix不能为空"; + assert mqttProperties.getTopic() != null : "Topic不能为空"; + + System.out.println("MQTT配置属性测试通过!"); + } +} diff --git a/src/test/java/com/gxwebsoft/config/ServerUrlConfigTest.java b/src/test/java/com/gxwebsoft/config/ServerUrlConfigTest.java new file mode 100644 index 0000000..a85bbb9 --- /dev/null +++ b/src/test/java/com/gxwebsoft/config/ServerUrlConfigTest.java @@ -0,0 +1,51 @@ +package com.gxwebsoft.config; + +import com.gxwebsoft.common.core.config.ConfigProperties; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 服务器URL配置测试 + * 验证server-url配置是否正确从配置文件读取 + */ +@SpringBootTest +@ActiveProfiles("dev") +public class ServerUrlConfigTest { + + @Autowired + private ConfigProperties configProperties; + + @Test + public void testServerUrlConfiguration() { + // 验证配置属性不为空 + assertNotNull(configProperties, "ConfigProperties should not be null"); + + // 验证server-url配置正确读取 + String serverUrl = configProperties.getServerUrl(); + assertNotNull(serverUrl, "Server URL should not be null"); + assertFalse(serverUrl.isEmpty(), "Server URL should not be empty"); + + // 在开发环境下,应该是本地地址 + assertTrue(serverUrl.contains("127.0.0.1") || serverUrl.contains("localhost"), + "In dev environment, server URL should contain localhost or 127.0.0.1"); + + System.out.println("当前配置的服务器URL: " + serverUrl); + } + + @Test + public void testServerUrlFormat() { + String serverUrl = configProperties.getServerUrl(); + + // 验证URL格式 + assertTrue(serverUrl.startsWith("http://") || serverUrl.startsWith("https://"), + "Server URL should start with http:// or https://"); + assertTrue(serverUrl.endsWith("/api"), + "Server URL should end with /api"); + + System.out.println("服务器URL格式验证通过: " + serverUrl); + } +} diff --git a/src/test/java/com/gxwebsoft/generator/ShopGenerator.java b/src/test/java/com/gxwebsoft/generator/ShopGenerator.java new file mode 100644 index 0000000..5a407e4 --- /dev/null +++ b/src/test/java/com/gxwebsoft/generator/ShopGenerator.java @@ -0,0 +1,435 @@ +package com.gxwebsoft.generator; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.generator.AutoGenerator; +import com.baomidou.mybatisplus.generator.InjectionConfig; +import com.baomidou.mybatisplus.generator.config.*; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.gxwebsoft.generator.engine.BeetlTemplateEnginePlus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +/** + * CMS模块-代码生成工具 + * + * @author WebSoft + * @since 2021-09-05 00:31:14 + */ +public class ShopGenerator { + // 输出位置 + private static final String OUTPUT_LOCATION = System.getProperty("user.dir"); + //private static final String OUTPUT_LOCATION = "D:/codegen"; // 不想生成到项目中可以写磁盘路径 + // JAVA输出目录 + private static final String OUTPUT_DIR = "/src/main/java"; + // Vue文件输出位置 + private static final String OUTPUT_LOCATION_VUE = "/Users/gxwebsoft/VUE/mp-vue"; + // UniApp文件输出目录 + private static final String OUTPUT_LOCATION_UNIAPP = "/Users/gxwebsoft/VUE/template-10550"; + // Vue文件输出目录 + private static final String OUTPUT_DIR_VUE = "/src"; + // 作者名称 + private static final String AUTHOR = "科技小王子"; + // 是否在xml中添加二级缓存配置 + private static final boolean ENABLE_CACHE = false; + // 数据库连接配置 + private static final String DB_URL = "jdbc:mysql://8.134.169.209:13306/cms_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8"; + private static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver"; + private static final String DB_USERNAME = "cms_demo"; + private static final String DB_PASSWORD = "EtzJFr4A3c4THZjY"; + // 包名 + private static final String PACKAGE_NAME = "com.gxwebsoft"; + // 模块名 + private static final String MODULE_NAME = "oa"; + // 需要生成的表 + private static final String[] TABLE_NAMES = new String[]{ +// "shop_spec", +// "shop_spec_value", +// "shop_goods", +// "shop_category", +// "shop_goods_spec", +// "shop_goods_sku", +// "shop_goods_category", +// "shop_coupon", +// "shop_goods_description", +// "shop_goods_log", +// "shop_goods_relation", +// "shop_goods_comment", +// "shop_goods_rule", +// "shop_brand", +// "shop_merchant", +// "shop_merchant_type", +// "shop_merchant_apply", +// "shop_merchant_account", +// "shop_chat_message", +// "shop_chat_conversation", +// "shop_user_collection", +// "shop_goods_role_commission" +// "shop_commission_role" +// "shop_order", +// "shop_order_info", +// "shop_order_info_log", +// "shop_order_goods", +// "shop_wechat_deposit", +// "shop_users", +// "shop_user_address", +// "shop_user_balance_log", +// "shop_recharge_order", + // 经销商表 +// "shop_dealer_apply", +// "shop_dealer_capital", +// "shop_dealer_order", +// "shop_dealer_referee", +// "shop_dealer_setting", +// "shop_dealer_user", +// "shop_user_referee", +// "shop_dealer_withdraw", +// "shop_user_coupon", +// "shop_cart", +// "shop_count", +// "shop_order_delivery", +// "shop_order_delivery_goods", +// "shop_order_extract", +// "shop_splash", +// "shop_goods_income_config" +// "shop_express", +// "shop_express_template", +// "shop_express_template_detail", +// "shop_gift" + "shop_article" + }; + // 需要去除的表前缀 + private static final String[] TABLE_PREFIX = new String[]{ + "tb_" + }; + // 不需要作为查询参数的字段 + private static final String[] PARAM_EXCLUDE_FIELDS = new String[]{ + "tenant_id", + "create_time", + "update_time" + }; + // 查询参数使用String的类型 + private static final String[] PARAM_TO_STRING_TYPE = new String[]{ + "Date", + "LocalDate", + "LocalTime", + "LocalDateTime" + }; + // 查询参数使用EQ的类型 + private static final String[] PARAM_EQ_TYPE = new String[]{ + "Integer", + "Boolean", + "BigDecimal" + }; + // 是否添加权限注解 + private static final boolean AUTH_ANNOTATION = true; + // 是否添加日志注解 + private static final boolean LOG_ANNOTATION = true; + // controller的mapping前缀 + private static final String CONTROLLER_MAPPING_PREFIX = "/api"; + // 模板所在位置 + private static final String TEMPLATES_DIR = "/src/test/java/com/gxwebsoft/generator/templates"; + + public static void main(String[] args) { + // 代码生成器 + AutoGenerator mpg = new AutoGenerator(); + + // 全局配置 + GlobalConfig gc = new GlobalConfig(); + gc.setOutputDir(OUTPUT_LOCATION + OUTPUT_DIR); + gc.setAuthor(AUTHOR); + gc.setOpen(false); + gc.setFileOverride(true); + gc.setEnableCache(ENABLE_CACHE); + gc.setSwagger2(true); + gc.setIdType(IdType.AUTO); + gc.setServiceName("%sService"); + mpg.setGlobalConfig(gc); + + // 数据源配置 + DataSourceConfig dsc = new DataSourceConfig(); + dsc.setUrl(DB_URL); + // dsc.setSchemaName("public"); + dsc.setDriverName(DB_DRIVER); + dsc.setUsername(DB_USERNAME); + dsc.setPassword(DB_PASSWORD); + mpg.setDataSource(dsc); + + // 包配置 + PackageConfig pc = new PackageConfig(); + pc.setModuleName(MODULE_NAME); + pc.setParent(PACKAGE_NAME); + mpg.setPackageInfo(pc); + + // 策略配置 + StrategyConfig strategy = new StrategyConfig(); + strategy.setNaming(NamingStrategy.underline_to_camel); + strategy.setColumnNaming(NamingStrategy.underline_to_camel); + strategy.setInclude(TABLE_NAMES); + strategy.setTablePrefix(TABLE_PREFIX); + strategy.setSuperControllerClass(PACKAGE_NAME + ".common.core.web.BaseController"); + strategy.setEntityLombokModel(true); + strategy.setRestControllerStyle(true); + strategy.setControllerMappingHyphenStyle(true); + strategy.setLogicDeleteFieldName("deleted"); + mpg.setStrategy(strategy); + + // 模板配置 + TemplateConfig templateConfig = new TemplateConfig(); + templateConfig.setController(TEMPLATES_DIR + "/controller.java"); + templateConfig.setEntity(TEMPLATES_DIR + "/entity.java"); + templateConfig.setMapper(TEMPLATES_DIR + "/mapper.java"); + templateConfig.setXml(TEMPLATES_DIR + "/mapper.xml"); + templateConfig.setService(TEMPLATES_DIR + "/service.java"); + templateConfig.setServiceImpl(TEMPLATES_DIR + "/serviceImpl.java"); + mpg.setTemplate(templateConfig); + mpg.setTemplateEngine(new BeetlTemplateEnginePlus()); + + // 自定义模板配置 + InjectionConfig cfg = new InjectionConfig() { + @Override + public void initMap() { + Map map = new HashMap<>(); + map.put("packageName", PACKAGE_NAME); + map.put("paramExcludeFields", PARAM_EXCLUDE_FIELDS); + map.put("paramToStringType", PARAM_TO_STRING_TYPE); + map.put("paramEqType", PARAM_EQ_TYPE); + map.put("authAnnotation", AUTH_ANNOTATION); + map.put("logAnnotation", LOG_ANNOTATION); + map.put("controllerMappingPrefix", CONTROLLER_MAPPING_PREFIX); + // 添加项目类型标识,用于模板中的条件判断 + map.put("isUniApp", false); // Vue 项目 + map.put("isVueAdmin", true); // 后台管理项目 + this.setMap(map); + } + }; + String templatePath = TEMPLATES_DIR + "/param.java.btl"; + List focList = new ArrayList<>(); + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION + OUTPUT_DIR + "/" + + PACKAGE_NAME.replace(".", "/") + + "/" + pc.getModuleName() + "/param/" + + tableInfo.getEntityName() + "Param" + StringPool.DOT_JAVA; + } + }); + /** + * 以下是生成VUE项目代码 + * 生成文件的路径 /api/shop/goods/index.ts + */ + templatePath = TEMPLATES_DIR + "/index.ts.btl"; + + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE + + "/api/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "index.ts"; + } + }); + // UniApp 使用专门的模板 + String uniappTemplatePath = TEMPLATES_DIR + "/index.ts.uniapp.btl"; + focList.add(new FileOutConfig(uniappTemplatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + + "/api/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "index.ts"; + } + }); + // 生成TS文件 (/api/shop/goods/model/index.ts) + templatePath = TEMPLATES_DIR + "/model.ts.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE + + "/api/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/model/" + "index.ts"; + } + }); + // UniApp 使用专门的 model 模板 + String uniappModelTemplatePath = TEMPLATES_DIR + "/model.ts.uniapp.btl"; + focList.add(new FileOutConfig(uniappModelTemplatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + + "/api/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/model/" + "index.ts"; + } + }); + // 生成Vue文件(/views/shop/goods/index.vue) + templatePath = TEMPLATES_DIR + "/index.vue.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE + + "/views/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "index.vue"; + } + }); + + // 生成components文件(/views/shop/goods/components/edit.vue) + templatePath = TEMPLATES_DIR + "/components.edit.vue.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE + + "/views/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/components/" + tableInfo.getEntityPath() + "Edit.vue"; + } + }); + + // 生成components文件(/views/shop/goods/components/search.vue) + templatePath = TEMPLATES_DIR + "/components.search.vue.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE + + "/views/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/components/" + "search.vue"; + } + }); + + // ========== 移动端页面文件生成 ========== + // 生成移动端列表页面配置文件 (/src/shop/goods/index.config.ts) + templatePath = TEMPLATES_DIR + "/index.config.ts.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + + "/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "index.config.ts"; + } + }); + + // 生成移动端列表页面组件文件 (/src/shop/goods/index.tsx) + templatePath = TEMPLATES_DIR + "/index.tsx.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + + "/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "index.tsx"; + } + }); + + // 生成移动端新增/编辑页面配置文件 (/src/shop/goods/add.config.ts) + templatePath = TEMPLATES_DIR + "/add.config.ts.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + + "/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "add.config.ts"; + } + }); + + // 生成移动端新增/编辑页面组件文件 (/src/shop/goods/add.tsx) + templatePath = TEMPLATES_DIR + "/add.tsx.btl"; + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + + "/" + pc.getModuleName() + "/" + + tableInfo.getEntityPath() + "/" + "add.tsx"; + } + }); + + cfg.setFileOutConfigList(focList); + mpg.setCfg(cfg); + + mpg.execute(); + + // 自动更新 app.config.ts + updateAppConfig(TABLE_NAMES, MODULE_NAME); + } + + /** + * 自动更新 app.config.ts 文件,添加新生成的页面路径 + */ + private static void updateAppConfig(String[] tableNames, String moduleName) { + String appConfigPath = OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + "/app.config.ts"; + + try { + // 读取原文件内容 + String content = new String(Files.readAllBytes(Paths.get(appConfigPath))); + + // 为每个表生成页面路径 + StringBuilder newPages = new StringBuilder(); + for (String tableName : tableNames) { + String entityPath = tableName.replaceAll("_", ""); + // 转换为驼峰命名 + String[] parts = tableName.split("_"); + StringBuilder camelCase = new StringBuilder(parts[0]); + for (int i = 1; i < parts.length; i++) { + camelCase.append(parts[i].substring(0, 1).toUpperCase()).append(parts[i].substring(1)); + } + entityPath = camelCase.toString(); + + newPages.append(" '").append(entityPath).append("/index',\n"); + newPages.append(" '").append(entityPath).append("/add',\n"); + } + + // 查找对应模块的子包配置 + String modulePattern = "\"root\":\\s*\"" + moduleName + "\",\\s*\"pages\":\\s*\\[([^\\]]*)]"; + Pattern pattern = Pattern.compile(modulePattern, Pattern.DOTALL); + Matcher matcher = pattern.matcher(content); + + if (matcher.find()) { + String existingPages = matcher.group(1); + + // 检查页面是否已存在,避免重复添加 + boolean needUpdate = false; + String[] newPageArray = newPages.toString().split("\n"); + for (String newPage : newPageArray) { + if (!newPage.trim().isEmpty() && !existingPages.contains(newPage.trim().replace(" ", "").replace(",", ""))) { + needUpdate = true; + break; + } + } + + if (needUpdate) { + // 备份原文件 + String backupPath = appConfigPath + ".backup." + System.currentTimeMillis(); + Files.copy(Paths.get(appConfigPath), Paths.get(backupPath)); + System.out.println("已备份原文件到: " + backupPath); + + // 在现有页面列表末尾添加新页面 + String updatedPages = existingPages.trim(); + if (!updatedPages.endsWith(",")) { + updatedPages += ","; + } + updatedPages += "\n" + newPages.toString().trim(); + + // 替换内容 + String updatedContent = content.replace(matcher.group(1), updatedPages); + + // 写入更新后的内容 + Files.write(Paths.get(appConfigPath), updatedContent.getBytes()); + + System.out.println("✅ 已自动更新 app.config.ts,添加了以下页面路径:"); + System.out.println(newPages.toString()); + } else { + System.out.println("ℹ️ app.config.ts 中已包含所有页面路径,无需更新"); + } + } else { + System.out.println("⚠️ 未找到 " + moduleName + " 模块的子包配置,请手动添加页面路径"); + } + + } catch (Exception e) { + System.err.println("❌ 更新 app.config.ts 失败: " + e.getMessage()); + e.printStackTrace(); + } + } + +} diff --git a/src/test/java/com/gxwebsoft/generator/engine/BeetlTemplateEnginePlus.java b/src/test/java/com/gxwebsoft/generator/engine/BeetlTemplateEnginePlus.java new file mode 100644 index 0000000..1a73826 --- /dev/null +++ b/src/test/java/com/gxwebsoft/generator/engine/BeetlTemplateEnginePlus.java @@ -0,0 +1,50 @@ +package com.gxwebsoft.generator.engine; + +import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder; +import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine; +import org.beetl.core.Configuration; +import org.beetl.core.GroupTemplate; +import org.beetl.core.Template; +import org.beetl.core.resource.FileResourceLoader; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; + +/** + * Beetl模板引擎实现文件输出 + * + * @author WebSoft + * @since 2021-09-05 00:30:28 + */ +public class BeetlTemplateEnginePlus extends AbstractTemplateEngine { + private GroupTemplate groupTemplate; + + @Override + public AbstractTemplateEngine init(ConfigBuilder configBuilder) { + super.init(configBuilder); + try { + Configuration cfg = Configuration.defaultConfiguration(); + groupTemplate = new GroupTemplate(new FileResourceLoader(), cfg); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return this; + } + + @Override + public void writer(Map objectMap, String templatePath, String outputFile) throws Exception { + Template template = groupTemplate.getTemplate(templatePath); + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { + template.binding(objectMap); + template.renderTo(fileOutputStream); + } + logger.debug("模板:" + templatePath + "; 文件:" + outputFile); + } + + @Override + public String templateFilePath(String filePath) { + return filePath + ".btl"; + } + +} diff --git a/src/test/java/com/gxwebsoft/generator/templates/add.config.ts.btl b/src/test/java/com/gxwebsoft/generator/templates/add.config.ts.btl new file mode 100644 index 0000000..a93cafd --- /dev/null +++ b/src/test/java/com/gxwebsoft/generator/templates/add.config.ts.btl @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '新增${table.comment!'数据'}', + navigationBarTextStyle: 'black' +}) diff --git a/src/test/java/com/gxwebsoft/generator/templates/add.tsx.btl b/src/test/java/com/gxwebsoft/generator/templates/add.tsx.btl new file mode 100644 index 0000000..73055da --- /dev/null +++ b/src/test/java/com/gxwebsoft/generator/templates/add.tsx.btl @@ -0,0 +1,115 @@ +import {useEffect, useState, useRef} from "react"; +import {useRouter} from '@tarojs/taro' +import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro' +import Taro from '@tarojs/taro' +import {View} from '@tarojs/components' +import {${entity}} from "@/api/${package.ModuleName}/${table.entityPath}/model"; +import {get${entity}, list${entity}, update${entity}, add${entity}} from "@/api/${package.ModuleName}/${table.entityPath}"; + +const Add${entity} = () => { + const {params} = useRouter(); + const [loading, setLoading] = useState(true) + const [FormData, setFormData] = useState<${entity}>({}) + const formRef = useRef(null) + + const reload = async () => { + if (params.id) { + const data = await get${entity}(Number(params.id)) + setFormData(data) + } else { + setFormData({}) + } + } + + // 提交表单 + const submitSucceed = async (values: any) => { + try { + if (params.id) { + // 编辑模式 + await update${entity}({ + ...values, + id: Number(params.id) + }) + } else { + // 新增模式 + await add${entity}(values) + } + + Taro.showToast({ + title: `操作成功`, + icon: 'success' + }) + + setTimeout(() => { + return Taro.navigateBack() + }, 1000) + } catch (error) { + Taro.showToast({ + title: `操作失败`, + icon: 'error' + }); + } + } + + const submitFailed = (error: any) => { + console.log(error, 'err...') + } + + useEffect(() => { + reload().then(() => { + setLoading(false) + }) + }, []); + + if (loading) { + return 加载中 + } + + return ( + <> +

I5fO{xNafw-I=ia`rxX-dJG&H=AT$D7> zE+R4pNN81nL#?=kL~J5(I_?W77?2CC6M_HB-r{k2hI6J%%e*EsRw=%V@Wqu?{M{c* zE(rv_ghFJ4#GlD{J$R5%OBjr8nvEl=&nEv@`2N{CGo}3ed~r!22(sHWaRC?d^VK#n zAlF4%|1#`p-?GV8uF(_-BnxB|1U)&z(5+qmauuRt7u|{8+9Lj7#Noe?iNCu0pJY#3 zp8}1#_7bIR;g)(RIJkJat2bdFNclYBAA^E~6*TahSOX(5p}_8h={ZoK0XLm#6d)hL zb5>UP>Sfrfs-ntD4Q?vw*V|clvfx6x4Gh zkw5|@m}i z90c!|+0JKQSEn;Qfkn5IQDuEZ@?EP51+cFaxQ^)&=W<08Lq&}leV=9=8yhQg$Tc|F z+q+`qd2B?!`1ZN%AW3R;$bvqyGqPLk6#_)Y9PiVe23wY7NG$Z3iTuDB1&0$Hd)f|X zI6KgP-k;ki$>_SvaC^oZ9r6`b%Gz$v22^d{s^60WCz6mmaHdFlYr2l+bJ&J@c%(q` z`SSBAj{&LML%~6Tp+$Glhw6~>$^ISOY2cIXBB$__{{w=K|8Hsco7;o+*)e)Aphjjy z{gUXI$Z);}2_zqzTa4(qY!%)2_$$%&WFhPX)8bPTS#4=CNgGC=do5hyyzSKkqQ9e& z0Wy#=v7o5PbXTFp0136R^q+(zQWmP@sJP10} zI=Xqb^<|#5ZFe1rJZ}%p)jk>YW`+P6Um_Y$ z#Yl95;Q`z<8LdU;-JQYVzQwaB<14_^pM7i`z3ZgKDN&CUjbEylc3( z{rhLYZIbWv=lT<)sM2i-ew9^JLh=~_5hR61KoEL3hY{ztk^7$wMkY&9=Vt#)G8|rT z9A(|Qe+-}rTY|*`BHU&5mmG8Ug)lvuPCvI zFP?7iUZSM2IMi&yTy)B}z(P!@V_(%1YHDg(P0e@R(BM@F@Brz>ETo6r($Z2mnM|IT zcL|`!ki|2w-ez-HK8p?djHA=l{j}I-#!jc%IOy?sq*=Hg_z>|qJmL9@1)SvoIL7zp zaHaOCrOc{rEwB6gPld>n4cYSpQ%RT|PDs>ikdvw*45QGqy1ChZ=C(*sjS35J z+e@NQMWbxJj|AkERaA6_!4FNuvcWd(vxW}v8CFUcPby}HBg`C=2 zxW0$5-+%t}y7A1HYeoRMQs-)Sx=}$ow-p_UZ%>RJ8>Gzo4kKhL>3{b;-)^Ct!hgXt zPu*+cM8#1F?d^}RS|0$%#pBPx?NL#d?v7#yMhcTGk#Xc?hU?w20v@L`mRf`5Uarpd z?vQoXE<)N?B9=bem-@u{0M7$Z;LR@l7rOc#ISzQ_A}fIr=|Y7P)gRqJ%^-5O zC#ZlG9Hc9#w?{0>`u-1vW1ndE#02601Yz@_&gFR=(z77FQ7AEvNQZ85MWb75$Z$J?psSsM9*98kMHYNI}Ws3qbikV1KUh zf67?IIg@g7!u=}$IXJ+s*)thExZcUop|U*hp#iC0m{?y_J}Xcy2f~rhd;iagut^AY zMmIc&4unsFem{8~K{U7=4oCzwe&DNc=2A)nB~d=K&zCybKT%S^nd8sgTnr#xqeX@S zgV))*PI!E3AgnUoQ#AWV;YQ>(-WIlTw~Ku;$NJ^iOFDlU2b ziN)!e1&)om%P7ku5*Z<{Qu`cl^zy#Et~|N-mhYE5FK?A|7YN=jiEUDUSTJ9u&o2uU z>ujMB;k-jbL4d$y^Ey0*`AUApN{6WvYd$ftl7$@;xbTs_h0a$=Hh3HssQLMMz=7gl zUFPQ{r4%vt83^@#+C9c5H}VO&98Ffw&`AK% zm=Di?PcNX#lv7x^Ui!nA6ct)gii`&XxFYa@5nq=Zo$Q>Dkp=pn|MvkAAmFviBV?&# zNvms#K}Cf|%2k==v-Q5Zan+MTn3FfnfH34i;HiqCa&l>?fUjSD$+% z(%_Z)vyv?LND`2X`VgZ+lTZM60RMRhywJ3m9o_?GsABSMT%*~)sDjasUb8J?Ru8*W zF`;&M?fUdces_0&Yxe2+BBV$rYv=mOjANqwfwH1$K9koYu=G_u-S9zG{<8M?8{M8p zKS2AGy-Rgi=+|(JFfcIKTJH(6`p#sDxgk8B@USuAwch%Jfnm~!WUKoB){_L>bcFo- zcer7RVW1xWS&6i^#TkH)To8ol>oZ%hPQ=D?*xEdVda2U;EjO|GVVfAZhO-)J8JHNhr{3~MbkFXJBL-n69V}v)U{0pf2 zNa*+oUbeT9ycm425557R4T#!&nV6M_@GanWmy&|b&Tyg&7@Em0O!aNW?@Y%YuYRNM z_N@OkQ#c_2U;(R4W-$K5&t{aJZ2AZT;$`0feO#jeom|cSd;u!2rxBK%xzWeE- z5Y?cu(a{uVHr+k9MOZaCX;f?oxBF8;z~3gOKlS7VXJ{hHAOdgC%veKIr)VdLV&42 zBrx@b#uS>&Sd)Zb1N!nndNB>YY9xw-Ed?me_YL$pK28Wf1WGnMce?ieBt@$6Fc$*h zOC(@0toQ!<#L*e9M_r_sMnkDMgtS)c!u)5+bLQHZfM3k*2=BBHFh*Ib&#yd-&cs~C zFpNfDZz9#`E+MOQa5`Vbc3Zj*?)F_t-eU##>H7NW_UpXT03MeU{_J=}PH*4BfJN8SPi7(}+miK94IdaqlEG~<@oisVdAZ_lMh2Z2Vj zgK!c6eJOM>gTvDIu{$_Mpe{IZ9BCdqv@jl=&!VICXjpCcLi9{)&>cz#cL)VT$&bt% z=$wJjau6y#;xo9(<{DB5K;2=ovhYs10lxENkZ~?Us3SuX&|E5 zNxj9@9;Vi2lwt>?Pfm~{{&Jqi0Gq7hjDKz}3E#`3o~D?BBy3O=Ud;P5IpZmT>B#vR z+nJiM;7??jQx&NYGPJorIlbps1m@Tq?~kN`l^*SG4ey%8{gwJ#AcweEKu(1;>+_lR z+5jnuXZ)JDM%z-DKRdko_|TAFbtsqT%bSyN^-gx~KNxt!_+#dslcWvLKCzECqvCJH z0s#qst6W&L(tZU#KO3(V!tal!`2G7wy^hjqyd`9aSG*-+SZIGlZ=>BG77@WqI9bdG zD5M5}MC|G{C4b5KrQEpoDG$Ehay3-%TP((@e9B*rL|44FXB^$l&aV~Wh%s(kJf0RS zI6D;KJyr4p*1Lx~%4#aYYHFZC@(3i~$f1Mc5Oyx7MDTcBKpqN&h%ScF@qS5*!Z5d* zETgtTnwU^pvr%S@wZIwOryEb)H=%PYdey$5ACDR<>JMxK#>hT%NM`AV{l||s<%g4H zG|d1QDC+%yKAFRX$x3HqINSpt9vav+fu_&-Qc)l4Yms_c)b?GGp6fnc{UWcE+G_5d@rBfaCm>5uI# zu-(0MVD%~a`LhUL0RCWprnFDQ8B{S<$)+4BTjOimufXg4lJ#U$uJ ztzi4zAX*&vU>A%=(k?#l>cxGt^S3dziMG=#pUKZRbq=pgElfaBscmHyrSAw=z1TFu zoz&y6MgertV0+rzkg8}wfRlXR?|e>wC-6=Rtv*O=P!Uf?Z-9Xl+=zLi`h*Cba%QP* zFp)nncwIh+2_&_ZpGRRaMFAL6iq2VNw^A{ewir^N#T$ zW-{eWj6^`d($%}kHPGyrr(j@8`5j}+g#H)1XtE><`xy}{7<9C=%_9#BnQ>}T8$!r) zT+%6gAc|}xd6Nwp0Nj_)`~E7M4N9>wHrDoKv*2dT3kxBj6?T8?NqRi^cmu*a`0Rh8 zx?u972Wcm_l78)25&)JD%`_Sw|fz)y}jy~nnYNXzVjM>Pl`|4S?z;ey*vK%Y?98I zfuKZDxJ*;q_wI4EN*e-KRb$@vK**=RZ9!0L?^^R0af8yJz5`HEf6C_HpY}E;t0cLN zt1nn&)MOArpAo$S>nU0brFuN-JykKV&j3|oZP%rQ0W0e_p;=>%x0Iu4OoI@SWR}vdNke*BoY~ z%kh#jM)|6EY({Ax^so-u01avnP*v9 zv;XP@S9m_ceY~C$9{ycH1^E1e^rWDr8XXWGOLZ8Bgq{9CTXZp=zAedcEM*KV9ziY{ zDMwF&SCl2|UG#^g(Lx0VMPwG$8V(U3UnVpH>3SF$VpD&FlWkx@0XPBk)wHs)3-z$X zA|WaztqZzqzka1z_&96E3qWbz2G}`R#;b_q(t1yKzh$lCG7ba|O^+CnDH9TGY~EC7+Omx3)S!et{u}H>0+}}u z;$TRK$P&#a`w(C3Ppx7-1oA?QN#plGz2qzcG$itZJ*rka%mz!$>X%K7P|JyHR4w;+a=w@SkL>>N7X?WSVp}6L`{b`bM4>A_HosEHWRBLv z!4Yna21pqch~)oKRAlOLg9I#N{Q=gV`Y?ac&d@gwdun(#Ve_1lvYzni8ICTvnb}z> zGV)9j4CY2c3BZ5cY{7Yi9J@^{Hd1$W7y59j43Z=zM^WW!+C%RU3D?=xMY1DC>xsbF z%y{SYs7AeWRcGKFhnWv9WfF^17i?N4tLv>LD%zd`9imy40o!;St|_Oo5^IzKHkmn3 zI%*Cc24;VRG>HONO>K8~kK|o2LKX_$b0z=F_Y9wgDxmhXS2q$y1ho8rvna?KJO zTb+!>8BvudmZLGSQN>S;tTe9CS;Z`sou45zeH6!$kQjN3YGnOrk0V`F$V_@Utp?ep z)npG%Ig#E!#caZ1^NJ-JffJ&6>W+@SZYcWBS^K`DB$cuip!bba{1+F3B#oKjY^sf) zbMpb3V{5xm#d{!!?Ij2G@bK^6Uc|lvG<5VUr*Xj4G`p;D2+WA$g7=&L!o*m&&}xel z;!L4*AJG1=xe&Z_YYB#Kx>o9)uxo6V_HWC`yLHSh_#fLrDz`yfEUI71M$4L1bx28h1&5fl$}0=V0Cb`Fg5|OF5=BB z+9Ug8le!C-;4`R*p<#^rgGhku-OWYsX%q8Q5j`sOY17P~Q7tMU3ICsJ@qcPFD0m>W z|>UVyxZ&vO12_G`XBPtHwtJMp^*M)AKVw}?ew ziS7KnJuDBU%RsBte<$H-cgw_eLCt0OQHd_azt4KB zN-ALB(A^sXgX-AevX)w^u@eRy4u3|%MFjJYPhI)l4=ZcIS`W0Zw)>P@jTt|r7m%^T z#Uk-^4)!^uYwSeZ2$nU_w9WWajr^F8^a7S<40Gih*Z?Uk0ekR}La-<5z&Jkm-}P_J zJspHbfp_VgVUz8`Mig?2nu`pGFF z%3VBmlaliVGOKMmpGPP(n&d4LlZdn-Mm5t>yZ!@C3*wEf-3fem5mt6%Bgd`&!tuC9 z3oxg4pB$F^f*9k@10Mn%WPN+6BXSnuqb2aAyPFiLiWlPD^BoEXN=Vik2~iOd9)5tX zzLiuAfs~OEIVg&3t9vf#VhQP9&2>LL{(c{QmDV@6nhBtV4|Je@Qs%7k+J7y_Rhn;+ zeAa?z{rrXe zuhHoK1~Hz+J+LDtE+Tu9XPz|_apZh$W4?jzcWWoNKxIf|^APb`gT*olhog!!sNXHZ zCd2l7^vf^(@2Kc|3708sps5fNjXferPeS~Pc;G-HWDyw#8V)xZn2nQhqv_RJ3{>|?%4+@&K_79S(gG|>6K2n)<2*^QchWN! zSt5(P$*C6AL$Gy#8uRwX&)wfL|8~U1(to z6uv#a4qj?l+opGxr#P$YZkN$Ho-cxHc+l$a88Y}i5dYit9a~#fPw{5aJ@ADVYqArn zLSoejpki$16$@m+!>J!J17)=rlL$TdqMP=jZ2jR*fw~Rlle6=_xw#PSZ|^if zY&(y)7Px?p)G*V1r-tM6g?eUGCO~Nu=b^0VCBc{HFL-)39DBdVb0;wWWMAvh zcoH_MASxyU+R^X~zVUtuZ}qo;X~{jcID8waxGr-w1q1}-YNvr;P)Q!Y#pyhNk~!L- zpP7-`|?z+!&yW!LiXn=ex(b~fXHfw8jFI!*R?sU&Ck z;yEh*@PN4aazwZpRgC|8aD3PkSw9WQ2?>AsEfSciM<|k(P*o}UmnpCyPKXeR;M^_J z-3)J*GaUJox@m4#tNUpr>9Voa)%vi2CW$c-yI#IXdT#@m9_| z#bqm9y_2bqTC2kq|C^Y6ax^wHcmSPpwLSPb+S)*m82V-EZbX+NsLNVV)NqwS9RKq@ zn>PXXIb)0C&UHXZMRNwwSyTF&l>O29ZEROs6b1!8}d$a&dX2(VU1nR!}jXr z69YC!beMOlIf8POO#2JgTC-ewKtA7_bjDkt>VcoIP(EuK0!nGxeEERPhDVgw+eyb% zLj|&<`HIi&sQIm|QfI@)`{?{wIr%U0*Q*`+)90s1d_8h!H|L#o{GZgCOyaDpwUw4< z6ljxg_HEG{W|B(Z19hx;cMT76qKC^jy7wfLa?G+r=Gsdd8MwUOnOeqkldV_5jT)|u z*V<`Ol4bc~-LY1j%*DvkeEmonCuid|E}mbVFBY3Xfgy%Yz()ib6}5+1VGcsW=7g<0 zpgj}8@nQ{8IB$Ki((^}|Pye-gn=7V9vr}Ngkv14OM73Q@z#8{gK){;N0{dH(hEN94 z{e8*k6#MdD3!Wf}V1fupi&qE?#ieWYi<&0Ggcc7hL_e>MAAVAyF5nj?1kBss5dp1- z&r*3nR9q)q;c|G)e)l4K_|Iq?x(~crSJSPQEKlE<6&PEFq56sKqnAB(s1qWHR%At`}LN-)*H(daq3pubN7$!hR@ z#anvGMAbc)TRl2PMnQspqmwNid>DXvnFeE(HQ|9Tz}*lIICE>YmjXpW@*6GP+GqQ! zV4el)`;_(SFwZ8AK;X!yL6TA0=Pw_RSUS-pI#Y`=L6uRcJB%U}pV&g>n8 zw#tqt+N2!mlK}xp@Ej80s7vBG;VF=6VLBDNXN|>}4#_lPcin4a9<~+|qt|b{P@)kt zr_t`{Z#7`W|K_a;dRu9-!^IH}@S##M>{;rr8nHlCyzT+)9o|t0k zo}a3WMr-&8i$z^3o&$E9c@`^=0|WW9Zj7SP>QEbdg;YiO@{#*b6uXJ6B+7 zOt7)BRT@291S3l`u$%%7^C%8HuRqTv$6(CAbNWQOkL(>UD&-vy6p^C@RomU4V7qwC z`tfe(rp%@@{S8JEuQ+f&v*+dib~{jOT*HBy|LxH!}%tf|3}taMr9TCTieAn9P8ERv0CK|=#}KG#-cfT?H`)&X&85Q*5bMuf z^>UgRgphDeS5$C!N?qRwM8=^%IS}S#3@+iqVNp)a zQ;KbkL*Q~5#4F7`!PiU66Y}m?BqYA<{_w@IG!xD5Qz&? zJKV>qlWJ(J{p<Dz zG&Pkyke_h@UhoK*VK_O(i{u3Gqa(;j;E=*cxg}ecm?h` z%T2!bFUS|!`eCoIMZM`^Lt11`F7@diFBJlM8pPj9lUXQr3Mj!8hD9gpP ztHjBzs)&LIu|w}0+1lER+S#3ogL4-BEf--pc@9JVa7w<6CZaC8y`g7f7;iJi#9L+! zD!QjVim^4c9E|d}!eO48`rXgoN;Np>(Oj`vm6Jt-Z1|zo3^qjTkCE)C2tBn179N&3 zdKM!K+r~V&I9K17S9vF^DZ`_tob_3WfDSHL*6>>$O`+ncjIb08pb&W(KWu{s5ZZ!&q0RSGkbloKoAIWT71iLPRANWyTw+ZeD5xd`ZGk7k*!VT24*k=Q*-Mq zX4n0{NzRS+MnU7?RgOBil5eBwlfiz=sm!MQJf1n?@m@kY~`^%~!Tt}d$=iSPTP zpV#RWa+d&Y(oVx(yV4ZF-ml@UL3VZ(VdL%rF~tU72+3*`7Ah?^BmQ<9xB17Rc=(-? zvOD98)r)sT2Y1t(cESK@>`+Y+FqXkb zt+&Aryx2&4|10UJSj$tECQMv_^u$|QE-m0U^{_{19>HB=4Nw*JeWhDNxFxB$CfrvxlRO?7 zNealUaYV$)i`#zS2KhHVN`o8W#^>Ytr})-4w&i9EQyD~=;7YA)ZPPUSD86CP`A!`; zFHa8R#E<0KFT72T$CpMw=X^FpOxm_Rg4Hu!u9Ipy}~cWv45-*@E|3sh8m>xS|=|d@QriFCoYMg+H|T zL1y`e=K)aR2C#M91jOJ}+gi{z$l7lEPpqx25sKLGi9Bw;Gv8@#1YJ}3`U$Gdm>(2W*L%j;sT0IHJ74p#W z#cbzv8&fgCC_u09)L(3K!vXMqS+)%k0GlCr!9f1$C+shk>>6|}Zfo+$*B8y0bmOMm zxfyRT+!U#Au2m&NPekA-?NEw!G5QRyeI0G>lFRtei-Hl z{4Op@)1KyR#IXLxK_d5iIa-}E*iEl6mT+*r#Y2Sp&=VD;kuGWZ_?v%pcXt>GBbR>( z4dn%DPRF9^7HFmIU`{r45*{?O*DxEqO+O#biY8(ceMqse*4loG$;%@y%{!Yago*ON zBY=q?f2L~Xhd$J9M-k<1I*rCQ2hHQ^S`XjI5f{o<(SF6M74)lAhcu!2tl2zAf%vq~ zDN)_axUY2Ty_^qGKs}5f0}o{79hfk3_7I&e=DbCZelDP357BRIjd4bG`cMK(q-lSW#tlo(qkX z9V*+vpe@*a{#OBeW3~7}M;5?D2}h=$5FfI?m`RpA*^d!5oYM3g%@jnZnSUC2*u^*! zg~G$5J6OKv+({UZD=K2Bl3XY*viQ7i9IgOXIUEA z+x@-!^B`YZ*AgoeqfhAsTKF+$VDf8=apQ;m4>@Nx8rX3})LpSYB=x)Pb*VPfH{ zzQvqD_+Aer4rvz0aiNiKQQC?=>v|4`*{^hq|*aHry9J~J^<4sp;uTZQ35P4ueeWhiFDNUFNlM)ut)}+N zW2;Ox|1dT)Q{7gua=8uN>@|8SXI`UmAIeSb$JhDOP13bEC0PEPA1-ehzIF<7K~p?N)|)g}`L=4}eK z?n9Ju;zX&y0^D8NH&S+p*mrbOeV(1{gJjKHCEZUnXdt@re;qLqs;*Q(bZxf zYRB6+1_lNkFM;%vh1w)PhQKTSI>+I;=-5LE>o;!%h9d>d?CPic@1pi|E6W0ye~EV< zarv`5{-RYhMp!L~hzcgMkkXM$_)}TYUIG)IPA44wE zJ&e~exm)r@cQzswi{6np&qRI!Zw&628vRx6S!oUfHrh*|f)DMWr%l{u-$?XADb0Qp zDZ#l;Lt|2H_ec+Sf6qm=_QhYbpPh^k6Mst+$?#g)76E__CQ2U3An80H_Sj>;Q3%iG zHeV*vG~|#{$lUyai(V`Yqn`=Rx?V?#S@uk1YuE%soO&q*#J0h>V|Bckl9j7FsQtSd$XYfKGyw*7>& zSgD(P(Me@IoPk$n%t8g9gu9RGsNFh;>Rk6V39mV}Ha-kW5_LP(Iep*=E4kj^&hZPe zo5)uD!PaB;ac@3_x~t8ArX_%EBdMdzh~EPWJ7z}Mff&p>NaZ1ajF=YewU5y%I!zWD zkQQ5zTmJk}98_5h3c7VB5SNujzdlyz-)eHZCEglKjRIkfl<{Ql)~Kj(2yiE!oR&W5 zC|-rBxyDyMxsQaCMZEN0ZKP&82*O1r3{k(9I?(@V#wj4uN9w%4>lB!9S*)dt(uK{C zInydIvh`zKd!GFq;`Nt(@}sXw{Qk;@jEqykl~d0dwFL!_y>|jZP!Jsv?wSaS4?SD^wfRjqM+#fuH%F0(1PR=EhF} zw~G}6KaIG{E6O=$#-MLgN-dDej0X(86<;;j{rM7m!d_hKOVxpP!+s0Wcw=2bxpnXxb&lM~QUlhdPA7j!ixSLykOA0>Fq@9U(? z<4!?2L}4M z%s@QeNM2bOs!y4%%)vS?RexTfbd8OmxAlyw4eJLg&KPQDgR29=u+?S4qC zj}Saf$ovV^_QpVr37Yq^ioL9+k_b2V_iC>`tB-FfnQ16wG03~$?FxHtI$#3{79!~! zL-Pb${r4tR9LGd`RD4a%Hg}u9!%1vvm1-sfpO+PBn>t@&7ytYWhi%s3MbgU$j$eswR&%_I%kK^#5}Ukk zvpc%x;oQG=Dw31~Msu_n-Av}w+jj{;5#l-7sRz5`k&RBHX^ohBEyGnpCZ(y!oy;#0 zuJCyXx&Fjqm@;rVxN}JsX9Q5&H!qRlz*4Z;mut1blqFa79`HMJxG$65u%7mn&)2bo zMWn+%B8_lp% zeRx3jjnfH+rFB^+;ZhRX;udGp&y_DIB5pqxdTFN-h@_Z?m=+03h~Vz%d;9vz zd~!nnh;3_*WvznI1H}Vl8LvO#LU6%wYZB-t0m;&pf{7jK4T_%GLM2j`b9S^0MYS96 zgQur)=J1KS^qH(AM!q1SZdWXvWp;Yu>B2}lNLvZ12@|~*eV_A<%L-&}Lhm;Ot#2*O zZf7uX@Wh0doNnl`n*q$lhxWk~eH`t1z-yhWcf?Veh9Xt+1<0O|C zQ!De+k3Gp%uXVxbRIqG~CKC;w+mRd;NQkCKW&;@O2&3z<5$S#+;4cYGT2i@%udm09!;IQc-a_=^Xbw7>x(UJ++j><1Yvl} z5u9e>_?Wli9B|}EN2x-dob++l`N!XPmi|*TH3ls0VA}LwC?Kq{o>w7WYvP3z3+0Gr8V3$@^$2$d!bL0x#^!Ssz23w%qq-l8Oi+ zUJKxim{|0rcF_J$8&H}k0G=J^DTw=v&L+2e<6CdNTTqp9SIR3yTkANjS6N5%SEkWM z&7B6k+Kmbp#KHG(u65wGYKB7$5i2Tt$vLt8Mj@TT&Sb?d6At`0OZiv24T_QfTjT!m zH#y>i2OPJ?@3@3(H_yiBgto)55r`WnUaQ*B|6hGj9AfiAnNC)ab3js8$8&tjH2pTQ zHMsC!5DhX%YPy(;D8%!D@Xoq92p&N~TN-|QyuI~d?>OBZ@~@PbKE;!B#E=4tE5y4Y z0giDGl9IWVK$RBs9x>O7m5(#duulpmO8q5Z+XUQc*P4chL28r#`@0P-8P)MxPN8$s zng2OGkcgxI_wEDu`hc@OEhseD(LEpFTS2Mb#TzDzot4#iQLlCFX!7C zRjqM>ke>k;fWT#O`q}XCB3P5fV~J@8sn4jczRFG&-8l&Yta&{@?S> z)q9~|prbkL{-T4JjQ*Kwq!cAa=qA}PiHV5^M@JKrxkUjOcjxOQI#rkWT#Yq>+u>q3 znav-jEa1|Y9(s&gZr<;^&N|@wz0mhGymw@nV>Xi4QtPBmc3b;rUrpO&ucL+awnbgG zRy4jg{hqRnc>zOv$@1!b2dl52cFJD+gPyLwd{)Xx;)fKUrIe9`=?Ytl&FiJ6gnx^5x(-VEetGT9Iv&_By7~hGf4~ z;qTdg@(TTJORd@9-b_Q)*?`!8epI}zjmOfS+1WjF8I53wx~4OQsq5cs?WeiH= zKvwPgw@J@a?f#5YEKxmoy~E?4>+U=C|5|(gjY>lWD0PkBu^9_KvUlDvq!=x9u6(ISjt6?ds&3_n(QdTs+r?B^{mUcnTwY82@ z<4X6BeQDY7OjUowhME2l&o7Hoh4bR*Z>)j_HY6pP6eI@k!)KF7Ni=pHO*>6oKW12o zMlXHfkFKw(Z%Y}7i;lYBtG%2Riso(?;&~lILq;MH9dBFy$mh1bAhDo+$XC1aCYt7V z#XjGgXmm2v+l}J!!sw;eT-{bTm4oq5`L{MbkFt|H?LPRCe(Ws!U~)++``YWNL`=IK5l|n4!V~B>o*ohZpSkQG z7{?wpuvY2XoCTXaDG@{-j~DQbnny#96r4qu>pRhx`u{}d zo3^tx)|l%HAiCPoR5WI9Xg$txKk&^oM6rP$FxLi#K=b9B#<*kUDBEw331kE5`xVwC zX0BXZ^Vg9HgJVN|hpp}^mj`JVOkjfBgjCcx)}4UgiWXNuA2-8QF+l7jhtlo?sS8?E z>~bW01H`y!03jtY!j}R{XPON%dhnO8Z|aV~r1*q$b9<1S28~d~EH0>ZcU~!if6N74TJN%NkiTi(jl}!+nIxK8%>6By> z2{K$1Egv6YmY}eRt#jwYVJP*+;@}gJ1(l&kb(@&>GicND5l27Q~$@|Cn@ni zYYZa}^2;y7{~<;|{sjI_#s8U+kx@~DO5Xiwk*L;2xx-q{Y?I z*NF8#%7nPuxZ#HFrVA7Z;vmLABoX%i@l(PDhlszECo4J!eoAosqMGDGrp1Op6~HQ` z1&%6+@T?Jly^wIS3RQe@5M8bZDT4L`?m0YY!5*pqc~DCSBo_hZFA!Wo90XbbmSFaO z-xaXR%oHpM6O+`XK=V$@nF=EV8(G5t{Q{7c;#FigoEaYE6}Yj#$nQ*-;6wu~$N&AD zkgKKGNdSOw>AOAO{?Efgd@|sb|M3AJubdbTC!KV}#{e;2M+NlDLu~y1Vkc|=&w8&# zIIS`5NKYdPG4vA${0Fp%fdjruE^aZz>$`qTfs{2~`X9G#{(paf5dehz(>c(wzrrmo z1(rZKY^`Cm#1=u}AcT5*7(gXHY& z`~Yyvz2K54T3O^4?)kp)IrRBj)X!|CvwIdG;UHNs4?p*KNy8$flP=r?FcPX`F^fw| zsd?W)ZooMp;~7xS{`;WpkO#dwfK=te>^mQ@DSR(fvl0q|;qCAie{k&{gI@9kExt3a zf!@454zbyaOa4#8{`zWy{e@yN^wB}^;XTXx=fV$uj~X}g4VV%V61kOyAql``4UjEp z|Jx)!t7k_`HI~-{$5lu{qR+PzseF#qz(e%yH9Q5UZXitFSCDsqd;1TIg2_mq2SkB# za&oc%?en>P5kazM1r82`BSBpsfQ`IzsfC&b^hkx1JRDHw`fiuy_yiP||2{))DjbB@ z0XjK8ivcavgd;mEG+B$wCo0URZNDc(5poegmTaz%UX^5cNsFeVpBR@wBLnE6#ITpN z{~aCV6142uBAzhH$3r(&&v$IB*cX?VacQhRQ@SXL&BOM(aDY1(m%$9N))PyDc}>|r zJwE>bj6JnzhC+!v@Ap0zDKYHsGoARyL|ho1HB>Aa6B84qltFiwV{`!G-EnI8 zQpnU{2!8p%1MoE)@xu|Y>H)-r4$7Lpzly4+jDbP+edxZohM zckkXo;D1q4-^B1Bqb-3R5BZh=mECF6(&cawK7DUbWp?s&h|AZBbQ`s@4SUJG;o;q? zhwYX<9-Ac_!QbEW^2=WM!nzih?DY)hVoa;6=jIh&A&gjS>1%sR?srs1r>7&RyB)~m!QpWaP|9Ff`xChz7zvV)==T$ zuW*rf^9=$|Jl`tIGoOb20CKEnmMmO9ZlNZ^_XTQHZ$q^$5<0SsI_>=f2zO4tzH6yH z3z_lN&pKRiH@sc%yQ$D%F4t+$@sfc5Dk3&_5 z`o>)%AAWy#*XZY0>0j95l7Mt1gOQ|U+zXZi@@$}L{xwkG@vy@|F6zA5MM5I`& zo2`T4Gjw87%BfTO@W@C(CpdZlbP?orF8pCt)HU&G?4LqGx)PlNAUByHV_*b+Nq;%v zLx+iERrIwDCS%qvAh;PGa8lS^iiKT(*z8&4R`8DKvL`H##t?+BaB*=-1}uC7UwGbC=JsG2G$7$pe^u*N(IR9nr&`vD?sKGTim7AP}O{y-!EU> zTolEuvOCcNfnwZ~1Z0gGi-WXyF@v~#6Uk!{cq@RV0ndQmCrS-tl}_w~DGVr&qF7W| zHv%J0^f7#OC0vmNRM^XzC)Ezcr6p8cT<;+3vBALApzY1yP>N=`o5$-FEHa#^dkhM> z?SE~+KlP28>S|J)v8<#c0W0O>NjA-CXk3n;xDItOq%|C|iIuffFx55GW3#eYAUg*- zI=UvOzbNtX2|ySb2#E5dS^PLt!b0r6f4G|>NkWfMmQH_u5D5U7F8a6WaPG>=H`O0Q z8hL$ueX-h^iJ4?$XoMM1ugMpC&bKGP)(JAi?-jui2Whcl;;LAo7`(o#XB)rNUR~?M zhsM3&^W00UuU}hi_a;bT`_LbV=-TM?vn>I5?uCWP3X+n3{krq)?L{R@=I7^s2!5lk zu1R_5L2Uf>diCTdEqQ)iVq#FfJX5(4{4A^ay5_E=K_gRKIGFwyJtP7G^SY%1z#g)m zl(ygN<42;HK6z1R;(Oz3DoRSIhThoD_B%I+vzZUjuCp+io6KXU;6`d_XmB9>3uPf|(G=+(d{67{p=1nW z{Kv({=rTY5Yg}sVU{2n}gD+TKSLnf5!hG2Nc$NysS+Az1_KnWIzm6FFZEcn3=w~jj z*L_=;`vr99p;8iZT7;UFCw+gJQ%*LA$||&HEA<0oVlV(-&Y`nOOGii4GsVtuCX+vv z*Pa{@B)Ue9@I-%9W91v#8G)1Bm7&EbkCc=Qm5>eM)`Tw9HF831*3<>f{je06#O#E0 z0j;K_BzRE|+^DSf1dJX5*5qQFCyvE=SFDCPl0Bw*$kj|a@B@;l=T2@9`rf5h#N zCJJ1;x?<-g8d3yMoi*I^5Vy)KX@()^zj zeXGjLrQ|;{k&Awc1bqdBWl5{|x`FHD7t6~c)s5eKFweUP1*TtI<%e{2IdT^&rgcjX zO_#i@vFxJ0u|1PKIMT`#@Wf7g&$@c_=*{*43@ETYZk+r^?cXoI^D}=y_mWnKm8q-d zT@faM9~2XisycJR4jp*g^%H%|NfApBZnBOUOeZ7rXZT&^y|MVnO_lxPufbu<~mmMl(Qri9aCmU`zY-CgCyzpC5 zPFJM1NUE>}kV`hwfIX^Ucm5nI8*v6tMQB6>>ehHhC^(22-(MX9tq!8Gu}Sr(C4mc$ z9rUTEN5=r<&_Bdk03CuNO3!|maV7oKEc9_#!LE+r01&=8Z2xdCbIxEM>2-egR7kE# zIh9#1l@V%tCfuT@Pd#{@=9ePQRtM_)-3t~7ta@=50^D@KYAdO%uWz$h4dy3H#@77& zyg%QWot!7Y3L0>Y!C8Hbi+^VD8_30OohNgM#>#wr)3AgoMBcTn9jr%knDiU=B6+7? z>|^YA1#P}f^(_2Nq(H?6|UVPo0Nt$kvF?Y&pV!<=le|FGEah@^uFfU^+#ZN z#T8|g`LygwDx4m<`q_})5Wi<(icB*(dB6dD z;6ZTn=exD1zE|Gl2dmTJk+Gf!epF0Sa{ql^`{{YEw;^6$<~kJ>%Cy*Wlq$2{j$}tN z7vSrB;&9?7Wl#TJ8m-p(| zd0*82EFG$iM5RG9iZG4j1sB<8mW)QjvEd}GMssu?_Mg?}eJD@vWP)zisp2*M?JBdC zI>Fz|-k(pX%bfF+e|WBCy=P$woUrio)Yri;Q}`NL2*{g4w=HM;`V+~`(R;%)3a;}l zAIcj!7UcFTEJ!{I-qaG;L~+NB1cubYoTWNaV?HqCZEJT`=njsL$$06Y7`q?$1H;s< zt7r}~w9}BIvvVIGFX9~fcj?Yg{p?3vP|+r&99;vb`#j}+AjK#)*$w17>h*vFlGMzK1-1E??U!^bSX^7cWXRrq&15P zj}D$VN7x&GdrpBWA{@c$b$%+`9&wS_8n_xZU|3Uu>6Ykm5ZHK?E#!O7>a-`EJHwMc zSY3r~HPq^G3eylOMa{VHJnrt(al8sN&nPn~_Y)>muJ8M+axu zz=mX=hEV%$n4*$q&wMCf#mJMTs+HVERf-);>+@`QC-GlDf35rNwjR{6W>l5spBsx* zE!1s~wAhfdFD#aE-R~=81~-VC|ddRHNp!K zm>QGBPR2`0yYN7vi|x(? zv#6*JyL%enwoDDTv$bw)mxecb7Z(=|ZEQx5J?|6-V!ZN&g@mdd)+AWT0|EoFnpv=A zaiYOVE)X@0bNPlgfD&v9M?g_GptN+41dtV>mHN*iFih0R1MND`!l;uIhoF#Pqw$0J zosvB0S;q~`I%fPhtOcxAE_@}Efiv>R(en3?564UG1Kvb~c%eR8ObPwBs01e`Prl2; zZUTac&D&0M5m(fH+JXXn-9H_~r``|v+CH2C6*-_S^SB8$!hbs}!4jM!t4(*ZO-uYMCNTf{p+mp20REVz4B zNr%gqM0vB0R172o%7v>Q2J{5@jVN~QVMdlO$8$Q7^z|iy9YhAQs$g*l<`$OIgL^LS z^sn92ZZRMF<{wCDv*o(|h3{*eE%+MjbYcpelMR;-njPGn0+Mzq2?;NaWlErB(8#{X z<1iRM{&Ah_U_n7#zuM@&MQvkP#p1GwNW(1lx=R$jhI@BmhZ4+pGYkvH z10th`t;>uix4$nd>sn9=Sd6LjNO&LqdJY>@OJf|Z%XFbH-!)iIkfU)Dh1pUqsHmxS zp+At=ytFt!68`l0!8(Oweu->(IopTii(Sgb+LHIdvF$RkhAul-lDC)Fj~-vnukMFZ zUj4|x!`n4oF6wnX6Z*V`X*0hwASK6W?EdcLl78pvARMoQdC%pe*E@iTvsbe8U`%|C zCyk;mULnNFVj^P*4b101rkvD>e`fvs*~Vcs6wsF{@H+z8pM%iY9DxQ_7?=%>iYtWM z+VpBXPJM#lLaBEhuz#J`I(}c}aTkBp;T$UJ9fav2T5PzU_q7}?=BUO(L{N0PN%A;x zdgEi*VnY^hhf3)TmZI3j(JaQFmX-rw34*L_F;JHWr61AW>}VA<%ADyN5{==NNV>1rzVz+21;(~&-bSjX#%LF!intW2ZZ#5 zZq$sFgspC}MsrBsj4v6_W-{;iTxf|v*3ky0w})uS-#*0(F~C>OkZjD_k7ZcJQQWTwPdK!_3}SiK zyf#Zs8ZGsOlJwX>PWOrhi#ri=t*7di?@R!>h(v&|RXYnc1@NHmm5RZ$9T=!;Wg_|vbzpIQT~f}yxtZDb7d6GPb%zId z)b6_mkbDMD46Ls@aso?A`VD zZi7C(!09*Q+ST19@2gd?+6wUx6r881_;jW+>_TpHucNNjXk#gu(4RX)gw?C6Lpk6J zmzY`tCBtQJ!*ayM#ha8Xgx!zWdpeLvX6yq>)9-n-oWp0pVD0f<+YqUjkoV zGBq}e>)+)<0vSzn9-;Qdp8$J!cX1Th(e7>1^%WjS-CkBXSM87^WTcv0k+N;-4gSb( zf@Snn4>@jHN%p?-?*=W` z88$X{HJcs8)>G|vNSs~&!*fv%t)38#MdS4jJC&i8IRe2phgQ7{`Han<&%WN*XME63 zTtgNCUst`L?F|{(*b*wGC*vuh2f5gmUTxN3Sk#TLYYjWn9*6>smtncqcieaFMaIRC zDm(k^(0Sj2{OQ3##F&x(ZEBDP^ZV1e*3?wOE4weUR&QQ<515|fR&qBenVAKq1=DkgnNCo zn1B>Es%XmY*erO2`2h0>h7oo4*u%U0088}q7YvzU)S(3_9tE5lLLwAU%b{^ zik6m^C#*{+#kOHciy?ExWQ4D+_*&eKBAE}&3@8xOp*vZLdV-NWK@j6(iRja&IxeibpcSeKF9i=EUNy&f`J2Y14j~|AkX0z1`aJ*>WDWW@M z+Ve_E!aw;&d1@AF*O?n%4*#Ab#|j5<1W4M5-l##RV^&PEzJccWKrdvtH|BvI+?OxI zBt>FLaVaSgNoAVGx;Jj-P)p?n(bj{bC@7#R_Fw; z-(P!bcO8=Kh*t9!-pHxts$uj5h2uV<$o(T8nJr?xw%8eKo69`z}wtHanHXF zJ{v|6@;V7?O#LP1Kd-ILHx!wdCHS@4^HJv2hZfR}etwhcU?vw=ZwcLe0-I4551MWe ziG4e`p(`m_eZ4B49gwKw$b^}sW3>B(?+6ocoy3r&?C{t{rq|(#zRmWR=1>bod8OTZ z+m8H|sPz?T6Fan#i3vIl4UI@hNJ$A30p3*6+kwLG8FSu_1n~*+ajsXdDBguF=L5CA znD)nJ7l`0UMf5EzHY5BVSiB&&Mvn|;j9v~Dc(2uh%(KUTAcd;V;gK-0#EG%pFEmD| z+@@E~2Kj~>a`lD~;cu+|{Tm!O>JdA^?$UU}D%3_tomZI}l&*qLOHAYMg?)r8CpEmZ zaBXO398xmn5)eWvHMzaL*Jo!DFytm0;rcbV@_tLiU`-2FVy>Fw4 zyEqT`9%tXakg)RF>Gc$p5yWO^hfgsmQ}LWa@z^Xz20P<-_e{YCvW(FMuJ7*r=>7wv zq@;9`df=bL>PCNbAb*s^l5S481^UJARH%hf`fFbv&8iRngT;)bfU5C|zRlUm z*x^wFXntWa#| zXF2>mGb0snKuTdV*y^%v{hI&N;xqC@G^cmJ9t4tpdtP|Tj7PmTc8ZD`82<$m`52~U zf;?HIw_>5uA$tjf;Eauuh6d+;1%{{ z?S*Ilc)Yi^#uAjtB$6LLe&kE>egQ^JPyWDh;rpYvC8b^gt=AM^>%Ts^q-?%)yJGezWj$A@puhGy_Ip z96ZmtM_pzcK-&JTws1x`t398;RumaD*0e*IkfHSx+U2@*nmtJRdj3Wt$*b!rYqH(# zdD%B}gg{m1 zw_dN(UbbZkk9ZF=W#GW8q$DMU6C(NN2LId?28Hxx9`Lz#k`kTP31uP){Xs#9E;x!- zgbHh~g_uy(=oL!UtcOQk%JQS2w1ewqKxilixp3)ypI22auKa*n4V@tkX~=xO_rMFS z8YlRp%rD6P4L^}7lQArGxb_>{!-5M(7gt#&uqN?iPEU+Z*&%Ku)6^MM-G zA($TeCAS>-xzt9|u*Cb}nqlG{#VdtzbEM`6dFE+uJiE&`bTws)gyiWL=w~~d z2Vn^o(FzJy+m;XFk!1YD7FKAu^07ZVfQ`kYsWrb8|KD8_0Rh1g3KL0F9D{$8es7?q z8BVia{Hd}`?&uAJNi3S7XJAv}Sdr=mY$L)Og=JyfVue8Mj`83momnQ4;H%(coQz8S z_Mr9dktPF&?WD&7@>K>KKO}5J=Hx1|Mu6HWsYGV6eQUXADX#mDOhBh)@|Tg{ch}s) z5*Sw&x>p}MGM}Tvl*0y2RC{hMpUz?}^EZ^_*%@8;?pOcFEI2$XQL$#W_QQ$syy&C& zai!mixk>Rksbd6CqtzqH_FEMQfZ{XG`02vJ#Tf_cfg~91P_MbC!KOv{MDKJIlaYmG?Z}rFK@RC#4u9aG z>Ozg(l8%R$m+^FQ+>WhjZMR5 z@AUNb(^mJwhXn%EftHZnab0Scx7v(0zsNw@?qZK4Pi9;0{d)owD}5|dOt@R6G1G>Q zHA&kzj}yi($3jvDpIBR%+`8d--_s+^*EIQN*t4PY$OmlN=9#IqA+aPpsj&@p_}-sP zDT^8bil^QywSA$ngeqB?mr^j4yj;SN1sfC@grk3r|<65o}|7c4s29Z z9M$WCp&xYbN$I4e4^}#Js+^CP+kbt;$hbN^19!?|S;(Q{<_!<-y-VKi9QIv8=ISKW zhc2#pU?CYl3RcxC9`gFIli40qaf*lcx2=ecjW-7w=*wEYc{^EQA09cp=Q$N~IAC$+ zTulW{kreN)OlB)U?5LI@+JdN6BK3O)rkA&d?hyY zDFF=Jq50ItETpV3`Aq}?@&#vAd9l>c2DaT?Y}r7w^DW;f+-Y_e$6S+2$=_L~ZX=rH6l9C}Oodfa(yLsN$bJf5) zJF|723W8T!4eGIeT*c|k4Y9vuYzjVH0PA;oP?+t4!}gIaD1P+R?@r|wm;Z>tdsLeJ z0YwjJ*EuZv=My55MM_ys6HOu+11V$lUk=Z%l4(We2GFLX4+RY;vQxSPDFYLJS^Vi<=&yQ5H)+bnmlh67Z8X8T?*+HoM?;Q+h zsce?2;r%mU=S8Fq&+jU1?!CS$q>LjO-4pi?!v;#M9aBVxO(L=OB3Vrn^+^S^A7a|g zEw?I9>C;bsX(ntL2!d)pS!~h{qmnE)w0Ir#tke%QhLd^v-cL_7!z0Wpr&4Dkse?K_E(%r=sNdnntf{4;FxAIa>JyR0a7OTB-sQ{vLSu;b$5=BnLb-4h63 z|M4d>F9hffSZ{AsJGtga=dKH)sT%V!;-d}45V9!hj93?3&)eHJ_dF(@q!r$ymG+q8 zFvgA*T+$ZLb8|oxbO#v@(5=_U(g5`PbEKNBh3Vy5PR=uW)*sivM|S3Kq@sacnp-;s zE2#VPr+7#cXAzqWO%5}#P z$W#cKh7n!!R&LeGv7F%O>E|uK#roiL$@J;=kbv-tvv(^>cde5H4MFjnUMDqCRyLX# zHlNJsYR)>1e7#t#>h{)p$Yix#P!OCED%ZtK^@{uZJs3f6T~H=WYdoqlxyPD?oA7QC z9Skcp5gSP5oQboZ@_U|GS>tzvkqCIG>7FGZ;kDfiT!u%yt84jmVrHh^$dgiET52)+ zISYj(BZcMpyQ;<|?((~~lgo*mvupxb8cD*78H=2#`I@~EdS5q;$=jX4W4{OD@o}YM zW9n7;f=w+vPzoB3!xh1Yx!m!%RqWTKZ$sjYG^Y#P!7_1vyby zV&r?ACTl8T0?TwzlO?E0WDG(OKUD5c)wkFbkqMXF5ta@tjBgh^X2r&wW>od5BE&OG zdf*3&jHkM`gj6`ME~KZ&L8$-`2PBl|53QndG#q2}P69e+D)m&l;^Q>u^Rd%w%C{2z z0=KydKRvdu$Z(e#r9ffHKJIF%8X*FoEntHY@nU}kF{J&{(Ao@ffWySY9E{RsEq41C z6zFb`cTl~3huchqOp_fuN~#gg6uFNTo<|3^w(Oo~rkQ*hrlUbxwDp5w+ zxlBVYbx0M2qbo+6W{&4^c&nC8XJwcQ-{!pVr6=yVzq5X&-$tmMBigfJk)-$Pc%=~| zX_Hk=CuF#kq3tpyQpl@g^1H@RBwsqYs85ifO_u4asgBq1KsmL_B>a5UsBHM7ch(XS z5v{H+uwo2p4uhyWQpuUV{Gs5xb!fn#u+O5QwQi?eZt^qMfyhqbw6@i1rNwsq+y9TJ zw+g7T>$--O?(URUx;vynx{+=W=>`euZb|9xlI{*^>F$tjr2obJKL2;d0ekQ3S~2IC zHOAqbG|kW+vX!2jBKN5^KVApvl)3!CPU)P|@2FfWC?8AWxd~O@@L@Mb!R^qfP+>62 z->5Aw^L-Qh{qX^)m^3nzzZIS%yRMKmpCfLfjv^6Ay{))9oQsCvTCFnQo9`JV{>u5! zXz2znFN50`bmQgjtD|?IewsgLFvb*EUo8y61%0;pL*fw5s2$-)5`J?OV%SU1zm^&m*S^I<;gmcD%ZCg^ zOv};S<-Y~g1G&VMOj`m?swH6pXr}1buIZ$x{2KHR{Qga&@#&2C~N$-*0 z1|tz$Vp9n@8lC^4N@n(kkS|EzUNg-jlJ<)#p;n$!2BL@%J&-JeIYKamS3MswF_8>| zD*XJ&1@HGM0F@}V84e_KBks?+IlS2$$Z9$dAqU4xL+dZl{;O^4JS)Md1YC~&SXZpm z1K+)=nK@5_XODgQH1xd#pQ-!txT~1%zu+6?O~L83d8Jp*EiR@KnzC}*F2c2C@D8>) zpeHEMp=0AjK_lXlN9fXNBnwLx*ig2h3QVhiWr1JjTFUfgW@X+yN0+mIndmfl9W@6d=OZdWLkimHxIc2fw_ud;vD$Lf^8MGM z&Z65+-0n!7Z4$xqk29CFs-IvnD9G3ryJEwU30&9h)u3rp-jo_$4kr5y_M!|q92 zqReo>S<;GLK2vbin6!v2z~k#*tt`_iM4^c@E01|Sw_%Pr9K?4MfV5#K^+ zYHq%@WZSXT|4vTmxx3fz+Q4eHG1T1n3;lSez<|FbYK)g4Ea2TGcQL0oW72ywXFpcO z$~6FJ3Y%=t&XjIi6Mh!_J?d*V>4NwNI#a4b;8x?@IWOre^qUx)uL$ArDOBd|^VG+4 zhotwv9g z-1`|F6o9MEo@mSTsxgMu%S7iYR_5K=d-8{q8nvT$(7rw{A83f3hV?(sThJ%-1>^s0 zA8FKogKTXLNJ4M`31`oRX?T^%n26PU4UV`gm2o|2nYkEY-;NUv(&WZ07J9rQzVi5L zysyX?fhR}=n!fP3%tpdH_t|Ksfas(gK)F2#YuKJ>g9*bL4ja<_rx)0C>%#>JGPsUO zq#nC5rD#n*+dfQRv{UdSR2ZjEc2xFq1lE|$^dBu;8W^!A6Z-Elni-c}!d;O}kou3n zc3EY87#SI1o;PClwtj(Zzq_ZK|NPnb4PSv7jz4oqXwX|o80JaNkYh)LmcopU-XV6` z3|{QrBNB3ZldfKNcq%}jVEXx%GmwD~b=IoJWCx@U043Q=5cbi(dE{SRT>8)71Kftb zRURy;B%=_)X}cVmLczl1jFS4CP3QRCXV>=c)r@WRo~s@i-OeV;{Y-wZAJN2SZk$?( zO6?}wkLNNPg~yAfeuNhf=j33w-iEmiA(`VZCeSpg)a(FqC%dnBIWF!GAG{7uD@lqJ zX#b5I7#Bio)>VzY3}VP$rb&A$Onj=jaNv~IY8Xo!>IB#6ALynpux)=PfPA>_DVd?O z=j2Li@QllJoSC9sr90c7G+w4}^xf{I6tf8rIBm{9ySV6le`8c@92EYfsBN~~)Y&(R zE~rA&z4TSEK;dUk1gAb2%%X6))dQV9{y>Lyb3+>@0f92+=XmNhq1)kOE3~8i#-^#B z{1?3s{+M+br$C3-L%`YHKa@ZBD9EU63y+^R*C{!{tvez~6c$xjTNIPe#cGcs>&HrQ z*#-PEO=C|OenHLbL_njz;&H*hu(Nk&qdy@L&E{wG{1}3@G#Se2C+p=1BMg^ZK^axd zI$GqGWFX; z`uFX$sm~OmGxk>&j3ZHn1+3^>y}Bu>D!gmAk)Lj?+FX9UF=u0q>9&ekACxM)&<*D~ zN|XP|3oR%pn9Ti!oG94EKCAAo)4KSL!cI}q7|MAL059}E*th+Z3q_E8ziJ(4Lxs+*^)mK z_85-XH7Ym+kAs*inFA4VcYKebQ5ymRtw-!`GFQ@W=eKFS_j%$!S4F2i&gp4YcfTM^ zni_X}{LAUoisJl0>y3?lQ-kj-Ev1B4>>^I3>gA2XwgM(_K`hmUm6hR4?T5R=uNG@Xnzy?(}oB-8uiV0JUM`6SHRZ zS9iFdR`X(+L&zo}*bi2Q{&hd+HqL(?q%51T5GiEx@3iSx`uAR+5LbtUhHk8FCC5HJ zoOT#bzlW90JViz&RdAEdghD+X;ztoxPzg8Z%eh9wp!mDvL7PlwYHbQl%0vnU1#^+L z5ERjwp7}GlDCF3UeDs1+!EG9a8cyG;BPCTDvCV&h-W2-+pxIB^}vHT*iBnk## zpLgkW7M4;@DvyxYP&6d6Vos{;AP4;YE*#gOrVtfyn02nKq_=2kp|I^RIysR>*gFIe zx8O3spqlwDh9N3A-lPD*=>v|i?w3MwjN(86#V!5^U+%&qS^YM zEb|G>m;0;AIs~YN>4V#P&)TU-ZLS{A@A!+J4{U-DM{JMdnJ*FJf_E4&9x!&MObs)H zobNa|5*{vnUW-`&%S z*2&FFi^J?@FnbOfZlTvY^Cf%vN`Ea?dkvM!RSJ7ai)BhP(zD}eM~6&oui z<~<9PiS^UA`KjO#CN;I$d#}eoMfq6qgc=orTOokMG+Q8%NtY}g(rU35o*GgXjRRy5A6taUd*Y2>iYBDLSdC*e^^ojS1Z2&OQc-QAmVj_rh+ z{Ua{pma56i=qx<&!{b7|{PWw$jNgsk<@e7q6WK`WwXzH7am1zn0#^15CSqGs(Q8)m znl*Hj744kt#7s1yK9R7G4$c7|^h;SwX^bt-RPXmdlmH7qXQ_VW>c5@B%(I4$7vh&o zp_g!`j=MOTQfAXz)RiQXqvuPrxw@a>h^>Qa@m{wk-6=liJ6IM1p(`};`L|UK!LT|D zch`xt*9OisRn4leLw}~NjHNw?HER>DTK!`6^d*wR1F~%J1Myu z4xCfg&kb(#!Y?%+7Z3Q_NqpCJC$`Cbk;+}M8|l}u<&Du>RZ_FxQUm#4q7*5&t&9Mm z|MS~eXgY)NJeowPf9ryV+nXI;$n)Wtu6td<=bo z7B)UZS2Gs}myx(%e&3TFyFa2}KtUQoLGGq0IAma~6aAP2L1~(2bXkAQszc6W9Yuug*V*NJ zc6oy>(xnmQy*R!CNit?*=VG5IU+lO$(pQ5`XF?eRv>mF~4nmoGewdAq3H-Y$J&lnv zBy6zu5B}K_ftCN9DmTIF^*)D z5Di2VNxF$a-Ot~4qrJT4c2(bTDwVdfBA1n=?{lOQm3B{rHbk6|9mnD^mw7;$;K*h{ z+SeSD8MA53LTeFGwyv?wOoT}s=so5oZn`QS#rilt{_^$#<#=Q3-SdEst_hrixhw4B zUg=Agx4-_z(wy9VfnpwsU$3wVrV2i?Wo#oC%KL{EGO_fQB%1ph=a2KPca8d2n~6xf zJ0EZw9%m4fwohBVmQd`4f+;9XkXj$THYmlKl1hV#25r_mg>ytsg0mrm-`QQgmrdb^ z-$3lC{_c4#73{`iyVOqX zYNPS;-I6A%z;LO6f!l`zH(Ha@0=p8Bp(u;3gzx98Y8Hj1isTjMZRo!|7l;u(UbhK# ztE>L$k4Oi~;PM~T#|uBU!i|~iu8aMVreCPVc@-|@CPP{7lZk0pj@v3FRLkjYUkP-x zLnrUnD1${NjRN>!u(I!)>#?}HR>4)g0quC;vJAjuK@HI~jJtXc*B$}_WW6!R7CevG7 z(fK{tk6ISC_R-be_(_UbL=xc#;M9-G)KLPBG}3BAC2og(xSRmIi)*{Uj>dXwGqZn7 zl0M>lV=IApw{a`I`#y^cqi@$d@1O}dDqwLXZ@1APoVviHR#M z6qfK+olG}`-quwkf`9{Su0lU)iJ1wIjb!`2iI-3By)P$3PfrdB-Rd{2$|OzGUEcV6 z0*`A>!+m|oFi?4hHZJOFj*5hc)En_-YFq05TY#pFPy+b?RI!)s^`2-u_SI_k!w*AC zKTBMAkhZPh95A)Cq^8jcqmkEX^}yEc9iD2?7h3MP#XvFG>~p%D)wNn{!=+1P>X}8> zu6CS4Ils_iq_9sDx@B)hQtO5J2i;lU#1WOD9LVGOj2Zzd5kDC1+p+z3`SOH+yi_-kw!jwawOiI` znxSq(3#oQZ5{iJQ_U^;U^D!Y;?9T*1rR{3e7v?sS%1JEj>%W~;+kSFVgNUV8wXD0c zv8tfz=GnG!xPVF_84&~NR<2cz(BgX3^;u~s*Eefl-t?0wddc$gvUuMr>YhWUH%l~p zbk}cex*gj}v#nh@i=0Bb%)nIcu&|4PmwTwTzBF=#rm(f1nlFxzTcK7Zon`S^f*5Ug&ky(P?}R5Dm1`rv}c zZSZl3g_=IBZy?hr#AfB&;GWo@P^^G4?U42xfoj`mHyb+BSGnwNT+o&5a=bKLfeu*| z9#F(wy60_Fkz49-Hp|)Qu(eRZE;C#1ACD%<{xNfRAS225L=I8_l87&svNx)6uJ2rk z-kb7?W>Cp-WRQhbB(}L_vhNa?#me9fk?v7!>W%8Y5WNRwi}>fo)b-W2LX^j*oXyXb z(iE~OoY>oAm;2|KM||!`Ps^=U%vNu0mYaf&&V@cr+V4YtG!!OlcHIx@@`pTIH>{Rb z(VvndY}lBpe4(nm4*Z3S{_8zNIskxtqOO zF@U+y`sEi|4Wj97mhg1C^jo81DwuUPnYQmnsNR#5@6;Qixx?%pRa_1A zLHa&IP4Y}6_afp?`nIdBA4ri*v)l8%pCPv)&948LYPMNLx+&fUgR!=okl(SXy5C%L z9b4*OyS{s*z%!PJ?kk$4u>1G#EoEWe@AtS^Y9=jJ zpW8hmt3K0`1kuDYk(kUN^D%0q2Q)$@Pfzy6ZexD>w3C&jbZfJ+u`sR{5>U8;Fqjer zqn=YldnH~*?S3Jqs`DN~PANO$yZzybF3)>x=Wm+rCOo?%sgR`lq}*43{YeDA{q~Jm z7T-yF=HeGXB>dV_K!VRwcn77%2-lIje^jK068Q1PzzAh%SB*6)VD*4-M6rMU1p zNU7ZU==j_!e!L$nL#1MppPcHf9 zc}o8|g2z#<{4LQX{lq@%&L&nD!}m%^apinH3$k14rQ7b$^K( zT_(E35RJ+@xmZ?GFb!+(FD=5XwG6=!evIyeqXlg#A$L4NR~r#eSqKp zJ_{V9eKT?)Z_RlU;mW68NW7*&k(Vush&Q5jIB6#suX%ibwY>ZIdhBDlF;+=KCYlP8 z3g)zwYwm%CbQamYw&rI)7@x%|YZ`gV%pdmM*90@czryLFGCy^#Lc)qcdl6Cx1cdyK zW{M)>kB}r_lGy}}BKvzQsKPxG8>{Kx1{)pTK0Vwf`Pb_-dq^*W@gG7+UfaW*8l3NsXMH;zC1F(xKvF*1?ZPM&u$Q{XGN2m|h;dovW+ToEs`|l54&hHGIE8N)nx& z9WH)APQpo*vfgUR4@`@a)9^|D(D3&8=Ng!FM=m`V>-|!Z&TG6J!qRA0QJeKX`WFvR zGV3=^dA0M5Tw|>kBZl4aOi7`NxDpI_z|AOj$XUK|u>UTXqIjY0D=D>H8kN3%C@Q?LjgmVqTmNA4+0scZA8KdRea5 z3VzeTlU#6cd)?PsVTXjk4d{?r(ORBoeHAe7mo3w|v)a)|u2YpwWkzqwa9rIRPZvE) zX5lVC`hCl&boGx6jNMR+S@UvQF9w*(BpK~4yKlB}5%oaA8n1VDogJPc;^HbaHZ}IN z2Mh2z?Y|TAEj=P9#K8|5UE-vW4k<64vmK1~xhnIRe97Q+)P1Z}lF8(1pk(F{i>#$= zEm45*vd&HZliuPiYGLsnOzT7wYuA%AlEWY$%!)^!2#Do-{(AhBS!MJAA{a`_y#DxS z!|yVn&Y`PKO8IIX)3NIDB-xvFmGx zq=l^w%i1Rfyvu!UEj!$Kp|^v1p<5f9yR;p<7>BbJ!tu?{cFJ&Rso%XYO5|0okRp%? zhrViJ=P8KX+MGEZ1;e^#h6OCbsjNgn(@f$bn*pK=s>Xlm11On%p%5f!4aqW~Le~+4eRasBMboEHeXy13+ zol3J@kkaZC(eCdF2pMroM8fyZ5YAQGEU-qP5ctcu+77i6FV%N1WaE6|Ui|o~=o#nqE|dmptvoF|bpi+mfv>#I<0WS1(#=Q+FwNM~j7p zy`?Aowqk8W)KY>P6i5P~4VCb?yxG%M(k9yFB>y^su{bDKCUdu&LmG-x&|rjtDJp{2 z{7&^|4akHsvnsQi%n_3urx>ov?{6)pM6dx(I;hz)-SGXQ_akYX$iiJ+c$`*v_Ye1< zd}t2pXG*`h)>VeDL;3#s**2DLkSZ$dp2f<Zh(QamvY_)NB(JCHj0}s1bB?mu9BESRe9mykgI{6(cH$|U#_|^5yr#65rN}Wh2I8+2Jt!rgJG`;)dFLs} zJ;-J&^ui~K;1((!9;nqWz1OQgx;1-j-m9sttvMggZWek74Nmg*^oXL9aH8^zcRa7}AUQQ_ zQ5+&tOp8^kYo++z=yk{$h7e|__IL>dVk4S>8??^k%P7ii<;5BX-e1oGOY>) z9qHE+92}v)Rv<^n!&=@F&31q1ZqR!cn@mqjhn3?fybn>PU55gD6K?(;%}%32X9EW= z$Q`~YM_xa*nCjsywr9T8n+=DGF^&R#iqGir-#Hu{9)v3w>H@+?|HFm(`N67o_WPUd z6ta-W=swU)6B8Tj8=k~C^y%-)lIwX^)|==OyzIzg!TnncCU2%Nh(2Zx;mDAHl3Gf| zi63m{<3>Ykz(oG(AOTEwQ&(98E8V3)?A#fP-FJ&@Z;y;Bbj>)GrZt~MDWr`K&3 zLx3Xa?(iCLnQwA+EY8Sw+#8iVD>6{>?Ff`;q^ z{VUF!lKR9Oy}c!^S?$?`#nTXwRWybWtwmY2<**YQHSE#6=~}EM1Mw6ENS*@C!K@R& z=q*C@jB)xbL$@K%8W$HA0pGbPU*^omQv(fqf^uD>NiK%-%aiY~{vva^;{Ln8{~b0O zQOt%$LJ-wx3a{Dm#UIJx<5(P5#mam1ZKPa=m>wt4(*TZPATgh-!S%sRNLWbkNHLq~ zoQfs3-KCSim+dzvi?z25M_Pj{MEBugrXhpT7^maq+VxWC@;QFZbHDe6#i5Vb9}`8% zvw`>?J1tw!G!(~Xqs|OkQJ=VYqthNt#cEsNLk@(}=OXxPM&x92S~8IO{Dqb)f!t%Q zbm9+t7ZM$fEnuw`D+J6~ z3@Mflppf)kP&I|8pv2L z8Ug{|EoRH`7Z>XMylzQYafEneqDc8jRA}U}U)Ii*?G(e!K7FDc%V_!Ddbk`ITHH3# z5=m~RB%VRep)?USpR758h}yS3q{3|Z@BP2KhwLO#rg_b!&f4NYtL$7UZ?Y{BG<@t2 z&`{7=Stp*Zz27cYS6bZ2SDR#y1I2Xr7MB_J|ZhBA(5I~7=A^6BxLtd@X^99`y#o`0ERk9_T#g_%skZ9 z&Sb~Sn>1c9O(JGW8FR0XLiD}#6OmJiCSWHTl0w2FZ6}{C*AC7QI5veU0198mCwU9| zD|NwE@J*l%i?eLyyY7n^kE*(Z|AQhr*pMp^#DhXhdujI{Lh zrayP7ALwbjiO*jo^`(jrF`Q5N+%G6yP8a>}HXNC~yDgCRPlH{+?BB>d9u75i4TqmM z-N85AO@0Pi6GBd#-sh(Wd)^QDpG88JJJdgpiv7;O+nsq=s7Ij6!tnuH5{0xYsQhbe zqs0fKK(};OYl5I?JZM`5+pB{anft?JJc1n4rFuJOSIHPO_)8JdYPSVeB7Qf;pjp9q zXBcXI>>abg8?6S*z#{?+@pr%vie{;2^d{pab6NyUYuM>f$9{xl z2Rx+>!G4br%WYn;a{=GMgneJ}w#i*WqSjmE%}r+Y$I6%~eb5KGy?>&PgR{Tbgm~X{ z)pJ(;Hks3%fIN3s=0Wa|Yh%6LUrI(nv>pwu;WHBJ&PS2;gM`uxch4VMP`wX!D~ryW z)U>yh5(B{z!7pgG^S1}SIJ2D0Y|MUjrfgTYS3UcNxy)W;CS{uK5MW+hB+d=hEKML> zW^={q@Luji2x(2`o7{KANr53I1EvK9MJ}-)1>v#2?5kxn`Cf5k!qB()6mBN5i09(7 zxs)9d{Lt@8`T>pDJ=q+OeDChQKK$Yt3Pe&Mx-eLj}5h&Ibh`7h%yvPnlLo|3TRfHz7u@rczQNlNI4A*=i-($H1yAb!z zpXW}LRtjh>Ee^Hksb-hK{|{!kc|sr&9CpNQu_6=nl4swXh4g=J^3k7xa68X=eBp6A z%X%bDZ(>F=7L~~D$mCjdJwSpqmP!7Zn*9q6(;pUDO!3TOB!v$*d<)0;srkC*`bWmw z?T=o(1KC3jgG!9O5Oqq=(lBV1oF9fsOucZ$U#gcXviPLw)WRi{yXGhtv$4Kg!S%wgN zkuLfKH(jHckB|)gw|+5I$(K z(m^-XZSg_`%?SP!A*_`MJd7%IMX-E~Y``#tLlG0FYY@oFA@h)<67wRFZHdMtrcnRX z29nSrGQacm3h>4tVNiNOhGyq|tZ8=eV^*gy9-Af7#p%xQZz3M?QXZ#AJfa6)gPoe4 zPY5wTeo$(&NV&zt=TTGgj-~NXNNT${+kJ0q69Bm(5T&-7-v@v>I${}&#LmUv1TW9G z9g}~?Q201)yZl=?R6Cv$_|ebpnQi{S4gLKLQj+g7p$KG0XJ!(-Fjh@_FSjh8 zb+=bqgFt5G!Y4{1f2GN}u!K)~-!p=Qji4fGY=5>wfPTb!Vx$b;HAMqxv+ixzfl<>VUhhr z*V-*6TY0YJWDUL=BWjpy;C~6fz-7=2uqJFU9M7iUPIiF6|D4k3a2*zrH(>0+%mH8L zr;J6%$G45eWz&gfKZ1px(B54mG)5olQ1s6f9vNqhS&e1vEMChWpePGQdzO4$c{D=7lO3 zCk6h_PvSxDoKbjm%)o>YM&8-cZKZD^i6G{ZK&cWZ!~eTDk+j<%Nni|g4yAa#kdm0T zrO|UfXz4-MNf~4Y{1)A!WM{F6Q2C(tRa)_ZQjuF82YwM=4bBwz2OtAYq3-Rej;}e~ z0r>}X%wI^q_9BEkC*HWZf6F$0khf55fFBt}4*b3!_Wbn3>~f?jCN2*DAUEIc)$*tr zo08$X{Hv)(Ti98ErWW}EJ?ljpfh;vWmLc#>9Uw7*hv!3ZmP0w(9<=%T6%i5=k{#FY zyH-#T;NpmhK@$@bGdRA#o~K{6o@V`#kruVQtaJBhW;oFLL5fllSIPz0gRlm5O2zFU zGSMJ|`D7L;zVskR&@k0~clpOKra*@!y~7Efkkht5WJ46K`W=Mp=?l(QTj#tU_ zvDgc`7uY_-WeVYn!k~0*yZ^E~o1cuUl&1U?giII&gM64BYa@z8#4e`Cb}w0p0@519 zJjtnO@Wn%wobn*NGWxxh?FPl~X$OmY`M%7N8iiCa>YA|LaI2p~N;aY|FJ=f2gBfad zb@gHx%<*0ue);l2n+FyZk3Lfp{;Jw=iIYgc2?<|>-^A2>sDhbNcK<84z-cYENB^If z73_2R{O6}a6&3pn3k(267zWC-V_W|ADCh^bgTY<@_V#us)zjssU#+{_1LA@f(;~7A zo0KO~vJ=--zFiQJ?u1I=0nhF2pAdRfnQX0HDKOxt={vTdH&p-j*&WRr^5}^urBvmp z+Ec_d+%r+EjG#Vs;&+B)IVTeJLaP&tt<;Ldn{}I{idWV_ve2mm6a7n?vRk2 zqwQdeQqh$H2VPb!OI_ZQA!`q&PUH`pQ{RDFGCnV^3XR+=o1Y1t+Rxc?LmNw!C9}kR zsuQmJm>L7&_c%24-36oC_WajBh~{H6B&QKR=`yb+JjBu-I0TDLPGV6`?Wn2>P*TzYr-SMB%33M7pfSdM$Hy$mkrbq_| zLe_fg{`#xMRDpob%F_?5pkloeT}pPqilRl7RJVmK72NAgco$?EH)O8$vU`0=|dYFJiI@l5gh@i!*+=5{&aqh0CqS!2tnah3JmNz-?({P#p?ITjgCw|Ci}5v|r|? z!*pt6#W{NMxmU`K2j5&zhpRH#E0EG5@8Dz5-gQK*XT4i+5P>x1tg96(Rd?B+X$!tm z$+d7{GanVx)Z|uY_#7E|$hkSOA?lzggCX2-_YyscQPiT;;&N<~#1m8u4|ZF1&cIT3 z$1pRX1QQbz_E$fQlz?tsB9x{3!D<^H5izmpi(Qa=ydvHEI@Jtp?3Y~FXrs{s4%P{X zz>_pi>(ndzthdR7JW1V+i}UlHC7;=@&~BLn{R=8GSfhY0kMc*wThI{9xOIR0v+;KQ z84((4MSs}#FstY%z`H5kDgBxHKsD`9Bls1m%FTCF_InL zcQ_9w5J%fdN! z0t&+35@^W&DkD>Cvjk%^W`oB1Hv@s=QQn0_m8x7 zaFvBh+op#-PWbSQ20%21gdMJe@n(OzLcr>0gXm9Am;IH}hSrx40T#iW*6RmH^nq`j zzao&31T3(bk+YIRfH{ty{&MYD&wF;pjA2k9AYx25EJ1jxRF*LiIEkvIgX+-CgSx7gq368@xB5~s{lgb3>;?d zXrH2W464sX=&_v)@U}2E(nY+K7*2#w)Rl&62H*qQq0!-zpg*%JDR+@&QYi@tPFFYF{a$iGQ87HS&EcoToABK8Nlo+5K)Jg7J%rb9(M=ZjC;g%Kv zj2I~@v92zDdo^v<7He*KZ*MPXpjq}CVXnkIu$itlmV8}sJh0$dxw$?1KUUHH=QCKy z-ZtNRR4&J@Z)|jt`%cnYe6NQ=Ca`m*2y!1SB2H?e@-6<*NVW0$L-Y1c7eK+LM{FGJ zgBlimG^tQx)G!JP%2DHp1va~d7AOOOe>c=)vjiO`U1lKbEa8dq&-C03K&BE*Y<$RH zFfiN+DFu0|D9$o|Kw}V;tF)j2m>&7*``@BTG!s^DUHtTYEfzV6@FQIjY`IA^gZ%+F zZBl*E{&Ubrukhv+<>A$PFmdNTj02$Js=(_t{9-9aX(!YDlZ=ioTwDa3EY_fG3aWSr zz%y{BE$A!8mBFAr>ivh2!)=h5ud1sv z%?zLga4NVz2st)2BOg34SUw9nrP#?L?H7%s#RgqzcQ4RqWH^${g2(B}-47kzIlm5S zD>g)+Iu%Ol{e-agbOqfXNgNLBB@(Hi$b7^POonJ89t<+!o>-^2H4WM0fEGd)4!OFp zfbymKjWvCrx3B|(U?h^aKjNqFkP3|fNc<K|}e7RP4NeUI1 z@-cpV{3`0SKTndrSc~J%Fu6L0QZD9AXE-NM-(WonL2PwY<^*;*hb1y#Vvqk@5pPLz04Qei{Y`;f%CC)Vfh3P5wV~dBH2c~Z%>~r)g)}^Z* zi-}`0=^7d3i}H?{2Q}8(^I=K4GPSkXho1l)vV_hp&i>34pb92d_D0_|4ULHK755TN zGaE1sD#kiMD|)y+)lk+Wr5r;?CDxZ^iqRu()=F>@Xqq4lfyx@Zf0g?+>W~u zI!s?R>SY(lFMq2pS!S@ME2xd7Tx^RC=RArSsubzW#a($;qgEpm^QnM3-{xd*6?{~_ z1_KoB110lfn3eRuuWF1a5(A(RrNL~gxZ9fpF_bYs>PW$XW)NUPNe$n!qyCkt>P z%vG**&Xj8OT|PoE>bFPGfeb9G;af0w%4fAuC0&Q!Gi@YAS!=tNAr7HpTqQN}oz@L3 zv3YD(ATV)ViW;N?e@b>6Wmh5x?H48|V~LaS%Qvk-7O+{TVyzk9`pD`m_|`kx*FVVU zvRQ7v-iBi~{I16Nu=?92V+1mapL`F@eADqY-)A;i=Uf+^-4h8}xh`^HNad~B*;#sw z+L(X6+#rMuDBE`2MlQ~4=kM$5tD))wCCK{{=7{<;YT~3&Nz`zbue(TBH4Otb35NzN zIaJS2GeU+&mPm+fY{{vqp{6RDpS3oA>U;M#2zf?BJ~XwUt2`+2F+TkJ_x`nq6uegF z+M2!uR)Z~-v_D={13{gNdY+9=OVCo^U5IWS77U1r`J#JAS|ROZAXKJaSFVNd3zI#|q(#}> z8+?4=444x(aIyxe|t;sF)e1(Z}p~*VJ_a+o73V1$BdJJ}h z*sL~sIZdcsm}V*Y|E@9;3kyq;M&&A@vyQss#vdy7I+ox}AD<)KoIp?1_sf5~{DbZO z4Wjd~dc_AJSAQKqF+~h2Zad?KmzC=QdwDNuJYafE$|>M zfr|G(cPu3>uLR6(q{C>`a0KTCXJljAPgiJ<`(`-MHyoW=l>fHMV;BbN>UIE-7EaWw z(H$+cX%GGHD_8kI1H=EnElTjA%#$p=#nUCPXaq_^JG*Nd2yHhAQ|h!iptu}s8Q5sz zv9m**=(PA2V^U{}KGbWrTj3)hAp9T#uloPCSlsUh12gl@6bvtSVmFy0egSLD7V915 z(Mo9;^A&aWOHZ!kAHdm}5qI&Ul&fN6eNuFAd|U$rE-pQS|647PA(0UiAxg!Y6HS!J z`|%qSD-m`xH-%dHiHUJsSQ^*8h%a z=fD6gCnx9Y-5U%L!#l*^aI&1q0e}O*B{50KjXC{8qX!opc%)0-jl};uguWdeLct$r zl#MpG45zEeqNp=f!L|U@viN(6J(-}#0`n-obDABTt@RU$MHx3Eey34F3btI!sNw0j zkK|no3;4GuD=NzPyDPytgfo@;nZS(fa)>g(6Z1N)gCFS!XYwr{<5wnlFkoAld;2?T zn30i@9s{o&=&ZbZO_U;CdXUAwW<5bPANguPUYAishY0EF%Km~9EJO0|k5R+mVP5~7 zLwxn5$l*}PU=5jKV_BFhZNE>Y`~}edJtLz8m_r674Xt2ar~^<)Nx%v+)Pn_34Wap> zAWPlJV{cN7A_W%*E2pRk4muFpAsvP(uWG$k_OI;m>k-|KH!wBrBNs*tjf@WV%|MAN z&|qjre_afOI!Zvy4GR#5sxn@XJ8|pZL@19_2Hj>=?{|kYrMMuXTR%E@qrs)Y&Bqh& zEO~XjgbR4UV}@lC>AL`@*N^g)#W&!gffGU1*4D;>*OD^vB$9O^AOi-TC}{zJKh8n> zJ0M@ucXPb-f6r~ZNaHKkU1K_&_a+O~CnEQ3M zOsU?)5)teXh=6j$5m9_g0x2nbvqKGy^9s~g>{D`I&kJue`YR4S+_8?ZsWn`o-akB~ zpvt!_zZhA922a-55_=N|8sY@}slnY;1BU=CcWpvrBR7wHw;&2Ql9dS!_GtkeU*|ff zr?CnZGS!un7m>gxi~I(nh8N*^7`3Ol#6C%+0x&yGruZ4G8q77D&3qLZ3JGsso<-+V zG=4$9A|GRi90NR%VUJ~i2UWN2q|SEuIS zXS`%|0t0?fvCXQ93`8JrI)^Q1>cZ!2ub(da%jJ9re;YmY+I%a_2=j2CJL z$GsZ8s~BJv&wB~T&!;L-$Yd1FWwjN0HR732$AO+0PDli=`?Ey}vL9(_X?C|Kx>N}u z7Ld;`=+5~vbL(?ud0PqIFbhAe=ir~&WrCp zXOoj{BOpc}fpgkR1MW%Osq}R+{50)Q>(i?P9UqU*CH(S1nG&zPSqj|qk(M#cbB9d8 z<*@4WV!MM7fcku{Q5F)dQ3hV}!z$;%P9P#~j(ty%l#E=0QD^TU6nr)OCq@-Q;MII9 zE19C>v+_Y;r7{H^g`r;W9pc_(j{giB#74)IT=ZUnmyE9bP5VoUe8A8e&c)3$Mb=F zvF?t(`;wW`E_)fgf?WU&roQdTu{qCD?x*aN|K0w8-?R@R9%goSF(;=VMqSequf`V~ z*l;JE*)JhKBCG&T084D)B>ifDebds2u1=;(#oj$Wcle;f!ounRQS)j85FNW~YhhIA zwxe*tqFx;w2hnNR4A(|nA?*|HQ_#D#>uAI~Qo{~J6DN7Yk1V17&p^$vBx?ctXv+PX zm#z&|D^*{&^?519f?s<&W8k!zFWY8DQkiN+7KAq+GwB(a!}Nzi?adEQ`l0%ciVDBu z`Gx|Aie6b+btH`q6X0P?7!p2Y>ZPiKYXl-mu=C-Z z!DuSBX0?%kR&9+0MfyFr>rI-`Q0wTcr+xK~_}Gx)FhNpsrCKuGOYgv1v;_IXq8jz| zBLl`S3I+a|^}ObRdfTPY&JEG?JKUGh_k0T{hql7A)BOM$d@;-{=&z{XMSP99!`~kl9N-OfAGqlz1*^TWi1G;$ZO)rHdo?M=_Fun#wUM3>0WiDDeucJy5(^i1I1vgJHJWR; zad~_OxqzB_{@2sHI0c$5AREAcu1YagoM*z4E~5NLI0@A2evsxVO` zl7B+ux#fmU>vzD@38Z6S&rA&sojEx92FCKRI({{OajV;^iH?qryYP7Ltr<~vWVLMa z(&9=2tSrcS98)n0l6ZJD=hVa(=cz_DblEQq&tMC*+7Y-Hl|8e!!aaDI)wD%(*E!`o~-QCjN-6cqO zr_zl`N{6(h(k0!EbST~3`L6AG&Ux>>{}~_Huz!24HP@WuI|eA`dN!OejqR*M*@1$c zWLx395S&!ULjQh2H%0dlJhDttnUG>`LVzPzNRz)TflbUe6DUJE^+KttkwwFsqi%Z`u1WnD3{)iI*L2 z2U_?Mde=v^w7;O2aXpTg{I;@6!u!^q=th}6t?mck&Tg(O`_M2jsH>)W#QZ)LrQook zl0vPF`EQpAY&|T-KmP#-bU}BIid^7pRV7M#+N2;ltc`|)-@i|K+cvJtJ|+i`-{#^8 z`SRGWR9uLGG7FmI=uWS=Z3E~?pi z2D-7bvoeDD2{wfIvm9BP7-|uBP!@Sxm%947bq4V@6(qc0D2(AdW3oSA-UYyQsF5}G}B;O31b-^AJg_%NTxj}=ak%#g4 zMwkQ`)2hzMiUqQfu@U*NwK%N$H^Q6P%vO9%Z|EDPS%wMO=vaok?h$k@n0AmKa(x~@ zR*uhk6ZQ;;dX~ibd#5_z(cP^R3_%mIwhYYn5@{_hbbKTNA??U2(9?u09uMwG!#pxG zH<@M*unxlFRquj zy8Zk6F~mQP8n45sP)UTlfwPWy&o+ZIa~5`8qbVOV{iw%L6k-!i_NZN7-C=R%ES{Vu zlU5xA35I^X!*Xvy%Q=sSkkrw2p!Q0G9U;&iZT4X!29@#^>Sv4y(pN~uE(ss98^J?! zHeVfvq=ez390*gCo)_fGdIuOe;OV6P?!8R0u}b6g;PNlOE^FYD+PJ%f3ppX-frq`j ztE#G^xiw&9WHfpAj^cRpj4pVZ4-*?RB!`uJ^OE%HnD+w-aEl`3u2iFBIVLjei%q(Q z1x2#_?Y{?ws1O2d-kLW9uMGIaI0Pv{X?)nhqoc=oE$AOymRIv7#p9)yslS-~Z1>qF zVZGz^C~-6%E3z=>1HraLPdZ(JSDvY_!Ej~5V=ZBSv+FB~>ukYofP-{ttM{ic2k0U& zGSG>?{i*DGaU77-Sc`3IYinX|PN`=?4L5@?9FghyB3W^=bff<>t9ACW!3` zEWxDCZ!9n1iA%P$wqOboiF+-`V$unt^I8GitIy`ZU+*T#a+TGS!%$HJ4dl{4vvMB-4eKWap#EEG&E z%qi$Py38q4dpUA^%=36==+``2y-#b1-bCm|Gewz?`0R+~ z7g*t^6Y{71_9rCorlvCw0Lb&Eap5Mp(7XKjVfVu3RG?tyHRpvbF^46ZtzKWw3s`(;vDELasoAD4d#?Qjh6O+Es5?%pl&d z;7USm$LXd10N*CGQoLFmi{;~CzueJ;y)UlAr=3=%y>vVqa)NQ)*1+W@`k}RCMzqiy zpWQ;f9E?2vy*+}p+$!EXVQl^C(~gL za^u-L&V`7`-?OW}A*e#0B;~`l6RI1Fwd(Ufu!ri(7xQ$Y3bio^!}?H3hGstaIz6FE zD7X$~5szCf!Bj^e3}$_}W#7taj}taETLTXFA9!_ zMk@pf_BneGKwb^@YtlHosY0!NMDf1nw~6a>W!zWCE0tfN`0MQZK!f>})O+rEI&|7@ z6-!T^J+9)gEd@qdzjA7mYSSKP`Aj}+q~gyEbp7mw!|0uzM1_)tXJWTA#QY}%)Y)&1 zj7SXlJwgr6aV%#`DHnd=c6Lo70-%Vg(S5Pbp$~P{KWODj5qR3NM2T;7vWSPf94!Dw?0jkiO+wJU4FH2Q*2AGZTk96^lm1{?m z)7Dy_jNWX6Mf0I8R|thfqn09LY$Q-ZjU99hhTIgmZ0FvDP}E)}ZaSTs1;fPj89m>% zHMAcBy26KJ!XaEZ;0~9=`WyKZiEdYghRs~$GkQAC2&>=OMhcGK@Z!+BQ~uAtA!qj{ zq@VQnYVRxHDiIQK7q1nvu(TZB6dW9dxV<5hz~a`6ET1imI`w{}dwKvr zCkmYJKuzP@z{_W{pW8Z^McPd83ZG5ff4<%SFzs_Gnc)K!)=DKP$k}ij5)u-MyX(52 zA65hMf&2qdIVo5cBz`x*zUlyzhlh8tLTFm59*~C47yCbca1?MPNuOX@83f1N4c(V~ zrGSBhBbPAqjW!e&Z)j+c&Q(GG0XPB!7K%5@G9~aD5})Z3_jbLM&C^Cpl#UH~sOZ|x zdpHZ3SH+;i$UHr(cxIex--pG)pJt)f>LYbeeNti( z12R(ACy^jW=_SH6Wt*NXVt#O2q>9TH=siLIXi3_=n+Y0IYvZgv-90(x)HYrY9QM#Rd+$#$uS>U}uv=P#V#4Ng&kUnpFq(xm|MXtpatx?2A-6El z0*jDCBNHh?_^eh6tOo5I^{-0vq{qMJydn96j?UwKkox(IAtj_Zr-DdouzCPz*5Rx{ zCY8k=>aNl2ce+D6F1F!&7#QThIITCKaHv6L`OtiBJAo*NKAoL&cmX%-BqmlSFN8@* zG8Fyyc-kC}h2+2R;CHNtnpyX&R|^ocZ;f=cjC-YW+NBi_M-G;!j^SYM>X6x;Wi+M= znGjILW`>(|g_)%F=RciW(=1E^53q>}Hyhf$@R2;WhoSma)7?A?=gc>`e-1Yvmx{U0 zu8$^&!TNZ);AY7vf$TS7vsh8C?GR?4$(LV-M$Z`Xv-)^JL332*K>6hlvf@p0K0d_N zhl`K{9Bg9DDB;KTF`yZV!IIy=t+ET`acQkPy1uE~)1S zJVv|4oS+7K4waLzQL=#Wh8Y>rVD#xuSEKOl`)iV%oSl-J!7=7fj3gA_j^R&P*c@RF zbnCjy^*qEE{Hk9mpuUqsc(hR8U2QYtmwXx5nffVWjnB1nEZY}ai`#=iw?*1Lzoxpn zkdVker3C+JC7-W{Nl5b9iCH(qLZp=#*F!g=TYSg4CpN)EGkn<2l$Y4yR0lvH-` z`%itky%E+Dpyl&sApk?qIFJ4{h%UH2+nkF+ysv)SoSTr#=Ig5MJJc+$BDwG*QB9Lq-t+Kn<`QY zi7F#xNYgPM|Ii^Fts5BNcUz71uCcKZkKOx6^ai`@jxvl=T6D+97bjSvwz0)6YN@(S z#~fo{D#S$mNsm^Vsp;3IEaW@xNgfbdy{|<2zaroPZ&I`#B|T*#@9WQFZ>^nZ<=nlw z2p(qMN(SOHj+zb6RpZge7KJFV`#$e;73^A` z6u4je-MYAip zl=T^yuG1zI{k|Dc3y+EvWmaj_YjlHtom}z>%*r!FGn?8wrn`yWvuPN-d#CmKz1FQp zz;Y93p6GkhuC&k0;a^HhT+bNee(h_nbtyej1WNA&Xs8N<@I@&glycVfIR3D#G$eWQ0I0Cr?Xzc*VFWlo^DN!-&q zrKR2*HRLp>DEI}I+{_OQTXe%we>oWFKj58PC z2A)3ju251nvGL-F7(E!h^exd~`?t35u&xdS;hQxXRkrJmCKgLjmokYb%D~V}()jq7d3Pugik%#o-jLAMt(XN-o@i&&B8V_8=FdIbEGUKqefJTT~ebjJgMp z>@G+@_{TAc9w?Vh^sA6(TYWLMMpf&beL^dS)S{3nSV`9Z)c&)Fg#ZO%hBN;lusX1&Cmc)>wz04Mf+#!qN1+1;lVczJXTuCwKm+p z!$|gWJsjYoF|gV1(iGRZm3va}@=%G{2)JOkSnRARj50P!^)P(>CUWf-G2$J6|9qk1 zRT0LypB378Fj>Ifa_1#j-;AE=;6sYBv#DR#v!-$CdKsKvMu>six4wA%3>{{O!bU+~ zi__iPn`<_E<>>6u@i8dutKhAHXXL&8)$o@Fzw<)*X8%Jz0*;cXJ3coS$KU@TpAmk@ z{n#;AC3%?+5&=-$+V0VTt8n7J`1NRJ$Oo5XgP=iYu6qV#`HKnhN_2hw4D+Zj`T0l$ z`*>?p0Re$WSAu``b{VpAAB|TF$b}wu{7Bzy(d(sHjIH>wSWdza&Q@rsW`R7RXfEPK zdK+eop2*spv*yZ)LKmcHRQ$Mg8CuKPT*hx{Y3oSwPlsAY2u_@M9U^8Xc3^#@K6iAG z)4^SbtqIVI;)FOBSpEL+5uQSYtQ)U<>XFm@&NUj37o)4&`9ps~xwa_*nfS=t>?in4 zLfy>{*e~2;snxa3&FBc~XCr@yUXZTBJ(wM-sIXR98i!B+0D>iO9qHJ_ZX%3l0r7RP z&NUk3ipe}EEuQxIHCdwyxa zhdt#@&SSSfJ+O0qTYYm5&nM|qWLT3Fn1`>Ut2?l+fP2r0MP?Eo>hOUI3~X@8a$~>c zsMZtjEr=R;f{2HR4=Q5j9@!RKnk==RZPF*`%u^sxxqn@dxf>yAlhXgYNghM(IhaR| zAK#E=2VWiX_g8R2XrMPLP8S$tQL$so%Tnrj_w-3t9y%popAMO!BT$(gm>cc1wPE<& z4?=!i$m1&}5!II@^$pEp7JZ+rHkqQrCoUD(Sz@?EW_1{xI>}GlFl=~3-zL^0Zbqa$ zr+tQ?!`!Xvl}AlYFBQ(?m&(ccu{}GxIPoSYAiyY*9gkz8tI$p9C87waveYZW5Y2Ku()Nb63PWqfY{ig>YHXpq8|ZoL zsHP`yCq^2v(|x0&kE`__*E4cm^}GFu^~1?w++0iJ#c%&g#APJ;!wmyG>W>cq31{Ji z46Hx_$$SwqBdtXuH3i|Xg6KRz6K!Ybrk5y1P&_2ONkGH=S)G+ZV;+5|TBUl2jIML~ zIGsqegpHWTnZ}uZXH-m?f>5f@FqOj#5xk$lphA@^TI`Wa{yF;#)97iN-uLP9{R!w0 zpn##xdYo`*STl9g({Hg8N1E9-tzrivpHq;Pg*}07IghJsQBt_sIT+! zHPtaMtK-E^e|Nk{Wpk@3rU?jcYo&Fy7A8er3RZRuOHaqlkdV0SLKRLlgI4Fz&Y)7I z@Qjysj3>)gSd3+Bq2@K2^2#32yd7rHtHD|hrYTU)-qhG*Hf(~SH$#$7XVZ}J!ogs= zyMtwWS7N|#0~3u*V`V}sr>jaJFaN5V!=&-RUVQd5-6c{NPL5mp3{6v|GhN|&`*Y4) z^$R_@37yw;@j1YT*_%=##5yB*!qWVG7##Le*N;^VQT;nl@5ucPdVd#U4jcPOr1{d- zhtAFYDmK>6Z!gcy_t7G_Bd5htrayMUk$N7`=(i|8z4d{Mg|DXeFM9F#IBt}9Onu~$ zR+i$|Bj70gf#$em$K0A5OiqFJ^$;5YjBp|1KCJ(?lDnsP5ZvNbdy-*F|525L(m?qI zZQN>KO5UWRGh?>^qorcM+J>Ty*7Q$E4>l@`q%zHDXD1X^)Zo?&Qanqn4az9oA?6jq zCbtyPu7RFcuL!sjo$kuZ8BXKc{ol|ZJ+(dlIoJs8lI12)VA|d??fi;xa(*thSZ8 zO5)s{r91pNK4g|azWwnD?fzh2iN(k8wj58DVrR#A(c)Js#vVB{&h^ZRiC95E&mm&d ziE`?)gj1C9Y7w<&xnW2{Fdj#JmFS++ujj*%s1##b{~!?1%bDQTP80)Dakpq|+d;%cA4`(76NTK7Bp!yjss|LAhz?LqxTJ>%2h1;v+ zMDi;=-uPVYM4a6ISrgBG1koS&SI1$wSy{;ZGDaUtCd(^^M=_GT2q?ek;z#$$-Ti)% z#NsI)A0aNG{L1gJkcmZSo_DUnE#<-4{d`M7r;!-%cii-ax(<^D%P-~G5@{9u(PnqB zn37Yys-53f(rZ<$-Bg>gjL$M#$UGM^4c z!f4@Y4QKHqS&T$G{T??FXNpEMeb;akllhwrM$cQ9;ObzRKGTwR#%I0;tXp_OLYrOq zw(D_`UE{^Yl^5%M(eca9;Xb1MpU`#}zEi_C+^=5@Oo2z+U(#yd+O(F&DIWK_wR@U) zxp`5nsG1d6dy@YE*)4T#%HCy~({vX?7Q+nflqUITSvZA-b4^xAnszIJU9vRGrYb6^ zc&yTq!P}STMa2s5NyxqMwj^l3|6+iLJQP5>lCw%_B_d6Vv7LHqM-{S{WRVRNREy0mX#U>{0 zn{~FaMtpI;yPi5_G^%Uan0pQKK)l$u^L=KIoHvXyP&WjYK^piMqt^yat{piYL58{% z0YeG9(Yz^~ZhQ(@qGlv@qqp-8W!NT&h zWUACY5clkCe7FJf)o(3AQN!E-J_39IH3Z1a3t>}uqSt` zrlhlqh&Z}CzAeKkuV!#k8PK|z{=vcH)0C54$uGJ++EYuf_0C&oQdP}-BMo!~a-Ei& z!V!?voIhj&3T8D1!K`mmA^`yAnF&U@yAo35EM@amCeZr&1}r`^w&I~uIf|{>@uYOa z)~2Fn5+S!%2M@O{TOB{erLgp(GZ)=~{_;Uw@};Va<@1YZLg5!jOhydOnEWuDeuv+SAH-GbH{uBBRuy(JAFb9OH}K zpU}YMQDfsVP(@5?&_{e^_v^c8q^u7oAxMq}iDjYUP;S|Kl))iX7laIkEieh?hY^~y@{KLP?yd1oq@KJ?{5RRH*I2#?j3#oJ>)W|~5So7j0GOUN(n zfv(Z|Q0@ICE+nj5o9nwT6|$v_RJrrs28Zq`T(&jx}!|A9XyT?DfepcEeqY*tscJW z5?V7Go^G0OwlXC@ zeezW0I<>|hdUto1?Hd(mI*D3Sws^3lv2#Dgd@F?@yI!48G#(e!dT$J7Qp0H~-+L;H zFh$&?vr{~7E*L7w-kU!uB#!I$LN)ActZa^{#WY8~3)nWSRG!Fp-WS0JLpZ4d{xt`c z6`^$S(zbU zr;xW4x59XXRg^6mwbE+CY`^OJlybH2;XuaYiKSVtZBUw(m6=RRj(NE|1#M;CG=q(U zWAYSuYq{7+2~r+4_B`oLzZGlGmd#4vy=`IWQzGgc|M~MhnGiNMc2__SiF`V~kQT-G zv#zUaG+J`&pWV$+QgXQUrI)|h@ADc`++qm37V0EGew^Qmuwkww~LGI@mG!@Vcsb%dF%Fi6;C7V{$2=eC=J7jFNEebrI89vK^% z+_~*VYK*|=5s54@+mcuHeN*IG#!K-}PY-I3TV!v;CsyWzG>P{8&kZohB&^6DBum)s z^>mp>hO`(l+ue3GgAAJMewyWQ$AXI*EnQ^ysW^qCNoQhpL!!s&{OakT z20pgcLicW}kY63-x#3ekIe2vgnLpmYs=KwdVoDLzk|J5H*M>NyEE8JJMkGcm2;S^Y zQ*bB*6wQa~0H^Kwbhorxvf(~W@Rpg@nS!Zut#ZCRs14Z&7kg3kc# z&N0`P^i2Tj$o|>RA2lgkJ`z5v+V7`jqOJN0BcH%^pYba$=C+rpoqR5AIUI@d(22hN{821*R4WU@+@<-f4J7_s(}gK|Uv z+Jy9<67ugfQlj@~%STd0^FoLMSyQVRTNFGeIMqpAu- zCS=A7-{#rg6_k_!AbP}T?41Q{g2hO1gI#Fc^DXm*`)uPE0*ZOPuIV2(3-U-l#emN z1LT^)j(s--GwlSUJ$L;X!~eGIOLGI z`rQTiFTAY+FD+dR2yQ|9?`Y_M{kig1Aq0C-3RjRL`ueYK2$#)j@~iG)gFGVwhc!Qd zJO0-Y_)k-ZMyjE_{F8Lfi{%CYYvFD6$Y=0W&vlUT6UH7HW%*Sd#YKK{a0Q1V zz&?PA376G=i3jMDQnw)rsU!QZt=&C+Vn$lHUo?p15AA;Hga-@x-LFk_g#m&Z$oGwH`+^pzl24WAazD&{_N7q!;6|fC{SHMhI;F23IDAz8GNTyYo(ri_dtpvWO z0=im@LdW`p-^(LhNEhFaNhGCw$&8IY;7BT$9vU1#yPYpJ!&1BY!tnKdku{N2VgHzV z67|pk&Mht3bM(>^a(NURTThJuGpl*{1A_|6<~DqDbcSopInFleRrZ(6k3?R`u7-&L zTf4Uz1E2beg|@VKB^Tb?+cN<}ti<))bvf+&{=~(>%lEO&28w4l-RgTc;`l$xw@%H6 z)`wKDlx8X9452SiC%l_@gqkEv*ZIlgnDvN?_EObnS;UyiVny7d_iVH$Sm|az#gg*t zZ2<{)=FnpnFZq-euiUVe!Bq0hP%XCAq~=`z54O6*LMAyoxy_RLl)6D@9lc9dp)pNA z6mvVPipFoFm642xQkh08wJM9My0m)nnls5WoOZu%DeF2V?TvbqCkGc)z4rV-399JM z%EiKwxLb>U1?U`z3?GaX49UPmY8*_YY)*L)%mM>X2BeJMu8zY?Dx`N#|Da_UE;li? zK*V%$Nli=bAL#CcqADybHD^uP-xDw?sw?;rX}ftLpvV6Gy`6;aytAw=t&yExhtOtI z&Dh}F;O6$+;I3C4vlo*E(1X~U&?aB1@0!+Bu>@y*VNF9ML{dXaP1Ec6*?_>X$wjuh zga&KM#*TZNC+~tpxP!l&^QCdFsGl2mkszHge1J$nBCYX8@G|DI$Gw+@9lH+@MrugV zc;R-8=Kd~tso@-IH7W+wF7cHP)@0XK_AAJ}Ydp~k-9m}$GXj$5+eGjiaWgy;V@zW!ze^C|v%IA5=BqHWd(DnLE93Smru%f8(SW;TCm_tM2Uc26KZt-Ot@{& z<@66uj!%nIO$*6`9FtWA&e6Z>Tm^sCv5lbh_DY_4W>sTvrbW4)*VLIdcF)Y+l$2{T zR~NXTc~Ndg-Xhuc)Z8_2RF1IMojv*LRi=tRV&P*-v#U+2jg;*1&%%zs|oS5ZcD;%FF9mq4>sA zqfOh8C_8+L`ZEXvJ3PpfS&jki_5S6URDvLIT>L*ORPVaaoh-vcRnE88JKWP9r|ZU-DPPOUDMg^$OXEj@>{89C79#MO7R)gH$`L3ISv|b@l~N4WJ1BLG6^3l(cnps-?$8*Vfi^E6aw0N?3NGnSxateJ?tFrL znD#&Xw|@qVTOc4e;5q+WL6p!!p0Byr#RdY1T#VctN+z?=0Y-v4G5(J&r`eTl-E*HN zw42?DlbLk_i#MYj_ugS3A|d_!%xG&*SG-0F6HTJX{7m6S7k)^#DR3 z@h=-wO!+a7&mnHGF{p*@+-%+ zIdpS+z_vhPAQ1Drzl;AljR)Cs!9|nuzZXqGJ@76Tl39$Tg*-nFJtxtlJzdpbqvK|W zQ;RBtV#@_0y#~za-xmcRanaZz$tG`U!K82CQ>%uKPwg^ZQ84nI>`%ZGk{$mK5qGXA zyU-FVu-eo;KzR>JRanT=F7C%m67|8TGK?e(~T2OHY>lMcA&-Wqco zEXdD_0a@373kD^COeB2z2f9^~CpqIQWRVCkW%0*V7&Ilp2VaAk^+c-;u70Dl#9zAP ze^jUxP&|{ZTe8&N2`EB!wJ^kG8#}F;`riCmEs(z1i(;;_>hh*c06$yke_JvCS6Sb$ z3<1>8fj%=dET8b{Hm<#Ev03|lOljs6~w#?Tp_?7S*I_IzX`c!`0+$hv}OEjqI@S^8?fEh6hd&7?{%{ju+U z?6cLwS3e9tE1qx9%}l#td4Ynte`pABnQ@6lmWQH;4a1E8uKfSrMW#WqW_@aG4J5#p z(+%hZ`y7F}x#$pBF&enfZ5xJw*Gwc!P#F^ zNeQ{}@$yr%vzS+r<#Uos-}ZM@+oY$a(Che2je6h+P~_4kSvhQ?@iv?%&E4;)6}yXu*VnHajMvsP=2;e zqp1sB?}hxk@?flGSS$k*ifkO@fU|M)w;znUucO@dxdE#bGY2e(xa?PA zvG=*n4nu!wf43)|vHg1M&1ytzJ5!oW>(JW2SS86BzZ|Mz$}~A^k5%_Welf9HPs!YJ zE2Vm|L3E(VZ?dT{lLxe7CE8Lear;CPyjLmR)!Vd-Ho5(x30DHm7;aw;Eol6~<2>A8 zjofsOi?743vk@0kn3VjAkbar2WpJKX^{!cQp@Od3K+CjQ zWx-zAwukf0qJmA8>RjnnWz8~!9e4Qi%UZ@Hr8=8x=P5Nk+0_>R7@qnVZQ4;4yJp1$ zkNvG$Z531cig@W&vWh=^R`jC^Un`la4Z^SqIqkTO%c>0MV?Wx`X4=kH8U!)EqaW2W zm)m}0+jCoizFG0PZfya6@zvQv@t~i#9bH>&y5Sk0ed)P_h2qDGZ19<)~QVan#>F8i_ULl7_7q>DZ_}f2%U~{ddHWw{$B7~aI znNFsnX{V>I$=e?iJ;VVE1PI6vyaGtv-Q9_xGyvlp6BHjS6kC|7scKEKVsF1Q(Mou< zV*VF1BI^x}7E?3B+{mcRWCZp-|8APrOCX6?58&0|o&?mnOsbRC;8PighOpdREe*OS zUiegsso{mmGXPrCP_@Q|Xd3^APV4&$T)4T}TpC+vfnEkkmjl0|YBT{mwx;ID9N;AB z{(IJdX&AmW8z-lPOo?9ueEc|wTJiZsgJHki9`w)&Wq$ap!DSx>45ZwOtRt=bkDvU& zKto)nuzjF*0#R@qt@+DJe_x+VX^r-6^6 z<R$PS*9=CYZE@{o`+afWo}_r>-2M>=gE5jVC6214EX2zZb3 zs*Qp@DXq`cYre)D9s!}GHb{U~-p=^*4T+6~6|H#ZxgW;PbJdMW^#jWZ7kJkHCq52& z)_kO|B|$JiqP~wGM!!>IlX&2rMuWq({e_E5$IU6Gtlzw*R)f)2fer&*t9txOlN&t< zUh7WeCCyW1pqm*ilpw&~i2PI-xjgc4uid}`CD`oRclT=Fd_FE|(6yQ;iyB+~8;H#g z*ZFRdu0Z~_ol9G~3ZqX*zpJdki+LbAmTD&m-p#Mqb z_7SSsGSE<6MUkd(J(!FCuSLhx1gOBM5fGd#-_4;7Sk=~3cH9DC(xyREut*T>Afg?F zCgm0Ep8j6MPQdvsuKjU$jO`3}b3#1H=qzAoB0o&uu+~~bW|NHF3lHV2#K3Wa8e@)wLUdek|7g($V-rup(kXC!4d+=(9sFdV0p(l?Dg; zMOtCoP4_Nj$lA3Lt8#MQXzMKktu-8*f(zM4Q1X_sKk+gm03~dAOr&TWlflVYfIp0y z3wZ|bwfpTi6_5NnyG2mn|1~)={`uhrSl)6GQBh&lZ7%@lmYBE@R$uf!Nf)DdvJvR> zd~Y|4s(!Fa0Wus07BN1{I40!f)A=$ko`5F{2pH=5L@M@9XrzSgz6aMRM=4vV z#zx_^D-048zVdvYJ)x%FKVUJQBjQYef%XApshYW-;8hDDL&KF$!o4D*Y0vP9Tz>{* zlvo5zs{Zvn<_i$F)FqYe4^{sJvWC?U_#NRxUlE6bu!Ni4`ePFd6NVHGueH=fzfX8K zCtaw({2oIGN5lYDAXs^V-71>@7Bx{F`^z>{j*g2QkbXjhJPTQ)@r7XKmv6R>asnb7 zjxw7)Y3QD-S%F(EtE6mNDVc~tI3l73Bh#oT2sfM%vDZtcPpN#XWQ6M$aEmV(vO{*N zv0cx1bCVDdX{WvxG1(M8C<_QVr;UDeb3lO_hD@0C{%mUGWZIxR_9eafUNicJgF)T@ zpWB1)V<*k4kBW+gTZIj&*Y19AzrGhFrWWM?m)WZa%B(-=@hmpqqNZEE(=d65cf!4- zp<@zlOWnrBiu^%_wS+sAwK&Z$kC z%0~sKK#&2={T;b2lDKa5j}hhp>U&Y$wru9ZcV4c@iBxBi8{&QH4D??gyQ2c>`jW5_ zI6hZP@ZINWbV&2WMO3zkS1WY8wlS-xscGnB(!G>M=Qvmj9eT2T&&HO&qu{`wUbuQ6f*GXJ2OtL zj{uHYU8B7yUbsx8l=QejT^?BOX7jNA{aUX3qj$}Z>W+|>p_)I+LP2Le>^cI_NIl=5 zO(w`s8F0oNPm$^TX_9-sw`vQdM2SC%RU6V|{dz|~+jB3m6?K83Cz~$-%(iF^7=}FJ z`vbliGW*k{4D{^|tW!6*UlQ=u9{&T}Og5u|r6XjB0MS~~Yn69XKi|O%F1QAUrBmzQ z^L31jyi!DAa&&eNY%XKZxaa9o`O4eW^s}b}{m4+!(bYL50du~o-!4PHo9l?l5ohT2 zBheoUx>ZFKMMrEad%w((@~UertHk4@f8KlOx`#&1tr*PlPCn7BcIX^@XVw=C@iS7% znrjdH&I3!=CAJY|u2p>?d5_h%akw321>$C zmKW;_s-$IQRaHOK?sJ*mmU?FJX9odZa$>cM&_8cdUSXff9tChQ0r@hmcOWO_rIC@5 zr;krZKov9WtJA(YRx?L1+bsBsn?P+_E+50{pxLt=Q(@Q|S)x`HX|n`p_}c&G^1i89 z?V0-K@;bhgTqPWaT#P(Jz+H7AK|5X8r|xE?`*`P11Q6>dVo+0{?dWIpICyOh<5Oz% z1fQyn=V~-#w7&{9Db4=E{(i0bxD9iR&uDI;uFEIjsr^0pNZdUbJ&PW%EdLSw3B4j@ z=;(ZaRPVpWd$38GHKpk2?i?IePo2?1{AY_S>w4|W(Z(jtSx8L2XPZysPnB=YKH(lA zH`yNOyQ3>>_(x<}muru2z3d|yM+(ZNJAvh!^g>()pQpgojOPd~WhCT;kywK-y<`fAO8U92Qg zI6Ap-4oi>o^o&9*P4Sc&?^+VGU8g)EtpT42dm%;B#_ay<#M`4Ivh2ZgSKQr1=GRxI zii@7!4V?<`ur)=#%<)G@0?&U-w-LKLMr-Zex3! z{=Eh-8{6cmUcUFZ)z_$xD^Jcw7iYF#7vZy?_SI-ZTOWjy2QZW}en!4hhJE4j+fK4vR<#Q4_|RNaUz z@Twu2(M+yF-%U;~G+oi3)9I-Bg3nMM5>xOhZBe`ba%*P9*o^kz+pq~G6(PNi4%Ozs z**Dv6{f!7W8yl5E4HpyovtjYH+D7DgYjcax!p`2gue^6xM?uSa*?w&-@@|k}TPYT( z=@r-3*Gw$meK^ohT4&?^Bjn*Nk2x>s&!r?d@Zdc8YYOQ+sidWa4fD_L9u-}MK`9Y> znC=VYv>oG#JPFyzNUJFe;F;5`YGCd>X856?5qoPiE%$5U)_&SF32^?qVp4q53WVWjtp3)1Mr+jh4uxc-*fq$ad1p=_x8Xk(NArt zj#s$h4l_;(TJ9H)j5`!ZsYxjfxM3hn6)Gh7-5wqu7Ceg^u?oAdQQTc#hXQylgJ^l8 zLQam>+qEZZP9&TZL(ebRap%`}+(P-(z(sSqvyGdjav$#b$DN-YLE0^z=*i4RF%AJY z8V6Y}rx%rX<_DbYoacKJqPR7?CKd(kMiM)&?ed9UDf{fw*j^9)IO(-%! zxOa?y2OLQNj0b5AREWYq3ea$5j^l4GaG@;vKYX04_5#Nha+i7*C}28AdogzLOSGFU zUS)JYKLS45s6v{Bl|y{;lwzsD7wJ7a)1R}(vIf^%f>HDAE^O8@L7$h6Iw_8qlWrM9 zgIxs~L;aXQa zKi*P*85;8D#Cf!V(D4C0fzOfn~p9wp(HG;;+OoS>{AM4a2iV~D? ztvL=F&1?o?1WatD>eu!L6SLLaEv+pR^;!KnN9g(aa=n~wa}Uj1_NOJ zZul`iLCkn+2{DDKWaaik#RNE=)`Y0kn{=s?bqv?gG8wd!9pXvqLFfrL)~f1iaI}X| zboULRr%X*wf)cy0EH>d&Mn))=0`f$ay&%v0wJ1sHPxr&vRezfgW9@gi-mX|l8u1pZ z->JWKKM%|T9J@Fb&wT%VF5ZgG?6)3X6;jQqdR(`HGm(k)_w*~+=)~9&V{JEhU>KfT zIVY@Oz~>IBA@JIN-D0K#vJ(RKz=C~(t>NhuUr*T8haal?-Kd4%MuQ>t52@4x6z7qWjSIRzD*UY>pw2^mc=&s z?wpvU;^UiLmi_{JP(;F3B%=3wbA_H5`dJe@D@I~3deiOE%;M+$O6l-ORn<3rvc;=k zNufYW(*^K?$HfTz{yW z&NlR81rrLf&)z@fp}{XAUp%M!?i zo^H%wqReYuoHimQEbUD~G8a8aJv;H_vshkeRTxT5mQ0(uPXu7$ikVbZv)t86>^>#fX~3zGTcUA5LyNf{Pe3E#2Ae(w zS`KHE!-$3z-rR;cu-YxS4x?tqjo*SMojXU{<0*Y`SO~qIFj7Qt=;gkdifG{HJc6M= z>tjM9L;oLBXBn2|@F(~APNlm;x;rJ$u>X6X zbNr%Qm%tM<^UmD&TEB(uxMR444bZASap4Tww~ZtWvYX-=2vcpsMn)%xOag|@1@?g< zA?#eF6YnJ29+hwywB`*~J70mS2;){@Plj89+-RW|-0#BN)IfwYvJ6Ik!uc9Sr8J?+ zF@|JK!{L-U;7Z*{;R$^_(%qS^^R*W_nt*WJB93J~W)TzPzzKVR58Jek-ANT-Vh#IM zz*%!VE6H7=KKWrM8dV%Q9DfZmA|SjFUpl5qK#crlrpe(emx7XbWlNeB+5O!_BoJAD z{@>FKUgI+S>w}hn@S4Zt`iKw{8}ko|)2UGn{J-6-UsT>r_==pP^Zmskelb*XYkhjH zU4Snaz+S4^)tiT`IX}O8XWiB*AaDymtv1xtgsfR(hU_8ialaY~eAO4sU~0}moS61z zN<&RTap-8iSBHk$69BI(`q5$=T{f&vn+II|@!|`jc;Yx3TJG0v;Jk|s)%&sTq82sA z9~`HzM%q^In4X|{NLVc_wSt zklr#e*~~Ckwv%xg4jPCg=IcDXcWba!-|SwL4Wju%l7SW-%t^u)juiI85x~W3vtF+K ziuApI*N^{GP*(19L_=CYBBv4S(ISLQ%jcKk?G+(QFxOvE3rz^#rI+RR+<|ynEf%En zBAK^(r$GZqT@@14z({mRktX6(W>sn>={$8LW{#J1f`S_~^;La!9^@XmbD<=hF&e1Y zpjwEErZlaN%|e42AUGb3fy>JI0$Ai!fY)ir?V8#A%A&?%Oi1jsF)QrTpRGNpw{EK@ z0=;81*B^>!^k;v3O!++=f0s}5;gUXiA*f0m4zVLDkWRhZHaJ+52^QsTShZ=|tzbc> z)a_`y0ds9dz_mUK4X#-U6e-;ueKPy|p2^*bjoX3I+^_1RFC3FeI{+l@cfJZJ2lir_ ztrRkVfJ#0m@?(435%F7)0T-Ajg>@NNljvOhmK~p;D_no1}PqSH9tP zIP~4AfN|k7?94{RpxjzZrVsfPmI)2Fp0DTCn|xU`mX8b{+E$Bb|#e(3+^dXa2zAUxphrUG_ zqJSPTFTHEyyH$I}>aH%dKRhp9p-4Gd_7C<8dZfC-CvmDt(xfk@Xyc{QD8LiX@W0W( zZF+%%C0NBt^785L)e0^vxq{=ve8~+82>5!-ft||Z&;g9@AYcQ>?+^2+xep5eUj=SO zc55qu&og4PoJPva`)oFr^~&@9(DeS)+N-ze!`IkSP^(tWlS%UVy!I3(3e^?+%&T+v zFgJ7jADYq(Cd^NQGwHVE+`@8I6+~~r_}^SyU3Ip&jYO6RM|2=a$9GP35zgUA2fl9h zykq~yq!a6LX}t(`XzJx_ixh9ec4&t~xjd%YemF_aY75zNm13BX=)vwWV&dU_Dy)Q} zJ}`4Z3t$8(LY`$u0LPjXt;9cRvOCylcY-|en9F(w7asMA3Z^l*KC@-dMUN3)fd(#5h&8+&F8)W{oy8F)YvZ3h}$H$QGbAyIDx z;Q;8npA&$x>c>^I-C{yOJLLy`hfJeU+(Mcnw1lr@<$Ey-Xyk9oLZlu-Gm4H?74h<< ztYn4b9Eud$BpRCOC;M%*;1k`1+?&HT8eGov2#P;qaOWXzGrhaEU<6R3ItT{xPJBYb zb(loPA$9X!%<_vZ^(3J0Ri1|TL>4EB2#RJN>>H%xv6 zY6ycwr?}rSRCdq8h<{{(&pmEZHmF{)Ud}j2K;H6j2AzOXSWR@$+E^CtjU*oV@2OUgKXQ9LP zGnVB_638Dg&@-*|HHw8Zs^p=%fM^9^ZU1-K?PK9J2|?Y?Y%CT9Q)G!*DAVWgs5`ij`0R5&rl4m%#Vm+`!J*@>_zdXGi_z`OBi{e^17`~~NYKM< z$s4Z%K5p)aJtOB(Ku_cU-Y12-Ag6u!TeA1QAQ=n@T45>WQhxn6*}*$t3O+q$E>b7P z!@~oI1>Y?E%Y!j!6rx2y@&&&yb$kSNJR~8`g|xBEI#+3`+IlH&`mfLqF-F4I9OPjAQ9Ey-~XG^ zLM#WBRuzexre|7dQ}gzm+UJ!iowWHYm(9X$tV2lAOl8&?+xFf?0!=yhl}?hK;zD$| z$l}+(BV0I_+SSb=7RBq@Y};`RkrwIdX?d0|cV$OzYpRdxQ+3IkaUA7G9_lJ-Tg}a9 zv_@LfVtncgyw)p`Jr8d&F<}r95jRo{o(Vk9q8Mvy>)F{^8)+YGAccVK6_Y6JA!9_r zL;sG#|AeW=emItAsa0b!@w#-OMzDCgR61{(&6;Aor#TlDGkKqFCZ&HX&h(CzeXo$) z;zNFjMbVe%FC^cW7tJWz75&P~v-m(Y)ZCvYjc@Da0AhicxE)1)vbtameaPRdm`b6o zDhw;#Qi&Q0wJ5$zbB=i`J|2H4<+5fvTCi@r!)CD5TIsO97z?B6rtNnT8%qDWu zphu9WD}bR5TwBa0dn#c>eAwB2uq0*&`eB4Ta=lR7!TEb>)T}ZR5vrPnEV@7n~Bm`?yr)f>==NZIEdC?~I_}WkYJ}<*o&oU4vy9V9FNs6+^b!=tct) z53oMsJRfzQw+AeYZ@@m1q#8RC=!UF)c6Q7r%hieRu8x%d88S{U|J`vJ;!w3Wv=S*P zeK`hl%X7-0;9$Y$%nZ{JqW||3y%Q2}fYgbCorQ?owMjy@wB;FqO!=(d@`XjNuqFm_ zCop-WL0#j&PKKleDrv_@xZky$FtlCG2!=*RiuG1HVD5s8?0de*=X!f^@F_E!)vsrX zk>vkG&rAO)(nb0x+BZ0Ko<%lNonTIPF&|U@r(7f_mq-UWDM-p()J&66^=u1 zOFq@U{;F0UJy5Jx_QT~1esZ}ML@iHxVXF8!tW7m}R(GDB$x`~SF_ooyk;R9B?7tId z-K{;3>GjsM**Ux>DmNVg{!B2#G>%?2RAiP9i%1R|gc5Pev=`%Mk(_ukf?Rh`qvk=4DN-Whnc!BPhzi zr9$il%ZaXb#&hXh9gl;LbRgMU^+$${a7{|dgZof-X)QUWEgJb3M5Jwyiv9Uo{DU6H zSTPqJ?qZ7q-XzAe-w_AW7O`h$a+?DWLGJTRhv^Z%<2wzLcd>O+O|C0TB6G2M{BT3- z%ITrtD{9$F#f5S1+~R?WMUJ@aI#oUV`Sh*E*k=*OkcU&uW}yX5xojwQMQu%Y1RzN3 ztsg$W|K|ghq>PMTS}VU?I(P0rZ{WxJ8B^Q655URN0A=4KEGc^Dm`2}=Q`>9JFV7Kow4xU65XLn-JKuu91+@i>Nnka$9fHZ>a9CbqEzTPqWHQ%EUhVf4H<|? z@?LY=Jh&s}*7^84Wy|s^MQ}?fhw|q@Nub&%UT5EIzi5B25}$W?Y9y(FneYOHzku*J z>bBkmgntJoQ2R4mn|+^n7WBm#g2u90)&z{C5KI+Kuhlf1kQS z8RMJ?1W<7Xw0VFzE*yZ=Ax`WOh9YqLBHiMQXjD1b?nd7q*637MtOwP$fa=ca>3C5eZ1l85cwsVOK!2nIBP3Sbz<@+7f*=?ytL56>C(Fc-aCTcmokk?o zUU>*Cuw{&`IWkSLIe0Twut;L6I; znVD^P2BdRzUe^nFz|ZVWf4s<7f?D+zXp3|)Sp6dg^sILVQ72oRE|i3hYxCmY;3wyS zEc7cA8iFnJxcT>ui}%m^$Pr%{hhnu-DVWY@ly|mHG5asxfhS?GAZaw%tbPSnwN@Dp ztHpKzg^Y_$*#5H02iS64I`ZfXnd>x!ZIhknJ0KrvAQ|dMANNNv&WN)PL1ZJ%n z_9&^8A%?QJ|6;zlzK$CS@2@ca%i7QgG1!kFw6U}68>f(drfX8v8@_&x$ZYoKqas2i zX5a0%PM-+N`3v(C;y;m~CH&|2`nYUX?AFFCe&%^T4Y=t0xoQwx;_Y&d9vfSa@$bhQ z={mZ;?h2r=HqONv=*Ac0lxJwo5Zw%@n@eWAfL#|BQrMo+ zQ)u(_z)ec(_}KU2a_`=Uhz~+`+>h0R&KVO2y^pPCbN(&9^mQDG{i8dZ`6TWqbMx6Y zBPQMyol0Bl-CyP?aZLAkNl6kIj%>O4rtjF;kb1FQak*SkL05m%WnAQMQs))$?J6CZ z2|cPkSLie4^)?Vrvn9(=MFM2)Q8{gbK)UAmd)AHG$k0$_rGHj>up6J3Hgu=AA9Apv z&%L@O9~I`|xQ42#uG$o4rlXVQN8L3@Bq5U6Gf7rXn-_a%&#|vkUWl)y(IS$?ic_Cf zYtfEE9x7JSb*WRODW|6{;SSK$lE%xPeRfT#>2F)?($goCE1NvZ8YAS=?unHxEah1y%)i|nxE5YoCR*;tKdv_(=7WGCZ45`xW z@(brY&z~3+{?3%q#)k8cQxsG@WcbTO%eb628O=u~qMe|zy@})c)}4hR^yt;8HhLS= zL+{+&r$lbcrjkVKl_t^Yq3JU1P-yWte+2UV5&2Luv(WI3`JN`# zu{R-S3T{PW|2Ygn?f1Cf#J;dLpW*lE}#$F%6Ln@VRJaUOBl$e&XMo0aHJWB}~iSDePP-CK8WznZTVz2H9LY`Pq!1VF=qd>rwJ!901 zZ;=&)g9D3KV88W36$L3-_P?tDIM9Iv5N)Wiz5Upq&M*Yi>DUC{J zig<=kVY3aQ8nG3aavpbowuwDOesxP`Wf3z)x`5_p6@|o5(uEyo>V?JZCfr{XX1hH$ zdc9U{Nt&Td;XtXB!or0y;P1#u_tzvCO*t#qYm0!89?Lkg(+>?Yvzd`wVQsH(UE&ut zL!8og>n=!@{3OgQO{tDY>hZv5!m|m*pCDmZ`c>iGyp#YA&c(s9)+ZxyicaghPqG`G zY{sh^NmW&Wf&o&~yu-~I4dmK#T!?=ZY<+NSnJX(Q7g1p;| zcTZ`*+`iWlYGvwdJC5AG$jK7|fu;i(>a;Yu!BR+paY<;5dQJ2*RAK4g*8?L5Ok|ZN zltv+uEd*^naw@G0HRjOo6*Hf_p3VhsHuTav%PjGetf+?T8!K!b95CYU^7&O>p}b}` z>PG?#P`}z}#R+o?zwKKOx9hmg*^tA*pP&9%6F(peJl#M8VsdQh>sSug3$w*m4=tMA zP0pkkZ}u!C^s0aMH~+IC@aqQoQK~*>ow5Hp+zKFCg8VLj3l2ep=Xg8@%_S;Z8wE{$ zX85WO+QVKym>`Q9Z14N>(>;CU`S0LC=RmYe%;EI0&r3vEvCzrEvBOdkA;0{bEbvWW zU^V1Io>uzX=Qn$MGlEMi_WDL&>1fZUE9$<4jGnw=^~y}d$KP9DoFl-*!dc5-Y3(UV zsy0re1m4o#?R2l_?BieOuaS{^x`Q#s8VrR0o^tTg%ghe;Kt;{;wNiQBmNAZxzX!R; zrLYjE!`?8w_Zh|6bZ&9v>2mR_<-j z2yQl&G~Vnk~dIn3Hi66;jng zC&K3I4sh6KHatMxg*G)ViToWynduQ|b+h%8SOmzY9FMu3u6P5Gi2MK&etmY#$oXss zA*81+IVUey5H#0Xnl9UCPuE#oJDDf%Hr4G9CMCC&xteZaCFBQsOBb9ni}UH|4os3h ze5FJ}Lb5%WW!VjrTO&W0VV`&04e9WFtU{MR7VFn)G6Q-OkS)}W0J^?mshqlXoC@z? zWvSny*D5FsbQFI(l6f;9PtDBRUwzYh2fsCKd9F7OGXgYpoW|I|f-m;faiHQ7dgFKy(m@VWnl$rV2 zQke0=e~HV$_E+CD{!FXMRxfDbUh?nR;S5AaW~7RU_OxeZ3IbSn*|W(SrPB?hGl7QP zFTld9X@ZP_{*k-l+_n9MUv*%yHcBe-OER+eIl%^1HxHb=oN)zCLb=T zz`;YmKlXPbtIKOBXtA}vbWbtFuhKH9?8xi>)HxUNmRi|X53Uj;{Ys$Oa`K(TY@#ll zfFfBT&@#8lz`_4x9BIcqb|nHm`WNKK$H(zX|K~%;*z@5oL)mI@W8MdXif~cu_rIbi z(j}GkbV<^52uuk2CG9#n<|5PgYF{$f`Qj#6|A1i}(vFj)7w_2CzBy}<6Wnncb`{l9 zJFDbu6lC4w?(G}c4w5#?k#_tp<(PM}4(*Fff5&mnDbC+*F5$Kzo_`~67_4Dq-lq8cdzUtmOumIL)#srU90FAgAcMt##Q zi67ip&(2gFI`=RUOeaP%YAq2({A``FHGS*r>+RP7zwnUEP>~c#rU8Q11v2zL>cy`B z2-@44FajH=quJBN(Ufx5HP$FE0b&o=%a46WI-9pXuQI{@i;)7Ix_>O^cg$$EG>R>6 zuDBGXTrPanJHMW)o`D~zgpoi$+EKjnQBiP(_B znOR}07(oj+!prNDMRTs!FUA)m#r0b7nn>qlZX|teo4YL|JsX0`_QuBHn@{kpZoQze zv1$BDG%6;q6u01n+1Ui8YzIKN8aH7g4EYrmp>2xGpog<1bFx zpv|&cJ;E>i!0mv__64)fVve+rfm&J>gbc!66aQjCbiJ(VOaAcHWd7h&bw+-+@5V&_ z<}IJWdxv{W!0&y9-|zmHYUh(D{d+9r^yer?d_nwtZUfY! zperAP7V@k7&(HF<4nG8pobcoVf5n@dP_JyedPX5*=ghWprUSRD4gg2=63=Eq0d~Xa(R-% zS!&ga5kxj+NWvN5e2Xj&EWr{$RAkl~N@@VecS_{Yv z(1@kv;lUTagEOp15Qv~mIJ&JideQ~+0uR8IOucwpaL^kti8D+bR z9YrpMWrodS0dTIy0>x`XAn^vGiNRQz!N}zBuN+c5l*OOgJbyYrGKwlH?p{y2kfOlzr8GIg`one!jq0V~A04SCb>z`JAnemJ9tS_9B{6v>^N4myV;X z|1!awc1)R4&f7`AnmoVReE>yuZ{W&EHO7S-qX{+~4Zs}1NWK3l)7GA5X_~@b^`J0(PpqiF-K-a1}fbQqu73(ql9#6 zibRIf;2_@n%KSlbta+wzd7I1m4~qj#?KLzpHXD-$6xW}qH*Wk8)@@IfcDZpp^gmU4 ze&`g&dDr%5H^DutpaC9{6q~pD`0O1^wV5I*yA}P)Tw~SfxpRlltJxxZe^;#GoZK$8 zMK`wF>;GF=8&E50S5NmqHpQ`qd4nI)@$w}un=w3;Cm|A^oSk z!~kUcfOPyBai&22U*#Jaj9;kfms0TTPG-FB@zjQX{?2orKekc40=X5HUl-(T!&`-( zgF8MoYm39Z`n}I)^XH?5ZW~@CQT!29jn~sl_q$E63TFGXXej%=MLZB|AA@4;lO^52 zc_sSv$p4GlG%N(e+&q!nJi1xUKJBHUmwg%ZElNAkFun5d@Ib8iQ7xJXI1H@lo0Hy`zc;s~5y^6w9FvAt*QjFlV-$qvc*Y|X^Pgejm>OJ1T; zNAPsB!&LEQn!v-2GpLa1_Ua&X`Ax*ly{}h>b-8Y5oqd=MDbF_jTYR?elty_t_}4@n z+MXtcAny!>nNYUg9r672;x#zX_*+5*rr^EB z4XW(u7>{qIMj`wl#h>8QqixAU$_f9n(=jozH{@`qkxhF884Hw>eVL^!q(^GdvglpY zChWp3Xe=8llouk<%*jNTQ7czg&l>KqGsT_~Z!@@5#N@bh_6mT1>P5qq5@ zSXgKR)mm8oMtgf350PZhUcvT8rF-`iciMiaZKt?BBDqsuWHS zf;RR?qPF{6I{t@^_orv~*dBKwQdZ^5C)dQ^GsXl@E=~i|GQHi$aQA+;|DJnx6}3l; zN=pU%f#@VMWA`BU-zL;L4M5>a(N@Bw(^NAVN}>brZLXXjzmLLd)d|!5G}G2!ezgQ~ zO5n%2y1Fu(Y31X8JP^KD+NQGKU3|6VZx1XmY6a z=e>}8_SY`}GY)O1qdb_#E-dN7vee{+v6km0`3W>4%`9?4g{kEF3y80QAEU3;C(H2S zXc2?2$w6F@YpJ9RWYUUpBO4Ut`x>lK5EEb`oqwjhS)D2GxkUG2*7qy03Bx3{eIe8v z9wQeF>e1ZKMNyDE>@-=dj)ML9Gc!AzUuznFSTb&V`$rj9SLVqvZ+?dTAbURH9Xth3 z4BVal-;tQ|mJYcL4EV3Lg!!N=Qxo@pl=5GQH1>9?#pf17lj-O*$}Bv2T@6E(^q1`4 zjoh`2+)di^@MTBnP%qJtk{URHKcR#{2-JnMAq6$?)FzqVYy5Ew;Hi6`w7%L!5TO!? zn5GF{Meer_bKNa367IKMYksBM$7WKmcgC-AZoKVm5!F+ycW$gfuCXc`cr&~8HT(BB5hx8A?-xVz}1+?Lg- zu_4);DQ6Pf)~+6B>{HLK4ZGXB<^ma3$wZq$65O=hgPOVfs801~) zhGqR_ej+(qYze(0B&_&6^ksu)dn{I7=np&I67bLo<_~+IVyTXQ=%49Rw~rC~Mlcol+1#-SLm1sUraEyb^`_{LgOp%K!Ccl2joBIC1nf(6X?O`Rh- z%qL7oUtdct`}|`nT3im2C<)z>;xoM`&>~G>eLjh6eAnFyV&{7eeK4gkKV}7!FL@aw2a%A|;!? zhtkvx#!5>_HibzSCZNebxkc=yNXw!RgtfUN;FuF?CoEydn1y6t=1n}=wN%&eX>)iZ z4b%)22CZM7vX8RPm8Z`T$~!2TyICzZBc$sgQBX)q%k_e$(CBbxC~i@kcsbF{)_%F-dLNU)N-NL&T)l5yVJA_OeQSKg5;y!J3+K zMxC~KREMIzQ|^=f`0ZN{WVHHt@gu&p9Va9qwK|#N6$g=xuuo$n*L>}M#Btjbk-${R zOP;{t*}Y0Me*R@Yl!nqZVy??PgS?zHl*+hPYa_R4SsDm!id$*rw0#Y&;4u zt7zNpr^xiq@$}j-JjHblPCV)TuY0)ux{vx;7cY>&!7g1+&2>8uyrWM#xt8Q(~IGF}7 zjp-x144$f>1M!B@RY+@h&rSby*f(DvbmXUdy?C#T$XR0@7%~0E%;ZameA_+x<=(Wn zZ%e9;OI3Ykm$kr@5LtMygtQb!sD9TScvBi6gHo;h6Pat6l9IA*QujN!*;1|Ax5EqU z23J0x%(U;Q?2l%N_NPnxKBJQrtC#ooZw}bnJb7-=xW1STj8FKTzQldm4p@oikWe_- zvx|%Gtx*Wks$#e%byoHrV_lCSdQ6SBo0LFYExK@CbT`_hy033)U4BCmCGiNqRrW6G zgy=KJ8g3P6#J+yIzgx*HoB=^<^5P;Q8+4yGZ(MHJ$rz<+<;7gz9?WZWO^IHStKj8G z6tvoG;agfB(()q~;q>>pR!QFpnxm<;ibwgrUT0^ev< zno*?jGddC)x~zKWm;X|-*Nw{mskO=VVZzzMlEsu&X10pH4b#oc+s03=izkPc&aFg<&;h7_^zzYJB~G0fk+EWOBSmO zGi^?%N6~No;|DfQUd0`g4F*)~>~BD(rY99&3NP#X$nbW);$t4ub>-23dwjp@YXjSFFcgcl+PO%8{C2u8vepwF z`bC_7=ztoVN-;=aP5E=UPPPU$E1@pyw%yb2ZI( z{9<>EknrF@nst4p`lqY zA3@x+Ezon*zpHF-1;pS%H0W6y6q!$j2RF`V(#{V}ml~DsT(BtkA=wv7e`ZmvyiLg# z-Dc$O)ym6k{?a1KTRXWw9QKV`l)V&YjdpRNtl=Z}ylupFQ66cBQ3EEZyt8z#!e9`c zO7>T1j;RIVy?94Q>mi-k(+Ag2wO^KCwb(5cW1ESrdN@Ykx1+3h1)4-e2n8Liv=LjD zXiZ4l(%IM4RGMeUIL907{`Rr_sb)6QRQgMH7`X6p)vL-#85mS?snvHlZFC+tj5-I_ z7Y}DJ+semz)5hP2M&dt>0BUL5PeTRLsMwK^Txo+-q7c83&c~BDLfa%46&1gi!f@G6 z=|myu@7^(^f-QiE@9pJS$THezdv{r6 z4%lQ7hCcl4D2YRJJf0(49=P{hg(b)4KgKBBO&CjF#$jYM>Fan<{Xr_|4;;}0?VDfv z+3N|JEwglTJ>J8qe(T=!JKr{#=zmSf?I2{P!5JK8IFl?M8G`y2o%CL7&a6f5x(AT= zMCw^19SLx7LTO^QvqbF}8Y~@pCa5Tot>w7(*4Lr&>G1tv0^ny7b@^QNCo7^t-u5(D zuP|2Kf~p&>l+;?)Faq|Tm_$PgV--F?=LSvFYH7LA>+)*V@MDcPInQrZ;or{Ra^h&Q zw)(h8b?Q&<*pj<*$Ax*kQ;M08xJc|Y@1Yd^UA#EZLbX`o&g(u3abt4VT$h$f=HBc{ zL|G>PHb1+ts$oVg7J-WZsN?gSroRSzj zpjBGaInhEI1`vLEG5W?+L_598$Mht2L#KUx-LIBMCD;sYxW4mg=M@>EFuJA~XN4&T zBc=9tIJ|4wpC5Xngad!ro##1!HeG3mVAOHTc=~S8)0zyk@aZSJgsP!6?Ku7Vmq}2yu2oAji1V8B zCR=;uv?_H}tGg3a4IO14IIJ>GPRVk}Dc8KSekOaUH3l-gCsV%ve1mJti?7)%K3u9tb2AV3dJE`?U>9rJ>AU8dp$ z<;h$lhN5B=rweNc()Np;J+o~2Ykp3bwOHH+23At^_c(+8-kFTijLo#@4f#g>8|LdGQVuhyW%PK-1FjW)$&_+Rfei}P$aSU<3ObG5t+9~?9gS}B!i|6s03+%{Dl zJUYtU<0VNRUGG1^tZo{mFqa17={WL}1rGL0^3ClX)9KKRz(?6^8UjKbqo-wPW;Ry$ zF*>hPL(+&F;=te}64$GP+cg(F05)?`HeDT$vp+rhbB2FG57K&Y0wQs}O+_P5$;(kA ziLqJ|@Rr7R6VHPNSYLg?5^BO%)oX4k@|5XJtPhjaw_0xYSL_mRxBZQdJ}~2F%`Z?f zq^T7G_W`nNv0U@X*oT%W;Jb?uiu-n{mcD_p-{>EGQ5p%E4I|%kvB5+ZXpqj=n=_Ve zd#lZ>OK5mN!Dz*Jv#|0+3CqixwYj^SB&4H*Pwa7PJ$bW=Z?=o4(8wYS`#fY$zGk~N z{J8iMPkY3N%Wi?|dVS=-xTq~6E*{$cneqc)sICR21|;v^7X5YT>!{d7@^7gEo{wSO zverO(sb$qpyuslB6sakjR7@cJEZz)BFiX;@*S9k}t!`IbPBn|{_wztD9hR4v`%F!Wk$_uSJif1z- zAuw3aJ1qF94At6deB{C$NEGd2mK_%Z^dZlj;nJ<4@88i?VO-YFFlm^49g40QC`A0Y zt5p3&qoGd%bR2=Yuv(y-$^TafFv|MRHb|9;cZX!e>5r~ts(9h0Z z|42@j3ut4SWP58gFg-*nJqDV^l}kh6VUHO&x^H=%3Ck12+h(RFbDYYtYo?||*Oh^x zb+o}&*zD`q(Hv+7PjVEoT-cb`;49cIt%rwv`E1* z+6_3=OI?xMT%RW0yywKTN3YB`$}F2AxO(FVkBB6!qQNN_?cF-&Lbf+!{k_rdbFh6H z8dWwbu*{aN464!RCa#lo?97$DhkB5{Vyh4&>SYwHHZ{CWDYS9CYEJ}SQ(AAWzb%_JiuM}{9O1vnBMj= z$6(Wf%p@u)9)avVUl^glTeM8Hpdjw>lM48hFkaDZGJ<^ho_3c3Y*$-$Y&;=y^xu%o zCJ26|_bt2kz#@d#pi>0EV9-Jn=zFRt=xh!~S!&FtQ3amvG4>`Okhuf)DlE){v6H`O z)L69M2pmc3zw9W~Twhy5r7~PxvjJ{@ZMoJk>EHxZr-1&#uyv46d9E@@lfvh8)qen{75b(S}x^ukilvqKvT+lRldZ`C&H>!L%=L0F*k5|9 z?U!I{R~PaS7_bwtrWeNo-^*xlG`BHaNtUrIk+K6Lu#cp2SFKzaWRcy^=K4lCn7;>C zq9GwW80d?n-@-C~_<)fl)cSNLO(5W^a;gVrh~+2kA?Sp9kessHz6lav4Z5$7XHbYe z?tu=HFa#{M2Ic7j7s%n2v$j}lS_MWOoi16czZct)2zi_o=vOHgKlUI?6q*+rf{4JL z^#0%#to{wuV+Slo^;#&z?@I_I0mr`9zR3R1Sy26sq_g%7{Psvn zSMVH zAvsII;iF3`+;LdU!I8UNismDh`8=&^zcY#Vlfz0aHWoQj!GrUId)WT;}{36+Qj>LT&rz#HX7Q!I4E;|Q&dxV5t1hy3&L^q z{e56i5N|jbDrB6TNY}d6I`Zj!eT_F}1U&Ze_1})Dr3M1Esv5MC!;hUhCdB2VqGNsV z=a|ADXF6C=8l9x9sPY5(uxwwSj@g!X8!*#TYxu=uBV4CplJYa4p&<4ji)}~A&i!!t znPDF+q7)61gjM&`{4C=LWWv+iY;8|ZE|=i&@!`S6q3~dbBn!-7R+3Qp$i1U~hm_eT+=J=Ji$s$75|`VZIhjLE zd@Z+VVL%Ui_xEozqp1+T+6T4LY69>m)!@o zn&4xoWILy2@%6}E6uxa}piUxBf^ZW?;~dj>aos}S)P6hrX_vA7YTw)o{rQt;aO%RQFdAi`4XSH}lv0p1N}>oKnE6?48qtr$M!NtZ?)Eeit!vzDOeC93>Ewqe38Za+0RQv8}R6-@tEE$L^c+GzIJ zZnUrhGq*3>rtbt(;LhW>n7^8kMt=Y*qY%rxN;TOUvoR<<^WzyVDLJ_SS$wXwZ*gGm z?}Y~DQ`vv0iBj-~Q!YLT8bAlbgRt=ENG#=3ewFtCC@cF_R+6;d)xnBI61*Bm_5rZV zj3hZ|daX(*c~zfSBcKOve+kXH`8FAuSM|rT2T0Y)D+zW4fuksj|gd$N@A{b zLVuc{)CIW>=_?|NxjA!Rc$HRrF1&sMyHB4!!3*F4z?}L-JSBN|u>OXrXiv!6=POx} z)R4|>@p4L6g-!%wxfFP>$5LDs79?xS=kW^5;{!3&7ujUmf8K6u0 z7aq|ltT-}%klf@-NJDa4sI!TA{?AEE1-ENk>&Se3gxW&1b0=d}&ITd)YJ%S;JK*U$|0 z>yK!lcX2DzZWQWU#$H}N3W;t5`FjQLpc9Ij%B*g=u zS^-s5tAu9&YAQPr2$xTi;=}Uhh zYaCuUZ3Btwg-!P@=ni^G>bFj>_Rg-QrWihyD>?jyWF66+OFfl+)Opk`Y|7nIEn@Ui zpTs(ENEvX&o>{f!x^czl{)T&dmHRc^)6P1+AX~F-(&iDU& zsf|PL^3JRH=y*u|;ZbgnmLE{d0(-;Xc7iI{Ou3#quV^t2xaA=OG=+;B7O=Ud%zk1a zT5Jj2=#~b)D8KS@rZM^31*HSC>o|>~B zro)Q8Uhl-^`?z|eLGoKZH1Kih8{GJc|F+F5!%iJ;FohR+y;mul5w5P19w3ZFs*)Tw}O)LW2BCUqt?${)Gquzw^hOi;@TsS`XI7 z*C#i-?{ji<$ZqY~A_xT{EPrwsJr3gGvKw>4UhD>vK*JdVnK4L|X6}u^3+(_Ihc~}S zaOKYbdhminhDnaCjkB{ph^NJ6aYYKaAR}Qmrlyoj4zkVe)xW7&#pN`F z|16&#@!@A`h1;=b3Os_ah6b*Wun!upJ0^`J4Y1`>R#jK`*oWusha7X*eLG=hlE09- zkBcG|d}U=0cqLIZ3dJO#9LTI%JbxsH{@cq}hsPH5hfa_zI4MQy=8-cfJ15rO#>E#3+EDhZOX-H6hiKxYSUrT>tzpKSItG}TwIIW zmgfUx17eV`v$4F;WtYDjI+E6}G??HG-D{5?=+OR=BB-qOw*nqT1X@k5@4%yF zrOdot+dX{6pd(98uCjz#0PY7G#WjiDd>+%Fib{~QP^kd|X}jh_7}-7EtR70*Csl2rwm~@)q)$0#6DvS~Gz}tEW?FJ(9tZ)VEh!Dx& zynQV0gSoGNeg!n>MNB-}j_sqP3cb%i3_kw^7%uVT(s&E0!RoB~GD6a9p^iaGSsARL z_(V@W_;huU2K^(SSGXU(u)$v914-ze{?@^#6BN!6H>ac52JdSlC55>X30YnE1DJoq$|G0kf^5sjzi-X$-<{i?*$`;uE3>BqV$ym%2CImU}EcIv0;UX0hrOMQROw^xsk z1Bc4wG@9*MU@r;34?7;RxZl{^+!BkZT_G#VH=(s?7E!;LLXysdP&4F`9|5}6%nw-_9{ z0T~o57`g=9?i$@xx%v5I0E>#Zizgu|iN^Tx{_fuYoYR99imFqdl$s3&@_%?b>!_-> zu8k`oAtl`)4bmYUBHa=K(jC&$aYVX1r9(oxyHlh~y1PR)on8glWCLS^xhTBSeLH?r8s*>IlM=CAeXmA9;Ba0p17FQBq5a>8l;SFxoBY;i_Dh zENP-ru87*&+U2et?+26FaKKvh4ZI2Fs;xsiz&d*Ghm<5_MA)lH6b?9!2KV*$Nm3@F z@aU4;HY8GTj_*fE1r1D1kwmc|)m4Xnt&k=IzTgOJ=29L~+<acn`R7@gVh7#eiy}3H&AA$l-3_=al5`&DNiy%}eXnHRuPihm*W2w1FjMDJ z?tg~=QffkZyd>B|NcCh5=Gi~7WoRU=<5Z2kQV3WP!+E6OML-*@S8jXN|9rR^XnYvr z6Nqjvd?f_I%{(y>IzxVW@>-ZoF(d@L2CNPsu)`O0uLe^VuYDpplv%e@5Qx+P@ zSl_E*?9K?r%ZdN;4RG8k_qffUWuyspZYK>4r}Fw14>AgPz2OLl2ALnl!Lmjpm-JdC zQ%$A0xj8edBqB7pKLi?dCvT6W8*Fv@$6Kb@57EH$xQohdz&x%wb;eT^>Nhp1E)Ye-znEszr5|WmmpBM;FmV6@fx|?7A zowoOPI0Zcu^>W zXjr>NAggSO7>WK%w@L8E)rxl*P>_UpJvEt!W^z4~gJ+b5juCX}2mQ!F zVEMMEYXG@%Zcj=&+V`h470zmBg99rE8-)B7P+Z)j56Uw}CA{&k1z)Zi9q1}LXDUFy zc55bZV0T}WD=7E)Aw$REOp_GHAnh*=sB%AHQ>>;CzU|%_NTt*PLr3+I40>mUzY5G% zTTp_#BI;c4(XOFGwv817ee5@`q*}h>kZ8`Y3qL(_H=gf!r(7K|Y=Hs%-{l?H7+YA- zdterlAMp$5|Hz8G-Y8_iOc(T^VgC(G8$=+36BC>ISRPzf(F|OZizB`hji?1WMYy%r zvmx2dTCR4qMBK+h?^Yj(7y2RJQnk+9<0kZMfA{x`*w~c1nx0jufU64u1;>9jkMjz) zI|TbH_qm1pHjek#_8=iBuia%`JpW5t!E0eM4;6G2TUl9oMnXd3dDc(M%E{^H)D%%( z>yUXDa?An!Dw9gwAS?Qop@+=#A6`#f$WWP3{O^V>2nuHAF2d0# zvM|ULlCDdJi!C;`8H_E6YqWh4{KKe43HA*J6QBF0G+G?rrcPUZV^|d4=eFsQ>D$z( z@x=MVQ`+$+xh`NY?xO8W%!HSNW|JpVsv3WmQ%ZvQ)EtU>sT7`3@DIlSO{f9p=|fhF z;^6LXF>&cBqm4|Fow+)aGtw=35EX$ZgLGiJ)aU1S`tfmN3FiMD)(fVUv#W=wx;i>u z(MixiP9U0iKgFpcM1qaLz{pi6YmqLCCD5 zpdP0x2p=o8BSS&12+l4>aig=v!|d1K#$fzIPbm&ekB`u6`?tW-l9`|HAG(c&4R&U$ zRG5wZASM%|`1uM@1>OxLltiVc{}X`}f*I@s)|b|LtL5iaG<;HVAV>sP&!o{j6D{^$ z78B`X9l|Gy8_NF{)Oi;-5m}WYki#DM_l!KDTV-OhvJUi^R54%vi%f)sghr<(dcZ~+ zd_Fn>^sUO0&LBh`Z#4Tm*L-$7OzeFC^tms9-ur(8X5+isL&NTwLPpG`_@*(C_$!~E zCo)@K$DuY+iPHO~qpKSv?4hQl90PK02f~hs^ko*gvhuRN$48L*c!;c$vfD7oMDO#5 z0ra}KbWeSP(~J29?Wlx{yns#z;+Hd#Io{7hmRaasHJ-7!@Nt>xdZEOFMb&64?q?Fe z84?Vo&uQ4BYJ>ecyfZ*EREO1Zm=5>$1v{vzs{WvJyIN>+K_|7bk@6gB>AqX98NmC5 z!#aGl(BMnL%q(@E#u-1WZreW8g9;J^Au%yOe*Dk}5z$&>G2(QYuK(3h+ONh-)7hhR z0Z2$x`#Zlh5dXPJ4~G-mx0uCMg%?8c==`=M(dTLWA2^ zefvIWoU1Bby07ibot&PF8T`D<4@yVqASXwBiTW|FSw?9LtNwV#8`*4_PDc03!>yB* z(|pKaapl9KQMS>_w#!Bj#AEvsu%$Ul?@v3r|0b|_=lVn6*1tvneK$NKA|e76Qo_nE zCj>`Fr^0(4q~kX<1{?%#qJij=#$tY;lyW3A-1okZ8p;Y+@C#g@t%?b-Hn(FXoGJ<= zF&2pHd)@{yU&t0B_(;BX1&GfWxVQl(0|^FTkPMT#ZPSwk1O#*s=0PUM4p@{homfVu z|5&FO7-mI+x*PP4Iqh?qj--Wy1~c)N`t0}Gb}lX~Y$xM1b>TacKPd%jJ+gyp(^lR8 zZVifFl`h(!P0}m%iuH8s14%Yi%!NCw=eH?~YtZud<)AJr`ok^yyTAzRv0Pd1gQZ>R z8rfQmz^Ldypa;>xY=a7P9?efzxL&{vCh?)}PZ!hIWvzGih@lX?#ZJ*lv!^|dByfxz z8N26oU^NFf3_c*lK{FY+%tX{Bu@KOX)Z^k5Nv*d5vxC+8sCarQV;5kuI82H2=o` zd-4ALU_p{lA<)&9m&B55GCt^Gcjejq<)4t4O3*gi_tz^|WpUz)cPo}(J2#N(zdNWX zX~uxUf#~!HbWV z+S3dkE9Ogvo2EYcYQRmDQqpaNhL>EjA`>}yVphT+ntc78B{s&nVmJ=#HOu$V_Jbbp=!RMN;XG0IViUSwBCn`;_L zu7>%MVVc))Fe>^Q@CQe8^w5j7D=<^IY$e{mham~1D%NY$P35o9aTH|MszLmVH{8P_ z{D2~>qQaQtpc`9)R@7%cBW94S9wC3SdrC zI*wO_z~kzAf6d0{eE4m(D}X^p7ZV4^KksvRh0C(8c)u(#AOxnjxR14L2Haga_|^Z; zg=mUz)2Zg-WV~2+r=UjZ{3cNm}7X&v%+%{qiMQR17hw4=2X8MN# zA2nJ%Z$g9jPX$R7=5s;<1M_(#R##b2AKHx`@nuGE$wn%V*YEG&Un7uqCf5|5Ox0y% ze|-p;skKf?I*?+7fCg3g!Zrdxw-esPt%vh+K%GS*78k_C*rB$qMmEt>Y+PL3@Y^-n z2a$&KI!wwmduVAnuND+^-1_*^)R=C5b~e--gWvfe=tS9uEYK&i zqZaV2Dk?;@ZZ{yGO=bal{F-g=&rdB+;)2xen(Fd)zhJCi-$wcXAs>Ul1-@;nEYivN z8AnC6;Ji+4XTKXPS5tlG00bd5Ev?-70lwbNV`XlN(NE0JaHz0mrt&LEYHDhQ+dj?p z(Ng%SQSVv`OI!QOb69)3Ws&RagYH^+=9zK}rHMzP>;^QucNv+?}3mdJ$Pbi-h}SUsDqt;vPdNDMU!ukRBs zxA*=gB(Wma7wyYuN>f=<6vDN(+L`zR&ybcF1Svvka5*;IUP=SBW}GeeoAR#R=ZLRqPf zfuae}r%Y2`q21q4C;kR}Ei zObx1IK2Ln`BJmF>MMFVBE&sadZZMf|VoGs2NC5W&uCseaOuNlnM1U-t++CY)tzTE*f-TK6}iKcyO>PDH78*FkM|H%2bS(K!+=Jf;Fur8-2y zPbZYOR|o#yg6>&(kV~59<2f$2=lk<`&W^3~W2f^qmb93|Jw`ZbW!72?qh{}f@3)tq zI!Bed`U{qQ!TPsyr`PIJ8^NWmvsnIRitzeSIE+?Nx3S1Xrv)9Bk`nVwesnC0#5#~< zkE=j-`_@up0%D@LJuuoHl<*f7WJj~FS$xDktZf?0(I^Rf-7;!R@kqWO#@bIP!RR}g z&uGoG?19@)DC-KCQpg_j)*)Y}M#f`~qY$BRL;bI4Fb_7MIXQvGIO;Kl5+;>IB)nh5 z>SqP%x-O`gH!(3)m>IsdQ0@Q5ks}r+&6lltpRhIfy^3{oB5*lNG$baGSolEEupM_O z`RD4oqVPN9v@Lv2JDyHT#Vllkr@vb)*cX*$W=7})lfHpy-7Ga;?JqFx5B^kro_doS z9FF4@XXW7F8y`P~>=J(Fs?e*hWRUfWNL)1$HFENYYLZrx;lo70r$@uLlB3|Is)q2!7bqyC%&(%1*b8vclza8tb!OG&zfw%n1YI;Gl#iVQ`#g*SC_sykBe4(2D&ognp*ktGT-WFGP{G-9 zCaexw$qruzyW3o-MtYzl8X3cy9Of|9J6S!fn!U;1Ehgrf`q>xz)BH#ktIg3b_DC_X zIx4VUh)0$`Wuk+=AW~H0?pDP+b2Wjj70i!+ciW^~xv{U}UM*5_h0C9_nso?_`Q}mm z)ucH`t*#ieD9VP@DA&ggR*}UhYNtw5^pQYDJy>eex>u|G2A9UP$R8P+PELPH0e^Lf zj?CepG4nm=tu-=o2U;3m`=2bRl%CeFr^(MF>xWd7)bIbY$K?z@5fL=ZdP4bhKiyTi zn#O>O3Ik-Q9y~7FyC!@i{8a-E>GC@n3HXJUX7O_BKJ>xw+(Z|Ik727_l2L4juvdpRCw5Mvp4!Ceu_ErmPZ*a6 zi^xtFxIB45(saK(UZeN|HF)Uqm=fF0Tu#0nZ!)V51#tMRgHA~^G6B;HbGR(Yo)!d4 z)z%P?^L9nyM?^C-vx9|(DpwDSH69<(ajcDt?m_dn zbVs@*{PzsK*N&V(;{ZW8iat#$!_;p4GW!yfyf&MDY@3^d0I*yL<6# zq$T*|1o3=-2~S~vI@FE1RIC4+4Si|a;2;XSkk+cX`RiuqTSRptT!!YX?=Chmh^NMQ|25P?>oaGxJ}gpXp<-pXDOf zNxJs)cMW8H&M66>$Vk7<+p*Mh<*+7JOpnZ*dn$7u!r**+YuHaA$!R?a_xz2>qZtcL z%!f(w`C%?sD(7U}WW|vjtt-2~!w`W%mUdr+D+~`B+-S3G`(vRY#wq^u+Hy-=WM1fQ zzw*DF0I=Ioyx41SD1#SJ!+DdkNXNy6+cs5-T0 zqIkPVaZ6cCtGRku)50@026jrA zQhv6+sTD=#R-9>3D1Y)>+n@_ewb0l0xK51jWrfCpZZdryH;qo_rDNE@BtlV9;a^)+ zf8=~irpLaa3R}7SqALKcD<)UAo0lKUE1-K0><;G;6%>dnVoK98zTP5H{v7UjxD%%# zqi(u9=uOP{iUg6DPtr(g&yIf)c7A@kO8eA4{Z=T@JD5~cv-U@SwhSlSvu9C%q{T#3 zF_*pWn1~M!qU^|@tK`dxEV?@>{rH{ z5H&J~S={h}wrVB|elZ>{JfgAO2L~i8duwkh**;=(RW>;vhKw0Wgf0_*6R_UnL?PkD ze+-Pwbdt3^n5~l>Y=Z1_xjP?HvllhQn4g}Wb<8HYEmzrLP@+GvzyxYDTp6n(To-Fi_Y<)n~ zbVQqv+EKUsgCgvCMFQj;cu!auIQ|$$ed{TNdYLMT&aS%W#C2b6D4A&@;7$f+^6mp~ zD5N~reykbYltx1dC`%?;`1Zd>gpTr)8&dblNJsnT@*tqy7w%x#dnS~{aHIbM6gB^s{t_<*r+qUmy% zV`lcemI{Yy1s+>Fn)M0{cvOPdVXR}yhljxv(LLYoVq#fj~@D-QC>8fEh&#IW(cu&TzG>h1s*2M|`I$f24fKgvj0H zINag1r$rt8NcdG>f6vqlaLv7ECIh-~%jx`H&l_)b7BE_f+WV34Sthty3@74UH7K#S5#hHc&A1r!|6wO^WfpB_a63 zAhpvnZ0CukkjmrTh-#VTkhr9=Rj(b?tV1Q%`gG*MS*(XH{CNL%=n3`K@yIe(q`_nW zx5o=6Vjyvhjk6s95bZbJ`E){e9UsRH7XLgSNGKKSjUQMeZ27WTi1=j-p8yTfG|Qfg z26s*~S)m zomL}t57!&J-k%{I@2mCNo|{`3g!)w>y+?HTlL3l4VtwMsPmho{-UFT_oVIf)3Aer1 zhttW9Of@~bGp=D|{FsFDQ#)zK3IptY>Uv(jk-|@5M}f6eT=H3>@Yk8%u=cBKyu?%$ zqjk1=V@BNNPj);rwRoj|&FO#zwYC!N%j3E63Q3H+z=AW&a$c5mK`(PZ%fJ?MX zDOq`}?!^+Iqx$o?wDJw8X9H;0Uuj7CL0pU-LxgAt+$D#T9E#F z`{--93<2^=9`1w- zKIY@fg_UIGTfPe@WX_97p)w?PU3P_d_xWZ`$%)o;E)KPXqOdc&t_?~KBru9Fa5z{k zR0pHvGisCPuma6B{-=YpZUR?8rSrq+o;B5gV@j%fmPMY`$-2JeQ=k%!cp^;mWfZc`YuC37!y;Z@<^((I z)iRoMXDg@JlUmiU)_U9JkhN;#7rTDQU)K*xBl6gI#MMMnP39~37e%_kg zRVkRS$8DDS#%`7R(pwhhUdBD|RlpwM9uMS}(`MulE88Su-J19IU?IgK@&bQT*tvvi`v6K^~DV z4`!L%|1Rh_Me{(9idlMDM>&1BDJ!Xu|lS6euP^i;HhR z$03eGH4f+*X}Y6tts+8^kV0Y#B~2E?DY_KbruAASTWFm8kP(`WG^#nf)b$|VWvvgy z61@SPPJuceU|Scuyf-DW)Bn21OB7D|i>!r(Iw==g{e_)6LBj+30sk`xS;*v51q~sm zMVQ^w8g^v!A#rT`5e^V{Nw$4`IQ2`|U)_7-<86fFcGjStsdZI)owdcy%1*xCGb&Y2 z1a%JeFD$@(PL}_0T&HKh(OyK68-=n{T)d_mus?14TEHjFi~arV!wMn6rNFn3@ba^> z+_KfB`Nlf_Y)l>{^8*RCy@_F^XlZju98tHsp4&*K!tXY_~HnSl^!<^PjVY)5u22YQ^<@pcDAcEvWU9yhRp9{*WpY{HvaI)SX6A*eN!!t9egXbQlTlfq8p?aL)T*^Wt%BBYbz}hA zppH&W^-SwQ=*5`Lc9b_4W*xvrke}jxsMY`gx`&@S9@81vXG_BKH$5w4x0+#smvEQUYl(*J4%5*64~ZG96-O1U~No`g?mG@oE? zsBXjHUgo-HncjyoTjxbKnOqe8YU$lwcBF+0-a2W#LmBb(z%MCDw1=&`82WlIvX(m( z5tSNv^Qq2fd0RBsmx+Ye8&xCvQk}jTM0|XFtQ_1-(L=iyV;pi67&f-Hv`2UO)gU4) z50eYCW6z#pqJOj!XuLQMqW;T}VZZNW=dKTx@We$zy|Tc39~v0Q9Y$!mesELBzjjs* zwcHHkC*+}dx*Xv!c`j6bPfPP=PHW2cD)a743)FEd)oTlYy}J_CQo#yTbw}WH+Ih`h zvav9T!S+JNh{Y$2O^_YUvsrhjY`9@@Rvj{ci)a3MkDMZ#2SRPjANjG|b=A=Izz*+f zBiU)6&7o73N=M4G;-JapsB`i+qHh2i_Va#$U${+YC$Dv^g0%CjO?_KL9(t*Gw0oYK z(ITYvyBxZDhd}kFA=Q<@w0CPp2EHsNh5HMuX(t>gH+;mmAm*8Gh49<)NrGL?2$m2t za8rrP4I))nSqILw{SlY*q$pk1zbY-t_Wu|d(Utniq31HcJVoMCM*xx(%0I0PI<(TC zJ^lSvox6HoRZ=r#*OJ;%h#%owo@J)>DF;=!QIAgB(sbneLx@z600F&rZk*70o1~4> zbLCRgkP1@1@hAIlmx6rL+{a5jwI%bTORa+1yPg_J$*&oCG*W4gbTpcrOM+7LwNr@= z%&me8CXnhS`U1*YCI?Ywc0TDZW#hJR`M(N~g`w3BQ=Dhul)6 zkOl+=WY9n^1A>VpaI(D5bKru=*O+`FjN#WwL3%|&vPC5@UFOA$EAi>1rhncky;S6m z%euQpE4feHxPGESpQpT$y+=zvX`$1ZLx3pZ9myzRVTT3m3fS68EFb`DUOoGvf{buy9^f3is! z$XkNwRq|U|Io&os>ahRjK~{2u3rzi1TZ=(}&?AU+P&Ts8&Zx5y^y6H0)p>Gm?xlC1 z-laCH%dgSOw>6ysZ_8@&84Q$+W(%2WZRYam=riI5ELhpNdd{4Pu&GUA(__%Abx>U! z@sK0CQ8@6GkwKTXF&Z4o1OGi>}3%4f6@5{PnTqFicXXr*n4|oSyGw ze4voVg_8~f5i=>I{I63Je|kE)?q1<|VA6Kwyu!qUCozHm_49KjtMha|e-$OABt?nz z^sKQ^kxLt$7FY%%S-?AyMv3Z!4WtiW)$zD|7aNiFMZmw(Rql@4?9nPoi?9vbF9}P2sNwfNO zmeRu7u%Y+lpYF=~4*iWFj3ic56r}Q)9R6*=L97;b&xe4~ z?rkbY#`nabW0aCnNjBntckahdx7=wJ{E${V22963oEIPhBq0t#T^t2D-!11{OjKB; zbi2d2a9#jBKF8;{q$^2Q+Z$y}qy!3Gng0oapyYv5n&|dV!ZEQ$dd@jm2CLWiMT$yw z!z`?S-g>7;kjjOHom{Ast?!q$eM?WkJ-uF~o4;!;NMev%5I=32;ZQ_o!!hEb_Ej81!~ZQJbl6 zxc_6Qz)ygW4-=Y@XQ$ojgOUT)ib=cVM;zB%(`P-BHzOD3Jpd~8A6wl457X?@xS77?{yU_o{zv} zxoltZeC2t@U1dAw#P+_vIu6gWXK7h&OTxHZQEY5N-3U{eCI7vSJP8@(I(o;c5b=ev z{+IR}IX*5Fy;x9)C0}nqV6U=S`?maCmXISDn($yoFVh#eDlJ2PGl$n$$afN;S#GiTVf zcfrye{TmZ$VWRygEVZ-FvnD|jk#grHSTXj=y^-{uC{rqB>96kpBHsyYaj_TVGOkZo|B;q(}(VZ&9 zWd3Otf2h1-Z`Tiz0E{R4jv@oE0Go(VsOXFN7Ypuz+VB6-a`xyt{~;>oX-2FL~@- zxgWlUQLwKJS{I-6t7k-Y6d>!sibgx==tcT6pk^sj(_GSWYA9lD(U7*T>7jx{$lz$yg2= z#jd^hBD?ERdrThMsJMhTnt`*2{`pmAMpDmSyyoMRh;zXG4yI$rkP)(GvR1a<@+0C) zG|T`kL;`!GKKXCn%t&ky>%kOm^=btUf49gew;P(;dK<~K<-C`LMzzCll`(bz5>zh=+@%3= z3vK;2MSKLw6;(NZdes0I+l)FOeRij{iDeHEKX~AnZS*s^26e|#LB06VIpO{0OVaaa z8r0q-=#bj|a|@E;}b^#l|tC>n<8F`L=4`#!*Z zQY1#Ufe9Q~A4$d7&UCb$o-fz$H%uZTq!g5e z+8<2Ffn4wYeXRGu6c(N$I=)5CyRC~vou`lC6*&?)S|BQvc1=nQEK=9CHrX1}%`d7C zrIVH)d1tdE+QXti>kf;4+`3ma$W%+L{o5WTm)G?Gf6#GK>2F;!YdNdLTF)A3P-l~( z*wS@}aoD1A1A1t>pnqfvf1~3bXctmtOoyFid1X^QvM%PFd2)h_%O=nm&lD>B~=Zc?dps0=nCGRIm#6&#Fw=)S zhZbVI>o+^#?J|TQ<(@q+trja^Q0{szW&S1bqS1vr`tpsrjxa+}0YGZgy3>SeZCGp; zzuFxO*_gIim6*+J1}@svf7HQgMH`Qqh;}Kf$D_{BF`kJRA4AT3iOkqa`tjkR!O3gs z{>JcOy)Q!a@?ttNuzk(?vDwW$?+-Cnui`6{J9J{8ZF;DfccBI595f~!)JbbqEQf8} zNJ3IfOgJ49Cda*5BPVp!aU{t6)?2l;0^QE|H8Cf1;eUIl;Pz74?&v zDIY@$*P84b%@P&YT%ESRvSG$$*7VDhPd4n_L8^Bsy0`S^(74#0ZxV|XvmnL8Hat29 zwi$!UdNY1rM?ccT9JWPK0p+{=3`l^4{C>9e{vTC|D}7`qC}I1?AIHJ12oaeCZ{qD( za(7=5g(i^iMt^_-0|p#ZwEPQ;N@X@W*Cc{(8mAkazQY=@P*Vjhv$V#t^gPnlRuG_lxd173N!SQ-nj>P;xY&-M#cBmVKZE5ZUmj9ozmwQidOxykM zOslus`^xcu@mN+_(}3sXc0hnj6IwP=Cl zW>C-x9R~jWOGmHVSnuJIO_}sDJ!97&=E1`6r&mOT8%uZaNl;MygmoPHEcn)rBMV|_ zWzUmNT9Tu``%f0f7;Xc8AtJAvPr^2><^^w3YNQ1P$=$=(z#iTL#06Ma^Cxn#^dK=2 z-d;YI#6H#4_5QU|0Wm>^P4~?=HVePWpA*ZBzRD93kTEJQx|9F*~~*& z^HR%h?Ch28+sj^I9bNEiN+SLD*H8_)8xB~V#LnFRCvQhKQ#a5vQFx-I>DkF)5gND~ z&UgxMEc|P6b~;O|>rA}U=e7h@nxDDPKxCj=bYgI=*8c9|F8PR55$MpfD}4 z=!+>cL-jl2K0n@YG=c{N9QlKhhJURR>iCdMU~=+{52hcl zgq}tE6O>)qMD$4$H}a7#TJ>!}lvKgs{~Y6=KuZHnLVSt`nScCS|ES4TKwKO$dI59SLgNl}yo`#x1 zjR_EvD!@=k^Fj?G_4ezddGbWx7p0V`3AjIFe=U4qfR7{$MpDVsXVe)n0{&azA(2&B zmx26qlKeeZ`g9MY-iVIw==IF8AJ0*S9POw@TmLS{nJH#a>=|QoS8HwTrs%ZsK86JOS7EHePco5Hskp9Y2Sh zxje=}2Q~l7O<8FFdRB6zFEElU=UZ5ja3#(qWNX8p$8l%oIfvQcE9SYMKYvQe$h`mZ zrShL|Vf&I~ySSJUC|a-t-7mXAucQwe8U;KI$E4u?0l+%u!Qmn3-DUl+Z3Alfh@W6% z+nKH+6IfWpp^g!b)!=(v@Lk{93h1M9eWEKYk*aq&CiQeV_KB^+0>bB@!y_B>#m2Xw z4_6>{MCj_~CjEVDZG(-tW(cvlICRor2KxnvhIf5?4qNW_*Hk=sV}y!=0;VefwF{W@ zj1@b{I%-z@l=A|foK-TB8XP1j#6f5oa(%O%p@--*J|Qf8trtch z_;T7t8(K^I=R`FtvsGrD*UfGZG|W_~B5S#!^>#wfrSW2;VjQ2}p&$9mGcZ)!+9KD5 z&zIW|;IcK0S9kOyF`qYB?5pvV=%LqH&#zQlfDxE^1{|`*IU^-R+=dMCWjQycs*z8L zPa{A|wr{aSQ@Gyg^wWyy;TX5DNmpqai~x^H1H+Wg_CeUt!9~ZiTMK98^njCF3(?M% zgo%#k7OrktrOmFjYO%T#Q61f0$b~WlxX-(}#@z?zC3MR;ytW%vZ9~5h{gi76|9xST z`Gj*PI;+qa}zDKE4F`X*0kJ(*{6*l;EOL~K3()5p zUQ`x>Pngoa&<)1~I&K-5!W*fmoGzvc4QbYj?W>jOzyB*gG-Mzhixgj1S@~X8TnD+( zQmwF*U`l0pyPMsxf%Ei>yhUq6F16-_@u1UBxf{?^{dnZ#PObqrItGxv3^D9hfiBoQ~P6%|32MZALfz^v3Dq)pX47>!vSWH?H5u zX^&Z^r{1!DdkeAEs4JD~&xno9;i04s4hg}e@Lml6A$9HR`iO$agNuZUdZ7+_{|Vh7 z;40_^<-bss{&$-GAgnO( z9}!lpYWyQV)O#23AbbG)@-#)|cYbbeaG1dFV*z0f4y@hthua!UsqIEi`-U&ApBGU| zWd|C+J~S4j{8YYr11bEfUV241C7+|HI}&bR5jjt@V|) z_RvI>f#9W#V0OAYQAKOBP@hE?eg6qcs3asLG^EMDCM>F+rzrtaNU?65UigfhZ z$Fw?pqGMWzo1!oEz$8C!YYq#_l!AjN6SuQ#4{bCM$x`K(o7`ki?{f;-zYTp7lpa{Q zq4nvqW51^A^zG_#WZ$4N%r+d&wUy+w{Xn)v%5VSXKCXZampEiyq#9EF&^)-3X~ZM3 zkF)`K`7-kX<%d&7rhA{A-_`e8->Vyq6C^SC7ahVoJP*rAnt$vIiKuGX#3oU58_BB$ zdz>iEc_q!=$j=|(O1dH=WO=5L)F#7!Ecfkrd@CS&y%#WkRgUK*`ij&Q<v+chX# zqx5ffsiB)d#=iZ7`u<+XCJ{b;Cvze6OG2=sa}t`7&yUF<)47``?x^Msh8Y%X7rSG8VHWA6Q*5u6 z8FZ%$QXAb>)%7YE1(TmDnTNR9MUdydlK5_&@K@^DOZ;zc>Qb}rV57)~XJ(bsSP_X< z@;dIuTBFp)CUaQ^+=de(F1yyU%|uL4HuXWT63zrkO8Hr($Ws<(TD0m3eE=4l7Bk&qGmTN3HQw=_p66xkyy=ikk{$FE|??1Kb} zd?zZ*FffRGud6D8Lh_!1LnX95n+u5Kos8Col@~R-l@?X1iA@%z3O(MWCs6P8Qf^ss zW;fUj4m3tr7hiMO_w0v@vCB=qolv*lMeG@)9FW)=_SzjKwU zta8YR((z)d!sUvzk{8pzrKeUBW%jj_@m*?@C(^b`tlcBZ><44p?kM>$>@*59Hur+; zH0f43t_GHALLM(M*z)`T@X7yGj6HNrKB!y^Zp#qz(45iM4g}vNP6iNkm#O#0Rdsd1 zBHUOlH`SO6Uc~YHOqN7?pUSeC6I*tA3Xf#OCA%s z_wg^a(O$m99Ggi^mOk4Ck>@{ODJEoPtukEfJ=l*t%AV{DYzF(4q|}P2=;SQ4Zv>k9cV}uV8vE2Lx)_q`L`!7!HF9LnBVXfz?qbXL~;6mX9jf{#lYh*yu zH?d^C`*j;QBZzjP#F&o*i$UTr=#TCHp2QD_!FUC_zk%*1RtI@Xv{x%Vdkhvm9= zaB#5a-{u0sg3x{A6Vv6!2;hmeqrKxwFSzb^kmWWz`QE1Qeci@QeWXg9Hm)+_kP~2#?pc`bdb}MT9-aZ+VTkJL$%LJWvHHR>b4U|w7MX3dSX~~87h1Ghqg+V{ z)XPBoLo)FGRf%ivo^_)PLp_g9)^%Q~WT){4u6I>An7a@C2NF*taTEri{Wa8X|Ig5< zuV$)STJD23CzAe=hNgkpw(|1!BSCkV5h z2q(J}O0>p$S~-LsS11`ZdC+fOyvSYxEiB9RJ7B^Hc`>CEnS(1UUC%ax?NHxUJU1x??>iYo2b>Ak`vET&U>M`3WEbXs0lC$8We zk$p0>f?a^y+pE#yUIV?lI;()^!G58esYFX&xJU~`s<~n?eN;e=@q`BU<^b^JaHC565!gHqVceGAJ8S0tS#+)Y{hyPl8}r&KCvLY$ zgR}gegs>^zG*Jw=#Wo5Y%kRBuRnI#Lb^E=H^JzF`a?{OLS^c!7xiL3_R9 zrL#bUlFfML^{GeTzVpuh-g>2>8?ba5c6KA-v+6&?)T=_f{F%Y;!p!Ghwr#^INOyO4H%Ll%3DQG% zcZYO$BVB@YNOyNgcL@yL49&Nn_xs6O!_uXD?YZykI?v;HzGp~NiHT1~6@L4thE(dM zbi!+RmbIYO>Rab{=;Y=!$dg{B^~anV_#%icu~{-=UdZII;}@>FBFAB6X?Nm&T~s8x zy%9g3)C}!uw3?gwW5T=M?xL)EZSJTXU#c6x#6#{ z&#PzgV>r3?HQQJ^k3RNKU&Y;=!f+d3PS?EGbu$3-l`k@T7?iP)0^l$+(3AS&>xSQs zH&^O1?x6G72JUK;=DlQHHt$M$Tn8(|_qYy&0tB)}_#OW9d{r^H8Og zb!?w5?Xl_k+v1l~LzCch5P$E+I5Fz=HJ0-1@!)^|5If7BR)?u zIIr6rs4`MP--u5bv=EYm-ghv1wt$CzU5YWlg+q6{LBQ%=oQ@8zAr?kGoQ?&KEC3LK32-(scK<)eJA=Y z04$ZSe(Ea8&6iy04c)Y$EbYJVY;_>%r&)vaai{m(qTAG`x-BL$$eR+oZ{Bb26~Eh) z`dvHhI}diL02lway!4x`P24&^;8Tx$jF8ZiuF*`V1!6&Yq}Yfs(^q`yn5ELs^7Qyp+AFW{R-MjrgsFk(!U+<|9LJftI$)5^efo< z{oJ>9&GURcb|~UxZ1sDe^i#Dr(0(rD3&&o;-S_VX{=BA%%rA&ft68;9wY_&I zsV=zX#~q5wp9uRr>Msl(-$n7*Y{_zG9e6b@@Cc45SooFjR`hfAM`Wi5`#XHerA}qC zQzbPta3&NcYwd1$1ike_`Z~k}yndIRro%C{&cPm1w5qRYnS;UioH;V*7(wI&)NFD`cmvl&{B4{~n zZa?;nr2*`Oarxdi*P~VJjW##&=IRZV8dc&Ky&Kxb3uB;>-Cp2w$kO+j?#mbLx|AiC zR1*v&q_I=k%5#K(?F3c{HZ5Q= zgg?Y^e3s(Lyfh=cY_>P2q~cO{-NQC5o-9_1vvPEuaF@sGxCQi< z8TA7g)S#NKX9RbkdV3lTrIbg+ zMv0=AFZ?ytZ;h)n88VtEQ!RY^dAYp_#bnTurQXUM_c`5<@?{p-UUlLNxze?HoY1t1 z{r_yANV7_8JPk)?n$D-^HzWs0Ie4q9z%|>k;Bgs46Yx}phanS0ZRTlIRbJhBh~yhg z$7unK0sH;+zP|74Pv<`XD8*nvw;a&tXJeZW@5)5MU!7xf`|CPy`@qRGp$LGhy5Q6o z$<{coUOri5O7^N$eZiQ@Q%k`DLLx*)>J`qv*if186n4%5`o=Xw z;z!k)*%^A5XN$?)P~i>lWMrLIDI*j;o?m|Sob8EgFnA{?JI>ZCOEhaM04Jx~n*uob z{RiqkuhNBsM+j|B=S6vb+{p+aiz5+=>e>4UhX6M?_Q$4f$cr+lF9?mu5$R1d9Nm=* zZ}>I7-)&7})=q8na{xeYTb@ClE%%e2?fjml>v3W9FCr07$R5l2;*bHL9sAI-=y%8z z{-Zy~6&~27Ml`fH{)oGz$cK#?{iCM{PWT|cNcxi{XR)_NHh?@>jkJH*wj58kI_aZhh)3h zT2y!h1QdLHcqAkwcwfAc#QKV`xaFM>6a)$8?cjbkkc6{^|smnh9*j~WU#CHNQP;dUr zU0wo1Go$)!4(uqTf-#g_?+II?MDW5W0649-srIV{ENq+16kVED676(u?^Xb68{{XZ$LDw8y59c&JbO$OG1nasfX<{QLu+++ZC2Y*-`7;7>y*S&795V+dod$Q zB=}Eyj+}^W78A>pNEd)y6v5opxjrIbVR!occc!kO{f~=?Av>7aZ4A#e$t*)c)JN{_?F|Zs4MOu4Z;o}9(9wr&xBv#y zI-`CVNq9D!MLj^mx_f_SoY->%Ia@;onA??Gx4(Vjk^qE7k^K!n8@G*Ykl}|Xl(Q%R zLs4jyY+eR}Slph?p}vOQ_Vkdy&KCFyynK81A=(U(x!S6!e~(~oZ2W`f$FBnbVBj%_ zi`1lj)~7}_QabQ8WvIZj{;Ak4r@OmyyAp)u^k&h}=PP)^zMcz+dW-Y(m9ENSMzfSL zM4T8XXZ&Q8oh>ErN6EB(p=A-mU&J;S7hzb|co0RnQ4GEA|Iq{Bp&-P}az|$!Ru0Ba zd#bKJpZGzjovUQcjabilL6nqTvWkw5E#5{xw%?LB2Qmf84@k;8Ev~|Vhl`SpOU0J3 zU8r2CgH_L;oc!alYqlp=k4NNxXUZ5LP6a&Y3=>h*H?x0d(2npmv8flLn-v3}ws-t) ziT`5yJ>6q;d#MBcD=4ajSjtl#3)^4s|^-`O%;EZL(JwlTkt{J7@`o6}=)qWRH*!aXCkq zT2fiEH7)UDk@L5F=XiLZgIYgroCUyABKjuY(rVwo-pYuEMihm3W#RNRZ;3mK##|OPS5^;cMf?HkrS}&c~jSWsAi8C|dlE^&k6tN9A<%@a_NoV?PL2CU630%+TwN3Hl+A{v*iNaB~a?P^OmaWyIJl&r}m&m6j z*|iYK<1qbb3Pi~&;~vVU=aeO4!L-tljw6aQ2zW-9Mj=vtfmCF}@dcK~5)^`H1ptOW zQD9(T)R&%rvT8G3$wKxA>ni^}wlyGe`Mr8rwLN`&C?TQZUuiiT5x`Hm(C+vZ&h#qd z-#oCzj6!Zc3z*zvO=JoX`rMzBQ#UJ@wJtDf_RKFMQ4T9ekijPbul%T%&q|MFrKMpm zhjK8#*)%z-$#zYtjFqLy=-582K~4u+IW7J}i;^=ePsh*-Z;SSdf*0CxK@%J3-b{FP zRNusuv(XqFt_lZhTG*Ykm|fax9p*m2MYJ!Lkr;T|R(8iV-g1ExWtJPQk_-QBkeL9- zUvU{K*AiH#O#v+UbsZ=5{ep*T8NR%TqVQLznlfk9=DxyTaC{^YoK8+uGmIo$L&_N; zDOf#y6e2|)wyS%^@*uomWv#j_N)7~3@O%w zoUX1Gzr#4-Z|Zom2qIcJ4F5N=#VdC)$L$K>7K>AEgk{G^dPK7VINP18QH*Gn(@wH< zL0Vm=UzmP7DSU$M)Sz+H9mZ|}?nd#n8EE_x9S2>-PmDm(^ z`0p%^k5v*uL0tTF09qsnphA)yh+qZwo>pTLdccT>$M&52xr!axO|!`8oj17ra&(bn z!nWvPJ#PVP0nph!IQZC5!~<~yP35vCB=4T40)Fns>tJ>~cJqaJsFQDedA3DY=}Rd0+0wwA!-Fz43LmE(1sYaULGxwCr$nJh? z>3g%F2~3Lm2^e(SjYA*)S#c`@uLLjJDs9fg>Cmd+R5T<9IXO9;Fl3TAs|NEO=f5X} z*8ZqUw(Q0aj(mUXrYWIH3k6GUET%U*`Q)b`>-q3;0&x@xjWo*>jmLMH@a@+h_NAmg z^s~E(QptVsOy2o1NTZk)VaxL9I`By>D5OO-6w;p)!_{St_d;vR|nsx~<7S>Y)#(v}F^>96( z!4wv7r;`g@uvyHP-Du-e-fbuY!}IX9=2PMyKck$h&3mSVuxv_s5)kggzcJr^S@FDL`DYt| zdLcAGS5K_FlC|>`OGpvQi0bW5y~kjlFo}bs>Z_bOXyu(Yoy`lCjwvs!_JGgr_<)w7 zGs97{#l+$(UTtQc#)#2^o*7(~Jm$n}9G@c^3=?_yH5CKCLoF3qPouBu#rMj&LJ-!% z5w*AV!3CgH1oH6M)jF3`VzB5BWIlKr;Z5i9qPFSi@a1V=8(vG^|6nQ8YumoQK}5sD zqk{RnERX8s|9F@nwjZ7Nok@9Rse#>u0riO!afR0*Dqa7rKfFbJE!pQi4vp$}tqj}u z2xuG*U>ZBr5vqpfarLRMP^s5%4aM{9c2GZWrB#xGo)#O3KOo%)hwdwC|kQ#cS5@wyiX5ozJ3PkK+-81uE)fFvn}#0cTMsIjTA7! zJJ^<5Y|1_Ph2?sTTdo-RJyNe~>4>RGBfdX^p@5pT^xER$!|n}hZCKjl3q2$nD_M5v ztD#E1Cppl1j|0-G_G?0xTNDHQd_FL|B$vOgU#@6Lm$I{WU3xlcpu&;*4)gqI7ER`c z2;5Onz-txhmhg>MYdAcv3ad$~M$+A9U;*Xsh2Nn7mpekiVZV2jIAsaD<|ou$$B0Uo z()4yjGVP#$jZP~;8V-)-awCoglTKV}g+^4avA>iiv@j99)zQqirGcQ5h9h-N1#5{! z^jfnu9w*!7@5%9)&AU!gDGmWI=ulwE3D#5G;Y8t_#lMgGog~sh!E(3jLM72Y!uZ5c zu*d`D$-l5lSa%@Wa|f|UZ7d@Dg-OwXrbS9h(7*+W*Za!-7Q7>`yGzWdo@7o*yo=6%T_pN4f9 z;DvEQWalg>PM)|`Ep}UE`J5_!1AFw^awDBEX~~kRP1qG05d2-=m9qr3p~Whaa&n-C zODZH-C9QHryyE(#seLFO(;PPr5NQwp)ZC)r&fGV=YLIjU2(sx}J+QdkERzQ{Tx?Wi z=Xd$+B`xD4!9=}PW-cm<9VJqx64K7OCtkkQm^o={hH_|zTqrOabwoBVQf2f4e` zpRW;u76~JbM|f;JzdXC_0j5Ja+x+|v1bLNN2?a5rtx)ag) z!DjzId^L8LH-*{YD<2>T254w7(Bnz4qk1wjIW2Y&Hpm43d7PgKh*cfT)n8+&kuR8d z(27HG*t>TSR9^GYN&W|f%|-G?^eA$LRn4vXrwemPZ(rpWi9h*$Kd^i zRzXp55OFXFsg;L{=+thl(53zU;lBNX&HPkL@SB&4P!a4^V5g5tI+Bd?bdr#{?Lehg zT~zgV#I_{^5fb=lNU&?X3nC})r;yOF2xzEbV7Jns>9DNT)aTLoHa(heuP9~Pj6$3* z9_F**eTP;plivdj0Un0p2;-L@v@nh;W*d|Z9)KpYkX>8>f~1@=0s*gO47N>FgN>sv zENbJ81ob6LC<(Y|I9?jSYWduqMhd?^6=`gcsx}^Is>ZZBsYbq-^8 zD5c>GW=pG>yQ)>84Bk|rY_OQ6UcQ~rw_;9!`3N&Jw#Nw!_L}*-3yATpo@HkE_*tYF z8DZC3(;0P2-P~Lq9}-B3Yo0BwHV^=M{c!Rx)>y-4;Db@^?21@(9C7GRi(r`Jhw7nb@KPr$*OB&Cr59p$tbZnP35BDwlZ@6Qb8ty>o%k;$ zF}%93Ru&epi2TB7t{q6RF_%ZMr1`S_VV06>lf$z~Q|Z)x?xiORRkCW9mNtmEaNhxi zBfPu|XPdt_gPeZfm313`TjAjoL`G3Dv^*0&5|VCD;N{wJ$eJ5n-N~O6c&G$Xk zJl|KFvy7ldOix+v#iWwMxwNdq*G!Z@Jek_igASFRy+Fdk62`co70zol{df8JkNRjOc{e%<%FR)a9FrM!b%wiT;qe z-DuC;=6)KY-C%C=FM%A6rl98*ypHcqrG;Smnl0zBI$v%nFGcHt$!Jt(?V=Ul4Iqw6zxfU{_$v`pn|V!OvA}P(7ECrDhmML z&Z{~Eg+}8<{e$nA!j6-lDGY6k7|r5zVDYcIqr{okg3HVCDJb278y{&=C3bY)qhT$x zX+)Fy(5-%Z>$-DxMy+p%>Lui=7;!iI>p@Ws4xZ)wGvIzgAKy-&0!Ej08-B2WGQGcU z22-om72bO33E9P5SxTjcoCEC9ZvoLi74gTCj(_Y=ZyFx=$dh^Q*m{R^CP{g%4^Vh6 zUI%#4t2jSMG6+f zT#2C^H??++$--I{SzWzPNXcY4PBd?`Gf`u#=ihT*;(2C0h78~*6%P*?Jl`<0P-~T2 zx|jk94BoTqo=2?S`S_|L^RehLupG?antG%C z>RVcZB_>)mdDWpUE-|B^e&qmKIdzsh##}awG5Ys4q&h;|o@{^sk^0kKN?W`xe(HuY zufxV*<&hej-4oMJ;jPaloWFPR?fGVFFd=Vsks_fDxz+>q6+hrA35u)!$m8JclDD%2 zFphuorpC&`MaT9%s1~6U;sRw0t{npw8jh!B#a})bB$l&{^zP43yL7JyUV=U3)1+Ul zk=&n7G4?`Iq_wMeZ6$p4yU{Rioc3UF(q}F;a zxkUh+0um9QL@~}3Rj}RP6#5n|sgr0xC(q}({lWe5yl7{CFZ;Xs=!X-l!3q_b#8kjrM2>w4OJLy~21s*j8wWY6**FUER-(g@v z`!<`9QAjsg-yG2JlQ%5ANlKYahX6ePhmfhtb~dSLu1QgMzU=O^@XG^f?L4P6fNf^ul=yy-FnXAucUji#miJ8&d7}TmQ?|q-yH?a)8Shd#kcOAn#tpjvmX=jJLc7JA= zUl8x~8WtTxq0n0-odMM31;NXpPIt`SUh+#y^xh>w<@GVWUrHbhnTF^ZkG6vrK{h%# zc^uYWByZ()D)a}Mst8}jlO$lZl3u_4!^=n4$8m@_I*sSy-;MGt_&Lkc#4Na{f3Vtm z{kgt>GN+nZ#Pw_i1?W{9-Y{5X3*3Ce;yl%`nd%HMgxi zNUJiO9Yb)xcQ0FNM0tY*yumB#r#ummu4#-|rC%@`!8d{$!npj)P z+&ipv>WyWDFf1hZ(5q-ur5sMFsT%K28_tw{1aEk-b%KEtRt@cXrx3f%tS{Kc+8B_C z-F^`KPy@=PfSf0Yis#GkXUjv}prONN#yfb$j<&B&a)Q$z16ooON4rv^wK+>oh8jPQ zbVB~Vw+gm!TViyYwv@0@>|%B5o#{iTcE&Z1)3b5@9W-y&)UDZsDUnI|uTzyeX@6UG znFvOfmzSq4>c#3_e9Mff)?jO{4XH~cKlXjb7JmI_f`>=gKhr0sXTm(caCOIPU@|_$ zOogU@KX&!{@9d2E)0?0BBN;!B&bq<6V-IuksZPDiFV?37RZzE;iLHkdy4m2qz##46 z1OO$g5UVWZ`kIbc5Td z$h`Z9Jz$w86h=m7!qej!@khlk)v8ECu5z+d!eA5$42Ru4sAvp|pElU?Kp|t`-ia<3 zSo12K-tXx7V-uO2nk{~yb|2Vh$Qfo)#`%^DT5Ge5k)26st7(rjSp^0P3D*p7v4wn= zCyL`e7q6z~;ClUz`UDe`mLXfK>qaq1X*jh)Gtu)3!}|^#6%cT#%u#HyXR5`=!O`IO zs?TCvxHr>j;}P?d?PGe4fjCW04U>0T=^P|zxCS+U&!rz`CI9VJcE)TQy>or}^5Ero ziZFZoD@?r(rdvzexJ!#rOe3GPMEbP$1r%g-S7afwldnAbrYGT`uy+|Nm=3}yykUw zmY@-^=i9aU8Qbz07gwg%43M-)S&{gw$2$*5ZVTi2-J>e;_m_ z^?x9b?B%yyZ(#>YA)5QNDCUQWq3;*(Z#4>u`Pu#tT`&YQe?};FAhxulq~eQzY}kH5 z7>2DS8wKaHI`@9}yXK5JW9eey$t^11*?kC9#rsn0k=diA>kNyv>IKq!lP?iG1n@RC+fjxL?ayNw@m*}W;H~oO zP|yieQ!DCu5M>J)56CRLS%!ZC({!~%;<^8o+U3MCk z78m!Q{OWR+`l1YitnH<|fL^8PdZ@xLO2$vOR)Dn)qdGWN=0PEcA6xqMWv1DJ^%lH| zs*Iq2w(q@Vv=D98GpYOv3@kJ;RuV#h zyhU6zgo4SO_!}02f{#7-Yoezzv%&Gb_aYklU^JQH!Q0#D1M|)4W5dYQXm4(gfTT@k zdwY1s12y<)CoV@=VEd%qz*N0VvsqXu!<|0u+Fm6J8oxzP$m{2+l!EUIK9EgJ{>X<2 zZ`u=@Cmg$-&lZEn84-gC3iLBxqmoOtU68@KYApt$ z!!x;Y6#GGlj6t`baN~)&fcy=8I{1sqsePl3$JLG5+3_DS-=ie<-wuXihs#(rz%M!I zplDAY+?JWZp-P*`Dzb8bd1mmIsO<|{$rA#0u?u%Guv zdMPQE{3YAk<0V7Hq(Q{MO-OVPCpLdkEV;VI(HmvBXJG;PC$F8!t1Cr9w=?<>8 zg4s&~iDkA56!uU`bKIngOV$ztvi}jQK<%zWMTe}C1T{YgIfvyOm6I|c%!7_O9L`evN!B07e-TC96Mvfo6AP+gd{X?(8{N{DK@xYfi*jFdd zJkn3%;!Xowqz5?6Mh+(72}QTQS0XUo{nF?dcudB&=UZa|`%l$lfRhX6mZ=)Zlcf?l zWdidVoG!aSD^JNM;)sJ-QCITr0 z{zLnHNyNi`R&WLzPYdHuD8-hA$aJT7e-cI10f-NkAc^2e{}G^ZR1AxY9_JZM+C=_e zgu8;)A$cQWNAxEWQjE&p^Mz4~-6u{zOgzHOzd|?MK<3v3Yj6PPP5M=Arei+0x4F zqm2zA9zOnD@FLnB!BPk@erBSntA4vb2+6Tb4t=AhVr2XETzWMxUZj7!H#i}~5kLMW z+6UviNhBZf@4&i5Fma~@t8iHJ-pFh}9)~3w%ISWi|FmS0!|f z{6-N+!XH&Y8J;Y95ce4o*AQ?I!=b>l%vQYvdO}UG$MO{!*y<$NF)OxMUUozc&(gN6 zF0eTuos}!gGHJF;_l2eSmCkS8JxR1mzK@Lli)@Gb@`h#m?iVisUR?ZJg*KV6uYXG< zim7NQ8JUs8QBH#i;29k9l47x?`#%}3H4(SDoy5DJ5wSV#N|3kknNvqXj|6vwQ(R`u z73yT@MyMV$0_hi%f19n0(0iMH?UL?fq^u6M%iAG5t|V1<{Q2e^q*n{+28lajoDQOl(ax!TeBA6av(?5-06gfBZ(;d?64_7dD6>d5 z`so3>8=4d% z{gVertJz2hki|#nw;autMv|Q^^=1yIRq4q<=EqMmc9>*O+b3@RgJBE%J;6~X#r{dg zjdB@nSj!P_zo(d61hbnAxM>h;wVIZr!C&aQQ#SQl`>KgY1Um#gA@HEs?R54Nm(w?c zc|5%+Yk4KjzU4FQa7_e1ArF+X7lpKgwz~WbioKLN(gJb*5JV&fXWD2lv3Y}KCNjQ= zx<4eho5;mfVH)yu+wR}*?fRZVgjn(0 z#vZe#x}=V>-zWz#6jC}n?uBe@4Ge7u`%DjX%d3pcLKhgZ_hV$8za=ilF4Z$ zvpUVjHKGj%|C(-13`NxS1KBABj>a*EPGgfG(#pEox?Krp$mY);W~sg5Sj;Mc3yhUy zl};}p>(X#&EK8MPcyxW&GW0gDS-q`ejY1Qb7owptSfwa9#u$%*udthO;YI@eOHiy| zte~WX{!{MvokeSu>O-T4FEIHO&`-qT$ys+he@@8kP1gq&8VMti1VS&>5(`69hXM4P zKT*tG*4|rkJtW{3tcu5NEQm8{fb!e~8vUxp5>uOiNt=+J7L?J#mxxWpLKBwD*?B1& z%UZB021Pkyx$Bb)%6qy$`iz>F{@l;x2)&p3%IKxDrzcGkT|knmN4X? zK5?jZX65rx(txP596!r1GxEcO3)F8|7(`-2og{*Og7UXWH!f+qt9%JBZjL&~{j;qr_TPzhmeI1Jh`>7giOO6*yQ zZIrc5Pydv90V_RRB9={z%BPffZ#;o2jN;MI5bA^dOmTA+UviSN75U;r8mTdb@W-pa z6xW#U`G8qsuEUUHwL_+?;esPi(_MQn>^BL>+xX?-N~UCw1eY}YOg%hwM9IM+Bs669 znVG=jqFY$f>AOY2-gD-s)L@;T6};g&f4E;zNqX;WR_GWgJ4}J#R^R6{MafUUNEKz3 zaZHp~miZD!9H2;IGwe8Vir3pmVu`ABw2h|G1ODla_f8x&usQ7bn$C3&*0PkwNvNA96mK4+=w@I=m*&x@zxsM@IO4AGbMWe z;S&7o!eke{^-%;J4pLg%+sg`N_Tivi3|OcGY3hG=CK$WZxna4oE)%%|^3XR@!xDR3 z_y|BS|3^WcFn*9~-$*3Q-oNd)U^W`josM20^jyt0U276G9VMT(W;ffoCz zX;)A}^KHdB7%g2|O2T+K-v}%*r05j{hDdyhwsC)^M^y6dwRiCCbkvc<#1!}R#u-jy zvqj@lQwtVm?gxD2EmT+61l;x!sKSrwBkLumOXshB+{(J*P&`$-{fkpdNMXZH!PdwG z+gpG&R`~V7gsU8+8}oU#+{J>un;U;Ji{F}!W{;ZTINs{=xq{7M@LQtnu^%Qp0P~HH zit{}iIYo*;0A=Qeyaa`32>LN)@u>XAg$@t!zYL&Y%O&LYQia*AzIu-^+@?Q$0S0cFjW9 zVGy#e3%Me@2xK!I3qD-$^Z=gc`~V!l^6LQTMr7dI#kjuPgb{EH;}(6bud^+y3cd(< zK*{raCbhJ-jOI{-g5qEx0B)nNP=xE_q$j~XW6S9q7d;=>^V4zSt1E!CHpB5Ea1*CJ z=6*xop^bu#+szer<1ihHA)xl>{5eXuIKTAO`aphXikT^!zzqxE_U2$pQb&h?NRtIK zFc04f-J@ni>=0+C=! zc{-Mb)+&G)msN7koItDIump{ZoHodjv)vR?z(Po_GewH^!q>>KslJJfTr+#MQE*v0 zcMq$>FKSp881NM1LfARfMYM_R=74S?g{C}G_yIx?w%qde_Dm|ngax^#qM_;?SjPi6W9Z!m(txTIz2f=;^<8PL zfRUzah;Xqc9#!~7B$0h+pa+h|_3Q?2i!|VgeATTUXkb*-%77JMcwrY0H>UejZy8zj zR#GSmT=jA=?qe+q%43jcXKVtANmzYC9-j73f*|P|+**!xDjVI;Z1aDzOoAKcqCJ+l zF{Ev^u6#0)qwsVf{Is-X$&MK!7AX94+^ZD^q%m1sO2vI<1%ns%UH+exh)QkEF2G`ZmoEy5-%A>dfJ2r}w?8|=x0blTR^%c}9D}UX zh`fHVgF7Cw9Yv8gf;|_-h_=V{0KKQ%Y*yBGbl!-DXMZnbR^OWXb{ilG z_Ht>w{~+B;D61d;jcUXRW0O@Yrk)*?>l2(b7MGk)MZQCU|K~nK*ez}Z1xqSSh7O-R z7zUY2TuD5}dBTj=^4j(0M`k2@lXvO`?WAzBApZ>m1~fp@`H|k-Y>d*ZgWFE%)Z+-` zoD4}gQo}OBOPlUf>Ws?TGlQ8gn>qFa{rZhda7y z*Nv&hWIwm%cF3C6dbl}$RV}w)|AYa5Whw|e+b}hKQ9F`HisKp-r&c!daPu0!WxTj< zb-8kc?4g{s-q{oE3#^Tm2Yl&}`Sf{3URG$ig@xndLb*F-0??*mX?a@N2`03<&qV2#&2iXQlCG!%djFCi>rQB9SE{3#wG03ecNi^bU`6O z!`d+nyxa52>zr<1TFLR-{G%z~=?srsqu=)j(4z=gcE1FoAX>Kw1$E*2U6Pe5XNCss4==IEp7ZVpBc>NwF8I2=r;8j1N!JBBh>(%l<5BUkEgv0is>!xBW?BCXbUS3fX8g|(Vsyp8d8>AM^5DZFRbS_VSVo0Qhs9|C0(5rMBvIPb7r z4|zro@pw)b-<;8G7;PrpoW-<3$W4z=#ygy|%Vd!d!!9iiSFR zD{1c<|7r5jV7n9Y9#g^+JoJLC+t^W~iq@d*iX!kfbI7ZAfB7?*!c_SVkcQdP?ZjAcv?$4LuE29qm9u-T(=Z zV*gzYs1s!fDvVAH%`EU}w5FvS-TOvU{#CUpEmz3fcpw4YH~}}luw5A&mEaP9&U_rCj&c!O)89E_ zfrXjWW|s-u#g)?15-y@DYw*9bK3#8DdxV$y5&#c)IX&?1xPRO|(Btu%V^t2tB$f+} zDpaF%4w4$q4>0uk z(XH~EM1~Q{>`%d~COt6&a9X_@w8-1Z;D|j%!1Vxb>tZ$Sxe7VutXKlpx7iPn^!I@a z|93)(5C*;UGCHbO#v2sJYBH|725awL5+ZBIVrE0KshM(X$PbPTM??C>yt zPvpv#6=Idq1jL;5l)szJP|r7|hvKzcl3a9i!}VsX^%aM&9w@~gWz+rG!%Z9?|9;PO z=N1rF#D&1XE$|`5t~}FWCZOxcDfTm2a-`| za@D@|0@IXCEBm=hDf4RowAJ*Ha3#Avkzdo^AiO$_#obnu4&IgJh);bf?;Jhr*IsLe zx2S&fE(lGK2iZWS%gVZg4x$X+whVSPyWbr6m@_Ef-tz*wJy~vojtnWC z92oRx)YUb{Eci+^gwe1f9uaZisg02oCKXD4+WcLi5~QO~le3eilBR>7{H|A8DQS9S z$MTtBu+$UHT4<@jYtTu!23s^jcfE~f8cc*1df`1r3nYMX! zqm15?jBDBo<<`wr^)~6;jVt3jF4HT}Z_uSF(@n<{YD%`fy*{|U+E65X_t4>V=}$gwlcXSu9dD|DT)pY4(Yt8Alu?) z%h9J@U}25@o>CXAv}v|y<0fWehUOvzmPM3r_p=6d1B3W$9v5LX?>wigt%#fc!ZLix zWM=B@cnii`Bd2f8kTyI_Ieee$1ak0!al{}iUvDYgtpCGI=Mu9&H^Q6`F0 zrNcTOPai)6>d2sK#z))wQ`+hh&@it~o>2p2t|c<4?^8&)lwsrS0$G6nSIjc?$t1{}G{%yN1T zLS$j(Y~fhtt_Lh~a%L5bklnil4}<=I8VXg|PNw{K+P3?Y-&&eP$<=(YLf$H(-4~Y) zd9O&iuemt?C?N%`-7grxq(!{vZG+E^2R%N;s&90aXQt-^rfqK9{Opfdy~&rR$I%{J zKak&JQ$6M0&dzK(GyFfuCV~7XAo>H7X_d$NfzFf3xj!4fp#(cH*X_2CTp+o$ zP_+-yDb5bKeeSEs7i28$JpEyc!QXk zQZLc`OMdbs=v)5ZhKBMUGdYC6;(}XQ?YyIt36-h0NvBl^dZk|({(Iu-40w?9HER4d z7|k~IEfZcj{~TwM`d$Y5K8vG<3p|FZAa`2`@EOjJBoZK>7P@+!GlB+CBS+-EEwqvHVBiIR{GUOV$khOH31&Xzyr+~Ta^{VU|1 z;&tgcVmdG29)Hv4LI_AQLy0qm_4G|(4AIcA;oALpAYa4{7I4%2O2DUPsK0k3o|;fz zZ?C0A78F#5aQBfYS|DO3U*-Y!6c=iOtTp@7vwAj`2p-@A7`89SZoC~%*?cO|Q|RA# zMhNAU)vHi(>RMQ;2x7n|MB(Z|)qNRwRCQ-)WlZ_8QP9hWMF_e;NN|5ks4)1lmex=7k4l2 z?oiy_2~ynM-HH?3gS5E22bZ7$iUoq5^nKp%tabi9-@lW!A&b4(vu~MOX6~8m3ey9L zejp$S>i~$>0t{*vtH1B;(X8}MR~L=^a4kQSJ1YA9^|cq$>4yu@OH;}uAxDy+AVv1= zj;lwu0~uF@%ry!ITGW$5>q2bIIK~N`CVjKh{eBN{jhY-5TQW{2h5jdY zf))fof7{XiAbp@*yce@te{DR4@QjqhtYvE2E0hEyef~>~*=hs97J99)0$z8;G-sq{ z=Zm=1%;10(Z|2-c5$4=PoiH@!4mtsI2<~TIPAvDGbH1I(NAD?jUQUTi(@4``5pmK? zQn>S>sdFDseQ%pu9rqySg;v37jHW_J+z?LPYo}HtP^*!EXyrv!oSKaWsZ81Hi$hZ! z@K{-@+Oe^G2R+od*omaR$Y$GT6z3v%c-ylrmi+EmfJ1<@^=ePNF*e;uzg91}(ugFJ zCstu%EONYXYlZq+>4A@9WdOwO*+NHLEH8g?c0nn9+;w*X!vAg&)~M4FPwG25asP?e@jT!E1OKZ723mdtZ4oI;Gt%|?d9b-ceXTOlWrQGLkVvqr@@{d^QMKOJ{CO{3X~HG=Sqbv z&jUd<2z(>YPcEa--?2uD()KCKsBIbIC|h_LMYJ0i_T{o^6utzF=E?s;HPOQ zy^KAU$@Bb{49Lo~wy4$;zaz0iVU5y;r;2L0v!wkwE6WLgVH*2unw{3y3>B((XH#V4 zC3@Xs)=W*ME+&$eB`sy+<>ZQ!*48glhnzgOZjg0BF7M=>v%ankOJ57NIFpe6&OgI= zTk7|yRG5mFq`&ZuntbPrGvCRp%1Viz!Ctia?aHr?_a0~jR>*ojdiTe8UEQaTXV&-x z?-1ZX5PU&1H1TL6)a+ANLpvMg+|4R7A!cM%W}gT6)o@OUN8~!6ZN8?SBzI_C!WV45 zmdJY>=JCNHaeZHo0x}HgBZk*pU24)ViiB*dkHtqX^1q9cks04#G5G~d{Sf%NzlzoN zhJR{i18~W_|IYR$(Ixvnx=$eSB4mOh>1I(fP2w(nSV9`AZrn|wu6$OjBt2(SyNJBu>liU zW$=*J^eQN-Go#YqqT^>}=c2uB?Ndi^)Yjr9npgOQwozwl+#JrxRd1VQ6KH=1C`g=q zJmW07R|vXc%qHY#t}a&o>Gix&+w z_D{qro~Q{84u1{5X8y7Hqx0sv!oCPZp``?-+ot-BFiGAHBMIa^J@dZ0T1ewHXEfs- z4}!WNaV_#1wWTTl+PFN3*{XkeY)fQlPL&dFMg@V2snV#xz04f1{ZC|AK~~M z4#?bnF%6E=OZQJ*dQeT2~Ur2)E>JIOO_JjwAeF}ZlMn?qLm(0ft z)0E-;o~#g|>P%c;_)IQG*=I!n;!KRBSguZD;k1!?&B`0psrEe$PuSH2J)19Na?k;L zok{7MWi%5BMA#_cke+wbJcMQY)hz3s)nQIvQ?YixXgo!2c@_FD8%{2rwbhO}002NE z=4%mfXe}?ttT&rTseR`wc$5FN`MpY|?PmMrOA0Zuv$YnbHYG;gt$<~>Pj9;z&z+rq zf|OMJN3cb&^%ko8ZnJ}S?dWQ!eTt&`$ST^Zt1Wyx$J*AcFr#%aOP?v~yi6UU_#r!A zK95Zf-x|M<%_oQhZwd#~!G_NK%qEVMCJQ3vdxO;vmdq#?+d zXOFYh4k8;$z|+P`g95gQKUMXrxCt_vT)?#Hv~c<= z61@RanbfU4!6(m`eH+N*g=T)X0nyOCF7lrJv;SMn|?GJ76Z9 zrlz*G|IAQybN4VD2!7~0T^CJC!RuKp5TsO4GFdHXAzml(-fZgCqvX$v(oM=MDip#= z%|z8Lmc9-|yW75*Dx_wtPJ+6giECq0pT+2=+&9|{T)BwpyGW~)px;I&bWyIZ#O4+n z3Sc76{?g{99w&ApggrZ~E39*sgye~^z?U#ldHtrkz1{qWpHQ}vl?JNI2~qzM>ji%6 zHlsCtTqgYh_cc!mTBN7F)UIEt>pl^;7uk+q9>2X@_%cwY0#167jA#MYT0~5@Jk)Mh zT}DJq>ouiZga6p2V+&=!72$$i$R4e`2ZMVkD3fr7pMSH{E_>7oa&m&sV9StBoMv_I zad-1;eLmqEW=>L5K@!QqQ6v_mzRer(7ASY>6FTBO7!~t(E?#l(IBe^B^v$K zr-EW8PR{=_{b->AFVSBGk%j5ZM94lY6b3KEFroHXbhr zXB`UES+-;Z6?n8OCVq$^4;GsPbTlQX30I}?nVFNPrR67D9Jim+_t6IM^1Ss%;{kcZ z%+X9YBy63Z76geXQ^Jh$-C92#%qUb&w>ZL#Qw^PjKY2fL#f z(!CKQ>yH){cWY`tppM8n=be$OC=jAXyLNM{$+Zf_3XKgjb*#xXja=(^^Z8HG7xoq7 zKdYssU(tw-o@3Q3g$X#|$5K}ep{~S?>$SgnVjwgb?tJTb{P_5&Y^+~v6dd)7s=0!Q z*X>ILZ9OqhPzT`p`@^y;moJ>nYB~QVxLp2)hVF@G@2BvP&tS)Neg5hyW3+q19 z5W}?bZ|1R5c~%{^$hv0BZ*WA8fz|6mQi>gKG0;%DKZ}Zzw?n=OA^I>6nd0}rfCO&$ zkW$KA$#>au@^+6uM)_ZN3QH?7aRB;pJPlZ$V;PrT8urS$3hPoe$!0;8-TR%-U!u zdf)fS&nmaEdaBBA%+0Ah==DM(&ZT?B`+rRj;#0+tR4nv{l@FtMc(fU(M-mPPuS}&C z;xpTjR~e|ae2hP@{S78W`&jAdRRDjAuF7n{A5Ak?U{yk*zh z@SItkkANf=L9YDXzz0SE8Oh~#N0crJgOj(axQjzv=xo4TUB0_5O^6@ zYVnDe2qV+ccyhOXqQ-Ovm;U_h*U2ggwj$>5&3TFRo!ytOpr`vxv+P4s zYBJe(iD%-mpQ5jh=7)p`oXiXSyV_oec=}SwX9S(qIFM&a3w`rQ^WIl{PHY6tU`GHx zeX(1ARPv39K#{=9W{OKl2?=$@D0<4Ke3^yVlR;n3+#>QW5qg|5v$Psrx=*HqsgIHD z>Uy@c^AhW&sBCj1j`51nCxK6NKx8Bg;v}ZkYW z#CK!%=c^#g&6vpo)gC9>mCYm2YyA`bmbi=%(<;U@-9yGE_m4`BhNVCi+P8<*Pkpyn z`lpe*2sUXb#PwV7YqFA^9IihYjyHqCwTueAB=(wRN!S5fTbMRrf@iG8#sqsCjVq{l zO!_v84FO57tgWqsPB8k0$f(}e`QagOT0_W-#Efbav5texO3N2LxvpPzKAsS2^?V-+ zphy$+^Y!^rPx)O&WG{pP8Lj6l!@$T$Bm=UIln28K-gY-4INfMyi=xH#VhrfL#)z22 z`pI?M;?X;y4tmaA`m+=NJ_iotE%&c3A8W|8&Tvf0k&;FvxHZ-$> z4lr1G#!ZPhTl zd%r)$o<0ztKHlolUtNj}`oH7?2)ss$B@#9H_ES~7yQh0c=UKIyei*OU3yIiI{ufzV zH#;P`Jq9Z`hxs{~emBpFWm5CXE3rM@S!)EpzLg3eq6U^H&OP^Ow)G!y;xsWgk-k0R zwCkRl`ao=VLQO|2;;6)YUVo8G<0B^>P+!kVOGlT)k5U($z$`=kcIMNk_c#RM2+*z1 z%%soF6ENeclX7eyJ6qgjKIj5ZI%m~2rS?}A6*7Vl2_zAK!>bG`f)K;CH`EnqG|0`# zmb1KsS*!b3!pL^>Ks^kk(Datd6q`Lc(QWA$TA0hqj;#d+BIr(jcNI?|F-_=h(3SAt zW@d}6=A6SM^RsvoDt&KDJudf20}%Foa5@b-s;NkJw1^Z!OTR?ACC$x$rsqQ>!>g&O z7bx4v*{w+?3QuKncou5v4np$ynm9N(xX6=I>dnVVIe2i7I4cLt za>pEW4pFE|JVgxuNT{kNRpvT;{;D|a$ADcX`A2J}H0hPR|1!5@L-T-vz@s8`p6+h6 zke5-00v2yWFbH03`@dvf>=rRo$cu>S`IZ}8R+dC0;8L2E5jiodP$wEhKQzPVvu-o_ zIgQp-E-GrrJrtdoenIT0fsL~|m7SeAJ{lsGl2XD$V9AzU}rp2TiT%{wUTm{r_w zL^3A#-HvpgsS3D4mB6ksghD_nb7yAuWu6LBra;U)6jyl@bI&-GK{#T}K|(%HWtx0s zNiG8}gic@Y-fE=!s11C%ZY(p7&rKc>aZ-pC8|>WB+;O$?rY8rZ4}3B0>tqMev#AlG z5;eya8rwG;Te=8xc+!*KWf{jmu?`!@4?F}ImvxaC{H`rEAta8BQ{%;zA+_e3#DD00 zg7TauQ`7lrw|25l%}0!&;rysbPN6=>=f?MakKj%2K>#uql?)X}6K?Jvymqhds{`tG zVxnecZ*FY`AX4qSTXIPuV_Bh5qw+g;vxux@bsbD+8@N(zE7P>Uu)#-dZ#?27h-54MD;+<0WCGP6S6E*n!9SqdkW%t&(G>;mSJ zS!&nMN-L0Z%KzLe3HPa7KS4E711Hxt$Y%M?@yrU+rO@fH)%>g$gVZ#U0*jmZ=n@v%MqtG#z`h~S4;`^$b4s- z3?$H+b2~IuyBHg$)hRT6*R&+0&b}UHQ)ukjSy{)feccut{AhlVF8Ne~K{D4ka{34S zYaXbxlrZ^%SY3TP2A@RvJfp|ak?TEnl&wzl_bVs&mNxpp3&iBqSRa@?#ufF!c=FHo zfbntK6uy<6zdvF$*|5A97k|mTV)NFfU(4`!!0R-AYuuD@N5}zdcxh>cirv9jq27yB zox~T(vR@IfNQn?8Y3?=z3ko7%fymRd)#!M!2l81rjX?O?tmAyf+iRu zy?KH+LZ5BSWqV5vMID)ehC~7Ebn07?6M}uCOZ8tDEOA{f8xaR!=!{t%pWmpdIXFA_ z$=;=3QA#LNN=oGg*oldW)m76Bnhwp)H43P23QK*M_G^#19MK4NN({7elqKY_>2-n? zNy^BFbY%1Q#}G$W>h>{PerzKDidetVW-u2M&gf%0YWfmAC6=!+HpVTm{l{A#XH++605<$ez3B*v8an>@Mjdm8N9P~G$ zVg+yC=lWhTF?*~>Sk=ZvB(RLSW(6?2T7u^{4V6a%UGMoHCNh%n01Qfh3$P4Xlyx=Knh6$fQpAKhPp+>e6>;Ov_17R(43qo90I`tN3ZZxbtl zwH8BPY+&GE(E+14uj>~mC@6|F{n>quYNcMfRNJP!1!B_MDM9np$gx(rc|S`ro9pWi ztx@8@@hOU!PWoFwfQt+EsO+c%%Za5Mh1W=zVGm`cPU9PHqX*TS2484Sbj0<=$_vryYF7612H}K18*B zmiP13uG+5hbJ9TrTLkR=&5(j`fMH?iQ0H|(@~e=)Rn-M7-aYN^2TV7HZ;X>pm~VUOhxr> zhI5pWBQ{N&gSD>~$Le}!X|UoP>9EB5#5~Sx49{l9E_KdUn?8t(INn}oo*LR%gh3y{ z@ww{)>DY+F)e<37M2+zwo-VlVg&_CS;o1A&zke^*nWF0I=`wroC|+{xBT0l3oayf| zV6Csf|48iY?G8B_Y$w68aiZyv5lk^$K5d3>`SUeiS068@=R`XZUFhUwa_i8RF9ia3 zSf^&4ATs1ZVo#kNfWCWuG(rxPuDb_&Q!8bh*SDTE_4R#U_KNa;(77he%+xqc867Ww z_@SL2R@#z~5g~?(XEc_N@AhNS8+9EP$s3$BVr`}4)~sipcq}p+s6ka zNm<#XlXXOV0J|GK90rXP#tq!)Tp=2W&5jFpra5V7Q)V@;i5Vm`l(Im3*Py4nk`F8* zTW?cQyohbKBb6A%tEdY}N}^`PPRgsRQ?~L&=z;Kh=YxZ)MG!5@iHYIKC!`jJi}&Xd zX@Szr)DyTz2r_%rCb)eMw7n$IdkYdx$a1r=zHVSw!QEuD6j7zu*moT>#PvGDw=2O7 zhmg~w$FeiAo!i+dr%filySB$3sllafGdI^K$Woy0u!eV_BWcA^o2rD#WB8 z9UX02mlpHOhq=E1uMWXGqFLMhl1v;ebzpC2C+O_Vfk08$<&@E?oY#C9JfG)Ck&ghL zv!yK-=fjo$tu83&;pHhMmRGcci#=F?eaI-nUBI;e>-jel-l$>i;HETB-j|^TM)4k` zYQ;jcVe)Nd&WLmaw-br zxpTF;>zB~n;*GTbj z8jGT_;UjX&%#Hm9?Q~mRUbk|4R^nUy;8cI1F(KzgvqHZttj1l-@ z`$aP^Q&ReCn%#d5H|D%WkNnmh{z|t!DPr`PkeadWX*j@(rf)yiQwsqt^EU|xk-Ht0 zOxN+({OoHbDCOuUVd85sC76=CGyhjP6zV045>-~lWHjic zA0STp9#ln7>0&QXR@Q(;yNPEHy^ z5)!5^0|YJJiYP=4q4@p*-`V0{Bzt-;Cj9!l6Nu{fJb#spgM+Xz#FvXAx%?h~;vbWU z?fZX49I^Rd9WC@MEfLIfV0|5(#)Pke3awKa_wx(MRJIdE@+&In?#4z(zk2mjM|~9d zh}dQ^Tj#%Q=bQgMU{4otO(Jxek5B%Se6#=RsHG(TI9B#(vL!)?Mp0_pSf3wZZ~uM& zXvNPI%-*-JzmSo=+?$zx?{|n@fB?xZ`YbK07A-K)-}_mfUV$q+${E?b2&C+$j(!43 zF4!jpp?6p!tRbZTS-tSZu`E^+g#O0Obo~vVS@>(z|D7I|0}#s+cH*xS@oFS=U9*2~ zviT!!>b1ET%5+-?{8wuX@#Q8NrC7wVbpAdB#K{q4p= zWVR}DMHJO>yGyU{6#v;N;D=1T`qZ-GuSkQO_1D?`_vMzT2q%xr^|6*I#s%%r=;*h< zJiihDaiAci$P!MLCcMYO3asvW)NUhG)6gg^FOLAzS-Eu1%*;$Rr#|P?)lJq2`bUI@t5>}8>Z*$yZ_rBhG}nV{?W0&m3^uCf1fRW zUir@^6d`rynfhPn_dm_`?7!0n;$>_<{#W9oKK^<_LUz-a4Ojm5_zdxp5|jT{CGtH$ zQRd|<>}PMDy+zz3eMVG&aeG(!?AiCXXwPsFciiorOqk5yhpj$CluyfPk}lJKA$!mw5F$AxcVZ?Uei;25V(mp}t$idzsxr)rL$FUqvHbJ$EnN*Hu`bVvY69a4C?68AQ zBfX0{WM_0f1_+5SdEIIH+!(9*Q=8?cPMaK_rS*F9q1VP097p*E?!kcCK(azoQI&C{ zpq%y)A$ju%)C0RcT%13#>Z17Z3Y*+qftByt$jLv@cvXnPn3kF@;6U(4bOTbcO^Pp7 z^&Q3wt|yuNxgYk7a}y^Q?AF)P5Aj_uM+E1Z>}TO~;KuJaK1h$^7k(ADLtuAkm!0|- zZ{=P~oC_G|>B>w_#`R33L2AL4>6fl$5S^>u!tY$PCkbHnL+WDH_(R!K3qTl%{d7qI zep)Tl%btsTf|O!EtebyBL3f8PH9uV%45~xdF?G7E?adebZb?8I(wn?6>nCxNhJkfk z$iX!u;6BjnzOhRWq<4{9KY&$OWcIl17->2K`Os)D-HTp7D99(UW*48!FSylv;O7H_ zb{|fPW)LyNG$WF0ek){Gy{BncXnty{S++hv(<9ZXJ57@vhi*%jjV4W4ZO!yl!=!a# z&JcZ?I0vB@9P7|(*QFgvr9oz4$LOm(uJx>^F8liqyHD11jQZ0p!iVH3@9uow`VQYm zo^W(qU&3BD!}%M`w{(Fc&pnI$*8l4A|MwpOuWn@r*hb@z-@#()X!=Ki+^LZH>fwBh zD$mEakK!>H`bQX8Ee6X-)$DN>uAUB)($Ai42pX<4vx3p|-~HR_{dgSjA?YEUq+m_b z7eB{?+56`{dnI5s{Oy`wMOXK@EpXxWzZI3&we`Cq%lc*8j`U}Pj{AsnJfyqbkV)bn z7s66fQa=ds{;i;|CKQ1h+zW494r2d+v-5qqeP6u42_xXJ!S7l{{tX-YitXq6iFYmu8N+evHq|r#*tF^C=@)jo z|5nhixMXzg?JPQe(C5#;`R3LJ&hGo}-Kfsu)z#Je?cHDeTR9~C{clV3 zBcG7fBiY$jC^Z0cT!X1A{x(tuz8i9;lZPnO_CPF#!32`uh64{~Kkf^!`T~-W@daG7TH>l^ewT)p8>QlUYQo9gL zgJ<^`1J>otew*lmSleU7E0gp^DlyGZ$kMWuL$s@HoP^_MjqL`$6FwbNIOp2jGb9z+ zn5XsZsi~xyLq0^u@Xlih@YG^2RXgi=`ERS7q$JJxe^NNUI*YX~6NkpDEM|iKz zTutd!nu{B~cbDfwDB{o0P000g%0^z%emqV%lx&9CNm^mGrJhvCRocW<>(=M9&&Sza za99ns`EvW+-FMoys`i%|EM!<#&aM#L7U_2~fsQN3MgW`x#srqqCA?m}z{&GA$=E-b zudn=4W?7~BS#@Z)cmK@G1Ho7+3Fof1R~fVia_Ni8w#;sos!jXTodUk2JIaLGthG(R zAyaLgtcWPsg10QBzxravHeC}ypMtF3+sxKjJi=PcKs>a&&~zA(`a7uGwr$?1VvAbWq012Bz2V^-z=fk+1v)@9wzaNqEHlwg zOjOp~xVWlyZr&zxKT6kW$c2~82>54Ql5dIC%cZSCSSJkjGa$TaxIvKl*d;8LadK!! zo!&l{vuWugPwSO^D{i9!j`fI*WyP#XWdrl7MYZ1Wd`c~UjLwwt3mk}3tL_B9W&%xw zJL5R^03OU%iyHNTK^R1VZIH!m{19&)#@X9{D%m{Hyw6u=ZMBNKlp$*gPH*#|#R_AJ zxqM|(JAU{_T%jOeqN;MXUaO4N)YWuGyd(KIePH^4qBf^Z=*wJA-7Uo~59Yy2@l687 z_59&#A!*9^Y|%2fL0ojYq7oK~%h9V+i%z7>uSZk?X`$q_sky5JPi8P4gt_;+3LPCz z)9^zjEBOhesK6c1=N}raorW$0U^%3%5@E)tqY>9YxFbC!BKSe){sjOdTdxUv901fl z8m+rQ0dU#dw!3m3K!ahsPu>=`B@D`JAP@dRdfo=g1d> zXy#L*8XGOk)@XO*HH(E7YJoJotqAbcA;DZqACV*4WV?AEiZY~V6A^gUTE<~i>Jh8j zNWxl+mxee-hlshPwfL6<7rt!f<-Q1UVC5JU%kN$%&~#VTut&KHQvts|{oDZ*T)KKC zKG1|~8J0@b7@@KmU4)LvZfIJJy>1xqAEoR7qtFgdlg-p-xE!g}Ns&bQf4ft&g`dSF z_kT2AE0RBuZ3?Vefg~))8PwKvtIs}kbbT|^Fr6q?>;q7&t>;L0IW1bN0h*fFXJ&OM zb{;IZ5|atcSG-p(6xJ%|PJjGx&p+P4QKetl1kPyT)B*-r#(C_WKQ}))fvkVR0&YQ; z;jsM%1O45O2ZrmrBNDd#hhz`GV4QS^VEuhrp}P%x^rab3drS*$Ug+tz*Xosm(2N)L z9n|=#9m{gW^aW1#8uzV%HuGJ>Z1sie3S+$S(q6`;PCbKNs>w95z%kL_$tHXDHDOZ^ zP6Oe^JFyeVM}yi2os;bD^ggjQgTbTvRY-1v;I+@oT*8j+rM+GfZu^z?{Cb_02wrD* z)057lD3c~CI^O$^fL1CG+1yh2j-A1F2bRq&h*Y0=dM_=fb4{&Q*15>n++)wJM6jIh zz9cX^xtN(TnkUo=nShQUoQd^X^m0CeX@n~SD$^H>fI0hf>!yaEyS|+kCjf=^l)7k! z3+&H8ZHXQi%3|t9V`rD~((>H%Q9G;wlL8s@jkw=lT#rxl(mDhEq_A2i0+W5f)r~Nd zQg`wmH=Q>7LLy0fbKd3fmA&7?4r?9Ln;7mj4kuP-C>^%fmPv2@&hmAiUI5RS7ElK4 zJ%gXJCa1pvcx*{*`7=Ynyba_^#k*vU*6hne8M;iaHg>aBY)hh{9hgiN%{qf_16?*a zeQo)wN4_Ju%YALNs(=e%n1Rew%n=v_Z8iXtyn;3pA>E~dv%uio>;-j7_Bo4G4&by! z>Poh17Dz^B-0N^J16FNm_!Rx8v;P>+r@dM%^N5)OUi>i(d;bGgtuj&7>opI`hKdXY zrhVpoguB2WxS>ifSrQ4zeY*bRr)z-K2Y8)i0zVEImGk1i)W#U;qVRS*_*%{7o*a4w zv^6|&qb{GoUI4@J?p9STsD7b!baFoEouqGf$Y(Ks)sOGMnV=`KA6WA{!C5MCI_>P( zZkhuXIp5`|wN+hFGBlE1xnF0J8@uSmQ8&xQ*qVaV-%% zN6r4yR;G*EeX+Vc4M-&KfzjH~d3G#W)V>!7i;eyRXKbAB+&lz$wX>O770o|U$*knE zj=_oEms$X$XUVUiPny?_$k(2`KDP{wBW*79>e|Jb)3x#N3I9=|wKGUlUkA$rE(WDj z<1xWXK4*!+MA^b=uKMXFp5?5@tV6L$}ijYb#Kt-SL(=`Oz`7 zk-tQJNs{Ok{)Q35`&iP}Ww>B7Bex_HtA+lnLH|+8>$V)=QN}NC+lSCdO3SrZ>W6i@ zV4ZyE@|BY{#0~v&+3EH-n@_C1n5-v*xPGgzBMt`9_ zAW+lZPqI2TQfR;gfPjt)#C6czSd^IID>HHo|C!Q3sRdJ$>(Wk@t4hy_=1}AKrlH{` zQC&GIuAwGC+);3TxIaA1J%uyVRya~(S~%j;ak*vF@Mo){;2qvX6l1H0NQ3X;+WuDA z(tbhN>Qq}9NhD)dLbQI)ptzuVeDu6Jc|oe2FLT>>Ck|~^8p3Rn3Guz;bvz&=;=5lz zYCPPuPz|eoY6%Nem@2PJl#MecZZ3WrIo?7|*kK~V!Q&~s6A(K^r^~>oS7)CE}DF$8S4Jl(kI8ou=z95<>(M) z)X}-RvO?FBK55Pq&u%%5Ig}M=>{{h1IUbpKTyi8N8pz+jlGwg|0@^L;qhp0}G!r|D zCBnB3W~(is$SpAPB4tE`6SO4ve5NB85_NXbQRJ&9wLzxs2``r)TdJW+;0ewjQl zc^1lRl3WS9UM0<)O?me07ZUBF$rDw{hB5)!0wDLH#UN$MNt7e;A=7T3$YUcEQb{zyMwh~r5&c$_pk!3)Q_AeF&Rl| zyd`Z`*Ne=Bfso~dal^>sQJ}nRIwup?@hqADJNqY-lcX|dhsITu9X^-LAT4yiqO={^&!<0_7Me=RqG5WH<44<=1-SPJ>;PvDiPrY z#{h++yUzF*KIR6RjWxrg-j0ySCPBMoy(~IjabL*gOK73Mwr|g|pik;jwQH<7>u*|H ziZARE5uvdjJc?vRaAge@jh*2%mgbh0%YZuLlW+v(4S(^|FHOxNXiOzd1#Ja0YlVL9 zEgXSD&d5NW$;yO}mfj*IOTgBC* z#KkQpv857V??ozoIHH*q$Ma!(AV0qc%foOI7CX1dBxPg>-6UP~m(6^Uwr z9%I4>ea^^Zu^IVgfsAyXnq`oqsdKSvG?9uLu-*|dbt7WwV{wS-(Pd>$GdQ@FoGm4!tejTzwU<$gKyqDZix0h_$A!TDR$F8{Afx3 z{5Ifon4nyk8s$2khHReV%)jeM9MR;|!E}?m#SxKq z-JG>gm#tKSK8aDC%$@z6htQGxp;uSTHu~v)N2Bc$@k0aD-3s~u=hnL*(&q(CWYmNCO=bbJT1U>wHbH$Qn7+xW?6ZWd6tu;&3l+ zas%azJwLSqXgK~_1pByom6d6)kFU$GHc@ag-fF|{&O&3L03g_Ue?khe#(T0(=FB~= z$d0?e>5w2xN-iK6Yj^@gaK;MaASjZ)RDPY5otRC-64R6`-kz_Tma2!yO(*g z-@=WCxxz{1{pIs9SI^w0hw;veC2$O|RmIwS@>8RRMSSr+-G%;9hkMw%sNdt_qun3z zWl&SDw(kY!?y_7`*d^v=lP++4jZlO&QdTL>)S%=Z7qj#xi z59nGp^m{07w=Eu4_LTA^x3}(E-o1lFZ`iXf;9@%*=wLWz4j6?NTAfKj- zz`a?aYp5L<^CM=>V5yO#Vlfz<3m(tR*w6Z&^lhM@3A&Z+Iquy-HTz}MW5qjQnZ#j5 zJR2&T)tKsot|{&yNM8Uift~JZJqPvDIbXv%KYcSRA@M}H^|$BJCpY;tHM?{jdO>HZ zHY{17PF`#6iA8f?$E5CFJA_h~tgmg|Y851$>ZKPkc7OcDR?n_yZFTjwu~;515&rN!Bwd@R*3UhLM}c>O2euE_-z!b8I-+8W;aS!8DvViHDj6Wp8o2Tz zn)8_U{CykKPZ~`n>$+hH!+~+baI_d#KXOVp1EK-3z7u{5z9#c&Q#>3@nIbBA3tN^$ z*BIN^Be_n*Dek*T@AjUXH;3uLi`1NsJbL6GMc?OG2k+qO->`M{mY=k1y=httt-*#5+-3EnCo@#*&QYJBZ5 z$3ej(-0tp!cgKdw#yglTZ@&}E-Hbm&>B9Y_Bh%>E%S$=Zlb1GMzJQ0|j`Xp;BQj-rsJu;2U1)Gm z71-&sh7(`5;WZDTnzi$NmrrjH(3{^!TQOoxG{2-tnx!h4GC!2R>nzu35WiE}Slw1} zeMlHT5B;fWmnyh0`|*Maq6B=fhrhZnSgBOlB-w{^@K*>fxHls&gASI*WjIE8&*^T1 z`N|swsta#ySMQj$s$qkL+IJORoZeFF*u-DcQ4ZCt^3XclC}%{SI%sQP$5G<}%J)?Yn#4 z5@#D&yVN==S3_WIDTjKs;>|geAZUeGgB|p?Fhknvp=deqs_oPKeD17um)4QH3d8(B@#UL52jPeRd>FpSHz~;HhJ{%$eECu!W?dp?&9IEHg_-;DZKLvmjWLb$ z+<^I<)4eujH(2%dRp(Xv#2p{Zd40j>;YMHZ1cb{nGI9W}%7`pd+;)kp8m*a-DuENnV(T{KKnst>y+0 zH&SdsjxRK(foacD<=~GMH9?UBrb2QgowYA;R_io|*u1eAP!h>w$GaV~;AeGYk!XPlRdpp4zVn4o|-t=831PwIVT@@}vozno`NY0e=sJTlg z`;*o9LOqJp>fK{i_}5hHl8Y5vPaYyr0@W08YI*b+_j1Cz!XApirHe&lBiD_iwt+!= zqw+_{#MJXlYm*xf_Z+ip+;cfuh+wCOOLg!^mDg2QKU| z6ukl{WCKfVo;VlbDeR0FTAu2Uv1#48inAHCU06)5x(-CZs^_CNdEB#QCSvzcvI4y# z+0j|snjwCVro@hZ;v!GuAOd& z+YGg?GOi)6h$v}IJafqTn(p*ISh}&L!zDt$B|=OvZpxM+>-RZvtZygwI~e)iQyQ!J zMXygjKA_A`U;FwvIH7BI+AYVUv~w(l$XM-+%BS0_&c2~f#FAXu=#Amx+{aC)AJdlBk!zrl~;9D#+Z`M6R=MV?2N9zl#{;rX+>C|X%{(h z4qj|!uea~c;^x~%F9`MG`1p8td8!cT$`{vJ_&aB@arblS?pdGBcxJ25?Vab@k(IUq zin1PhpFngB<&%V!I!3bkB$HQZ?fWCtq8gAx`o!(MJQKs-;@O6HH?Jr80sMgafBs3&)=Z*ZV#aTFaX=rgp68bm+=uj{lO5J{aGVb{oBWF0lbPSv){h)m*B@%l_kRFIK)S#9bH3*8Kwj+M2JFw#(y!-BN*D6!wOV|H`n1PX z$@-zRa45XyEUOEbtd#yY7e~^@T&}oSGcFH6r%Z<~DEn%>vjb%<*wNF2&w;9D{CMtw z$@iuwo`pVUe1FEa7zx^wYj@;Vd_r1a^uCrP;NI=|b|O#On&ZRy3scZ8M)XZS*@FF{ zjFCb7Kdrerl(tq&`_dA-i+VfrB=~kpjr-e__pHK4blk^VmT&T0##{U?s?ct!jh2q#pY4cI>>-CHe{=S{#4|2yR!M`8p z+~GX!df9iJcQj|#=XcgbA75nDC(?sc>DQ@}=+hb5jhwykQu;2Wmsdlnw!;sO%sZYrF!-B`>4odb$ljV&va%<=g}jbt z?ha@Co;On##1&+b47hfGo_M4Bxp%;8E4hc2I-LG`4!wl?J$ZF>nZuwHzK zrSx<%dW+@MCsM-t?aZ$m^9#hzp1fCjU{@%?3#N(OMQa#~zOBpW?tH$NUnWbBlVTLs z+cj;VZT#Ps2iM|TzIwlmiI>kNZO@y1hJJogv#KSt<$fBUC8N!IhS>`ig^WN-r0`_! z{yzK%Rqx4jE)Cf2%J^+8j4T4?50 z`3H-!!mtCKxvhFe7yY!XA)9e7p3kXVcQ#Kyl71bF96&6dx|;UR)+}$$5kLJvWM?T? z+!`z)SwB~tbD}I2UuHhPJ{a)at!N*}dS~q0)xocQl50MUtX#>nR`WX~M%#W_R580p zBAGPLFH3vNpRiUZDh6>j&)SjS7a~R1OYa;_i_lKX}ZmvO$CI$Zroy;?5T;Tg*uH-E(*d z5I0HXsXZ#H<03r88foEN?mSXa^5y6mwtPC*y_Y`R7_9WEA^tNp_?g=?Lp1-D*bSO- zU0IKdv3}Y;m{A-}3vcDxTLUJ6gJF-SX=N_I+#j&obXqxD-WH$XeD2bdV->fV52g2q zrYPs$%jw1bvJM~QQ#5Qx=7?s5XFknypBMUAincp9Skc3UUY9e+u+5&dw>~|%mS@8t zr}Gq6mY#0y&(FV*;}eB(Rzq>}FdrR@&uE%0gO7BkbpG8!5k?`pgezf{<sN zec7=qT477*()g!+Y0}zwQe@-K5R2(3o|pxm*j9efff|X3AD@}`OJlb@94W5+NqyT? z2=Zo*rXo$Ji_f>02H9KI=Tcr*GcPndf0srl!yUhJOdIp050)l?R(z*l-U~m>6!zE| zZ6vz6H*HOotX?aAxLmq!OM3Et_~BIUIWT^R7x1ef{O`x&#Op(pZChCvxRurZ=kX8W znm^^-@2eNbi)Xf`)sCxfO-swM%qS#Ncqx{djUy`$(vRZ@ z|5$m~W8o(p@!Nbm6)OKRJv@|OZ{`eScr2EkRXmp0fxQ2mx%xW27S}miQK)U{g{P72 zdxam!92q%Ta=kI~#xjf5!e$*s786>?{WlB0EvJo3gHJP`dlm})^SQ;QX7ar26@U0{ z@Z#29|NnYsyp1pWbM0vDxBvKBH{LDVxvne`81+L8CB!GE3!mx?Y;<~qLP zk4Hx*=!195r(Iv(?%VloL$#pYK7t=#&FpT9xA|ttEIc5}v$Hsj?L1e~Db4~Ril8qS z2a9HqbQo-Fs4wnK3vUfC+^)Mn$(>>c-%M2X;(FS-68^lJ?@-U(=-qAMa0r>ie4KU{(=)b&HwWc^RaBrczRne* zth=HCVX%$q>#el^S!p5?vLW2JBl5wPepY(1a|#f|d%62q-s|(;l(8O)6h2RHKdzp9 zTl74aU*4~NwqCkdo~Hi8OmGxREM_bhs@;X$jcR-iT=Z@FN%zkWQJUp&H~Rl7-{ztN z#3No*OMBDPQyGQG*RJ&UK<1mr^K9@Jr;6V;hYA}DSI})@7c{FVbw@(%D?`#*imElQXw7@9a2V zyfKXtiZO1kxez_tSbuQ@Pj6%X_IqQl6jKuQ=;+c~l&OzIt^e(u=ytzmOPk9y8O^Oi zt}8hQ4eSf=-KkN0R1(ih;1W7ce1s(xm+vQ|+*`T!-SiNj(0F26o-va%8(yAcz4_iK zMK3`F>vPxo(#^bKB_Cn)3 za@RXAHCOAsxjcz{!e2aJG?~ob*9WUVnRedE^E-x4pFsa)62IbD86WL=HhE_mJjWfZ zyB3W|q}O+v!jW;406q$If~);5^Kv7U7J)E#5U`O!KQsp{1G~_x;tZlQkOWjhZ}0<2 zAl)S*A!>Ui)LI_m0;@##-@{24Cn(|K@ zCGV*Sir53cP(};A&(M zFqv!+d2+?c`dv&0>Y1sQ;nKqu?U}4zis12OU$A;oPM|8xV^ww*>u#?Ogi4~2fMa3vRMJ`;mn>Zp*X3r2k#giSwl?UOY z!BP0xXXi@#Jbz}ov=v?8x4wrg^-;e%cIEHn5cSjco*cWs+xxex*(2|!ZfA5o3%9Y# zV(jFdG`B65XP_O)sGh4&*gnJ+<)u2VPp86l{)A5M6w4AfG825? z$?AtVQ&RzIF;X#qdW4kF-?*yd9X}~8#yf(8`}Nc6t(0rLVlw0CyQb@K-``d!4G=XQM}F%8HxE3P2i6}3%knN$^^($ z?I;fsE%XBZ?iv$gG%9pFJY>)1LZFfjL*)GR;0!ZuOz;^mXJ6)nJz>?v4A?pr4duR$ z#<`Rc&BfN8h%P>xYd%jqEQIGk#~%-I&ZYd7A9zrf$*;SjK{w~#sUX8!gVtm{Nu2xe za_3IA>V`o_K~JK;bO)RPqtPY2+C4)Q z$!I#$DCgICURrBv$Ze2r)l@u&o1xCZ9P!Cuf)jb&4}blx5Xh#aco64jdC?&>-m_uN#YZIxqEyykXiw%qv>2B{OGis*AB;!U{4Tb6 zJiYn4c>H8}a%XFno{X*>Sxt|Bo$Jq(wG;WcR6RP9wzh@tkA|uTnwh2|1NhmCPU^oj zAN-CEF#Bwt;}<#)=Xd@`#BR1Qwdy#OWNmr)kQCm5UG&W-@1t|$Jj%U6B1`GtuF7+* zR{fF;%gL&4B8@7`?uBw+7hlWsTqrAcY^Vj>mir%9@4Gq&Kas=7vHhZayMh?D1OYuv zo1O8b(;w&ip>RD*YxIyN`yf|g)Vy@H0jw2_2Kn9}P?Bn+3#EVQgiE9AW!54ryw=X1 z(G`$8zJy$$%UjWUspu= za;SZ^{$8ypu-B#ZppUGoSv$I1cvj4SuVYrWR}Hfm_k8&6qmn5a;Z{Y5Z^u%p%;Ooe z>aOidG8$N>ko>$~h+uO#@uMM|M*F}Sey8F2yB%>?)kT}rc)>RWo=m-_PV&1>nud%DEhSy`T5ZdS9uYKN)|O_pTcA!@OrhAICnu z&*d7P)k0N+;*>Md@rR2RbSCY@m%&>yqFry>wQG34eNXCZQ$vu3t}n7 zDmD+<&rPE<4Jhwfu^%i%IPwyRYm(tbUJeR8PT zrP2Ax(7Wj8heMuvrgDqFD*gQ5(*ssPZbjaZ&AVMOC9hjA|30Zt@sy5-`KA?rwb)m| zAFKc*&W7^*MHlG#uE=6VNIKc+itOz}cD?;J*c(!!8)T2fsP+z(o{$nR+MG58nVM3e=@9||(*a+x1 zG-7Mk<+ZIo;Sc!=c?+K%-JBevd#YeJyo_whMXU?|UC9eR{9`PNi2v20s=Z^!UDf&X zA=~5}I{;hoYDg5z(SQ3T-rGm4UsnYD``<~eS66?~Oy*Z)14`~F+gdFF`D_onr56fz z)ZUdCq52W}aINjm;K(bT4cB+XU!VASB9xtL=oJxN@xCGSfI~>Pmgv$QrD@cm9v(j~ zw7)4FfeR+X(f!&MH14$}oUuOic{9Jgnb(gay>g6i=C}3X1!sAdZ|CeU^7ic)xk^PS zPTE-4dG6-?#+Gcz6;nCO1Nuq+zLMU)Ib@+;8CLVCe4VN`{e3&XzBgXYeE%e^&g895 zR_ymLiVL8A(fv2aYdKQe%v-<*YOOaEHf!H*rFub5rnznxaQ7KTsb)fM@UM0uU1 zHR&qW+8O;>&Ft^U`?bMCo%zv+6BFb6$hI1mugBkw-4pNC@t66wlwZCde;$7d#lIbY z2v;78L_dmz9UJ04k0NpB@_BOn&s_ag@xA(=_R)9cUfe2n#mkugPyaIUcSBvVY{p7- z@ya+eadu*2;??oD;~$2Y8+>_vLiO@o>7y%o68ox(KpaDr!{3gJ6K8{2S>q$)e+^MP z>#;-^)RG>`@upxNc4Q{~w&nmP!%HG3^3OA6dEgUq74e%LY3sM4_0`hvvlGXw2KB4F z{*d?M&|qcaFDh^RRmOEV*Y3^n!-;>XXh5&n=?uXB@%oSnY5$q7!@+!Q*Z6xQ^tqCe zUMoE$inKH1XwO4TQ(i+YQ(L`-XgN5aucJO&oPy@zCwF9pHrNtfOy8(qqQhh_MM(CS zeB$D+<7zb0m7h4fTZUNakX|i7Yl;ka3{9VSL&k@<+Bc!uMC^K<6}&-upk>utA}QME zXSP&{6|5gH45Z=dyf>(~tAW?@+VXnj(|RlsN{Eq`?0SD~ZXdD_Bw;y{bYhe98^>+5pdk8}K+xLe8~` zn{gjs6{qp^%tuF^Me%w2>;Z367Pj+KP$$hgm$7Y5OYi0dxrm2pkM>qoJXKXnX5Z6A zM;gJKcdh&8dew!{5tJnQqF0`5H3KZTzGwqacEpE$GKMWR8?pqjFN@MEo4WUSPu5gi zfJN>cW7k5nw%u2hTgxi4lxuy$zkb7EFsGjN>I`SC+i(2WJkExqKy#z)I@(SSIYBFM zv@XWGMg_9k)Wo&Tk9uVew1mE0^|4qKX_N?iZ>!7`Y^R6)?5;UByJ(09=xrX5%E!*G z6$ zUW*BI-qF=0a7%berQ7;?uKw*Vjk=@i_c}L7^LM=RKq0u+7>julYVmw$$F&J4c`6$9 zS$cFjTCwx~qSN@~dCqN*&G}-eii9#&O1fA~I#AtM*GC$Vi0)6$vJ1XiE@ zt}KY`mCWeQs^Fd-Z{#&IR0v$JwR{J1r!~vFD~ETp{K>~v5vip!$BWX(ou{N7#iCJAtU~0ACx(ATTX8)LFY?@# zwkkE%T8n(#P+W)etpL`C>;Ypb06!YZ(iF!GwmESA2uhGsQ3p8v|yiFetr zhjxeQJeO>W9q6^F4tdy>t2%n8P8CjS1R$Cyb_0Dk0_VvdD|;k1t(s0>=`5Cm#^%qH zqxKNHZkP?>Et3^eCgQnT;}t9G`bS<8Z8{rFKz3LS-a4el8q1Be6tgh0q;ezdtscsi zHnxRkc`%=h_6gi$wyZ9+atep>%T?O18|;xH$)9&zpVH=L*(IR&QDIfo8#x>?`u5P{C+a= zN}=3O>wBO3Jo0jy~fjo=-z1m~?06<9Wsa7yK&LY&mWJkdfc7wMe8+h982vo#);w z`>IOiSvb;4vLABKhnWk08e2IZzk$Ax^|5}$+OWS*-#@9jClRp6)nP3&3#*#EJ-vm{ z>XVWIXs>Pb&dk=?(C$t?KN8SXe(9I-w9=PA(y?5Q0&ew#ZzQ^n0!8PBJ=;`5=# zn9ibwW@@#-k#LbaKg{*FhHQiSjfeDP!9 zGtm@S&QsN=Ks7(h)7~wPveNu&X68l4a%Sv`oxNJ|jjjlgqhe8Ew5RD^ukhgOyMJ5B zD!e6nAa3-2)d8wm@0z7!g=^WHyF)z;UqY47_hoUd>YvW}nPC;ao~jFM%W-Y+H+qez ztXaoMpA8j-*7?chTmQ_%V~KW!3&hVLrJF+?8-AY29dxkuxcnxzgyd`I^iV_j+E4@V zO<8KT;NHtR28<`$we?^BZ+LJuXuoSj zPGmmjh8+Q}5B~DUk-@vs&F0XoshMh2a65mkzPy=stubLu?GB*siIv(lte@u%?TOs0 zu@jG;sYw0h%;4#89_ueM2>r3rR#&QQg!Ao)!m6Ihd_!;I%wi|<*Bj~@_S9}D;;h?q z&9BnGcPkoVeQ#H?==mFk3ZM>oO`g@xaNfI_(+iOR`9V=idC0DKhE?EfD{T1;uMU;W zH;0`E)b(#4zkKN@)9x}q55`}XeP?0mKeeTH;~=M}(&ppxX{U#smiVjFX>%$(q+)z_ z*z?3%^gDxZgje1e5XQB^n`yl8U1crdXp&-yw?)0ye7BI#d`|5mkTGSr{v`g{59 z^XMlvT{Gc#S(C?=Kl!k5>g|m7!%+W0u76av>QTAJVf{MRstZ#*LqPh2W7wV|1Ae9Zyc4 zlJ9xG;$kvlkTgAM)#yi|)2a0IN;EDCqlsE884$UF<=jR4@(}I5!1J)8lr`$r%PimY zuwuktc2R8YNJYlgeyZM@8CDhW^E;M7LaktG&E9L}w4@pWzp8V0x9q*&{%-K+_h*)0 zeJLZo)>fT8y#dFa&egmX6>#$G>@2OpBep{5a=1nQ-`WJY_MO7QB8R;B1J!Ri2ApMO zi8V&>CS5^iz>ij(vvXp?#(@uahTG)%ArNz*GKkc@n=>lu^n5xz0ZWn*wP$MdU~ZKm zVr8&``b2fp^Z+fYg40S-d0Vj%RcO4JSBAP=*@7G8@68UYm|%aaOUTMftrgl@NM6)L zW<-V%ZtGePUN$=k1^12@{66(Eyj^sAEzdn2&cS)2H!?BHRdGOD%begJnFwpjR0P|l zZ@w@S%Wpl|&d@={k`*VjRq6YaA?CinyhJp$;zlMPLc*_nIa&5i`9*X>ZOZwKhOLJ9 z`NP)Ns3uZlffLz-^FutKwGV39%jj5P)iZJmLWUs-FF z59-Bmlm6|l6-8I`_iCX5c96%f)_|txG4-xPIEe>g&6gUKZ-=@GF*=b7@e{sz`9!NTx8)j@f7V=_igqAFz4Hej$y*hS$P^qPuHF$D((&1lO)+cM z)%3nW>itEF_NJY`81!)0HNky;6Hn5D*K+zu2JEl1l6h;-ZvC*9-aOo?yL-ho8=?(s z*t~c~xawGD9`3T&O7Cm1mKo}7Q2Si6l5)Fpz^W<4aW0ofA}=R{+ZiMX+3&rM)l8Ed zxWwr6o1eputNX;!R?qYB%mMpMhId6G%n%vwI0h;2oef07NxNti4ns%v0jEl4)M=jx z2ariJ(lP_diZId)v_w}Wn(bc6DpSuF?u^x3aU}VKQbdu&<>a&14OOVtUx@#7-LELO zXPH?sEp<;FFO)|?7hdqu*d%+ms7tZ#z^L!04`Nzf?a2piuUaq6Bz?1csB;nVIac!_ zuLy6ri>86)em<-@6BjmDbKwWoF-y^KBwcm*jT(U{feM-BXgrbG&h4^Y;#xdGj zI6dr5_j&%_8P-faOdrnFnB|nL^D}Ge(RTkQ`)i1T%D3RB_D%5Czpc&dtf43YjU+P6 z3zt=t1>IDhd+#;!qnZJ+Rxu=bh|Y+}{dfw*DlVgKnQieJHhshBmG@S+@DU(Ux)|kY zYWusm((%^d3RqOcn*QEi@-Um$-Wak=Z&W+44qBNehl};#M-^+_5lT&#?@tT%dL7&X zg{<+&wop~I-y6eD9pr&jsjskKgE>>B(-qrlsV;_+R>88iY}Wk5JA=2?alyu8o**** zedhA>pjHtTI~hF;+Ek75Q6$>lb7B}iZw$4qH!_d+^LZ`LxIOI9D6*@%=|a9g$oUJw zzE?B5A62yATHeoxod{tl`;o{Femv|IAbx$Ye6`cL-U@YyVYO@(ezSI+AJ2Q2h3fiD znz(HdTVbBXl*9(RN@yX-8+Y{1DRcx|3pc53I#XHzT0B?0rK-nO^y7sA*Q2@26{*A} z`0&TWPGNqft5~kyO$nDA4-H`-IcM=Y)`_oSgz`CJkIu8=>@n;^cd~2aWv|gdFRYq} zPWat2rGd5Ko;!3nUte)Kv#oY3C18YdZ&O`!*w#DBf35i2L-%X(QU-uZPkFJh)p zLsxTdN!y(hqKRk@x=(LuUU?tuzpaQnJM6r)xpGvU>#?4wxl0wUyn?+s_@u85`yY^w z&Q##&UX2BT?<`AgZ0OY4lC=r|H8kv6yzzL$KP~NuGx)6H@|{`fbzjiVg~8{iiL~vA z?&R@6$eo2-tKL$P%{SOJcBgHcOe~4UN&nJHz1*e0@>}dA)L;d{H_%f2BuAKCs`Jd~`wwvrH zd%^z6FtEvHRK85kNX!CXi5{6H-$XCgvfth?k|$pX7L)n2=Y**D#$kN~TH_Pmy$F5R z=sX&)4{>f;1;|+qN7n`Mmw5&J44+3To+V@CPO)o!vg4rZwCZzlY^)hvTT$`*Vc&=! zDrP{tt3y^}M{@9jSFew9^kzxH3dC<_@U-<6Mm2YJPtLgiNm zPqy<1Fg!o?o%CfxX_T&^nGYAdA5HpnSjRt~S^v%Wb!nDwh80nh;TSR8Mp={Pdu|xN zEOdl3zKvHq7fq|~@7IM}KMk5YTU!6yP-0&+FXUW&?}Fv96z5DuOUPF}sc6^6ac$z2l5jg&@C@OTcdAYhb>91bz(rYr?ci=^si(?sW?*JxO)GIl5fl56SGL_7c2lZ!kA1=fty;h7b^ZMZRizs}X?_KAELT0k}qJ_uSR#3sSG)3=M)OlyTEOH@d z1@rZq)!rpgtp;nLH8$7Yy?hi|)BWMdwh`)0?4Ky=qk_9Bc`kiY!+R&ZFXPCYAXx`< z-~G}WPijV1hrRBsn6zddwu3tP$uynyE#k|sm&NP|k!Lq9nhmXc7d|K*_O+WWtFWOe zs$4f0-nF|Gjx5uQI=ROHpGROy;x z)jHyso`weECHw~zK~+1ZSxbY?YwM3X7A%s3Z|!hpzpma9#R}eCh3RQlcC^C~y9ZzB zpXexVpR8!9Dr}yIY#;&Ct z&OUpn(JO^mZ8*7iyZ-s;J)zG>3a4ssD^HfA6BYB{GImZWts31?iK(A61%M}$e%8u#dIyrCrN_(S%*!DZs zpY{;CCa+^ss&jc)Y!)6?Z_zu@p?+r$_#5s-N%|QAqqUl2Ty115>AOQs4Vx!VBQGmA zB#H$CvQ66PXt6A6?|opG;a&;Z>qc>w?0nOoztH^Gz@EPP^ie z74OP!_;;<%uBa4`>6&1p>75rm!yaR*)%niW`H@>Tkj5lyWUu$puu}t{t9KGw3Wic! zDMuk*i!*x1gRb{xw`7Tp++K3N^LF|Lz2KJSW&R^>R`t;JrSPlS#DTCNq}qErvkY=- z_|DVxuDPM*Xfm3pSAep_v}H%F@d7^QsmrIR$m<<3wXsrKU9^HFR~tvZ$>(IX(Ca6y z)#(_CJMc12?nprIoqeFtk$Bai%yN4OsKsW}Gw7i9uB%*JFJ4Dd_Et1rthD!sR}ThV z^S=LQ-f~-#uIWrMGw1nl|1t-2W{2!mQLtV9&#pLu?1WT4sRf(81>4EOeCim{4nHs3FV-+N9 zOVuTame8xc!&z6@y;i!=rx=3XTg&qLV2Q|_SRJ2OwA%{TiNbd@znv~Ru8R67X~tkV zHGe9uL=C&ji#`zXG@jli6=D@3KREa_y@wWb=B!!hT4RzaMzg)5H|!dV!W*!!BK>Mj zEB!{ysvY~pKwE$GPFOfrZ2}Zz zFMGRylFF_ZH)<6%cZU26Z7O1MAmjQtG?7DR<=DF`!#>gu%;ZyX|esGp|jkmOz&m)koDm z_?EBv>VPgU=PE0}W=oFzcZkikrc!)~kFY7{?TMf=Urfll$=A{oE01Vi*$7n&RvD9J zIW&?b#Sr$5_wE~;?~Js z{Rwx8gZ6(1VL@<#XvtiyQd6ts8Xmmd1Yd)WMjPG*9^!G}lin-AIq?fRq<3P62>D<9 z^G`=(RhkMGVK-SY{-PCK_AZ1K$#+-&ccphnExTTnT_-nM={=ob4Ke)AC#jq6wGms# zZ-yOTM3_W5n&-rO?W41{gFO_E=Dq61I&gE|QOx$m8(Fb;#xCa7dwbfsfS&9)r?{bd zbpAC8%PZ^)AJ|J0ek2RLDy!(pzFijN@Zl$PvdWt6=s94CSS$0kwvLiD>^*OI-tfxq zLc#sNK+I8B^?TBnMX^Ti?(IE|I(`8`K~iYJYC=U~roHmOvz$XG_@L|kWz2gg56F>+ z+w<7#_24!!mR{4-tFUIu-nsw(adkhSde&E-z|VVc9dSim*Z)SG5Jb{cB#6z54W_NN zrHF{QA|WdwxPo=~1PLLOk_|zE<91exl#Yl0~?1!KoECs9Rhjgq;i z|JgoJYIzIxuCa&d`kcC-?8wwk7)6#rItYXXBwR~&EZf*VBJ)!-seXq3OVwVyEZ4;L zXSIk^+Ho{PV%6@~E-`}74GIfcO?K(}epi9B0Xix5w>enC#O%H?g-;)d#1R!3*a~4NSZ&5rAOP@hDXQ8+{hcBa=D9 zvULnZ7%IY-xN)v2c{%di*f8P=C;AL)3$M;C<83@#>{a99b1Z)Ld-&(9zUL;YNQS_~ zJj79XB(gI(i@hF((Bv-g^D*A}q(|6ZFzn0Hk(HNwgNlhrCeo68MR#G$NI|j| zR&N(Z8e4g}2Z$eTOQT>H_#3Z{mg0xVt>=Ab+^pbrgHAk9GKCXE=LMM;Yswzs3qWIF zfvTouC|j$0dXB_VoS7anPOuL4>+V?z_8IFB4gwY70U3Y0DHTZeoNJ^zym2JOs&LU=F<~B{vlAYx>HdFFQlEq>rggGS` z5_>+(CZg=*rf7-E75PBsn@qv!6%B4KtWKEe#=5omy7p!m;GNOA%&{B+dYb%a(R^5# z0r&`za=imm=XZa-RXnWqZ4dFT;EZTVi?X?jh;N%ht^Gdw1;Ql4t7ZTHxyX6szZ zohNU}J}9*sr3d@lMX|#%9vkN9SX)rE2iyKSaD|&FT#T$g_9CR-`Xr@sCa@=X=^}`% zGMQ=zM((WbzdPkW%qk-7aT{kIT(1lQBZ8aim400nlF@?llFj(bJ)jK_)-}b-CTrMp zc3JpHRh%bt-JaWDnCQ0@?sp#g?$Yr~ROiu_SKwoNwMYCL_< zL@VTfADyfoc<|GcGrVVfu-Att-$>ena9j2b9m+e#^CD7Xc3~o?p;%C4*Go&Tj1JjQI6L;0nlqV9ya?RnGT}wy zU=xwR#mML$AWXNUXvmC@C5j<-xk5qn)`3++$(%0t;+Hf3l-v}YQ>yr|RI5yp`%C^GuD56Z$pT>0uFT4?W<=v< zPCVGNDECVT4=5AsP|gLJo}3t{I!g=YWn{&OWY2hw7GxW29sVZIcJ!G@be@HXnVft! z#fg!_{5aORed5z>bX`rtpuBG+&UY&Kyyu(y?vwkZ-j#I=M-0l(t5OXSB$r=HKiUDt zeN$l<+A)~N>un{d1;YMf71J8*WcRqQU<+^!R-%qGu2}Rzc0Y-7u*5X)=~s63tje%H z*yn+Q+G)@T9D%gmD}$!IS#Uu0J-A}i(}xe@-gixe(Y$XRoyteXmd;0m;6Bs;z1WsG zyz0KGoDyNS1IP=PNc)YLVVDC%zBQHMeS$GK^9EPjp ze#tK5E9w8BpX#2zVi}_8qsWF}5##J;&scW{3lrAe74C9ljA=vFzdgs+k9@tXwcXA0TdZ8fI>X_3zj||yd>Y?eE^L;MeP+2qbOV$=XdfER+3wE(m>B4fp z)bOzN)jDDNJ6*7bRBl-JeygB9)$gbXN-p&N9t|s&7uUAbRZq7S$XDe%xvCBIt1J81 zYLDlIXKZ^PXKko3=FiGk<2|XF4Mt2Jsn}wL>*BSsXEzzFv{3qa>?sHa3E&EhgIDC| z%IzRW*wsODwmWckUDvK#3rmATNyRP~Xa|Fgsf^caB7@C5JLo?{I%B@aC=WZd z&((ce!DORKvz;Spj`fNR>|5F64f%R%q)(^p(+wyiBWS*GMN(_jCod z>#<5hA#}6h>G~!*`9R6?;xQ+yU}x(&$B>@l2#Fg(J5&)`12P!kAF?H$XlZ07_;z^y zV6yhcO?|(KukRm;_+;HMwmXZJpFz6tXX(=TG|*)1)T~6T)%!+F2}cd>sf8B+56aSf$&oEERb6f$xLh zKvC9C>;aA@>yx!;S6P$nig0w2mdW*uoK9Ro+&MTc%fb#br*eJNtvg%(uXryTHdv%)oS57vMTKdwSiWieo}fvh(qk?5qW() zAET&uB>h1L%3?cQxFq!$qpkP=bR8u7w!vL+uF(+g;Tt8ot0ptN)qGagjjU%^!S3aW z7%AR}DEjW1k;E$OkK{41g-B14H+@82EuK*rXI+iG2W#bBiG`*XP-ikIwW~WZ^B^Bu zof+@RTIHjXGq3k&)zYRHCRqa6;j8$Jx8PtrQ7pOaY&Ob{$>pH2 z*tmQF3<7kjPS{Y4<0YbZa>n~B1&xu-v#BuY9qs2j8s1WBM88(QSX^7bEm?U#?=zoi z+4grcu5b6fywzURoh>0xj@+V!@96i*d!7jy@w58rX!SP>KJl9#+p9;Xo6*xFLFDsb z2UUxAu>b$4A38_DUw+o(qtaGn+Bxl2-n;e7k4!Gwd-L7DC_K<#>T6P~i@}p$1;LQB zC|Y%4?(x}KU3^zQu~C;>dA>h;Xf$-Zu2a39c+-wNU$9q|m}(=fn=nn$!=l_5#|vBO z&!;Z1?Xt^%C^d*@>WAamTG@0njUY0lpWz4f$;3{-KXS_M6fwg#iGz}#O&W+BKGXL9 zx&DD0L1}ex<}veWFV6lVO?iiJj(!t`5P=c}7qwz1Vxqe+>zvGo(CRT@ZLr+c`tq1m z64TGnZ=GWxeb6VpL%!5=oqDa;#|IF%6JO>b1oPEJZe6cl+%aOr$#~q@SK=rfm3J3r z+jKz{VF=$723yz#K0H0AwoM0xmrK_*{vb(2-||-P>2c9{;GI5?t73hySM|QyRqFZO z3CVW6$Np$rIrso*91`+;-xJ^H^|1_Cmg<8q^*8E4w6FN*kcI2}3VuA6A{#(F9nZ3L znmR5!aYN4uY%DE|kd+k#4B9Y4b3+IB^!TS>xe7skwz9Z>9vw2q7gT247Nru~j zp0KL@kR3-yu5Ktbr;qmh=xjuOVwPm}1!E^;SimHgnr)dYtZCIL(YJ5`<9(<{W#b$t zQUi^7sDJb8u*q5H7;|Zh*ZaHf>8mpylao)gKoLas)F376 z*;qT$s?ddpA0W5#f%dB*8#niHSI@r7+07Abkblo6q|uYV@4I{(lsDao)CZWWJ4$6$ zI5h5>wc{I9(6|p#vPTNHDQppU6}~Nq#K!p+b*_n4nG5k8NBp+rlGAz~kJV4cn#N)Y zvsG!%3S61*a3&Em<0Sj+rIuUp5WcABtGF3E%TBkZWC7Dg!L05pmAxXv&XY_gU1-RC zSw>_`YyQR6y}G|Ml2L3Ng81PeqB>cH8PE){1%nci4&qCE(3eZEPpgs5BtpRUVbSv_ zSwuhRz4&3_kA^2~Z@B=~=bgVa{w~YlNbwPV-g`oggq`!oobQ``O+V=W{?FXQlYN!) zHc}1BvY==goJ`e?6BpFk(N0g54&wSzo}U=Nxza6D z-2!`MYkv=^Cl^UyL9E>31bii{(z;^(t3bO{a-LYmr!W80Fp-gy_fCc;L_w~~Q$04x z=zpxoYzqhyBvN5r zsF(TqNGui&p7b*XEqTAMi_dm(?vWNWKcqB-^_jlfo&8O4ef_9k9!qy)--k?4*M8^7 zdtL(j$gVrzvz$EOEj_kSTD|%{nA}Z!mhB>U_`M#v@0QFWKDDR=t^C1!)^1|MAM9C4 z&aiof9wzh5ImsM%X3vxBGH6(^Y|OJg9_-6gQ*FbZBI{Fe!bkt^NZpO~TF=CZzB~A( zuHQUT95{UPvm-@h+{%>O(KG(#u_;xw@-f&`I0WU+Z*FP(NUvn;N6jGZEC=T6b8Z?3 zmzHjpN5?nQ+t*#Qk53OHh%u4rqlS}j54S!)y_~FF-n?GBWRWarXwIHqL6B!VezgZP ztA|x7nyBaL09Qb$zt<)k?wRST5sXpiZT|SlKDX>?tQs^u>Etfur|{NUjpWs{gP+|n zIBcpQdFvlla@NR<~+2aFVXrB+9bY#fkT8Bj%JqJ$J&ua z@-)e06~)kvFigm}Bd6VmOm7*hM@lcM7fR)gnc7wA3i!LEKWq8!k#FK^kSG{IVi#sD zmLmHpSgWXjXg#z|WSRA~Yj%si2XYKxPxx-wC^R-4)U00U?>gM;G&zLMO!Mg43Z=Cc z$OoG8@Mv5Zf*1vT^-{}l*%0Iv9V@;g%V6DDJA7(bX*iJayy*dPq_EGkzQy~Dp?FtV zD#%AD$br$xMov~gyA>(|qdQqD_LK1wF0N$6rl$jMDXf?xX5kN&B`P-u4=5RR*LZ-} z%m}kySR$!pl;5?nmP>a-QAhD1HVwUeUtL$wRd~w1wWjot#qCL!dt86T{_+29eJp+$ z9O%~#zQuNWsqfwTJ5cx-^e=%uki_&mn__+8`$(-ON#&|p*&>L1ebLMfr5B@~wqy_Q z{nMV1iO$*`!8_QAb{dO46`d^4R7SCMQw!|V_)d7*cNXrGFwU@hFznGt8B22vCpV5_ z(N?K%mPI2Aj71@5pQXnBy{D~Z{up*YwlXIBJBtP99_3+0j)#Tz!ReCevw4Vqq|c^@ z3}kc3pYaG+iM)*D;$;O`5$pxy2T^BP$D;Pz`f?6YM|o4p5KiryW5oT^3C2~#Pr}m* zwPPuhQ(*&k#j=~j{hH{D>rX^g9`fT?zByIP;q7L-C%Yq-ei*MDCwGf)VJxi0WFz_J zcm%0CAxmYHTKQJaZDXhAeH9iopA`>%!;|&(`eL7h&6jR$_YVsdi9sHkZ#5D=E#8wD z9P5M*PLCCqh-idZw=05&ZfS3x@0R|bJRf83jC8dt;}h{V!!UaH<^NTXyL)>tScASf z@?M5;xRbL6Xl50e?!oGoa|7*%7bTmC=9Yhzij{CkJEvT0_Bvli^oxxt?|?3X+NHYR zdDDZ;`PoP`1T37?l}%^P;hb`wAPj84Fm~{aAU|{@dtQzktDA+#x4@cXRl_}1_NH3N z-eq^t_?VfUS4Pv8o-MNsDPvvREy?9d&TLkqcXUNyb@1S^23Q%gMZ~bwdSb;&4nuS+ z`^J^iy0WHZTF4-P&p|c9vL(_XY7Xi48e2Q{KWtqQ`!LSScHrNTG9sNXPHr|vKe7S3 zt+rH#1wAaT={|S2-r-4!|86h50Y=fPQnyNXLfmLpd_~!TMv+|tV`Od66e3**i{+Bu zagLS4n|vUAO<`Y^wVJ9meOTo09O!osjxS28V^gB7WErqZ#2xqc`T4YZSi!KMKVg-5 zcJL2gCZsAgY%nP@DEYPZy+l%ZV$3-ac(&3tr=`L#S+-wEcubNG?-zwo98TbDw=2Y?kL^Hjr;SZH<|UE3f1(J-7$GfVK}wl z%#S)A)gX{=Yw@LmS{^R_l1>cf23y}d(vyso{UQEuUSO=L9E5_~w{(DG+<`13QVpsM zQ^i*4UXxo-mSQCqfh3JswzrP;6(%k`ROt-H?v%Gcva0};cfi9ZBO`%GF}8?$I?6`>o3Rq(hqlQ>qq!E>xDP{Laz$<&Mr4fw6JUmBLw;MoD5EG zbtI#^Co2*+<^70zlf!THm0cRYF%|X@WY{-k8b_b184+6%0(o6ztV|l~R6UPA6}Zn1 zmOjA;+piRpl%2S}R}@awgOg(dn|QZJ@a48YQ^Cdp)i-R{*vY(hxrub_bEUH{J@;~t z&(C`_&K-RoIH`E#ixPRd{}BD#UR8CA#;~Xz9!^`53}!z*4#64)HSK0lNsx` zY#`i-Y++t3#tQjiyiF^Rr;(md*A%>yoV?ISiBP>cayrog%%kZ;pbzJ{-s9HSd-dxp z8TEJfr(Wqb$VWEUa^uL$-F;2B^;O(b^7cfcj?cbKl|{N3hy)os3^}fe@36LXz}VL3 zJ5g^>tpB;uV_&Kl7R!^ThJo@@-{bRxe?Kz)S0Fd+7*P`O2bMPf5L?8VQeSm_pA8np z`VoD8vv(hRn1)Cc#7IN4HWoet5lthIew^v6`b^KuN5!TPyF6R;{I|!Vw-ZQs8eIPC z{ezc@k67=z$gdwf?ZIBNRvWA3#omYeCKHbA3{PHU2$Tla3s0~@ag~V}t?T>Ke~=Xb zHNh=?Z_5JukFB6@+r?h{5X7IB4%W2GO5b5d;f}(8y;5Srr`aVh40{GioDSss`VLsGZ}hWNgXn(Cua=cTe&dwJ zur0^NyDB-wXVewYM=WD#v9r@R^cY)1WSll)hv;o8dntYHc=>Fu)Hl-f^eE|qanBe- ze4u7<0G?)00+&{KXOUo@^|mV!x%LUp6U=ja~_p6@9s}S510x>4OyB zNSG9R!W{7jMfActkxt4UtF>>ivDDl92KP!=1Yb$543F5H)3L5wJsvzUd06s-S%ds5 z)ncxnMVLxQnV%4wo3E@TSScxN#@v_tb0tQXybDt(W;C(DL<1oyB3okYWW(LVA;Wuy zRNUA9$)pk27SpqSQfm{=2j_7#-z)f;F@yEXF$>%MsuiCOL(UuwCNa0*80^|`zS%WK z&?<`ml&&_e+&IY*fkE1X;WBjBs%&6_R^Dn)9|#WY%8E{Z^x!bocF+``4_}ngguwf~@0Z`{ipu)mTOv+s zEm=EWw~wIbta;zPnhj^{NrI<7rJZ^q@ zWG!|C-X9HXeUcBW6_k;%_I0Ao;YeUlSkd~sx>hkS8d2s5iJ`->Ys~z@wbE_Y6&sH* zb)<4P9IGDk^JNo;zi4w`6`AEap-17h+B7&`WJ(w^%vo4v;2XRGJ0^1O=F#nmanrVN zCi|OYQKct0C{@u=JGiAWfJVbf$=7s)=Z)Gwp39x{*-drBAbrUtc&v4G_5;kF4XS4H z&bo2dPk3r!Wc15wEm?M6Dk8la_C~80vY=8(Jq!L%RR}+y(NrarI)LJFpv#q4EbvUO-zb`V>MG`mnZW=MHnT`DzcK4S^7G6t%EXj`^?x{r#vZJU}EUJPBu z*P-iZIc)s+K#jJwvv;h-V>6HMkG+>3-E1KCE8I-Ak&4-H!B~GPXmRt)BY~ws*<>iP zqWHXOGwvPVnw}AV$h?N@P8E`@OFLIp0etkkeT;5D9~QrX|8Mrhd3bVr+p1MPVI7kp zcCwfxoaX61PPI5&8gE2${I?B`vUjq4#eHOpks^2#%!*2&FouX#!EjM!4kWvU06mFwu( zt|T-snY>c9s~?a2o8)YeneKr7n=X%bGpm1ktn7zq1-U2U^uBJ)( zmeFVs9HU8_M;m$-W^&VfPokjq727T>*N^s^vd`2DKUFf{Sy9$B?9q|w4Y#jkx6o{O zNze9*l0kc-ul9rX^z~A?(4WQ?$@AXR&oRclH|Wu&`K(;ut^FyL0B`nREPpho9^4ma z{n1OZj?#6O#k?{zb#I>mt}2rlpNiNN9^W%FF6Q37L4{egn1EUSnW@A`{4lf0S}|wgURpCF10%4W!$rql_J9Foj?!O`=Xhe~3YMU%&@&nK zG@kv;-gTtPmgM&tH&J3bKb{24k>8%)sQjYvQ>au5+r6Xwu<3YWY-p>=6_Q)5J{Cpl zCF~k~rf4JaB%iU`*~Cwm_?1_TT=M4am^~%tpS7G!0{haptEgfr@-xW>Rv`((?xp2@ z0{4{f@Z9k%V%@LUfU-Z>?A8hVJ)JD*d!7Z9noTJhskMQvUu-{B z^VZh=<0Y^Mcn5bD-$q4h>OIK}-+@sKBRm_@$fgGbzs`Jy9aAKqU8t8g-w!i_XpQym zYGrn;Egf}eS#_UR6^m3JgcTO9SYuDq`j+sVM<$6@@|f6A;b*pUScvjx7fksmiHQViZF_Wz1-E@*V1D{>GK6i zgXV8)tq%#+hd|WlbcqW*+pl7`r_T?T3eD}qIYqJs{}O*uiIMn?N?GwpR-?)R8Ij4M zv=gj5EElp_?N9qAZ0xkao|36>xQ>s-$qPuV=j6<(DkS--;s)xjt8CgA^L4Xwh|J3p_s28`)&=gfLXm!!M0A69KuW)Oj0q_Og!NnEYuwTJK<~ z6yWzgQ6Jae&0PBiS{Y)?#bf%W^q|QQ-}AG}>6J5+@u3n~wd&Ve z(w><8cCch)lP(ZC^PB!gRuaiYTCaMHZ;CJC)Tz-srS45wV#(vOS|&fS(R9_ zgIrwGzD0IDbRe&Yg+l7t=_~E?utddK(|MOC6T4nUX)?9>@p9MSD78^Cf`cc>Q&v@a z?8=PYIeIoZ+Rv5PoDO^cZFYogms7<;H=608oE^Jn@^{YksE|w|5ysBCyV%F_ZAdWk zgB63n_J#H)<%8Wgqw->XGTMB7=`?GXh#rVL+PQ2Ua`T^_f4zP* zNAcg23-JBY|B<{)b(zuKTWXA4pLzzd9&9z_%1=r@APa)E9&eUpfJ5T@BDZeo*Tv?% z3l|mh@J0EZ$Bq4No^9yo{M9uj+k8!b>y4#O z^x^rtrf>x3m1cxh4=I1?9}*&a9+6 zC0I?WdLu>XW!J&031$h4Vt9vN5I=IoiU6|$mOPp*pB7j~s+ z3Yqs9zw0>quLv`PIidBf z1zjY`9r)47!7|u6>!zc(2D8y7cX-;oQ;^fUhM6d1-Xkg&s#S!|#?*YNO7`Fjivbrho}rAHIuM62*~l)p|F|#ef%3e0%vCCNDvWI_4ZbqEN}e)o*Zp{ zQ-7ab{Mb}QK2)bK|6%U;XxlifJ{3V&UDJCq?2|hur`_s=K40kZrOjw5IqfW~)XB)1 zIJ~&JM_0t)?GhpWZvTaY?dWU4r_T=E+`1r9{zvp=`YBl{bWxpjlNIRtxQhsC@gIVOkgP_8B9$DxUIU7IM=Zt7_K+~-= zK3C4iuJ^gsGx!T>;{ z9!NM-;lx&#*Moi}&YsLpn2_u}Sfm}s3Kl>1zcmFh;vpnjBBzjsX4P96;-vB!cTW8{ zFJ3O2dT|=ooQ2VYs5IYk|Nv}W^ME}KO+0E`GIX+mY^of3-NC|;B!@GxL3weT3XhZM3(N{e6TW%-8SgQv3z12FB0NcM^6Qi1 ziJgsjb z{UBuIo!No81qq9l7rPI(EOtgC$Q2EbPx`{xgRb-Of`g|fQhXw|TB2w&L+)w0z@CFm z-Zd_!q0WzTdJ&B;i^=3_Urbv2uk8@&Mmjd{D@ze?Qnfsx6Z&;!W?G5#)>36 z?&?|OH71+bzD&)jym0p710%0>uT5pgp?MAmgvO9yI%L4q(yzmb&b?0+7kkxxYkjKYZGzl7qYb3}R`Kv%4S^p3Tf$OPzoa@G?swq{{^mcSV6%C?LR z;G3Zu>AU;bFz}}mnk?2W)e1{ajeAZ8JqzEegvBPCKJoNBDjDt8p>ItD3MzGNshZ*o zI^L=?mNM3!=uuv24Nb;w8~YkFx=xs9+9uI;3{T0~er2WC-kbBx|GJ;Q+RxePXG<6L zvu!P%oL4;e-Zu*$pZAaS%%ANutn~l-RPO0LBQwf_b67xv9@NBW(W ze#S=i*}e13^IiS>TT}U)$FBa5yxiTduM}pOl{rqeS2{Y%-!>OyG1-fG2J!$)w^Coe zBFU^#*71?qQK`<@RjSTpZTX$!Xr%B*l2R~toxItEXQu;|{%o+Ev!zD@Zv4l4jGrHl zul4N%q^9+~62;urpCZ*+n|F*Jk|io<6$7U(b8ptW1?75a*eZAq$=iByGPT9tWDDC2vjOLlb97?kvYim))lN?oeH;GDr zrnj>?_6x%=68nsV~k>Kiuz&%C8JU?(vOEeJ&{x zi53aGr9Ba{k5v1l*9^YJa~1YE-Y8oGDoG=fd+a#=Hv2K1Hc4gi9#SL~Gx5Xt6D%>S zpVc7-Ad`f35LuZ1U~*<;%8C{R$9cZwf?{!C9q~t5>xnGF0( zs^Kyh`p7rO^Vre*5jy`?>C3B*TfM$GC*Mr=2>kwN;hcn_!KFy?)4hM?SVJiIoR9Qp zp6(x&W$ZK=?YJrn`nkfID!Phs0e9Vzkr$`?3Xc_zN4r5~H}0dO zCF;R{a)g?KVCRrke3)X!kM&3_4MpbI}(Bc?Rc@zLvrB?y4d?-Ykv>x_=SGX z5?Yyc0uemb*LrGX>cKw0&xqu>o_k7Nu^J+5P<98;_hhf!OT8DqSF(P@f*x)U@|`}z z{@EiR)R)ITm9g+apH1b)v3})D|A&Frirz8o6uTxaowIyzq~pFm`Um~{srh}depIq7 z)7>%M7wGln3OgAM4Mo#ujs2R{z^2n}7>=i!Hrc<{F|5m~&;dz4`cLKzVT`v*;b^2& zye6L#^RZg3S#ywH>oVy@*~D&O@bWtDEX=OK^ql>c!sI3&E|DHcqwkm5ArqBEW*Lbr ziU)}R?d#8|PZi&TK8pmA3UbEOp7X!NKjhG`_eBucB%4bQV+avDN5?7lJ1d9X<{o7J z%S>T$(3x-mIYZVFt2DV>b{zY|{(%lSc(pYMG)7;jt9_lM0-S_s4?(G@BwXb>C zXrX=e#XkS8((810Uxoi~==*!PzETiHIa3eUU({>ki>RTuhxJRkeyWqMFL7B|a#rGpSrb=e67TBzSy69t&h36k2&1$ey#p}OAnZ0j<|U;X+gV?1DRfug#Z{%|XlqOA*ZS&l zUfo_bcI8+4nnbi@souWszmvv#iSb6!_CZ@stDvJLYI59F>s|NSLr zi=M`uYW>{T=f+UHQsOisi>gTEpM0@I-K}!bs?EJaXoKvPvjt1Tl>sT`yU`;MU7fPT znBE&}GV!(j{mGk(Z+dPloUr_D>FX3*-rra9QH4iTbnk587(OsE?r8B@5A^YV-Yf{@ zp4l&t4z|kUNHwb5v|arfddH}rDY=KdX-ucE8`j66zO&tfPqE-Pj0NGF>8#YaV+D|P z$eW_U_{Hh^K|=C2bI$0_uTKXNID9gC>@|Cj4FR{e(%BKdn+;BKs-0qakOkrd_NC|c z^`_E0Nf#ASOEDLIV*ch4@95o$g&i$%78cK=y#vA|lo-J(U-m=w(^9W4>nxR6zE9LH z7XD+sa#*pEWqHI#BYVg`6|c0hJLf^TL(h{R%xX;)xJNn_!jk!D87UHutVk}+9hJN& z{#<%>iTI0e+&=F(e?9{@@QR0=KaymX_lgzgcRK&^jMLva?~uE}*W4pCF?g66Pc}0f zCf-okR@3?BzF9l*NX@tJ6!Ua7@F{3tszK6?A-QF_mgFY;P5!6H5el|frgzJl zezv9#Oh5E{Mjqe#Cy(2&{Jn2}i&nGDy}Gw0^R1VEHyY~3e&zPw<*UbEH~+60JJf4e z&+}{glef-~$6fOdKH+@ECvNR4$bUEXnO)5dZLP3t^XgUeyZqPhuYT&aYx@5iYJGnu@Ayty$Q{kM&8w?dPlsI7-|&Zie$AEdxo&>@rq$1U`d#ie zuie|v*8YD-F9j<)c|3 zhNUbxA=bkIi@Ro%hfA-l!cRp@KRa z71ER@3v)R{4a160v$4HnZ~69cl+zt-1v#3j^Pof2x3Y`3v>qk@_LRQeDs0&s;$X>4 zVgbC|wye20H)kZ($eayhU#aKC-vUpief7OLQsHF0ws+(o`lv;(ujhLW%6vWF$Dgks zm&!)^UrvmiwDbK^oZeA`9=M7`SjBE z`r8G*w(a#{&l6u)yd*u9XjmMAYJ+d8XBMdm-B&OG^(yH~_t-G5^}brVp{kFm+j zaQWlqJD1;I+O&Lp>6PX6izk-fUi@tN{lyQLUtiKOb?4>ZUH;wGPhP$5&px^8+*Jqv zY~%9o&pfwu_tHcE>~Y8Pq2*nlIkx!e|}OQf9AR6-HUBYA1}7`E8h32-@mVqul!woc-Lzm z{Ik~|>oY#KSaNoY1<;%T3PxL2hd9gYsOaH3=N7d5K`T6U7V0^^-FgzzQ5SJcw})?{jz>(PU_s!zZ{-jvAUlv zf4KDT7HfN+e!YC>;v{3n{+@rm z{K@h+7Qb2i`IVo~_C0;J-}$QFi+||HZx(C&;qQN3{KL|+$6{@d!PF5hzqa_?@)zpg zEPrA7+x1uV*USH5@&DNS4yY)aX3?41C4(Rt6j*YGS#p#tg5)eBIj12kDx!o%P*g-j z6crT|P%t4P7?30i3W|xWq9hSfFrg^m?O9Oq`~2?z&Ux=Y|2_9S`*hD#b$4}DcXd^D zFT1Ki8<8kj5uySdEg>HWcR|{N)PWU@0(2Cha$IH@h1C&BoD=8<;Vi6(fPTm)LNlyL z=te%m6^J8Z4S17)t`SNJ63807p2oX2tPZ7tLWG} zRDw9eYzt5clm;k>z*4XzOonw}FX$QINN54}0z4^$l?{OfVAEf15C^#LM+?XbvV+pW z92fwO0EfsL0+nzUxe~SiE z(B1&A;0f{o)DJ8|7my|z@(0}ZgBitBJHX;CdFR`~B|lR(yb0b9=Py8Fa5!`sNFxbW zPm%`Nfz?z7W37bO1CC|!cOW6H4qgVNR);E}+kko<@E>(xZ-*~Pk3w#c^-oQ~wTE{> zle)q9KlEr`(`b4H-GjA$w57HGp?mUxuYAB04^3}YfHyDjd<(KN$TWR|Cvc6LFM&ql z7=4fp=XDLQ2kEEYE*kky5RHr9wI4vdAWZ^|7r=EK>=z#J1Y?7F5P@d_JOT8Z8}_8> z`!viC^Fi_8DFs%0_Gd{;kQL~q4XZ9>ZG8RbwP2yNrlr5({=)ySgzqwtXsfgE2Ijy8 zX#8D*IYAF3bAmb43~Ylu@b&C~2LrIGxO{k)1iSw!9ETtaQtJSD72!KD7ibj-WRnHG zoB^jeWpHu<81;p51Yyx1BjSAt=CBs@6xcrsSW6G!?JPm*@f)YpG@9yAAT3=4ElHr4 zI-rSDCCy8NTNN;y^D9CkV1zKjYOoUmz#B$jC9n|K2&M!zT3Y>+@&=%Vl~58C4{|MZ z9P)-{pgCv?NR9t1g3BNmupiPu-6^nt_ksOq1N2iB(u5vC+dxi&RzhVUa#e;@=ULi(UI2EA)R)&(Be z4rE_I#{kreLy7=)2Q6d)RYS-L+6CH212_!yw*tJa1i2MZi2xLnKwbsjZ9uk!w82=$ zfEz0?ui`)+aUhR8jam?(p$G7KfRA`UVFiF&AqUXc9rSYma4V2i7sLpV0EJ1HgQ0oq8 zc!K%Dd2j=x>ViJo06rGr9f15U5dPg!oPrgQQ47pK9eApOS~3_>2hhQ9Xl+1!5#U1^ zU{?T~Dv(AM^uX5zzc(a9_%~2QfV}vpuZ9E^3IXR1{JsZ|`NBYQrzQORTKPb;ilK8r z9}2;HKEQ+mOf48A9})sj{9Xw^JA?w>Y-rJEG~jCk;MyImMLdYVM8F&v(Pr5lw8FC} zkWGO0jRRVOw?h>$jHE;9@D^ZCK!Pg4J{W~YVP^mt5DK8}@F=tsxdJSV9l-ZPExv{>AK%_yxOF-N4D4P{zUXT$8#xBNI0~~RoX1-*meYyk(`$UF7j1UI| zo2;@S0W9<#k8c*rpKXEl|Jj!Fx3)Bjv?mN9>jPpa&=Ok7{o82x=y(el^hcmP$Re^c zvI7gpBCtpd!=kWgEC!3kC|De}9@~Iz#Nx3{SOT^g+kz!xNmw$Lf>E(lY%7+ArDNN$ z?N|o31KWx1!ZNYlSQfSi%f|L%IoLid7u$~=!1Azz*dgpNmX965j$+5K0;~`_j-9}Y zuwtwPJBgjbO0hERGb7U1_fmk9|z&hI?wul|F4)H>K5I-aUNdj?WA#wsKLn@If z4F3NnysADYg>x#r!aTEC37n zIg1cjFR+^a;gLZL@5I>ocLk8uR{2E68iR>cAeqF+ge#EDV*7UtP2Bd&0Ig3&y zrx6$?v!iVEOy23M-~a*JbfhUyw7lK66+7~%Y!Fh58e49@^8reg~l&Fxq>bBc9v;2U^Mt5JQh%O;4KC|V<(YNby8R!ylvI`QxEP&5MG7HK?PjDvEb0N-- zWG-|m4lr^tyZXciheQTZut+iox&%iua4|Rp_yY~~Ckvy?aSSsTk9CM2uBm|(k_i?Q zg~j+#LO>50R0^*raEUCg_YWXBh6F_dsw6uTBa#Xb5*6lSQ&B_7WU>m1YLM030jLf@ zbOA&u{}%oh>Iwf)k5XZ=#S;bQpFoA#A%JHF#}*j6wWVB-_-W(}r}}Zr=el$ryKMW^ z=-1qfa4mWdYh3YNBO(!zS+#Re^o8cEsZiiynVoNM$!caJIv?p@?tFD#69~}sM?%g z)}_ku;7^;nF7_JBthW}Z$ceeXb-C*ox^;FbPbv$C*9c`Lm{g?E z&+plO{Ls72MfbK{=}tR#8f$&`ahv6En-T1q6}x2VHzp8SLGTGG>jjk@RC@@bq3#L$_bd;6-V#1XoevGB$heaqyg@xc)<)|3UKhBR5 zs|;8HN()pLXsZ{lt%|6oBC4ST8lmn#M1^6Z4Qhp2{(w;=eZ|5M8#ZkC(-6QL{CBh| zC@ao07Yu(VA}I8i4NJh)kKK01;Hh&K>YLoA43~dnRTO{b@olRPT4CK-9NMs~s_2;r z#-_@4ve&$beArZUtRQ!KYERvqr}(>1^-Kw^&m2p24=YMj?*Nh3}i;g`0_TopfWE zLe9_!s~w*nJmTBBb0#tY&MM!@X=`p0Z8+vtvE!+_U~{v4@iLQZa|O4gDg8A{rH=xp&TZbzG5fGOSkX*0;jh*$W&6Lyd;sG#Z$4yy z)jMDqXav?aBrwF!hY~NxlxH^l$Sr6@62bH;4#zg_M2|DrN!$vN0?oGq`kMYNO*cWfI7tYFl*K{pK&xG z=bnP|*19c>gG%zJjO6*NkFSojunOa0c=q67V}@|_zPp!_R#%>9>~7oM8OFCyC+D7& z!T89WTApi%&^n8$3OS{Yv}v>ux`EEzW(P6?Rzeg3^Ee$O@q?p$vF^qp-@+O&6-HNlM)?w|ZlHTV# zSypZ6Of&D`xN>W>?Cs7_MLG}fTa|Ka^Tn5YnGFhZ3uBtC0;<-@Wd>Ya_hO=Xj&LxZvcjQ@-|QL5zb2dcrwh&@{dlZNrkT zB^O0yhl>kCc8TS*QSx3Enwz|LJX6n(h9u2Blq5x9K9BktI(lLtg^=|1`u2S#+N*AB z`b021brfhl+3C|*H;{Uo{dw5o+|DgB_T?Ft_C<%nTdGAY1($@Bc0FlGd|(k(R(1AT zjJY4**OJcTCF$d*I7|=wRaJy!&^ND4-^6b)dQ(WWBVig5SG;M|S|?=H!aH#xKWRp6 zwZ)D|p23o&^=ltE#%HG;>3GRPua3|9ael@`L_@qQoY`e8>)iXMm1Dig-a*^$uNQK`!#*tRk zbWwL!vIeSFz@5wqS<^Ne{@2s^5hoC$6fRE|#CLS;pSCpVFCK_Ug`t}~>JuSvlwUU| zMK{mMrAk%r_tP#sTv4SNp4<}~dEbjVT66!L?uBh(dnXGLCf$|iisB?LdNk-86c+i= zt>#^6>NH-vtoXak_RM2+GyYFMw%8K``ahhQq?fVd;*)K2m+DDxRPCz&=&cw*Xm^49 zW>?LYAKu}-u`prOFnlx?Rx_d;CY9%VXE%(=4s8_bYWsFYjg?eMYGag2bv!9`?XZWl z{<(m&xvCt-JLF?-^UEFZnCS_^95Qc?Nq=KmhID>%wOc=N<>6XAPTj8tt0#h{Ut2B9 za2C9tm8ZO=sm?%m&EPJZw90Ww(&dQ?cIHctEa|sS+!d)2Hmi_+GxO|tA(ig{O6AM> zIlTk|Os4Xs0$kE>hfzKd67hwDqx#|p5@1Ka)9$F6*dBMs}Cx)fj%#g-nxdF z0RQ3OnNr@zdgPvqaTQSm~-skWNt4|;^f^qfK>0u&u7J`qtQ z$AFmiA$|b`)TM7wDgz0n(lP!zZ^IvMGMk(uv%`vdsHUnA1Z|CSk$LM7%o7WyN{ zzn!*KLHMdc)>6|zRpx_GRaAWe{NJ@eC>8m8NQ>YhEdoMX;L_)gmGr)lPnnX~J?tkM zxlzuWey%-6q^)<2sDE7rLsz;(cV5W2L+>rG*xVTQn!Oe4b}q}byK&8*P{wqa&q4L# z2TkdYV#1LA>FzJFPN$lySo-K^5`*ct6B4wSD6YI?3ga#&|n9Sw4Kc$geLoP!Kb7W5m}z^W8PJ2XEeom%e>f zc6+dKdcfeC2-n)PiAqEF9}B1)9?)t3-j`(l>bVYSID)TDXH3p?!k+(ZQ|6f$#WsDOyyzd23mt!7df^BA_d+nYT3)-^0z zt5bh`{I(o-T}j*l}WVhxfA~ z<(N^;w`zwdo@Jevh@q)1p&G^lYgU}dmma&87|USM|Jc+qv)4^Q`o8jk<{u%w1+ar= zivsu?{ybB~OYK>AvDKHPk*5k?voTBj7Ka}@$+Hgf^j(+warj}t$z_h}09%hCe!@I?W+H|mLcEDFf& z{^eLdV)Fm9(f)b~!HYwZIJEedhmmFpgxGOBIKD6>|0!_pZ(5!;^I&eG@5wW_-7oIF z^k{!9;|d+l*Lmz=-!x;qj%Rp#chB4UF|}F3UDF1~K8GQlqEhbTqU++buGS=+EKY5I z!oGgu;myl}S%zq>ls0sAttr2B)p5JxnPfWAiNS+M>KyB{ zb&D^+di%JWmr*m6lFqNzFmfxU53ZYR;O!X@8}Lo6iP2`2N*3I~^_(sK`&G8AC-3ia zz}CiyTJOWIhsVx~vJH2~?>&+Jf^m7+6CLI9ozwE?tn(@@jtQJT=PMs%-1=0U>-gxFt=>}6_KIOCeCZp) z%1rn<`k33UXiVQRsV(ile3bXt_s5yf7|tFKvpcp|HEpu!Y|$sV^=9u>G;hCI^X%ZW z^M)tqj>L=9u+?kBh*V;)!mPTHuy=KvAck?4cM#RAc7+jVHkJ8Ra#`q_T(_CKo4-^q zc)%x(Zk<6c)%>ZjXo&n04bR(^I1iLpdogvC3dgoNJ9U6CW-MMt%?%2&oCI{R0*Dy8>84VpXZjzO& z)70Sz2VXQ3nna1UEEL6$k&r?)+alxSX%N=#sDL5(I^7+8^D=YZCRlj@KHV1yrb&Pf&NVIgH zyQ~qEwo;t+QYKgX290T8~P(7QxHgOln@*@s&RinO=PeR$- z*L|GZ?bzNqp5|gb@w6thXfQjgLR-N;|5a27+Z_$XsNLiib(!mfmD?n4Jinclc*!a2 z$yYk(yog}kLo2VpDLS`FR`X2j>r}IsD%lgcA>9ORP01`CLdC$fV9o@0|CN>SK z+E%-f8slIkj zP?9RT&I_ev@7vdX#crAtry*mF^^S&p`9?VueUVbxd%EQ5a9cQ29)$?|Ao5F7{ z6^fb2oOWnR2SHqYLyqr5R8x6UP0SFZ>iP&)g=!94{I z9Gz)glIhpSZLFNB%zs&#nKCVtQkM- zr%k|y)ZK8UOZitpCi>+Y_5a6j?z38=XCy?XOVdc!qs1NhHZg{<|qah-XvnzAufkZwq8ge7*jmq)V2uchmQM-+NSZU#7()a%cIU zeq*rkr{8_daCsZou|FPN{!q@ZStXu&YA<)V?-J1JE(yDFzqst~+2q0Sd6ycI%S0r+ zJIqZ}oSki$I_2C)Aup?#&7f~ zk!4re*WNr%{Beswi$6+!W%K-b_8D^%v?sbe;C$=g+Rs+G2hXIz#?n^Gb;35@W~s+uJlTx=ul#7f!EseCU-Yv)F3;o#=zH(wh6U?~w^@ zNQeZP`>m6g*3rJXx<4rw+ziRj9{;*G>cdas>W+zY;GOvH2wpSZcTb7Qj&~=oUc2o6 zE@7{#6(f^qzV5pSIdIACx+!Mu`A+Tql?SZip!KT1J{kYsr};@q9Me7ekH>cHw&n|J zs-Y*k`+mJ-_e^b!av_E@kOjN_3N@*D{s6*hN#2IvwAVyv{rffX-1Nk+yPkji4iG+! z{)Pu4PG_G!YlBL3DLExtdSVz<>n5^{!IU2a{(Wb%t>{?$yZyCSG|NH`q9US$VT8kl zp)nz!9_o{Wz68B_RCB-y0?TO}v*iG-S_M_nTU3SU$N}&088agf!PWgH)C7ENY*%zk z+#%mmH(~4-bRx?K? z{ludO+(Ov4aE!jTRnASbhl5nVs_jL z)Q#T0HgW$awj@9m)CYSoSi!u$M)<=}uTv2%-V0exXS}z1PSobC8fU(J)z?3kR+M(C z==P#f*1?rKC$s*(=UDRB2?nlHO8q?Kz%)NYQWbuG|7mi1=j~cn*cMe}JIK2LT{$=o z%5a(FWqWeBf(UN2N|NLE?bd0GiynCXSYH}3;n&|gXVXm2&;5~o?pcwq;k$Kc^iZTn zOa0>Ah6exNk8x&lPtLHv)vQ)+lY}kt=4HwMokXeB*QW7?yMI+xhTrn}mFEa}`*@4x zV7Ve&@09?WYy4400+UInIJ8rDYQ)x72q#qu-^969)o!z@Ol$G12 zq4Ay_X`RCRznyBXXf=B39em7B)6s;T2eIj&zuNpM@=Eyam#o)3_|$s}?l)lNY@mA$ z^s=kZRcH5F_nEx3Yjpb%9Tw5U-^{wT26Eoan?^93ELuC^gdR5Uc&aII%y<;=C@$$= zhgTlQtrH5`Vv^hP>dI<-?mr|nHjaAE^M5{BK*A6{*&Yq(dnTEqkg1*senCb=$JJf^ z1=Rvx$lR7D#hyb&t8L{~#NA1MTDt6~2CR>t3$B4TG!g7SwvI|;o88YIUL9~IG`wE$ zjflI+luyC7m!$rE*QfafN4?X#{%&OGkl1@F3NTSJJW6f7XWTSQkNCISXg>PV&AQOB z2mL8_Va{tpPrFgG@9gZrgSh5}`GrWLMnIl+ zV#Mi!^z`wn(l7z-8;d zwl_aAE~C=1W*~G)+qDqly9;A4ms>A(%!RpjEh3u2ZWSAj#g_M=lz*qAzaD>B)ymci z>x>zD(Lf=(w1_rb$CnE8znPBk@^N?q-`v-;M3YTMO* zRZ~+tqSkomp&I+Snp#w;hT4J6CrH?%dnWf`VG;2EU(XrC?nOl0@6H7+mV2Z6$jBFW z{tVm^8Yr~y4!PYa-Bp5dA7~FHNK||& zRoJ+;TBmF>L@(qc6~j~v-xIrG8HSn95=QZ0%5FFyno6|iL&-9ZlEYC=xjUeu2aqkx z5t4WZQLiwjUBZ%06Nq?$B8O_5=c?7bqVBM=gsVQA4?>_!D~Lo&E#h80FcQ-&R+OM$RF;KS&Ng>teuAFV*)H&#>7)6P_7 z=VnZ>;^#tys>?yV9PhB^pd3q4rsg?_n5!k|>2iJ?b+mmcX5Kx;PWGHiR{6mu0u*0^ z33^#Uyi(K5bCsE$=d44Vi<0tJ^qja}<+$IPs*`r+#yV|gjq4Z55uDN{u;FjG2_ zx^A*@W2}5x3;2b|x}@YpO0{xp!K`+NrY@Cq!mN1E9;U3p#|Rx_6!CcRdzPJj(yPG* zz(!#m_U+m*Yr_&FtzcmmOXZ86%dCa+_6)a_LA1Dnh%;cg%4M)nPS%D{(vqG9QZ1}z z4O-d*!-p3}i-&WSi%e-9bzL@NhQyQV6{`!lL3dZM-4Xt%lf2EZSF#v2mg%L@ap38^-@j}7$?*+t8myyao|faA)LzJM`xt$oAZ zcD$)IHRZijzMItjL><^?t!6!-Hu-bs`0(bursJA=aTTBSsP9P}Q_beCp_(E|ABX%| zA88|h0ko)0)=!+;MiA8&Ja;(9s%{0j2I}J)+owqS%&Jy7GV0rIlZh!OWR&=#`d7E# zuc(P|q#64N8@cnJz$n=^d%r;D^0W4t<`pz6f6xdS)wLToHbtu>psDS(lwc;xyq+YOvPv!PYU zobns(PCed^PEx6{E4Gn82!-}+7JAa`zNXFMIw@{D z_&>ArBjUKRQKxxLhr7}=ePClNYjo}-%jb1Ss2%HcyRb#*Ff7rQSGsQjf*&#!;m`fC5* z8Y9KeNf411zCdq{`CFe<^~Y^njA79%pti5w_;D;H_ zy6@e}&%2tw>!jr%qCWIzfwHn)*WGNya_JW;Feby)E`OWwNgU4fv0GMgq}s?<`^Fia zh&GR?FT{#Cn-^!wZs;vIIv@UI`tvUnHT}02Pk-TPj*VJ8(5+um)ACtbvV3F@`sxJU zNVH#12i8+PSdM`P5y>+Ys_+LiNZZPYRtGA9L z$<)b|-m?67;FG##&gsnrblktJ|E{*O-5}zbj!11W&clCx7Ywao;={GY#Ib*)jSPl6 z*+u>H`vHLwV#^!qq3~JX?~#j_0FrL-crU3P+}G-4L;ZtmiH=Ri6L`7bek87p+Qj+& zP&24rf3|V_%IsOf<5)W8o~=B=_ZXd>7S-MVuX9lTmalOd(&tJn##U%M`=6P4G8~|2 z*7^cyNQixCI~L~vtj$CJ)eK;U4V%%k)K!jKRt}AhU!IgQ^-0xvyd#ls0JDI`0u9u1 zeC>+9? z=}UhJ3^E=!UXa6T;k);$hn&9c5PqUgwHnDxNeSbuB($X9mu3d*R)82vWI1HDp=pn{ z;3^eJe{&76k)yHO=8J>sM?aYp^t@EyHBd#JAN&QUFVk$vDo~B`LwE%K;S`73Jb2=o z`Ls{CGxq0( zQ!U)}=!}k*ZrQgC#Sh$+euv&j-5J;=W`cYJlA;f9CVsj@YCt)PTxazg$(jH;zeQ0C zS7FJ|r_}r6ErlJlKK{j3LZ39?pS$C(J%$j{gxfM?m#a6K<1hIJlZ!t+L>dzoT0NS zdIWp28Rxx?SWw_bIO=5O`~d+tn!{*UHVG%)1pn53yJ)dN8?-CzJ*t-%G__6ZVHTt; zY$@KL&tbM9@~3w8^x1fWX6m;<&Dix{7WM8`VjC=vW-#*c-!5BNT;NS~*{HiHIuBV~ zN-(MZX1?6#0-Stc@|gVgHvnX}X6-=}NTHi9)G}(xWXMo=oTG zu=5(+Ev|Y_w7G89wWzk9~_YH&jhkLmbg`6M{F*j2xjH5AJI8xO73^jO!(w@>ByujJPQ)84 z!3Ia;{%K5asfwg=*tAoC6Vzbd4&f>Ae(o^WLX2;=sS|mw+~<0E8|C%P(2ZA6g`Cxe zp~A?`PFnE7%*O03aCyi(Y$%dtN$m{${)he$YJYqQtMfg*JpGKB#{>t?@_%=W^_lZ` zovauWL=Mk8=b_A&4dQF+*1E^RH5su8L)NY0g`5T;!h4k7a@+UE^6uV=;g~;>5mcan z+F~nbE;O|Z-!SnlIRlm3laX^M6~W(3MGWN#pCrl!?+hRRv%DZVvklBob=41Qf$+f0 zo6WXf*wIM&*co(a)Y8|0UB)vQ(4}~}mXuU5nS!lvk1@OsK-}+C@FvF-W zH}KP9>=;8sCpy?qn1>;qx!>Kr?E)xwMc|1k_ja7m856yYB<1hy(WOH8!^rL_+eaKq zWG|Q*{^MEHkqm3PTF{3-Jh1G^uETchED-pC4sX#!K9Rw2!5?T~e}=E2Q=x8>i;^ZA zT1KBoE$OrInyuST09nflX1AmRUwj{RegJf7RiZEh&Y?-?#oM59iSWo``&qAw!6Cq@%>OA!MI z0n&iWA>yP8b$s*h{(@^Or04}b#C}b^P7AU zRXzk%aI#qmTj||k2(>9ltZ>Xd&RPM z1V7Ttn#i94{fBz3)cVWV!bdyFld6NbP>#Ci|2d9>hYaVeu$|#`C|;v-WgN#T3v57? zHSUEB>mrGVjE3j__;RIu$bvmvYLL`K`Zo|==qI=ZZlZprE5QI zP*o7s9{QvDO5>0=^Ud)CP6jp&DY|;FrM|J2Mg5D4snA8;xB}sn2I-NvM%}rukUVpS z=V2)O35x3lJ_^q1(>}D=kAdc(gO;-3KMTx<=L)H9>S2eQ(+Pgb!EFRBvF=7J{`Z6m zPNDh#0HB?O(vi^Ql44~QJGe@DzBcjg4ZoTsYlI|r57#lkCFdo1g;DO8Xz!k=S>Lz* z;&wOspxu(a*(jKu*-RUj=ZmlEveSXr-Pl7qP76$`B-(RJlKZ^6UY4%Q z#;h)ss#dkn_vSZMAn;cUh~jwZXBXK%>5xC#ll7-3`2Bm-QnUn7j^g;g>s4$e)y}sI zj(rv1>Uk&(E@%ic2I<%vO*9ceg9i|%_Xl!PDId%S?apV|Sf~hl*mmf(yHcJWDgqFZ z^0`>LYZc?JAjF7(jys!OVM_kgKrq1tP*Tm|WN~458cgwFgI%@k ziKpYZUD=WYp@^Q$OfaPowUE0atFbOi`Qp?v>F3~FOiF$-pz8@O;8Bw4Qi0zGXmU&Y zZ-VD^$nb1%l9^%IwK7NIE>uqOYR`?CzC(A=Rqrj=C|j2>46AI_V@dA{PxIc81ksjrIcZaGPGHiG)U9pc$EN}0D4 z4==T()W;iXkH^=JkqfuZJA9c?o|#EVFkz2ZfLl|j_>G<{bglj5Ha^Zb&9OtZiO_JDJG?t4Of#&#{qt= z5)taA*;gdm6}u!vMcEV#80o(8Ffpz zzh_oB?n}(eE5+W&{W@O;i0-YpZ_25S!p$S4A;lLGMnC04z3{M% zn3oEkxfu;Y@iH2KkjGjR*a3*VdmHBjI#l2jIs}Bg3Kb-k9P5jU8$0Mao&}oI0H96z zI%&_*%+~s*h!zP9o>n1@f^RiodS>fM08z>LbtfJdCsC$8H?J8KhYGf_uCuLX*aeJi zV&(M)5#cb05HBM%SOrk+{HFXm182MrQPVo0^b;%JtVK}+4ar(;#*QLZdMH4b{j8Y} ziV1b@YDDf>@F5;bS>SQ)6<<1gWW^DKFm0lZPO(K09>dLEai?}@&HMjpugIFA5TO$$ zqA%NT5`!UVbM$dzd19?3V?h`^_`pxMG}e3S!EkKCd1H6geiKQPSSzcAbu*rs;$aG2 z@X9-Ju+=&6Vt62C4QB?g1ZG-sg}UE-CEmfFvGy}V*yqD`^!8Nj^5kH4Rpm8rn*5!M z(HCY4)bI1{$;I|9&gHXJY*N_Kn4P7BRJ?46 z&b6gt5amh{p0g7^^fn`eLuQ$Uh3+keX-P;5%Jw`vqnKJZhnK@KSl0B4i}CK-OByZ6 zDDr3wdFwFjHFIutPc`!-ja5ylD{YzsF=C49E#ETs0DjS z+~x$hgJ(UF*X<_LczR0$R+zl}(Ik=hLmaCvuwB|{`VGzuPctI1F2uCIFUE*`MJ?N4 z_kQZxPpFSPndmGBcY6v2WjXFlevG9hIn$CwyLMQjf3D{Y`nVhX;B9OKH4?vt0o!zI znJ68IlKgLc3@#j)*4Y%rfu6-rQDwrOp3ZQS`Z}H)2MUH>gnpeZDB~VWs$ZJQA+pxN zUnY7{aEkp$1$*0hg?zFHveBtdMM<8l|Gce95~bF`T(t?1SNmKK>eutEUA>F0aH z8Qe0(F&fVDI!E;{!v@v3;}2<|ygm6mCmFkOW86~aVSr=VdyY795i=id31wRkO<&Es zj1wCi=Gj67Z*WHv!BJ(cP0=xwbL~Kg)+J{&zoFkw9UrwRaYTY(LH4PH8=W_tpf8%2 zpOZ2?uoXJ8I`{dhR~X9Z_*t!kF|PGaW$H;OoI2zlsYEbuU3PlD$4Gb?M;5&=miM*H zw(JEyM$mHC!loOM*Tiqh(T$QG$EZw5ZpvfRQeSujoFAiejx8IJKZR*iUoss%NGpPA zAK^WA@wucNyBO-*4B&!~@E^)o1uZ78F&zMJF?t2X{A_Jtd^(M?^85rI8mwqA1O%Y) zWoK5CY8n?xO9s}cEhqSKF3%q4^pc0)|HJYU956_l_mYC-N3$A-?%6k=%=(s)0?Ntx)2hnLf8D&k+V z`p^Z$!baNY#sPBdG8%#K)B9*%tOl>G>~gdTxLHK7FbeEN;Gyv>z>hs2)9=&wBKY;; zspPev&drS7uFLj-ZUCG$emBH-j*~S!{!2G@^-6Lt<=txa)OotG@ue+)OJ0B=f|XS_ViF>AZocI)#&JvVgFV@+|U zRUSbZrf*z>ntg;ru->>B7@PLo`r+yxvHIcFqqQ<$)nH@%5Vfv+S+Y?Zi7@%Xj7h`< z0e5r`fkm4;Q8%aY+(L}!cA5U(Z2JGiZ$0$IdFKgxHu_z@pf(2}I%hCY$7}#+ zNfc$f(BB({W^x>WJ3m%zeN1bn#Kf-(SK>6B_-ZXkgMjMa7{*S-5v0tFAADw&36>&o zfL}s?mH1u3;@sFzfC#{sF#CVqR!jat{BJh@Oe%I0-o_{a1nFCnC)=Ah4w+E*pam$( zNVYIL=31jJHsTG%%26Z9bXcog&w(>b6b_|Uz;IUf-$YOIk7e8e1mFA@B(n(IWa z+`_AqyZN$z>dlE=&iL_bSO>ZA&`WNLMdg9nVY?9Ay6s1HCTg^S((m&l6IpFtB(xiw zgEF0~I!7su73b(RZmgOETs>5fBYHtPyYaCUR!rph2Bd>G)f%x2+Q@&-4Sg^pFaTM8 zo>Ai3lOGIS;0=54WLPB2Ml0kHI;*2V@d^ay5=GZ>ir@r}D0gtpd#53KrtmaBM9{>v zBCf&Ev|KouDlKsf7PlwD+AooxxPSs(Gcx#TO|N@$bE5wN_DnQT8Na|)<*N^SGhNYb zd67NMxJnZ#hP!DX;mpqo_iekwJrxR?D_d+C%Falp1*U`YK${xj&6ISQhiE7J3Zh|0 zQ6e|?UKaEnpHFG>5XxLG-}zJ^inSM^23)6ILcmWBB~MdP$HZA$pzvvVsNYarSqU(|hXG z(Zvbws!o>BfNKxGW!qH8Gs}nhK|pzb&&aBwT<20iFfp`_tW6HK<^6!M{SlK(=vG$F zdE6{9c4LB~u2If9@Rr6>mmOkK7+~MX8Wdd53;sFo~_jR|9vHqAZP)@{eCfWjl zp*X@H+~?}YAFQPKsxYsUC|}7bBa%8Twi*1AiZaN$8l)XPF&-u#Hz!vm80IY(m^g%9 zw}20)Alex(k}*0NDHS(gL~_SUETeA$;B@h;xYP1ENLWch%$V8Z!-TxT^cT6+2N0UK zlceP+hc9fGmNeSqL*42LRzHo-%o#1%gyH>f`%%cAGt zNwP90Vsn1txeDaNDLXP$63pw4$+w`Th*DMKdNk_c5RfjV10nqfl_c-hT84V^+!W^| z6V)v-Md3Gv`<2~W+8L7@)!zQ>R}hIHk$7FTrg>|lv)qs1K1g*hrsY&!NA;d`GN6&M z$36pImzq4_bU$Y&)Y7gXh7DHgVtjJhb(OZb*g5NVQ8;rYmU7t-iX3BjD!CpxIZo-G zm`*s0syN%8)KS*qUd;9k4=gW*Lq^c%5*Pk{mK1g6xHn^k48Lm2A6 z;Z2gE0B%0t$bn{=RAAwOi(+g)f55%A`GW-Z_j86F>ZU}LWpT3zb5k!`93iouq@y+B0Z`4|w>48)&n`QYa{--4E&7#KfLkXP(7fKoV zmbdf@D?`i1s0n2&E+MSy6)X@JgbStCo*N}AvwnIWq#V0qw<5RVFk1fgtU>lk_&{B< zZzVE-mzxu-wrG}8>~ExC`^&CGEa~AbP#XU$2dpTWyO#vNC9%%2l^omFn#5v%?rA=^ ziAZ*PkO&o~1uJVYy)u85b=X8I1-1v5!B|8}8N2F%h;7kYJmHfjf6yXPU;!Ursgb2+ zR4%G>eE>8KBij>XTH_?LGE1*z_`EBHz-nGa&jkkx4`#%QrsDt*(Oc74MbSy3^K}w@ z+;;j8?0zy<9rOhCTQbmVt}QWjsgl1T)Rc}rsXQbJPB49=e5XGM7zDC8xYCr3mi+Mk zBFwQhP^qa#yKq$CxICX@_?!vbyvrfdHO{iYvgXwuM^$frnJ6N}UzW_T8&6mEy~*j5 zHVrb&#Pp2LllbJ)y6_Oqs!-As9x1)H!INd4k#+LC_)$eOC^HBYW$p&{hjmmqK1#b`7PJT{>*0UE?le-^x28Vh`(1_PgARHRCPdgUqa zaZtEpJ<8=DV;_Lfp{E~M5(4ik{$`SoS-1*wn$Tl}1 zW4`W^Y{JZ4`3Ez}8{XJ35bG}KaQ0oG6%saY)h|)Sxj5|ef#LY{U|aN*4`R{(3>BxF zu2IeY2`@~r62IN#c0xPSZnRW4MH3%g#mG|4C3Dp^l?hd%6dOb+g}jnX3l{~GTnsKj zP6(}$>B1u1ATW4mQn2EtZDuNcV}N7iZ3cdTzn<4Lya7a=+3ticU-|()Y1QC_R0r*HFPd!M{lM^_U2!h^5jW+HM6Yf;J7(6_1z=x!=QM<_KlW>5MRJ&Etw3|THe}P zbpz8a*Mo$bdR9BL{^z!+1EX9XW&GUeF?~;g#aH)QZW;ic#W%scJ?q#XLuVel;53HE}u%mhbdL7sn1W=vO#h z#N|)^J?aYU;0&@HGW7?+@o%q3x+ZW_W_Rz8wOrak>| z86hu&!h&qp4q7(}a^=Jih)11++u?V(qs@@rzUT+Lg5-*xmOnHxVFDMqcSfC5kXuiv zq1^(Msz;QekV~+mvg_lzsiVY2-ByuT+Yr`$KuX_&{-EMhQ45}kkJ06;1b@H5Ae}NB<-@hC3Xr+UQc&?2aJ0ow?Va|3WL&dcjbA`HE`yR z^!~_cq}Gg@P)i`8)oa7b(z{b+fJoVA`Boa>hbT8sMFez0m?_c4{Q_pV-B?k%*d0!S zjB#6Y%?nmn&fMkMhtdyzXw<2$i=8bLw4|CN*<&{hXr;CE7q5%}LdT>eg>39#X>kN$ zO#D{SncL8Z;b#Cs(Y9!COHtA`#mK>+CM0mZ!-N$VbwL81}-S!irMxWmx-Zbiw1wYa64ksHH43}8dK>&&L1MzG*ssq z6IGOz!QB7jc{zCkT1My~{f0KObP!)=k(Fai&%;*p$WU9=_TO$BOiHerOoXYB z3yL0%P279Q(l6~S%s6tgQaM?XY`lK0p)V$0!Xo}xpnCD|W35fS1NprVh_&T)ugi|O zxA;F=vVd(!{^N!TYHMY%ge?y@K&N;EQXy=LkX${luD>!MtL2j=w{&>bB87fpxET4@ z>!7=flngj%K;}o2!E@ykt zcbp)jSSx3Y@zVPl@m6zY@@aKiNi^?Ra4z1?_CVlsXsNR825X^7R#u;?s$Sk37T{Y5 zn;Qd55d$7>Dxh)NG^RJON)M%U1n;Tj1qq#oznt8Uao=YFYWqLbj^}wK2cDM!}`^|7l)mEY$ySu#1R6Yi-NM8k^{?^S9X1ge5INgv)+yiq-g1`YmsN%9xm*})CC$~ zA6b0Mv8L{X1jl|Ammk5^C)$JUA#a@l_8;yfGM7Y&Pm-+zzB$^2zKfS~8lhYbt@QyN zs{HvX&C{16Yd7;(E}aLo?Vr5{@@JZSHYCxCmGZ5t=Tf#wG*>$J*$vK>q8D$?S!=YA z;rAD_MYH)>wKYIZYg4lPKrBnI+nR`dEW2=c*3(UV$#7ZE;oiHQTy{2ZjsWP%VFV<8 zt1~fKNRKD>!{>|wKF6ICYsR=(_v*$7{}lu(w&Iy7Z(dQi@X}>oMr986>Sgxj(4DcH zt2aPs&*-c=V>QeeW^^=0@9P{hA{rFvZa3q==1$hF}(uA9zRV(#)Q(9;TZ`4s^oUmpSSE z7_=FmFtIuTY1w&mSay#wjQ$ra;or#4axdiT{WM>L&-@FZNeW9+ibD7VaAM0E(qZ#c zO8wgsJ=cs0(6^n3&J5|#+q)^N%DWHy)l5bepDXX=`7U(UHC&I`;b)X`&8XXUayHP; zvob9_IF$RPQEPL_X@|Leb(I|DpUl;j)xp`mYVIZX1T1Gs^wc2t&KH?crWeSV z6!Z36sB3pFe{@)=yY?0mT1#2)xtPt}zFxH8*Kzn1jz?HXii4L8r%Y&YSqHBgCb3FO zQ_u^C<7^eDf7Ze7QPtp)vX4LlK3ikX9lMZ{`0c|0V1C>Qs? zW|H-Xn6hEUb;US7&+)x!*x{`+mUCWXUBxrpa|_TqsRbhMy1|^Ef44kI{S$Qv)eFif z%1zfVJ?MJ)0{%02##`@WRr1xbTJ+3TD!s1O=q$|e!i?0{7nRScb{wrGq^FYC2V4(J z-OjqzOep?r;k>Zsv^)qX#X6Pth`f5hh4|&`I%sp=UqW3oe53zahY6U-WFNA(#vt2- z`{9geKWjXr+^UU-nryBJjy*BHz>F@II$L$-NSOOc4dLm=jEIiiTR$4bDuw;DrV=|`?njTJK4<@}VCP0(ZF+rcIpG+Q zw126Fb-Tzn;FVDx>-3i%m`>X!sk%!>&%Ob;MV39SKz7l$FQf;D@C;|hSBTLU)Ey+* z)yXv%QN3>9;bPfGAsypX${dKGcEd1939;4R#JVuZ-$uy_-rF-IJ6vvq!08D>KAu&` ziy=p)-(lXYwd#cX=FsG>tgB5PzT_TVC?bd@9N^}glr~{L_olO~0~i@ZUS2%Y&J4!0 zU2{V5@QfmiXiIFBh0~A@c$4oKIL_5U{R8TB2XgOjye5)kB7dc{So`~}>M5AmuT^X3 zuL!oa)1Eh$cFO|k?-^AL*g8X=EGbMT{6ShEXs#^Iql6TyqA?JrI)#M-dU{M8>G-Pv6|#< zvd5ZdzY6^e=bCn`F6u3=+Tz8NvTq59s3pFfQem7dd7+~p)uMgKSt@JVzA&2~D!vt3 z0}CDS5FVrdHRA(JdRxI`{;ylY{uhMOwS{^?3!}DDORwUJ$}U>8{Qm_xuA8Whvo7(VF+H0m2R%Bm(* zl_I{`#bs<#^Rh^mo}c#jiUpRzK04t2hh&}qa{{u6yB0&;pHE?}CtaU6^w^sfKy3lHo(eJ~JuLjgq4 zDdn#~dRmjmTM{3>r@7$5U}gu{4%O3evOPBQJeL6IY^@oHGU4ZSRn=8~ z=AiTWxRa{2(VgoNy|#j;QLkv718$>}FHO*547pn)+EOh(~0~^DJj@DJB32Y{o-ow$<}iQd?sUFU=|P1lERXI%5UYqsEYEE<~A~HGp48gPr`W2YS?AgIPHA{i6Kly zinlj96vbj$UcgfO2RtQY{Gc3(W_z$i`#GDJ25qjX zliY!rQ{fDy#RIM%X+Xzww6~)!`YbMH`C%fRDJduTevf{;9(~qH&bJ3P3!FL2n zx~hd7>vF{3+zNCQ%-|v$a-K48xU2l&EYhSN?0(mFNYApKw8CIg3+mEJ)qU zmL#d(8F^urtg8Tc5y$cm6x2I)mafvDap%35-1CwO)<$)Ql^b|1i5tZTQkZe>1NV8_ z7Tc#5+bj%cRaGlM@pc==JYN;1D;9ANBXg6N!sb4!x{=G-JQ+G>|U@Jq)U1!?`85{m%7+@eIJjax-f#3&bwcpOCXhY8_17A_Ozg+y!<6IN9%u%^hi zD<>z=i6Gl7iO{9W*t}LWOzz}bUQ_hPT$XIPj;A4e-SFK}RB6^&PkuSew~w$Oy-;2n z^5kx@3#9h7q|?bSBPH^uQzqy84v$Aro-a|qyl0)x+AK&AYcgtK3{gq9=n;nLe#leW z%kumOW28rX;2EA*3w~!NCf{S-syA}zlZDNDTM0vZf8*K-e-*7Ur|f2MaxU}m)(m>4 z&339hGT9@C#zpz$HW|Y<*jHWlLeN}KAmown#hGr} zQzPhFY{l#?Z}tU*ksnKM+e(Zm#Md)oE+#$cc~+h0x7p$5<{h#VvMY48(1!Em*Bq|I zoYoL~m>196={>n>+;MONld`8y9LV*F70NHoeD1AjtvKgf6@cw<9yMYPSaHaPCB5#o zQMAkBGAuo-W6)z%Otf){mb#5);$uMd)qpK5`(#BVn=vc?>w^vRH_khMx2>P$W!u<+ z@XsvP$0zOGAZ`y@7aY)4-3lw|QHTC%C$MISa0<~X>o%(Q_HZ-jx- zuNn-v*{$+h{<1eB3Qwc{@$-c?4>dI~Gb5>DgYVJ88{8Rh(OClyKCj+-1g9r*CeE7U{#gs*K*Oo#qg*7BwL$kpdO4NW`m_*&~pEZ}F9TK9sS zp*8Obe@!O|e?>nx?!1!-kO>Qh;!N$TW7uhp>&)eiBAUTPzcQ0mg=U}XsK6O=tf_oe zP}<^k>t48#!0)n0YDh_RAj~HuJvy}!G{L=fqUjH<(S(nJx1p$X`wGhDY+rEDh}jB6 zG-$SRO#S%9H~$qTg0`RgqOSMTA}i#A$nfP8wGOMtPqt>MZQuLYnq@Cu*7t9DaLx7c z>P#t8*ucTX2u1}BlKrEz+aexm;Ru-HuYg=tB;*S`IztqwzNb&Ojb7zjF z7pM|$XcBq46f#h^QWU%ByEr~F%szM6@>)3^_bwu^z1n8Br(q2Jm=B!`<^K>l10Y~% zYc;5L3kA7vHm7om_44K z{JMPF?v(|s-0?kf`dKrhzE^pT_fj8kx7XoxDy|SJJ(4`fN)C{^yq;;fizk2%27|_~ z9kQJ58aQJ2QGZ%gOTzsyoSb5XAI)v{@;RWz@77*C`2N0Cd6F6ZzN3}&1e(sb%8kz} zE(}0bC=6cR#ZnBCv7l!bZ74YGilUEsP`%7Jr0gy*Q4f)5JJ{A9wHA_Ls)e57Xiq3- zwd6)@-c(k$ehO*(!O;EHzs|@{f4!&?dv^B}TNXe@-sJ#C+h&?WdMQ!gd=qP1t?!b4 z%%nD}4^w_)?8n)~{Z96v|F{Uo<&QMDd(LKwDtYJSlSQ$?sm&$GPF%z%2(P$4Kd|#@ zpvl+#T~X>`RNJ3THD;Z-m5+&^V9Z<-`LZPclNR?~iw2@#hCOW)njdS+d!3q9~)x-%wdQBe;#FZsB3R_BNV;PUP% zmmD`ypzU_tnh-J_Je+=fLkkY_)iY}_*s~e_p%LpWZV%ox>&tQS?{+0&@)sMJH;ay= z1FGnbKUc?&gKHmET5k*Y+C6;2sQsbq9q^c8nu}MF!Re&qc`RTmtWCY;x6xHu#(MYW9lazjui!>^^Jp zK={G<@G->|qPVN`eD$7zDSd7Zyt4Lxj2-xEq|EcX!%OtK;Cp+%!1j{nET2gRoQVzb zOAnhL8eD4zMSs-ovqcX#)}2Mxhp z26uNKoPpprI1DfY1PJbK0~~VS&vV}ITkl%u$Enq;YS&eJ@2=X_-Bqirdsi1bbLC>U zCVwL9tR-;7@4di1?ii&P63PexI>JI{4Xdu+7Z z-uJ@rPxvP8G?{(9GUUpe3P_cNVR(_rEx&YyjlM#sEf@|D??aEg{PL;YZeY!D|5JC} zx!&5|)oWf0YLLj@V%eC{<;$fP-Pa7iW6oto^|gzTx~eZ;6zZG!>r;)a<;$g*)LpPgY1`td&%B5 zB1afay}vZMcieMD4D-kMmuwwhT~IPF{R!~y=DGiFJ4)hk^mOTyFT!FIF-YR^Je_M! zg3$EW@r!<3@6fT1!${A`_%B-;~D=JI^m|g zrV-OBYCpvI_p%Z7Sb3hv-4n0V+tyi$_h{pdo4xrmBu~di(2we!Hm`AS(fS}gw7L!O zH2&%)!YsC-MAHRI%LVvqfcYqzX~V<;GxJg+{$20rOF+K7BF)NofqG<+mzGb(!#n^3 zPJmR(Y>4!+SvjK}^+(oR(*-B0KKBm=uf-RA%vj)~o$)Y^P`Es6 z{Rqz$8s`z6|IpwdJbSoB@N<67?(ughn<#W2(Pg6y4$Ms{eShH!WCaUfVI<>pIH+VM zE%0~{Of&6&&NtN8H6U+MR&Tva+p|g7HppmdbSy?jGZTNHP<7F8Cn_iqv<_XJuf6k* z(mSUA!(Ec$9KZ2y@_@T4c+r)t!B;teD}MI1z-ic`yjWzU8;W;JqC>810B)Fb=44J_s!6sla#HP7=iI%#-Eh+ze}j&73l!^IZ;>WE>f(l1io16$ZK1X8FGZc?9I}r! zdVCK_%crdejEQ@mf!Ibh$h6!i`QhR63tGwH!Gc;O)n@|%WbuOYe8H|}mVp;PkQRbU z9)J!z;}4f1KjDZ>-m&L%JrD7x!TUu-ceLrK2AFXlMrUr+0_OgS{{16 z&PE`+N_31?%_d1%p~qiSozK3vV6tLp*YEhUNGNzWyCS$=s(UENsRpUdud~bK>*t?% zm-Z|X->}_zfzeDuAH46HWwWj21-J94*_Se2Evyu6SxJ8(GxQ+Uzv}3{Sw|ou0v^++Ac2Kxx^Hws?3N1|90bg*e7 zJ_-sSAQ{_!UljW7+E{L|jbOdRPGm?pfnw8-7JxJ4k7N`1fRoM&u)7I^LKQZFc*7#@ zVAsz#e*&)GABP6RD|Zt}jt9_g7aY6!oMihxO@3*;)3*0gDnwu^2&Il;9=>Nw z!s>rI<`?Pa*p!7blDUhU)$q&X#4Ab|vy zok*?N&k=n>Io(nCa8k|cu*QhDDVmjcx z$$=c+F~%DDb)MN706iXTE%*xAtQ4%T=crIFq~EckiuP9*zbQK5I+U*ly1=kd?){{% z4W+iO!PrF{xPEIgG|(s7+E*i+SstlHq>SBeV&>A#?SJdEswnAt@Aq9YRsOZv^GS7K(ZrlTXw{?14XoJpK>L7wQ1Kcigoepg?pPi zr?s~E;G@?#_3<`yH`QRN1$KLD0FR_xOK7yCyWQ*6pN^<5Y29(pZ36@Y`I^_~Cvz)9 zjlHKX0&FPJdfwi=FzhtWl+gYKt&`hIuYG0TX`4A*W0BO8bGIUu_BpGBMfm1}+Spf5 zvmTFts({%0oqLxXN>RJNyw@exSqW?9rG;_jlT5ZppAl|f z9Wy&UG)bOE+-ePx@{WZSS2cgTBRbXGyFe#9LnFVLtHa$Iz7aeROuObeLxXg(Zg6iO z+(OO|xM|(G_m5!ZRnV4Fgz-T(d|yI$3bg3=x$tRiH{KtARZSSRJyE--8wV=4GcU&#UC-zE%Ryh#|08abzp6PKQocj=?lSzqP7KUTVMc>`wR6 z(hc7JhozPKlG{zpAj>5S09y<{aA{6jR4=%1Y2AjMl)ZUDY;?K`w9z{DEDK0eLQ% zaH=G#Juh}bPIX9`9S+fYE*_EV-V~B@xY|R&HdOeGYG)3^P$V&orBPh$*gx2Obez7) zhJf)P64PZ}r+{OJ-KEf!cQJ$hk7P>XSIF;g>misNFk-VgRwaq%_c#8sx2* zNk98GF-k^gpRfj zCKICPiHat3eww`x!R;0`og0?R+2W>iAbYmXPLc7R9)zzo9#k~?^ePO#$@gV9WJJqU zJC5+3o}-C^P|kDuogyu#UhR_E5_vHC^o-I>+6?~qbGzU2VeE5}oL0;Mexo%Up;%j^ye?X=#Pj}@nzmY@bE+D!ZA-^$pe^ej93LY zBC|wB*q*5J<|RUGm}JVEul0}UE>Me1$C{%kjM?woA3lhL6{H$s%FJZ8I~q0|r7)7Q<|xGmp_Z{e!KGKS@1_j!nhICOzuC% zetMmO=D88HxgM&S+1ZTjLl$WL*K(u$|LAnsBCH9{m`f8K!gyzZQAiJPJXZ!R!&RbHBhz^ktL#6N=>Or++vo_Ma?M zfaTLoBRnyx#lFeqz30lCFli$J5yDW+c>-M{T{KK{?7wP)sN2ZiXqaM07L&W2(>@~; z6AGo;Vkl?c!m;;@Jd?7+kR~PpiAdC48P!j@_VJ$c{CTxweZ=BvK>A>yPKX6T@c|K^ zRp{9DtAAqMk~^i)NQx@YGZYHvn*ap~shCw8YBZjdJ9Uk4^G;sz*He=WV9(|<##KtU z4ftP7*hF}h=p2W>`VbZFd44@3Zn|{HoLrwH3HFGL2=T3_h^kav(i;hNE|@H?u*R%g z-cQ=mrw&N1%x1vVqHugiBCi#ymD>P-DvDAmM?Hslk1G8=4v$|SWOKt51ApEh4VsbaMiyeG5U za?o>{=WHm(A0{EWE_x@U&i}`xB{Kjbp*4yL%hhk~j5K>C_EyS3bzp)k?I$QwCjGNn zSaFdTl~}+ixAn{jVS>Y&^6jberQd*a>~;=%ImtA0VS7x+54%@igd-d!$!4GT5^eV} zN=PL-Z$5fGkWc`nLdnmSWfroi_uP#lmGq8NWr>1fF7@}m%SJYnsv4HMeNF(FMUFFb zeqJatSCErn z96jz$u{UWYUS~F!bD$S1Qk)(t>0x=Vd31}938Tw_bT;o~X4U*`1)g)B-)lTpsjWt2>gXR!m2_W>Ke2pIg^G<7Z~iI&<8W@(BKWuL<#Nju zO5hb!K+9C|+kQ+>h}nylx@l5%PNqdT6^?S1xLy$P>}NAAPeV|*a?OmBgC#!t&UllA z*M3pDH8KPySLu;T@b^Y?aHq(p$`sq0qg0 zfnP3Zo3IpBC*K2)prGJBo2_vnQySd5`jqlSgHY%MNL1ZvBZX{j@aX7gt;bu%#WqKK937lg^X@h#MSwV%SH z;^zsk*H-rIhc>f3S@1*9T&wft5$bJ0mSr2*^@h-n21%Aa>&SqJ{2EzjrW+J>={_cbS#x zaVB7`>pI0OdM?t?ZmYi$)l(_d?L!!*wPagw8kvdUmOqclxThZ4J<)eAs$Q>g0^=XG zm3T2}qpw`r?#2Ov&_^c+)Hdp>dkyDGm2VbR2_kd|Da`#D3ydd6ecUgWzYktw_oK{&763x z{+$>Rpb5QmudijTjH#(LTjKm_;$b0oRS^2ZoBaVE&o{X(!Q{=HtCjGJNxY&}v(|TX zBA`m>*t8s802SMk?PiE3!>NlADu2k%03)nzrJ4o1GbWXE?fDC2#vv~4L;PRy_5>)*SZ zMxL&}#iFxJaZE-;M;U~oOjXe*0qOpNT;pqi*iJ0+%@TZ7_O^~_)I6dQ(uBqSN`vsJ zvwTUSO`@A=+wd^^#mhHDws6cNOV!w5Kpq2v1U@nS_NKOnpTz=j=$P!R%q>iHIH<`sh?4*eydD1 z!0sW9IwXibA%r#tvh3!OU-0qg1P&D_>ebP2a1Y2hXPJ2u=> zh!&b&<@p4^E&$;04XZ*A7Y&OP-D;sK=TwNKf;$=x+ULOFc|Mk-C<+-SmQ8IhIJ zo(yAcKWR~C^AbqFdZ6kJ#!~h`HvS`Is7Ht{s3d1@R;9mYaa)V@5OtZ=yfWF7rk;$k z@An`pUp0_DqoCm2kSR&6MCcR8&Gscmqj{6(Y#y-++3}*BU}>KUh=n=UgW`@C;%qA) zR(1?tlCNJoL0|177tC$=WDe(PJ!TPV^>L5yKDA8_epyc)NWe85b&x%B7wmINH-@)0s03 zP8S`8n`iF^-h_J+?0+mQbn?4gxwVPEPd&Q)IbGCBFocn@XLL-`;$5%qs2R{g_xP^akT8L3GFQq?0^XY1&LKQ9|+1C;;jctPI8dl-TYDx-n%{M#Zz%WVA2X{xtlJ3f@Nc%rJ zUTEb&bfME#UI!QI@tPT4=e~$T4JsqxZ0u{}Gi^6Q$qwUTMq%6^;2&mQZ@&`7H)J|$ z2$f-Ssm~%<{Z&&$cV^w5EB{?z#r;9B z53Q`*7(dTyS?0@MoD3{*@Ku7szyNKV;Cg>QTu8^yyiH~DL^h2Pw*L&nouqjAc)+u* zc-U|v$-Ol0!Vz_sdr9YLs736c7C6DZ__A@Cxg}a=H}9kwrt1Pkym9-yWB&sAv-P)} z^hcjAGS@68*diaysUM-}FT$X4%5m8OpSaU4HS`xCl0URu5xp0uQWCS&`h)}tQn+E! zf5*7@aD#h~dfWOsuLuy_!wO)P$Q`TezDs-Lpgpp6hW4P%ZIc3`qu0NZn3;p@q8-pVQ_-_<~C^(vi~D`ep5ScU*_W8PfuuvGmv ztTiuEX*qNwRKOeP+8POodnW^QmCQhd$}OP@j5QKxTokO&1Co;DGtyHtbW^NGT^BM& zkn{-aMwoq#q4>9NtlNwqM|CYTTb;d|9j&})crpI$G^93Az^!%@`AcxslLU&il{nAM zy}ZkAY{tEUpiqf^T^;?7_La%gC-(BTUm-C<#~FAjgVJ??x|wuamgP4HEXRxavNGCy z6*GD(L8^BoIHd=Lh(_TFuh%Xzuq1)VnI!d0Je#D{!>cSt9K^~-61Z)_5@E%~-+a9R z4IkdE%di0R%~a^?;fIP_EAQ4p=lh0+0-#y4XsvvV^-2d<5}cm5lM(`+w?pUam!+fJ zOOO_1fJy>?ji;BXaZx>U2kdnq&;mmNMbR>QbyPezg(4q=+;^wF0dVPU-G}CNmpIbZ z{Z7gtw)OmdL3Js1{cm)uaNb4Fn2AK58qOz-bbFBCYu^v(vEHq$aF+V;eW%j6x0YyU zX#PmLla7!ZIlj`b$|RG!wdf)t2lnsa%3+TBYzspxaM)^2m9D-|(O!PqLxO+Q+H&S% z5swmq7IcoBl{tLw(Od*tWlJ+o}d3j>BU_sEeLCH4Dwn3votyo}PDH&W+D) z)ZyO>HhFVLydK>-IHmwA5!=>Fd`tA)(e_}$^~}jcQmqr{T1IhUoWP9W3}HvBPnn1E zUcR^EHK-&>efFno(W1_-Nos!)563~1#<}~)_)p*!mU+(mT#;y#y_q z(#oJ|7=Ac`)#{?1!#_+7p>>3#B2*7LB5*+$Z8nFh#e@9bCNq*o^=V;*_~D{Z?1zBq z*BPFxyEC*J;Vs-H17n_>p#2&3#?m6ks<7#c(h6q|%xduZ{Ahd0s5N6Xfw<-_qGMje zUL`G>_jpA>BmfAG2X)o6AHQ#mKJlQZ842k*TL!)GO6E(lz@nL%y*gHldS1 z7nGxXnk=_@-`lq%ZS10&y~;37;wEjny$dlz=*!pO0uLd)uS zvkn%3YKE3P(LQ!MPmRqP3kzg&LrmXz0@`t3hMyKa(;Q%RcEU72iphd$L%{;MUn5`| zt!uZL_^$2FpKW(k0IuQ+N~d~>A3lXj@E$u_>z3D?#<^>|`Z#!7v>(sioJ1pJ-r{p& zHtK1BelHgD$nmz7!g}H;e{vRAyr2|k8bwpQxy=e3Ez%-kUEeegdAOF9iN(w;b#V1) z2E@{NJWvIWHSm|z7*B=_N+y|N4Y-Scai72D{IJxZG4sxom%pS8!Z)?=`&_rEm~Eft zH3ff-J2Y-N?xZYW11UfM@NQEe&_mIgmP*h9Kt2^3`FW_T{z3}q>heS>F{k?GcfY&| zSbK0}`4})MUgmE>KYXo)dpR8drO;1M1%N|=rq8+wke|0!q^cMVLl}emJ}Np(PG-IJ z8NH&{aL9TX1ZGYYDl5&~zl1QhU-J0wsW|D`={Z|SjdjeN4^=KNrtGw}Otw_CJ1xFg ziV0zey`81b!bQBcw;0`^jv74cEhElgWY*+FB9@d&GH13>$|B@=7cFaY?7r~Ok-@xp zx`J_GR>E*8DVMTo{*L#MuJvm;uaI~#=@wK^(2}KZh4&U% zK_HAU2(0805MK_ms8?{mQm|AV*HnegCnyP*If`auHqOG{Eb!><1P3P)Jbxzqv0R8o zR(N(@$>dbkaQ#CNi+e~_SnK+)s8Um7^7jHM&c7!Xc~f3txvKFyO`Yf_q0 zQXbJXUL4j$etDwvTCD(be#@%w2`Tdd4td-w9uU0?1-56_O?=QlYU?YF3UO(@fNB8lZv;1Smx6JuKO@#963<@ls`xG@^O>f+VyCB-jNVPmi z^^>$>>T1F?x8`Eret`%kLx>l<)w= zXZ=QyC-KVB72hr%3{DBALezbZfzK~z&L#5r>AvqqQqE&LL)LrYw$`4><*UtsEcj+e z%#L!uL$!GHbyCZhHLPG&thTf2*mko?IEX6W)z7W>UH8x%@Lk2tjCa~OBarxf7zjU? z0rKt=xY^HUEiabpyVt9VJZzcWHB0jBZn~=On!AB_%w26~vvh4>S(=FQEWNq%tkmW5 zEQCjgkLbY~bns>hdVAi0kcWbIDYD?@zOtF}qjs}d7B;gKRra$K@ET1K9_2EM49l75 z5t~_@vHIQMLf&2AC8FxkaFH3r>8 z)j)3|PztRi&UYaF=-_g>8D*k?HXj69>ti|kQB{htU5Pk?O?#28p_`zdm@ow|h1&$l$ zSTRHFCW1hM9^dJG@49>Ddr;$Ie=ixTM}qyJ(0@zoB|}b|*`&~|&D^Hl^^$A}^qUuz z2I-M{&z;5|ysRtBmrlA$B6}6Z3f#7LSd%3SO+kgj^8jx}3pi-beYe1!K=WL(Ul&rP#H@+{ zfV4_P_Ai+4_6+K)sIwj}eve@vh@ixh3OSi<8`wI3Od8w7$EM)!rINhJciQ%%0eOCu z)*skF0vJHInl#GaifWa@C!_5_*k&A`c)Kirr+ruynjlyL%s@M=c>567R;`n)g7v`? zN87_QsRH^XiHp}BYzSVuKnCJc93I~pazd9)bd0NFZ0zREn`i4}zly)XTl0($n#Gl` zk`5!9PEU}Rhp%oo_1m=*S+aN7U4`e69>Cco3{WYMMV)SVQ^Rf(+4cFs$gr4eEvV2u z-Ng3rMV>oEfJrC!$Q*g>^%sUrC$Z9yuKxA{vXDWXZlK zgT);uASctBM`ztVxUshY{__8K7Cy}tW z6!|Pzsrq7g3l;=^Y5WPSuFl_JuHF$z5E>!a~;&TpvaS`ch=J{nl* zhI#>N_^c>{@l%zEwfPxhnh{&O^Rk5?sF@O=*^?nn>hK%p`?Qo4sTmR_!XXo$>BMSQ7U=(L=d9%|g*6)q3CE8HQZUfC5`xmli>p$!)ys zuB@qag(8PElW6iCiKFrf2xWQjb3<}H3?rTy-<66DgYzW+4&F`tDDod{CogF}{Ls+a zAz{1()t<0Qn_?RG+7!CFK7G!iGk{}01uG5c)!|7RF65*VlbrrsX;RzljKP_?gj_xd z5@2AjxWRE!q?E>LExI?-uRkoh#Ua%cY@uTAW)+4|5vS1=gqP^}*wJN>(MA73ciclX zUh6+DD*G~zx=rZ13?GO$&MpafwR;Ms2_5bAV2;h`X=w5(h_HLFE`ZSpSjKdPeUP1% z{i7nT+mrt7!CJvlWQ?SAP2RUbSONvkY9Elq(s0l;-{ z!G2cWHB~Tve~T*px@1yR6!&o~Y?RJpZgp#Ng}V84@iyx#T5#@hF}voXIz1k%UQG1Z z9w>S<=_b$O_isyj$lFK)dF%*CiwYs#i;G`f1~qdclQ$bm50psl5j6nlI7if%GA)z= z(kVPk_|h|lM5eNciOK2zP!GMby;r|nUeq5V=G%2`y?1jv`+jhM z>k1mo!wi?Swi*w|4ChyCoOI;sJmXU0mFf9Ifvk);|k_4#Qx>4M5D( zB@?S_F(YU6_K9`XQDY-+V=GlFmmiH+cSr+DV(Taz-wLJFdr#A=-Mijv+`Ea+P}G{- zqJrZw@m*j0a)pE8!eL+DcDf%KLhcUo%?TvLBf~q4Mq_*{jr7R-t~=!i<`}7(K=1tt z?`Rf$E3kOVPVTS?z8g0&ARO?L35SJDrLt9!!8}hpJ!-}(*jws}R3lcM07Q)lqRSBF zyQlLI@4 zlo&^j_)sCaTB5O(c$2XMv(YHdm&R@VQhic9nJzq`b(#XSK-ajD!PhvU&-%{p?au?A zfBZ?S7WKhiTq^vzSZ!Cw>#(3T`5m+O2Ey*DcL#PYLw`|PE;Kb0qmYLOhVWN}vL0bJnx$;goPg#xqUgz)9kQ04D; zao$;@AC7&i$Yy)+t3fu3PC<4vnyRYIg4xex>k3MKg6O1W^wrgKsh4ph763}^crJFU z&6LY{X235U>I2|~;hiv=aH}#2-APm3hZlEDn~v?uE-H5VV`NL>-nl}OuCG+%sIO^u zMVz3ysI)WTV8!kHe|$-FyzetvmoGxz(V`I=@`T?VL_@n@&^ z5O?coE?H4VWktnyy8bjFzd%|3-u!fIS&=A+%cNDH1C793?_jdUk9c9H$&6{ja;&+$ z>|jU9vQWh=e2T8=YgiwiY9ZzP<~u1jl|>sTUGcSPpS>67kuo2gzr2FiiOa9;&DYJn zQVaeR4eI+4hie@%TuAilr*qnH>i)Q z_{!5nfqG8T!UG`kZthpidvu)F5!IE?l7e7TB&`+Wwsy^jov-%VJpOXZG8Qr8MC>^$3QRHj_3n{mRBjGTNlgE967B<< z5$m>Veh!;KE{=XuRlsEK(s_xJ(7UaM+_XidW-XSYs2ql^Qs+)?^1^_Ap`G_bP`gO% zXp`d6@9cIAEp$9|Gs%>;-gLFj5-}`|(gQ1w2cNjKN)sZIDHnbibr{hr&V(gO<}Z*} z)TgHUzu}l}j-r`z*NY=95}dXPo=E|K@zexu5~X;eEArI}#MdN_6fF--{5DeI)Pf23e`}BM%)mYn#zFy znjLXfhc<^hI3#z+eoZlE{cQ7?N)t5$dTu57hfSxW0@mHJ0%UqAF^nX68Pgs%uLjqH zh?}@cDPE<}6U!u?31Jew}V53I}dw<5htd z3*o*6Z`3bJCps7AL>qoG7sOua4kkvj-?lU}*9o;3_MPr!f*AMx3g^RHTWuGo)K|U7qykh=3xigiO-It^}}~_S6t~8 zSpsFS4(dy$vDZNI>MHK4sxC7#WkcO+Y~=KukE^;zi3%E%4c`{Ns9$`j-EU6JdHM<( zCcLz?q)K#n6*D3hZmmr^FISIH&QYd4DM&U8Z58E&>AfQEJ*(R<=hUoOi4th2Y%QC2 zAu(BlJYYyRHf1cpSdsGus=fK~EpHo)Cltn=3s6)6pC^pCrrD`0Q~=I_3q-ot zSiJ9QUE)VwF6+2mY}u)om@Fy#Unt=$mD4%VEi4e5uF4;*AJF#N!=NVI(}}Q}NSidm zOYAmQZh*J?;fRZFPKv9Ts;zp`RlwO%cjt>2es8Mz0@`|m1w z`r6G51Oc8c0t}J;PTX>hbGgg!E~%TX^(KBqHya_S7(LO77FGgOCq9=j@PUq5cuCn0 zh0KD|s3uZQ$(q4lYMX6eODZ*T4EQ3Wnm%a0)kS^?VmFrJ#&HtI)&OzUo}+)RvM4Mk z3?jSnT^Hi|xCQtTrLJjO402&VC*iF4ra2y=(kpGz8>Gq6t%Mjlanfvs*YBuMXP1^1 zTBYWG6Z8UhGAq6s-sTXKR^TK%)-4pzD{Y($cRrjNPOMYiqaprcBRyJjzTx7+Y^gcT z;a1t``u#LDlUzQM&RO!$`Q;*_(GyJ2@TyxoR-?t)zcKsD$pZwuK)jGeq`BfRXKdaE zzjdT7b=|N}apI*>#~$z5ZzEKGhhtJvs^E7s>IoumfjR_y5s7Sr=(R=ytOJ^CYj(_1 zR|3DQnS_KPl%#Po>daDH8Yks-loN+;uxjlj?AWLl8$`Ry3YLuIi^9dz2wtW_&GHoy z+dM;_bv4F31>n#R2mK=>_S+DofU_@L_S}ui`dg5Cxm9^(RaL2ljgAg+aAGFFE z$)Y#W6HJle`_&7*K8V3hejXVGrVO{cVVIp;(LH^Yc95{CZy%&whSkFVTR{m!yR+a|_=M5`K6Th9I zj=0EYUez~*AGC~h3~Z1iUry}*@l4!{9g8v{V2VWwH1B~fYTpdwVdY7DpF~}L6? zFqeqYv(;(A2L>ybS(&ADGR(CC%6QHZyZ%4qhPb&4D7zr+rH_q zh^w5oueDd)P5XYiQKqS0%g|-5&e7hHcRjpEV)RvO_$DpwRSDwDuju6!RKA^I z(eb`F8yx{BIEhW4r?wd{XpT+;`qV zs=2g(IIkdf$UXVMkomM__EP+h#^6!h*a+Rr#0OYNp|-!coQJAZ=QLi+n&PlsSrx%@ zfk-!Y-9tBo<{t6j8`q-_7hlfty<2tL(r>#WyD8A*eRFl_!}Puzw!Jx$6~Jcm+>*M{gOkGwV##!H;3XUWhEl( zWRLcrEJ~+|dD4IB|DDRZM$~xT>-K5#ke$V6Y%obz)lR)to{wy{Oi_Keq#ic(GZG!q0YqijL@FDU22^tPh47JGF01XiX%xJwlE}F#QYxuU&|C8Bh*yMYBoZsP)%{6=XQ`#Fp zKzS>gj0+lVE*qGGvG?Gu08FUO`NHOC223=1A8^-y@jZ=au>oQ~RZ9X)I=5YLus$rj zhA|@?9yS8t*cHnSclV2E=t^*GQbeja%n=8Ui=S zvL}Hhe-aV2+S|<;Q0miSX49hHvEFUhhCw$H?XjCJ4_G((>(g2xqTXfqcC5g;uQUm zqN`#@K{o8x$ZOOJv^jS05RFtq#H%Uc(4ur%4pW~jXFMWa}`zD@Al4)cA{2^@6y^Xg1EwFOD07(DO& zT071=Lx0u+Ox55>QE}3JErvo=8Y@M+KmKBEKxZYBALE6I5!pF_hxc* z7|CsMPOLkeTJf*pF}7$%^0l`g8Vqr4LB-!EHrzHYTo~I=vpY6#k$_bzTl>!CDzCD) zw@D)Ww@yG%1|&;w|B|Bjq!BO5%!e=cj~#;}n1>-s0q47E^hj^P=2*-gz8O4vZVBr1 z-8{NtE2RgY$;9^07yU9{CKg-YihKS`g%q)Raz$SF-NL-QjC;ru^zh-=r5hRW*xT3X zG-&wom8bsR(XdU?Y<+y>l77hk$OU1ZJ*+#>R4_qc2OU$JCi8L<^hMd;wtY>7Th~ptGysW{!DrQ>fI;q!QU&$oS{6pjUa_gQ*;f>hq zr(cjhx0q{5qL0+y<4-gt7lgNuHBff;_Z8)fAMaoMKpqG7Im})a{L2xuO-#(%*T?Fz zpR+Fh$oI(bzYp0YhrXWW!uTv~-3q4vqkO;w>mCw0i03u-^Zyp&06+ej$_k7;Tz~WQ zOC=@ljQ;9jt@vvb4s>Jje=GK%IK}7J<&Xb;$^i=HPsXPI(~SR1=AVZBgT#nrI`pL< z%lzB0|Ea>1P&&IeQt5x|eMI7U)7jk+qL0)-0+ud%#Q&E5f5omH`pW+nbEs{RhDGUP z{LhL1W8MFwS}|pToEwqaFaE!H{zuA$dTXUnJ z=otMb7W%t6@!tS3Tn7A2akm2LS04)>hcL@kS^P#6nw%TA9FpHcE&_i+S?x>IzbQ)R zPj}LPh<~E{&%*x>X6jeO|Dv5wfp`X~xBqDR%l_wM3EQAY#IZ8+o$-ijrha_krZf#F zh`j_<7>RHu_?{&1xQ~LOJZpu5($v$*?fLpY%DuWFn}11~aZKeN>wmzho)jiKR9VsU zLs8yN;EwBGFuCF2-vQI_1cl9X@fiwA zlKU00&|~^ffK=V+{iTyXn-@MO{|8N`SpdvF*6nsVkQQZ0S^vpnv7hDcKel@u=_!5w zEBlE162|>&OLdE<|L8+WHn>BXLj2R9zj9JLiphU6O(Pz=lF+#F1ewYuq|T925>bNj zo#e4E3(Ja{sQ=nb7X`&3^w9&T^1oV8e&3cJs{U2*(%H=Fx8h&a&oWoYoUHR>{e!Oi zlixz;k8*#F2Kmn{19$cEp11Jd+Wal~z3}b%-xNhB=W!DBHyHdlN0SA>|I0#CB54>0 zOl0>7%QA{+9y+z2JkuF8DdZ^h4Sej+v*iiryLXMxQ4EG!a8OWK*u5elum0780#Wz= zXKo`)dhq?t@IPQ-s4k!U6IoH;Tb8!xM|yFe{-Uzl|Cv9rQL6p-tdG|b#encXGEH5y z|3PWyP9Kd%@(=nL#mAR-eT4N8iuB5}_vx%(b6a{=ns6u+ar(+lN!Z=MxO7E&D^MS& zcaIm0pUf-$Jl6l%!0%9X8U9xbZZJ@}hvP5hXKAE98jlh`z6`;vLTyX>i2WCxQu~jD zY{>G%a`L~1`iI@R?yl0`x?v)&}O2{$(SZfPdVfrQ2LI}^&`(NcOrpNTU zY`iZ_X|FljDI=n_it@9C$A4>#`WP{cO&(&3Ww?{N$9nnmU(k;os<8v^L4`o~m8R^r z+x0H}=e(H^B_}=(s%*gH-iGuVi%r;C+cn42?Kwwwz;WH+M%VGaXSPY`{#~3(^ud{_!%mp(@D&hKOx|ZMk&?_cs4D7U?s&U`Xk?m#P%XhXPP@<91Ga|HC zKKpjH1Rcz*y8cm-i(?U72EKRiT2Fk`-1b|;q&)v&EE&5^f4XyC=y+!pBXU6}hfA!k z<+q*hms1sBb@AGxIVECX>YC-vCPb7J3Kxpd@I22LTeWgIYF^jnwBMq%?%m$LpkYS+ z!+_T^BB@7gp3fp?TD6u#(wcq=Kd&CkLHmGxeves#z5E*g1d^0sbcW5mw;esIE9)@u zhnZTy^mkH1CY!9>@C05#!LG2g`G1eFxbNSU|5tFgGm?~0cFCwG>oGvbT;Qb4;z%D7 zgaG`>_4znsFS`ogSa0(ROkSS25ZQLx97l<<>JZ2tI} z5XO*K81{G`?o|AX-NX|$OE)&pX{Atk4X|HM*yC4AW9oa3EUoU?Gi4M`>2MhHX%l`z ze~!DwI@{F4BDl!1J@MO)Qh+Y$YyIm@kyoqyIE%KigJ)=qm?|WE*JXLYvc6Yp-zItd zt`%$p?Fe#g?UmvHC|&kw%5gpi_6U|HCPt48fP~d51@C--jR(Z>?cKOk3km>_EH`E?m%!0!gcBSAdBCtRy|*XOBM`Z!n?vfk{owVY38&Jq>-ZsaPzkB)B_}HT z?r`QsREPI5;=0mPn;2cA5ddh5J$_g+t$D2q;GMoR+h}R+wk%j&4k%`_Ysv6doyr&+ zO!%WnO`MLKwe;k&?PNUIoPUV>`!yxGUic|KEv?#D{^&wOf7FEh*=|5{So;Jed}!h053*Jkd_I2=G2&8jb(&9_T(7GdruVv0la{RHP>7b zNOG2jMYxYkGAg(k)~H#4+{s$(UcxxeB&Ov{x8*lZHL!0)H8<9F>^Tb&>w-Y87uV{{ z?ie`1*hAgf@}7vqBxzXet|b~e&*G!RDFk^OST6$BK>CuOgqOP>iy12eCLWuJrZijj zGE65@?70Md#~RZzg)H>OCrI)tWM9YFmTm45O&jOrER2>kT?M!_|0AiZFw}o2{vYkP z8@&G+tw3s)Z7MqlZOT^NCd@P5Ck{>TlNK?d|FOd1_Pc3J(|gdi*MRW&%{G=wNBW+7 zp66ZbhY>t~<)W0d#m|u-QsBkaZuJ$9oE|MmtshLBt#v8jTo76t069??r#k3a@E#TT z_Q}a0nsM&$%GT6RP?RSV|G`p_p_2+EK&&JAR)UKa!Z!N>vv?Bi3=-uw2=ice(%dBCrytOKQ$P> z!|r?)qGD%hwvMIN;qPjxKap4AC^C?A)XK&UT1rdnZ0(Ji{`c4;tWxZN50*~IwbH${ zj;-|ZcOQHal{mRh7DqggCsENKcTF6=F_xLO@C~uQpN4OA6ZZrb&RUn1y;g2rmpv)@ zbvZ+ymcXmaP?M=ztYzwD3ZRIpe8t&~Q%o|EJn4P{mYV*;L{h2o;5z@zo0-DK63@v~ zw*Iu|>3D1R1)~&cY{-SM%hjaC#I$ba7-1y;n32&cHOLK9fTTQgB~kG}uEveM!`iGF zP~NYv^=w?SrsrEPC4iyn(~y^7A>g(|GTi)<=?eAlRcc2+s_^xR(+$!RNRaO`mRsH& z7FDQOj(6)3Xsy@P!r1DSdA-vqMR^r1*tS5hoxw=GHq>pO$WNS*PJu=6=PY%ZLy|+W zKv)s~BXt(lihmY5hBjy3*m66ZCJI+pd;}S@wrRDlN{6-Bp1h$BLkU|V;9JzNLL|`m zm3(e4wwk#3W8g22u9*D|uMxv_-dIgt1*C#!q4L;T@Xf@`Yj3kW4KrK&-pfez*($%B zaTrSiB^V6-#x#L{9xSEp)$U58XY*eq!&S1j_kewi4UE5(Ews;w-TRB<1Nqm#2GEQx4=*ar&SSpBvXZiF zj+Ri)3BJUYLg$Ipo~yQZuHFvsyS0YlUPg}7)%R59PU^*0-;2A|dKXrKHBkWKGu$1? z=~`2OQB>1S4osU~J2qV7na$PVTITDHh|j9`kDnx{r~I?q70ZC?rhYx4>g07D4ks%| z2WA@R@23rkODN&}8%IYG);tRda$|=7PZefOR&oL=cVo{P!> z4FU~3x8IMuXW>*)3+}E|Ne;~0scvaYDoX52U`m2Y#7dqKuK~ZGoQ~sXUuXSG!qiCR z{h4k_RY{eE;t3RmQu(u<#E->~6H5RJLVuprk7r5%a!~JMj*>2Ef9_ki}gKFSu&AGbkjJw~-m+Xa+ z0bjC09_U{wI3DF*;5gIX-W#5kZ3FfJ7IQDOFSMLrU+CZH_1J-W~dyUD%TSn@kcpKe-mpGM7p>#s}(QbEI+6ATj# zO`{5$@|zZsr*)2)^e8%*JH}i0n)SOAAoAWcCyBOu{EhFQ6EL+RCBH$b*WV+nqxV~N zv=x~TSBAHb#Ob6nY>)J$ zIXlWCw?=Ze>+-j2lDF+?$DXKjCos9fCEO)6B_v(c{(7^NbKAD+zLo_|B?BY7E-it_ z(Z^B83@f=Yr!BW{@4u{XZ!jN}gl2f^fEVXe04x5TtS#s$7iZb`!5RNL#30hyTVyl)#E1dPp$b+bnHc3V_Av(_edWf zVxNDOrSaQOtd6vrf($p+4B?t<1MY~FrnowJY(9M5q#@VyV-?VkIoU#WAl z={@TI!C>t}8eFoQQO&mHvz^7y<+hK}&y`k2Gspx^ABbKNPEM+{foll`Qe7jTwC(#& zL6sACn+s*AfFOJ3_x>K=)Kjt>b=hKX$?I(Umu(4+=EeqVl0T{|wANmi{;9_1hB*cI zx~xDnh|i{LI$(dg1HPpA%|zbWwz>YBjlG4dUaiS>wfF7r!Zuy0+Qj?oZT=iN!9P>g zWD2$UFlw+?HeXJ>^no|I*9{0dFXL}Z9`mWe-K;(OP)PJ0{PTG1*t!HP+(Acv30H0V zv*#7{JYDqBHYB_KS>h_9HDAfTh_&ns2pguNXdV3yM*F~D#m_=(x~RN>?O&H1m$g!l zneumn`JI1XNfv*`oSHx}HnC}kxRnDXTf~lf8BoP}A2E{RSd;(J|87^!+2-pJ*j`JJWhMr7|NN$_Z2z;2#eupP zKuFhWeAHjjiTq-h;_A$Z|C;)}1}@29O!5)&P|&DI8`c1~_Rm%}+=EW_<+kPq?73!& z`@5Q1oCqww*~9avi$H;I4!8Gk67;K1&ShOa4%DlD@6T0G)yqoO3H^)b9Q5L6z^)gz z?^~lK!~fX*m+}7t*{b>^99bfLjCM(=cjx3gMfe`yh#q(E|!o$2YL7k({Ku?3z`(x zQ8Nd{P%;HeG+6*PBvplz0yeMtV|90Eea3Z!7TeT32tM8*FWtB2;F*PkrmoQK(c_QCV7!Hd>?zFQ2u` zlCG_*dddjF@j-xGueM;i)Yj>ziiXa_3kx&PWhL0v(db|drYZ~sdj%Qd7R?oF$bJE% z4^eUtYtE_G$;(#SB;JTN{BV@CNuHTocpeoP{biP$J5tAbgH?K$- z@$VdFitxk-xSH&xdPv3OJ8el7+oH~<$8dDJBzp@rOd5VAz~VrRtFZtj z4LQWYRa(Hg*;F?3_cl%S)}$EMr@PRYIi|9Fxovy{?nXk(&r93k>C42b;BE8xj)hLX z#6%C(Hiu{{LA*~xaW$K#4@arhZSbHXS&Vf2Ad5!W z#k@tsH3AI@jKx$6rb(XZl!zs)%~{E4mQ@epkzL)%^>@FGgeKLR5>{x6Y9LVmCyP-deDrh(~BBk0W*v-6hMsj z)Fk0=*mP%*(I1(#+QaGCGMlY4ef8~A!9Uxpcq5+@=EfORvw!p<;eQ#F3p8_+i!4nN z@%=kz%xPRqX~nUopoAtln;d)qACnhn^;`Nog^W5Xo|cZ+L4~*ea?0Nv)zDY|c>&R-6}z8*bJn>L%HmRdEnm)Qo{D^6~?D_XqntK~9XBD{#p zU2gVnm7T-fC}zIpQXOflhFA^MXd>&fLUOb)d4 z8RyCxgLKqdpqkZ@_=+&{Vdl8KMswC|tf;-f`&IGb7rbLkiDR*2_ULMkKyqV6IkH+5 zi(=Z_aXf{qU^p@9M6c)JXaDszBv1s^we(~WCznN6A9*=S#F67->*mYrFuL0yIe zB`t@U%UH7a^z2#UCy8e14}_!(JhFdo=#;bQhj^P5{HFPNO!M*KvB|_RkkqJ{&*lH_GnsJgBk9iTV#8fXe#*jr)naMM>S;JCEcD|!I#+`FOM8&L zLGv%&&w}hCGImxiH4Tij^Ri70R1zSTfiK1k(iyDSQsvWf&Z3qD`IrnPof*fYls?#V zWg)UiBzU?+oXVlgpOLb>{2P8|)=z)uefFV3R5b*rCoo)nvyNq3TzD$B>WM^mtn*0mEMwExI#qeJWyW>SE%YI5d=YYHV9 zlNPE_Elc-UuKmru>9jes>t>vgKk~l4kL413>xxuQ=9aTnMr(e*_%VgVkMpt`DppXaG-W{m_gAJFhZ2vN z(^e*?KcSl|kGR)8zY~Q^RHvN1vA~#Nd4L1GB5gt+YzUVIC+UH>P!lDLfX9Bh6iNd7 zK|uId)Fc7F5&8I_t(0r${J+_pxB;gv1oQxFq`VAzJ}o%OJL;Q8*U2)RdqUnzpkz^L ztwp^f0|)L*QB*;5UP!C3LsGhPk5@5D6=Asyx?=zd>hBqJy8_Km$hVxuP#sG4fpFn( zwF6=U!!<+^`xK*u36;D&D_m(61=6H7h`Hd{NG6Xd-U4$EYrgZj6nFzlKRz~u#KQvd zDiRVfoDymzmu2@O#}I9-_#U|yIDQ0b-{B6%~d|ys^TJA zCTMnM)5_MpersHc;3$eLOKBHGeU?C1KO>Ms&Q9t#__xjhbxHyC#-~BfroAYs-V@<; zICUUD{Qd|Jdn)MjowD>-nNzBTTsH1(iR2je7q_;MJ4JnifyU{CLdFQGVkiM$gCL^qN?1C?$&Fi?5>+&CddD7Z-;JK4 zIjkzN2_x}r3}#tf=nkzko1bw?4J^4%>qdy#=BqV{PYasJEqWC3ur+r^NrXj3Ff#?M zO=4e|T(0W9U|Y5N4EhaIi7^(4eB*!IT27WuSr%$@%~tDC`)ohyo0;}5uGswgt-isz z0RoMOK{v^^2J5+15Aev027|X!E;ZyI*lAwaXfii@X;b4G{=ljBQIYfWDkpCRf=6cCN=Q*A#XL398BgOz0|yR^ zqO&Y+u<~f}g}M)=qDd@o@bWG=e11=P;`t{|E#UiyOp!~XvORsu19QCJZa={D51lH) zCpNp$ihfO)@{hh_YfYY#!dvS^nroJ2Su0lynqfP^@shsb2Mw zbTn}z&WpmgH>>|Obrh}1PM~BhDiN0ZX&PN9d@sBX9hbaPR6o{BU|THQ7d;^4fg<-K z&K2pFnlb9z7dx@l2s&~Uiq2rK&sUteuzI(~Wx{rIVHHo+8l;WKy_)`urKtbK4DV&b z%5!Ja#r2I^f~9D;DASdiYAbT`IbRBm z@01&~kDWFsEs7&kT+KMkR3+M)RmZ*pM@Bpqqz&RFbVkobSeNiYa1QqUp3&RoOEsMK zbbJxF>SUS#4JAx8dSxD2@IxJrbf6m5B5!dqPk@g6IjNG>@paFozKt}OPGXXnUixEo zae8c!nb1yi^i*ng;2u7VZFsJ@2pSS$m=h6vxs^wlcfTFr z`$EEF!t2n=T0UIsVq5lvxa>S@Pyw*7-R9MJF?}i;djfqvHArWR?KTf@r)qmJ;AGF_2N4tkM2+|CS%Ioc)+S=B3cIIs6)zl|KPD0e~ z4T*{mWiFsp>P}LPM{Gv34yO;-6erGw{bYucDq|^WA@NvX_VE@wm2fYayy%H%xD0P) zb@!GIpa_~wjn7j72K$XY(C^N^etyLBXWqUR?czp3T_IDs@$FFgh&ujnXw$Sy#`9q+ z@B%z~O-d#Rop^Y?K{5tl-y?%aS^9okTeF$l92ZYJ#L{D`<*LRy=&NHiIMX=?pJEbn z=ZF`h6h!ih8ZBCi@oT7nU;Y(O}jU2a&X0ff;VUG~s=gf9JKW^|z z*EPy|#}(-Le6yao5wziU2|^M3#0`9^u|E3t)`c3!%l2p06V+MCFYe`%WWv6UhjGEN zBN^|49vAAkDwai7K!tjN$U}chs@iJ7o`Akr}^bq z9-YD53(Dltw{t0Rzk#g%jfEn&`>mevAhp5=xO;}xs#A^D`^sJ?Xd&P5+bK(gDI=Q$ zb^WkHfqD6n!jc2`?_7n&yM1Qa`=q!YXi(fVu_yrO9O)V0A$#;%ALg39UgDH3x)4qq z842BnKaJ7cSX#u|;+mmoXZn{mbJiCU;7Pj_!Bz6=u==L{W`p{4uz%a)Xj(>fA`+ zR4n8&)L)VyVrhqVDLNtHqPXfTUGXi!x12u+dWwdkWz$ndE5&-|)Bc<;-ux47ZcR*E#k?_*+1h=*wwFX9|J;~Fx2@cuUdnL zS#?%Mzzc@G8>siTO|0>B|4%B6ZJ)s{itP&zuZxb4gfpVnedU`61 z&V*OuJaUmS53P$#?}%vQj`(E5_00>Nq&&RRxAq-+x@+h4vh>=3qJr#;BbhS0KanZz z_JkwKEZAJK9y!zg=~pRFoXgkh`4 zQ+OL8-e}CZ(X8&AXgrN1?^J4 zi+;~F^3zv?(a=KKCr7ng)#?&Mf(_O#1zB{4lY*cWM*9?&e#AeE-0sx3G*JDH&~M`E zgRj1Jq*4(G!!M7_*u+#wA`q@^)@`kDBZ^n_E6#2DJ|#3IimdN1cvmoeJK=+98&ZDn zFfZHaeOhP%Q4j3fQQ=|eVQ8r2=i=?e&Wt8sohi5N!wJz2C?~}1By=!g;P@e%qlu7=#Dp&nG89T}(xU<4^TBn@P5j zHvA&+WdKRm>%=r-udx>i@M9C}Bm5#9Am%s#0T5(pcLm>^w5NB~eW-%+Ks*~!g zmcBeMjDnwUtG6;PI)E`#l==cpL0r{@RON)xuMC6o&G?1ApNRCBeXP04`_>P-4ZV=~ zb5I-eUg50*;WU&2L-pjeY<688riTuy;ZI)CHWD268zDl~I1a&ZTg3mg0-A+hZak~j zjXtbw3TO0JRAVo=kpMy*H&-MV?o2LupHAp+#v7U$Vz+$+zpjKMdi=Am1|4omU4Ev2 zVhLb;*r~=}5yk}~KTkQvZaz6k#8z~>KjISk_daUKphWcg*CFl4zgIEgWE#DC{&HAF zT-XLZ3*Wh7PqcqY+B|P1@W$OfbmZE~yHsBcFMI7`r6CdXr(XTc7=cN-=UN@Vxam{> zhHi5a3X_j!zwpnL3NEw;`p7%#_;CQnl#}FxP%;%f7(Pqk)i&(_5ZNDuN1f{AWG6`W ziER_;OLX_+4G{Vqk$HIu$BSURO)gv&bmPeS4R`5R(JOR=mvvjTsrbML8BbY>pglBv;8}%<-8MY#uE{#{ zBzcH46rsNs5j>BU_REE$s6vNEDc?(4CFz5kKqFnfGcPz<2e8rD>_eAtXuP*P7mOQw z7Ic`u&2~-uNg5~wvtK?Ul5}GR;7${=8aQvYzTm%#?XAZv{f6uLBb&(1(a!Hjxo-aU z(eYo@B~v?U)#e27BM^)W0Yju7!C3YPe_YbkS3G zDapF*C7IFs`T=t~>DlV6UhW5R^B-+!%@i_K(=WzGCuB4#{2sP0+4?~MW);83TP5ef z?^gO+QwPPW%|^%WP2q`Ai4qmwJ}$N3f++Alhe2wrCZYc3tv6}Kx0+>d(ri1%M(ViK z{{ExVM45Svb-QnJHO~(>Buxj!4MV5*+Wu1Ut^UGvO;4*dmrH`bb^K}SHbQ+Ia}4B# zs|c4R^1Z8#nG?Y~95o15Mu+y)hxlsSUK2LXN%DKL^8Nb}pJ0LH!+w>mKS#egV{EJA zLuxDdE6hbjyy5}iVvW=u+lAn!)Ze;=ZMHU*RxAzGcZ#;^xv%fDlm3llSqoS8)P9E;kT)Iszfjc{4henbt#C#P_j29tD zH`_vEQC|v$Bu#tHZSwk)4{N-=z#X~F^roUdPoIH3LME*$70U({u*eZ7T(lOKpP*n2 z-J24!-+OB&_pJ9hxH;{sG3U~JN8G91r0D++>QpMOc-`}`pS!==W%(wXJ|AJ8>wxS{ z+q4l+o<`RsB=!bN-XaknP7@+J!|P^y`cAzRhlUvF3)5MKBSAYMNPno=hP5~~RC*Y_ zg1A=hm&`PB+it|aXDfF)S^v;_Tefs8)<2K+Ow#c7;VwU&NLS|`CaI!xY|A%m*U=-o zFFLW**rPg$-0`3}Mdx9X3{5fKmxrfg=Y^UlfLuM!D9J|4E(d8K&U z`l;43dwVvFZ;z}RWHt=`iTjCaK#&(Ix63s`0{u5+J!L1KRylj^Q=n2dB|k*7zkUTn z$#N!^9dq9ti-Vo|jus>ce89;t;Xq+-^~)JPU<4?3NEt{Pn0G5O9dJ!7zw&Wc;Qo<3 zJ|g!VvD$ONT=AYS$*B5@YK|jN)x4IFu;{Uqw_=|gTOOJ1^%$8ZlQq|A?u-$o|%6pQ3T29JX_6?vTT=|@=4eS1TiiBR5y8Io;-?9Yp)6QWcGo#bIHj> z%`Nk`P8#yt`${-|lXK^xyxwgTqdL2#ckqUqD| z{ekhG6tkPxui{AtL-lWbL@qW3HYxf`wp%fSwJ; z+O9+KKeqil6IOG~_VIC`+9}TzAy56%I%8}DETA14+A%`T!YKn*f*Hx6G;=%U9l2&v zjZz~BothkQTca$Bp-i7cOTnSlA83pX`!?oxP=IL3y^QFG(gC@j={QRJbe2k_)M#*` z!m@?v%IxmRqyu!!B;WFUEJ!VR((Ck`xxK=#L_!ootb3r>kAlcJiR4*Lev;Y5yvYw9 zj0<%2Tct5Ot(;h;L_$LwZ?yVLI8QW#p;hpxoOUonDN!|5GJU3W&|mn(JwRrQ?yt+s z@>GpY;)})l#H${HJ+sQUlzgOL>FkZU6N8_+#bYoGt0DWoebj>P_w=|>;qhgJwT_)s zl4Sm5S8-YH#fv(6AWVQNH;7+2R`a|9uKtD+P33+v$$#_6%XwqyNY*;lC~F{icUb(f zrDX5VMFrD?alL3MtZ|YHosuBt!y~@EkGt5Bl$1`rL)Ndjk#>sxMkw{MRwOrZpB$!2 z8zJ+=x%?>d?bdM7Q7Z{4DK?8egC+z9Ezrf==PRLWK9Hdu_WQaey~+ur_5w(Si-2*7@&dLidA9^BHX>byUSFjs;BomdbJ z%9zuFqFy(o^_fs%r9-Swxod0LB3KnockBf?5!Uwm`mV0hSy;K-is5EJd$TTsE-`_I z+7d_k37j!_U;ADCjHf`4Nn$S8Fe8Z7uQv7{Jc0+QOx}gWq7h z4e0U3Iq(irQg&|iFpuuCX>Gk=8U!=GvJ}ooeIHuO!ESPH!%f-{I1YEeE((mym1#hU z+~ij=vI>!6HgtT=8~*YIrjNGpBqKEzAyD(Jm`jJJd=$-XpeG;6R#!iP3*}!_7>g38 zwrljjHc@O`c6KA<;B~-NNp1MV@G0P!5%M4?MndotYVnkTvS;x&Op$$H7Jrpuc}Rtm zbu(!XJQoV$C{oH2>`3l($IILaSDNA^AOEW~iu}gSqfw=5)0?eNFi)rPG`o69>Usvd z+jq&Pf>cIOZ_(|$&lnq5TtKUONutv%a4_~^0qbF}enGIOx%uUVSIvUF$QY6ubb~vF zUio+~Zub_HaC3mk^B5rCy7SU^A~oax)t^`NG-+4n zZw{6?96!tB*NL0D0A-*3Dw)#M|LE;f(ee$;DE@y8U!5bMZ%sFPy%@iMFtw3;7oo`S z;5_$EiMd9iW}7@9Ai0VllO^9%RO$zj*QjE7GQ;=w8GPXj%%BlLv*XshT9fD)3_Jd( z(NuNKn5nWY0%9nR2!kz|mPkNB;)rhX+>Fs-J|v*l^ZZH$OJ}31G;26huCOgp@#~Tb z7P`rIeI4=7pK;nhP)L5piI-ugN|d)4NQEc=%BCOu^%t3BG{%}2;@5DMZbXJplAkqU zvAT!s$tlPN$e0F3bD^)LJ~TYnrzh8);B2Mi>oZ{k_ttMkikYRb!c+``r88g|p5x!Z=Z-o9R&$AoNNW6LCHS*>gm!Bu-fx8pw=t^1lXMZj zat$DFOUnu9FOkS>%0-|H)zQ@L!e1h4q3Lo`g{MBN;{l4{o#GiBP-6r8r~(!rK3f(3{CQ4r!a(VH<`cJX@Q5 z4-+*3)7&E=A_Je!HHQfsVm9>He1J|!-zP-+MqzOESSZQD)?^;0ja%zx`$j zqTF+kB$^HM9@?sN(|!3-7{m14Q}RB>#+N#Maks{rWwG7FbH=} zo;C;NvG4i`diVWVaCFQXR~FKrfsc9H&GQR&2r(OzmpjYBHo2L+BwgQdGqyH$s0$ev zGDACxQYTX_O^IrJO=egg3j&dzIjxw%pLi*Yk-LcP+>7k*r2MM&yUFMpwp*7DVB=PgJe$`Snp*pq!+!km9F6nrUzrMhZMcYGYYJV{)%;ZDnqLibkttWd z3<0SC7AC$XT_(LRVSMyT=siWgz4p>aJ^Yr{Z=vp9oO=zIPtoos<+AH0N#U}RyUvk3 zwGyO{#mE6{y64Sz^GOJ|lxE=hx*z45y$+#5dQWkTSxEl9HlZ7r!eR69$zIR_y_}@z z2K-17L7VHVD;0ty1zW@C9Pj<>9?#??u4LNw^o4B<#u@DuZZA`5My%v$+Fla~*K}tvtBHah<9?n{r(-=Rq+Khd+YnE4myv2WPttZ3H`>n> zDb&b?(a9IDChP#uSL;{Gl6*N?SV~j5h1}XgJH>L9Vy~=ZhD`^?q#46~oVH+ifw#eXoH*$0;iXMQ zPA@$g2U;JcP2l7ExjtrXK^rrGDeVBx*Wm%(h-H9*O))tK6Eh&}S+n?J;K}g{n}k{H z)v}K+3*5nT1KX6SLfYg-H-Ya9F#4ENhX#gU8eWX|IuYF9ZNY1}UdR19uKamh(?O@o zTSUo)^Fmp09?$mA#o!r~;g7r($3@qMeM@Y;K`ds!l`a#wqftyy4Et!)Fd#Y8yBM*yRZe>Ax_^o9<9s4&o*sbOSb7)V`0d$zQ@r$ z<%?BvT)<+{66>s55VgNdG|=tjSRo6(1qE`_HvhLVk2MhF{Pq%Gu- zLMd@0hd1DB;98VsIg)%f;b{S>e8^b!{NY?B!~vkxZaKthr{6fj4XuGA{wm~Tu+y}^ zo3~i)eHh|LnysA8HIo_7BFQe@zTaTECtz=A`zy0)dOP+`r#l&u=+&-l93hkGrvLl^ zcBt-3@SFf2rHv8hF~s}G!Lph*Z>yyx*GoKSVXIHxZFB#zn26h@)!z?AP#YVCw7lPT z@CC7~x$o(h)Z65VOUnA;%i{cZQ051>1g~n2A+6!* z+7v*qkQy{GWISUE?S=84PaQ;7{jrO|#mL#reeCp3d~1#KF+DzwKZb50Tg?*fqHz#1 zMf2rH))vLawIgd=6}yj>J%9PL(a`%>mq2cCPZ>{A`rqZfHsqg?2>tlx_OHhfps9>g zxIbu*55q7dDWq`?Xbrx;xvISu{SLnC5t8`*T9jQmU6X?MN=Rk3OgeWlD|3kZrULzD z5G?!-!vVQsnO5AGGf09yLkr!H|T#$BM^}n&F-?r>B2NcSCQd6BHzmY z9P@Dqe@fBQjd9KE&=7eIIJx;&wpZ-YI;wj_^Suez?F}iWd#I%@^Bbw7?V4?lFC}Lv z+N-b4^5Jxu-jPR;1A?1l;DFPvyj79jr0l)YB=M&;w#jB(KHxbEdOQ`yaoIM~?rId~ z(%G&iM8nw(F9K{;ay>yQ8kfhhynwq9+akv#hZ6>HKs~wyuOenXloEMH5$PODMnEFWbnTiGOA=bm zXC9v(McHd@CZ6Zn`tZ1JR+!mya(t{A-V}-^48IbkN1N5#QEYC1x%iJg^V{nRQ!~*4 zhgl`IXBiLpPmiaYCBnv*t6I6K9Z+kqC3gT%T@a)g%yRX1dZ!_3jqD#IedGQFyQ|>F zvCDI$7q?M(@qJx%Bjkm|pA@3miVX2+g@J_C<3Oa`Xm?|ez74ekp#Y%Pkbpbn(mE@ourJ5oz9DkoidG!oxzM#s+Ia`9<@k8QPXe7 zwu_C}s;PpIH-cV-|HD3u z^ZfVDe)$Sf)K2IQZ6m+fdUUErE1&WcTsVA@?e%H~tc5i2A%1@v9m zh9BWE1^zZ&G3A&%fV$jg*$oH6QwTe>k=hyL2ai*jRw$-&G4Ux%fQKnAWI3)Huw z4;EZuCxnP4g^_`4#(oJdu;aSK7kq{ECWVpX>P3=+>&AivH`qxbpGH3oAu=P915AE} zY2)bNh!~C?pmQunvGQq<1fPt*XHjOlAwPzihQyTl)kz{Vj3KPM(FwGhKCynNF4Mc5 zL-!;1qFHpN=0o?x_aXrag7AEad&5Ji6iUBdcpe?&ibmAPH&`}Mz0(AdAute_xT4V+ z@)a|}T{k{rm(Yxld}9I6e)TUcZ86I+90*?VolUO>eFkTE&yQD|Ci-}SvO5Y_q5 z7vt@)I9tpbT}*)N`&v!(WyS#%U+^H6;^nJ-YnM(me(7(>9ONoy6@B5up>(1c9B2~K zU?y>Nm-Ixbc$tB!@E-Vq$%1=%JO^rL3Mo+%MV@ybQTo)J+@6FqPnwCdU}Z42D@LDh ztFlHjA&q-Wk+E|Z85uKzCh4!8WO+i63*M3V@4rNf{H7bRqy0r+b*L%}wp&&88T)Q^ zoc&ew7b0wt--O8=BTsaI1rgi)lKN478i$il?W^tjh2gff&370JuiO0E`ce@mUg#g2 zv7@!2Qk7T1Z_JhOd5FEz6hCt4qWNHVPLSH%L;RWW8hWI@8k_}eJUDicZ*eAttIfZ~ z{{HNkA*r7q&O{4Nh%sY?kK4(Qe~}cJF-wyT`|*|`X;2^jtq43Fvxp57-Wqem)AN>O zxc#R4!Tr)zcYau^eAch`?-!CTo0DwyQVuXSR=KZ+NTXA-6Rd9A~ptdZmq(67J z#MXRv-Mi7`|LuI?&bxoF5qz?IYw0hua$tX!eX=4iST##|)3T2h1&G2VUw#M(n&Cm+=XT?-W>t?BE-MFc0Ob?vv16zGRXk}@w zcaVFFx;R5}cT0DfhuxdAFsaYfu%~Boe!p~hIV84f;mKG5#`O+Kej3Ul?-A!S)Q_t< z(?RLW2WLsbCexX_)v@Idyjt(nFYsmdI?8+;&pOIH|K1;ToRHg}`L`%(XIJ{a*EP-+ z1zdoik;ES_Fc%O%V+O?1W-SOC?8D!NsA|QB<~h7aB&7XeA+x@=vRZ7QmfSa7?p3k7ja>Y}9=(`1arG*L9#-(UaC zAY#XKV6G5ZwvD-h-+rNaS!R8>ce<2zgN@G@@?!l=n!+a}U<$|5sNc1}&}Yigjowf-M!k;rpPVd3(y2~b z%gt>;TM~(p&wJ^7L9$%0Y{T?=0aP~ZH|S`l>jrHao~lA!ueI!EwU%!*F-!>{2$uh0<4Z^TN4f*Y!e{3LvRc3 z5Rw4F-QC^YEw}}D3GVK`ad&t3jl0gy_nmX^ng6*nckZ3}pQm`c`mL%}tLd(;+O@h? zRVN!F4l5-n#v^()+gl z(uohQWjWgR%zB@t(ZfGZRT+@sFkL#!#`K$_7P*#UPNq;^@Ox7Rh)7s1_D532SDf74 z#|Ovr9>r5Kx_OLkhsBKZXc;^KDfWJR?|A3!^-M>))1>o>)d;1DcZ}~`T#YmUQu+|U1GPaqWT!C2xYr!W`cQY|JU)814vV`3f1trs24FJcq|q*U45%2c#z<`A>ozljK~THX~z#u zp}2B1HVw=tpb~#fsHJpXMLSKA$4%C(c~hH`b=iqx^6UNL;9_vjqVUXjI{e6ihUsA2 zwQJitZ&s4Adhy^{OF1~)Sb9xWUv*v34~+$c1u+E&u=AU8>)b&usc*pp{n zU4gcgI){~J_oFd~#OlQT&{70C)@sE~{@y)+#~LFXL%^ES_Y1{T8CRS?8$ofPpEZ)= zmu4+qTY|^eih`FxRPH1IdH;b5cVt@b_TlOdts7CgRD1p*YgAeRj052VhvpZosn3tj zkB|{rv)6`8AsM;GCUh^bqB-kRuqi&){OL~m;Q`afDy(7o9FOk68K4@c@Ghx{S4+m<0@n563{a3EdO3x!AQKx2iPA3fP94IZ8M9Mm zxT)gZ%l6vS)#8rbt@)}puvghs8$7CyXu?FRUvdC>`_Vm=!En6N*o69t$328W-+Xbn zHm%IKVnXpmu|AoA-9l2O}Scer;TBeLIl1r4Jf@bhi@ZQ3_p6V3Q1Y>&e=C`{T!&Xl5yXfKsEwgq# zb{P>h&!jCq%(k;qb)z`Y& zw|lCh+Q^B1y$!C#j}NR@XZA{OXp{ADDo z+`||6AJcI!Ls&85T&4I4Ml}8Lv2wn4YFxB@xgI4Rv>4P8Vma59cA^i0G)i7_UI}}& z9W))Hh>D`|xkAONY~o?FCTzkqvOFc2rXj_JwsOf!l9Dk>#ma@sqPu29Y(m8{^0Rok z1+#dw5OVR{KL|eYRjg$L;9(R@NV!9-EY)y)DQh0(;wR$WM8PuTV_5wd#*_4=QhgPqDw0G z@AkM#LT%0U%TG_-AGm~>Dn3`Mq_sTZW8J02mcQ?8vK&8d@I zGP+U$(Szu(G?#dmc=j%qT9#V&TIx{h)UKc{3+nsR@Gy|T~nMS zc&zG8jcaT6kjezjKQE&Te_Aojh_-R=g05lImFxOs@6{FevuH0Fei|)56%l<3oq5C_ z?F7DQFZ*XJbmBuav~-vMUT??~+Fde{ByZAvfUD@%e2DSrFd8HItBF_Xa+1~di1aTL zI~k%Ml5XgulO>0yZIKz1=D8OeR&B!W5lC}da6~acc)}?n_ZjT$FIPyn1^$n&q?@CN zz*fXhp>ljaX=+O_HyS9eNQXjae|{;7R21|!X3jmV|J*%r{1oF+!13kn!}TLs0j8n) z#B0(V2#rN8C3s3KP9ewG@c_K8P4BB`!3{2xAH7NPyaJ;dSA^2o0mK1p7M_Vb-Zp(s zEup&wp98fTo9>S0wSSo#{s%t(vVu$YDmd>T%FsKBN4yE%ccW^P`z^NP?^?(7ChbtI zRg;C7XC)uMN7UfV_%h!3eq!g`(l6XaGAlxy>!Me{s{K%5xa(a?j?Z?^;SOJthM6$v z>*PH$sWZ9UoW*zFSUfp)w9!a2DTTbYwaxKOru08;R*0+zT z;sd#L9rj5J<%6Vv6xwI35Moh*FK#)dC_fg~ zv4WEC3-<+fX%O1!Gn9d^MeXrBE@CVYk07B91jo4#oG;eB57ADJH;W)d5)8uR{Ne`5 zXE=pUvP?gb(!LkU6>r!MA8-HRfhUY-Scee@$UO>F`x)eV2|xN+-F5sG`{*-hS1S8| z5#^tp_3ms0GC+sj<^71DroAmAWRmw%uq!VLgP-@IqXeYU2f$VpzzQ0wb5y5Q3q`x4 zp>#ew5Te3&9*Fk{F-%d3lQv^cf3rc`rUaNj-hKZ8KKl!6Fhu$n<97lv zEk|s2biZzVN{?s_6TJj)eUsnS$M0`xL1Silkf`JmC1M@J?bz+`LAYYLpegtibg~S1 zV=DOWcZz4hw!*Ui_F!=V&rqF1EWp<1w4>O*y$8Dne4zXVHNfNZBmhLof`5MR1=;K+ zOX33w`$?y3iP``22LS2yw%Dg&$Vp;YhpFSZv3JoDhX@8QK6d_EB?kVGe+fIj83LT6 z?s#v!!k-3psr{j@xK6oX`X`NmR^vO_{UZ+kK^YzQxp=n*UI< z_N7Y@IvDzoPOzF^ls_OEWchKvlkOI??h-@M$>WH&a)VKNf}PS_7m1PG&-B62fGCIN z>r~t@n?Toqay#_0qdy3Hd_)b@ABPyxK))2w>0F=@J_~>8gV^W` z$NRpu6YiOqXapKY2b1cjrICM7C;n$FCmKi;f4y2Jl)~v|6{7WhF%b8hStaQ5Mh9x= zuSea5pcR7scgE-j{eZPU{lRd{gg45i)3qhsPSnJF)I}?zTgyc&oZUOaC*rf9XX<+- z4*Rok#&q5oc*uCF*y6*f@E;^zXpi!lkc)k(6dcq(Mpu5Fo}=x zJ_@ZD6cPhIWw%=q!xNWNnaB&d(=5r6w<$%yGG)c`%L-}8ZPJ=?@KXAI^cg4xK#~DT zadMtgETT&$eg5Pb)rVU39*U(>0IbdVoolQR?%9iUhdAOGnL zHedsE7%sy)yLBL3;1&GYsMN!rhIJ_Ku}>gdqC4R3ynUoQwLZLi3Kkb7k&inx_&BCh z5dW1~JX!p)#2iSGrb;_V1;Sw#cTX@dGe2*|Jbxb5L0rsh6IU`li+)w3UkKP=b$LZ< zhWkR`&_2^K;q`sdkoPTgU===xx8VBIcD(t3iz7jk{AzIQdI{C)kJA|~n2Kn*v>0() zF$T#2;^eJXB7pdX{f_ao0U$zl0vjemHk%cKvOqX3jbUSFhN?7-0IQ$?k&{oe^Y9Q- zN-BcWkX3KrqNjW@ShEJT#fZ#l6mfx#QD+`ZxkB(5k(m)iE%oQzUMw`>R@rNIj?Mna zerO`R`W5c_Ly6-E;zltX#W`YK8>-^i_b1W|;`T_W7xWL6^K>nj0<4CTYNC1s(ttWM-7+-WV*WmH1+A&}nUA$S{I8?FlXqQ**tE#nls5~s?daOX*2Kn0 z0>l3p4d)s1 z6`4UFsl#vGCz6F#iK~~U0Ii*D^wBlV6^io;)@BcRV+600i^FG?{!g&C>V&MQ$~wcm zUiD+ign6m^(;c%UbZ@-pM2Pg=-pJg@)e5ncea7GTp1l%T&C{8# zLzlhAxzg|7@!Nx@^De^g3=f79l^p8MVg}Snus(s{B+wh;PT2VX-gtE}%$>iJ3$LV5 zj#@Z#&-X!xQJ&T=0Ri6Z{703{}j4EfFN|-)TW}CeqI8@ICrz%J{BRpYy;NJMK#I>hH@}UQwU<&||N+Z1A1)FCg%L z$!zcixgjfgMqHANb=7T+XcV3F@GRiOYwBCRAoEs!k6xQws7r|iE$HeIP8KzTQe8w zZfv%2*>0-}T{_&pY?800DlE8j;ej{VzIETee~B*sz7v8NU7iOvFG7utI!_D6dURs5bMqG=K(+8zKx{!_bbjn5eX!J zT_lZjV*aM8Mhl~^hgkbjI15tH)TcW4djjmY0AZakp`YjkrywVnLqC3^QN&Md>>=-P zKWoh5WN6GaJDc6xDjX~%WB)Bd)ogd+^F@{uk{&<#WaHmlWfh?KjR><7dQI4AuGe@|VEd)O$?Ao`ws8d7>_7 zPb0wkRa<*h(CAUrb0Cqqw_m2-JG={)dGFIJ@87FC&+7yNe%fSF>l@D4mn*uK5FPzQ z=OQ09-_QwrGpl;*P{(m+`S)rXhG?U5c3klKMh>;le?H*$duN}tykSUhQS(L^pVPet z?pG4<@LsvL`e0QJ8&9KqI0Ych_M0o8FV#vUls{M`l|A6QUATHcX!j&e1Tk*Y-qW-P zdMT{&3$Gk=v|eIfx^NoarT!QNSW}G$m6(HHMEK>AyTM6Yp>X%i{A zw8H?ZDHkcZ!1Vx%>4R>-^RF1Zsf$<}yU%3pDo^bC(!e54WMr3?3;j!=F>fd*Zbxe|0z2e zHGtT#peYv>JzU;y7KYsf0+&g^^EBC8Zpp*Gve%VShyI5s&euAAYqU!lH9~M+YAz~r zxV)XLIQ+O6e>t##SJ6QM>IX{lx7?k~xaZgyZ|_Q{eo}!V~PGs z1A?^wUQ!GoHV771C;~W&_IKOK%J3(QwtqcvgZB^wSM<%))g*v-h6FAzpAmU~_#5A) zOcZ`xO1lQea<(P%^rf`g>)|i&sXs^2VFB8FZ5;N2e<+u)jWzNsh~|yr6$z&DzYNYbf2_wk&K=KE|liBqNP5 zkE+o6X_1b1riSY__J&Behe;9&atXn(S^xe_q+#8*J+EmJC9=EOqWthFUCAZN`^+-c zP=^J1`xeU_?xP)1kmV8lku%elX3VpuY^H#t?)tm z?2(<(E-ZUG43S&MH!9m1%d?ZtCJio2`U|1^m3;Wgk1CC}M@qbrX!W{C-avG0aAOYd z2*leS50Z9&S~~!_=QNx}thGP2I$B@PwN;gCt*h4}+LEqCqBJ>S@rOrou7BVsu(&k0 zV{;E!HoFD3FQ8a=z9#uWF=0&01bxbX>Vpd!pTXCIL2(@MH8*l0OHy5B98Qi;Qyu&d zK-E%7X&!&ym2&G-THv%T=2#yWC)WO^2VFNz66e ztKKKij^&AfS(WV;IiFeVil%9MejoaePuTTaTox#BrD9{nJO4|_=f|;1ee`IwT+-TP znf{|b5!}zz0!g6n?CL|bDIA{tdjN7n?_01X=_WkLEqrJ1^6-_!kgPR?Tl-U3$F?ZP)*5_X^8#7+yQg7zQ6((z{c6JT!Gw2^M#dlP+X6qPIE)ExD+xgXy6 zb-uPC<9MOFxu+BOn(RVmv?K+u62+Jv-XA}Zyj23>u_XwJ_vyXnupWT>zaitD|h1{|pHP zcbqlc&hpTDXCT+;y8@gIW+;qA*HJ60(|FAlO(~{MawpIpAra^x0}XWKw56otF3WZ9 zO!}D&l7-Y}qvQLA+kQ*7byhW*Mg-^V_th)AMMRfLwpp>&VO{mrF&j}$>~b+^T_#!d zJ+vwt{jaa$C%$}|sra|#w+JjDZQZ^-{9|{>%V>HVCBDhF- zIc;-ot=X;0ZABgkZ(PD`ufkJ_*gr)AuqYy#tbAVsGHmfN4+XT|;4@+`(;AKhWZy&| zr2_9_dzP;=v)>e63J$H$xeA`rSe^sP`&hR|Dg|s4n2hmiW^U}mx#m6hxdhg4C_vTe z9ikc=ZuXo!wzBs+TY7hze!Kh?y*J_!?uoW#?fZt0%8hS!APld5v09uv!tHX_QyTs9 zKDNh-oAA3W?w68l?TR#3A zII7mDnvlBNq#BYMPs-t*lzLNEb%$)1>s zY+nPgV(%OcT`3(Hb!%F5vzi+n%S5jgFgC zQ8@f;W+@k6_Y|&8A^z>Z z8$(X-{ojot?SOe(3EUVub`i#}GKRofs51T$Oc0%Fxo1RfrL<-AvQnD(x61WNIR;!} z895)h#G-R{IET9N^HOshY~nB@V|J-rBjvWJ@&;rH_o(>(@U=E^5Rq~_R6>zc!?JVs zaaNI2K-oRJxKaGP9$7K`yv&@^{l9aL>{3DDgG+|u=OyM`+x^Lj$~9#4M;>|P)VQo0 zml%r3;U1MIeqK~gE|(a$>SDzE9(zd(UQQvt7KuD}T^Vh@3KZt5hp{tc#ZKKagvJl< z14+MaEK7G>f{qyj%3{sB>tZ93q(auwR zBxNkHBt<9Tr~O($e1g70dosJ5p2Mx_XopIZ1}eRb+w6@ty_#}=ttVM5ngGeR`990hch8w(>r+KM>>O`EybRc zJSmTO1|J4pQq2oYq-QhoD+Jw}jWcye(B|=M^UaM-$LAP&xgN)7GlH%(t*035&tR5} zJh7VY^yV1_!^{)S*7Gdc_LxBk`V1U<2=mHYeC3)&ZDQ_qO!EgVa}VxSLv_b>XVYsR z0F>59MxU$^q<*Z0kLhS#DuJHr8V=RVtRt?LZ41m-mrzUu3au?|T*PwLF zIYK9+1zg?_EzbL~Gxfn!NBRQsIDz|%@EK|g%b7$n2jjL;&Rx(djj1c2^6sfh0 zp(GZo+gGJ_(O+&6nH)5LTSkM7cn5cddU|4h3exPgoMfok-X9>$O|hz5^^cnt?!loXBVd z2_)fZ-BW7AugLZZFfV{7azZDgh^RQ4SDdF%_p>-bX4a$F?8gYl-a+~@T0eF}XXC$* z0v_dEN?y@#GIP%i=;q$oUkVO*Fu+5A3Ge1{F0vDadA6eCl)|!rB?`*1TO4ssfquUI zE#Ce8oI%fFr?qdE$RS4kSVfBr>?nzi)?AKtG8=gc*9^thH)+Gy6%)=)w+JAy%t@ma z_wbgf#*!}F;#3L8<5@>jM@__=vg~&Dqf}8(`9kaJlv6>3ldMiw7^4}7Hie|@uBJcW zb)(VX#vmDiV|J3)_mOSv4>08Qk##NC(I|8{u_-eTd{*-Ct^rshio{ZSLejRv~n zSiw->PXEn%6!N1$QM-F*lJw!Y%l30^!}z$&wsALZahgQ@@vOb+jRvA)dG;9lQJQGF zydP$1bvd0;I#R}Zt^&XDe25(2p-!g`TsY-w7 zrYk7!NDG+_$;o8+h4K~+#Bx(piqm(`#Ow=_@luWZE6M)I_!`<;FyPKjO)8Bllp(*v zl^*x2?oo(ezG$yTsck?%^cif1;C2!L^jGh6|Fq34PL{ZR!%MR~;muJe}DwaW2+gxl^B2 zRH~CyGOeygR&uiYN9jwNk`;;;R@FmTTE0J}Y5u6y(RkKynZDINc*GI>^hz~cEBnQt zZu&b6@>VAZZGhV44I=3Y+$MF)AIf=27#;d!$4hXs-Bw(GQ~?!BIjc@FYolzFVO zVo-S!sCO@0?cnO=B{^mtLZ2{)RMkegTmGV?f}b#H)Np%W{Qo+Y4t`=4s{3xMy@+#! zao09e=hwVcXf9gI!pgOTa|Hfp#MOsREg9rcn3-qQ4{o(9uiSwzbm%z0RcgpQyA^gQ zmGmFl7j#+CoP4~ak85PTH_6IW;s6-%XX>3J8QKyi{|l|Lm{8@2R3FI};N^NYsm(zjKKJaQUMp*%^&vmYui z%6BrfEdca8nQ4V)-7Z2ik=o-*cN9mb+^K8xTbbo-&7fWd?o2Ub`NKeX_d}3j0G6yC$hk4vyM~kdZ8BN zup$Tk__6#cWb|7`p6u1Y)u+T7?HcbUk|oQ+6K#h4sosjkj+GA}b`6!qj?zcNJfk5Y zuSG1-X%yI@OD$17b#b|}yqDTyGOwit6Io|51i|hZ&@x~?< zvK`k*QrxWEVf1F6hSs0+Qh_Xc)0TpU)ea_pqKUkYz0P-;{CgTkJ2*}FNqakvlMv)> z8C-lZ8`o&l^{O0=)=zDlhDH=wd^tP$sw_ZmmDMvVtJn_S(Kf93l9e1Fzr{%&De|QK zBJ0@h*7Z6ME4CsLLKesQHRPpY@vWH3HtCN9-leyrsN(C{Xf#@PZEz|YDBn6ormR7T z=kTv8-7Q0lZ)f8cFhTC%OB+~Uyi>ylNB>;;Ssh97nzH=X*MAj!_wR!5z=H2*fop7V z(1Rsi6eU;@32a-7y8T`qLrrYDA14H*5;JBf zGjpcKrco2^Lhj*C(YIwwy-U-z!<8$xCH0c-b+_CgIcqIz!>9yZ4S+@)zzNIKL z4ilE8anp|67qd!Y7x$BiN;YBp7=&U!=@JR$4~kl?-8p+&Qpih3wHhb>z2Fh zEa>Pc^>oek&~7{NK1aTN$MNosbwQBc#^DX?*yOV42k=H=os(*H4KIXzwvdD^^c@#{ zOv@b?Jq;OLKy=q9Itc?f1eC!MU}FMOLb-Gwvh;0Aw_#Mm8#b>iZO)bM3E&bdk-gy& z1vN*vH)VF$1?PE5hDQ;NE)wgi^q-gX{T~+~`wJd#&fYuswAS^GB_JmTcvd0SVI@c4 zMQr9*sb4#Cw>!>v6ab{k7tt`y2>r)PT>;B{7ZWxmP3uFw8SHQBo>S9RIeZTbHX~{l z5$QYkU$>RGJJ9BT%LCf!@k^fU8@pSr+0N8QpOmiJ*)F8!8G2kejb8gl-jW`OSl;ya zyhom+lHZuF1paTxvORC}Q7^9x(hiqs?bgD*kd3&M4%kt)@eQv14u{TYBly6 z0`rYmwfjM!Dhd!xABQ<(AGykDhV#RSlE$7#z^n7NoyMkzp^EvOxxVs~)%*a{JXibg zODn=Ow3fv>b&sGsJm=J7=SDB^@b~^pqtlcqP|?x*{jj6ZGgo27GH!Vp9-_UYH;{8k z&_4a4(4E=j)q3)3EQ{ZkWR1Rh$^>Peg4^cL)M6t{dLu^rw=H%DUADOmrxjnGN#u3v z3&CgfIXwdYpDpLQD+FtaDpBq>jQoC23VPsYa_bFRnf#TDlk9?V$yenRA^JogyhRi ziZLYXvWt{hXG8PVCKdksBt_KzY>|%CWtj?o8rad`2)Xn(D5?JC3qYDLIVY`&R7OM| zCmeV@mKH?;o}M-76^wTADUY{un&?I3FM{tkoVrFkX7C>Y(URkfDV z;G-!g%o{XHvB>}50^qh>DdMWwdMSths-cR7tA7^%NjTD0UIy{jZqiY7x#z_%}g&kqXTj2W<()hRz+M&QaJfmjn1Y`_m3kyTd+D6 zxjT(d8}?Ov-dk*c{Y#pj<~FlePtW!5Pq1a zdc*xMks)6E4^c;(%YE&_=pHoV1(*LH2#L^TxL|q4@93m>#(O2`=&7j0@xMpm{o6j+ zpzAAuaWDU02a+c3&m5{@96H|kKGd8a30lJY!dhnPKt-Cgp zBFJn$t%Y+Xh2_XZHRurTZJXfx>sP%)1WzGux}^DhTnk0FMU(2&Yeg>Dn6ddaj(pi9 z$j>pb5Bsn}oQbz%?>{K4KZdjii>QdBuJCujR9658R2aqYf;U-ah?$D?3TA~d7FsdA z;>=E>bMynvFscp2$C!;CA;XFP&j1gzQI{kb<_YG>E@x7wVyJ`lW;Ju?y=S?Bkay-m z#dlBkB>Yrbep{nb{(Zrn7!>^DbY5SMjxZN)_i~V09{Oa^?_4Wf!fW0mlP36KH(Fb-i_U57{2f_#5RfL%Ij@%Vys| zI@XEZz71zFv9j=~2dpzcncaRwG>>xeKoCFD+kQN{z#QrJz(=;W8~jJZ{4IBp zktJ!m9*Rzo`7^d^;bFNJ+&C+x0}oc0MZ3}JuSK3**0JxHJVT0;i;sWwiq~uArXEPh$V&7NNJfBq%r z0F;P-yx`F! z*gxPD>F44eo3xGm?@-6gNu|$3j$T-^`!`2dkMK9HH*W##zxj#c7Uv4bX%{VJjMQ`Z zD-0UaPU?l7h6k<8|B6yLQSE1jba#vBiI_!ADIH&%84ZICWg3H8`!4QX4aL6QCH2!gm6XO{;vu0c8tN|jB?(tI%5nX_??qr~}%4<=Q zS}B&Y%4<_vGT-eftDP5fFH37=v)fX_wa5d4VYla7noBS`itQGO6z})MzfX!{5s!BK zxSO)17z+QBs8Rq-7}{G1^K0n8+HxWZ{>7A2Nd-3LFiCU~E}+L&VcqueFJL{v*g86B z5D+Mf^v_QI{~N`+`#1TnU%*xFL)r3w_-DOols$jdpC%5l9VB7k>LCwm-1(4*zZLUM z!|uml`o$6eOAVAi(>BIi*Iq?9WlWBdEzQ~_n<2YNE%bus>{^~UHCvWS#Vr$l-!s%H zW1*<_e8ar>CB$ohs_1m_%ZYGm?Awy-j}~sqoUy?|d*-9G@V}xqLw_H^lm<&T8;b_Z z|D66;RwR+pe-`Q1WSMF8NwId#%Xj&a9=)Ble&yL7MYIv*f-TS$b%Fkj+0p$WP`2~z z0`3`A=ovn0Yka{EHGGRn!Ru_BN*Ay19E%r1^{25M+yCZP{g^EjM2fA1yX`SJ323WA zx$V8E?kQQ+gFvy|{|y5AhP3S_L4ijKK+*xqkX@5*N8rPtx((cEmq;{l-w$Vy(X<11 zr0^N5P2=>dHC=2`uExuq*?v=X`24d1FN#aaBAHXM^Uoox6K0Kpj*8#K)g}J3?qlU{ zX35RMMZ;~+_mKOeh4;t3OX9`uc$&QRVOvAI^&|Sjy!8^EhGr!vu?a;b5}JlbH1f*H z%|kPa`Q^-x@&8AZ;w^Twi!s*O$yU1N%$$&=W@}B@Rx?$3n6uNxD8tb4R;uRAypX5i z5v{y-^5f8qa(+AWO?*+s2*6`J%UdVA4bQ0Oue}YgHg$?9YCd_7tg2F*M$B`J3*}boakB8N&2h0zSNe{p4s-zun4N zp;e1W;19>B`-S!$2Ld&lYUl^70Vh1fcAVbrTgxpMOR^}14u;qp-@~)r*y4WO?UMP+ zoZ6EFGhpsmr|mz2u^*G#a6kOZzN{U!Le!g!Jd`5NxUj`1r4sK^1UJEh{k!|Yv+qO!f;QHH`n>y58@83uybu}C(!K(NtxPTu zrEqv(cwA6hq1pWsfwVg4SyOlqxWs4JqH9c-j>Inq?@TK(ETF7?K*hqy7;U<%^bZIx zcF$m2f<=KNFVP*E_>dE+satjB;99lfh z#s70^_MjM7QxPo4K{2Adcv6wAxhr#WpV{WKqY)6Hi?VFU+7pc@X3=+?1`qFJWOP05 z@z*5=G9_d~YhI+7JfT-HCAC!+Lw+33r+$C<&l7p)k_jx!WrODdBl#YHRjs~Rh6aPO zLK_52ZD?(@HuKF`I**tD>FRS)O#Z zkHYRbq}v;d3Sg-d0sMhIdV1;EUBV3c#_)Qy^0NHzeB}W--ltjC;*;NRi&JMKUHYts+>cbO96_psF)gcw%(DX?FW(v z-yCU6DAHkE+-#ATc8onzT%b8?NcKKy@XH_pktT4S&p3jl;$Qxi&Ox2;ci( zRdrLPyxFXKKrgF*39_QUAL$Oli9pfEDsMYA1T9%iwNl18|77s)!7jsQCw)sN4oo)) zA68fOl8_SV$fmD3s-n!ouk1e#|5Vzuf6g(-F^uP!Bc5!C z2CEiAUe*(%q$f%%61on0r}B|snMIDyjPd~d9lq5$#Q}u1FWo)j0@N$QtzUy6<3xBC zB)<-X05)WxUw7x`#(SGj*s%OX%6fdXRT=Rr<6@=8$EqPWr3wvMzA2E%Ghlo+ zZouc8oiCz%YF0hdi}-1h3;QPSYTY8YmRf%E&%e`D2G6=1bk`{UZskTA2cc)Xx zb9^%;h&PIJo+dZC=kGxsBxBEl&Z<$ir`u^Y8sL4(7qoqm%OOh^y$D;gh$^{NtYMbF zQ1&<9K)rT5G`#)zEWvmkrN@_W%4zbJ-BQgOfoAnXZ5#&g*G1snVfn`U&ncj7AZNX~iucc( z!z|3_FfU?GLWy_A)!vw#srF;K1V)}krvR;TELh{Jc5vs!>ci!xyNl>1zw1^BQR|=B ziMgsM`bInbjzFOu!s+;Qh(i#?1@T$PIV#Xn1pMX1KyPzwsRu+OTm-$r z%X{LMIxQtbuao|$)%)R2CY_JB-;69`A(oFjDSkoxQ5K@C*k7cidc$@JerZGF-7s_o z^z^%Sv^1CjJ^%Y3=a>^&W;EI|v+>73*a?6ZxdmrO{!qMl*AH&gsb)HNn;NkdWe z9!uOMN5P$rCW@Y;H!(|e2cM?YvReIoEWs~N_0&;qeh$_9%Gsg<70ngH3aj(PL!%DK zdQQx;fpx45eMekLr4kiU?F)~tM^xMw z_F5u$*J}jQ zE2nncrurnrnsCAA1n0|t4u#6${2c0^A6xl@$H`K6KEWHC3aZ}+H47~qQXiSDHmB`V z_ymBA^Rpim7<~QbVoc~m=3**+{PSZze{nz8!`_gy;cA4`fY0|e2h!QF+`~4)=okXI zhO!$(-BNS^0Viu+r}X~-xqj@m)*wRO0kzjHN1SCn#^b|XCzq<&<_rrYcbgu}@QN@i zgM`B$4<(cyd4rD;DjdzkZ#{5o2j)2S>pxc;3K<`?j#rP}2`nH8&OK3%8@?ztbb{#5 z-DuO+5Hg3o9Ur-!TZe31a~3$YoGF(PJ6FG8?`k#Pqn?FC*?Pb)i(ky`y5&FYT+#5g zZA_1Y_NK>8d97%VEwf^ujBoBLZ;NMbBkN5%U{sA;Mq9ztx{`AI`yGZqEzyy}?PQ{#YCL~t->j$@HT(jCcyf$@SfqpKU zq^F*ceh-$20D%FOV6Q|p^~7!~^BXh=F7!QdpvTq3zH{U7;K3zYw}cP!mfj%nDEBek7)|#a5(zqdJ>&YR_t)dU~5`2_p5a8Q$U&ZLFK{ zF0Gs1Zb@^IT~NjV!G(mSu5x|zFAqDkhn-KCgQ7dJu>+Kp7r7J}tgDaIPP0@G@7SI- zE!HKU+6Fv{mQnoEdinb<`WLmkF7i&okR7{D+;u(fCrHsRpLDNvZ8l~5ax8h5ZZm%@ ztISa{-3~h-)|0KR`WZLq2wa_?Vm=8~Myv6Sy3zJG;IveoFsvG!L9cGmkB#~@I6_)m z1Fg^kmj|jYJn$+SF5D8=^_myYm*+W!F)+jRjcb4nohY2_LHZ5HMzyTzw$e$q9X>CJ zgY8iX(zLs4&09C}5=Pf}*%>Y`YBX`I3*{89PE{YIGqWRa?}1F-ik0}k3{!Yb7-gG8 zg#0sQHaE&>jxPL=85L*+oc8Rpp7)6#%xBDw`!!flMiTD>V4ks#-(Urx1h53o980w; zt(ck0fRE(IZxDzJ?P11imODwNFQN5sFmG_D-Flc+?L^ONTZH{YdaFo;dkbV5PwMsF za9;m|sdIqwBF(;P zQ|J61gdzmmUB;Y8vE^9D8PZ*-mQ;iAYPId${`+m0%4&AwYNL0|TN<_Ei7JwdlRk@> zYW;3Bo*6dI>>s=ewwn(32E9XVs+#RD`ao5ncDx?Pv)m%J$eyfg+%m>U7hN6fB6m=_fj>k0 zGjqE9{K4L(j#oLYawoVZ`I>GG1AZP+p^8I_?vAAG+7g@ zvmshzQ?&w0s4zFiSyrL9fk=sNDZIQFMQ)pnB2EBYg)Gk@-Lzo4Xy%t@mZceWtIUv?{z!H(#YV+yWjpIDT@tyjL3K#OgI1<*ZUwBB zBt9xLH&BYQinyebbV?>{kz)Fztj~0;i#@2;d6TAZuplP#ggVcOqttEF;=Bve-R!Mz zkUmo)F-n?kPXI^;gcc6}-~;55-pc|WGSJ{6RaX2{rAz3RA}e|I*;$NP6w83muI6?* z^a34|GrLir^7gVN%lZB!Ng^F72)Cnb(&lDLBb_PKRe&m%=#8SV#k0x3>ntk(^Za`{ zu<8|1S*%`RUcUGgOFWBOy1<-a!OJz^SrD#p;)_hZ)Hcy_1%gPM%sx1DaG=!_F9%C3Da(g-2g)V=r`)5u-hRT1T*@v*2Gf=-FZOyd(aEir~QK7S{>mYWh z9iyUh=#~Bui^y-B{je}6P{W~m%;;3fHJ7Macxp&E! zEhejZ>Gmy{rr5sXC?C8Q%#*XQ*enC!Q!f%AqGl$S)*qz)BWwrtQ*KtwcpFh85jP~a z*+Fw!%o#tFKEe&{cW6P!M4Bgr?X&IoEHSUpnEf_pG&MK4xA{kV`#hs2>Kja(FPw zlS;El45&SXp_x~#+ElD;F)VcjsBewki{gaH4v0SJ-Lk;ug!C`exnO~3KnRuUpR&Ma zhvY9XIBDRjCee>Z%~H_gug=iOjA`jnWL>=saTV-x@3PV8Q8WuCvhy3>`#6k{9TieV zW`0{PSBIp18>)l^kHN-FnyDXqwXNb*PHS6Nv=VAHrg)C~!bmvQsyL=*PPr0}`mV&} z=fh4CIc6IsbGLGW$65^&XWosP@HAk?TBW2DSeT(qFMJ;NI&d%2fZg@+4CKp5$UkT- zcDW$!_=dNn*%Ti)m$_S8=w$Nnh~VWAA%wnjnzZS*j!PlGGAXwKMgYXB{w)>A(N#X# zdR2*0&I#w{zuRjkElA5x$N zW4@ELpSn5B-5nMj#FP2cl9WS}5z2#jiW?wEToQ)!lGKM5okCM0TV$n1qc1vo5O&yW zaSPHD3y|71+NZ;*hvnb^7S4^wVdmti8S9hNR{u&hg@;#vTA)E_bDoY9s3H|HMCDyJn10er-`F90TOV|m1~i8Iu+7eZ*XF_j zO8Wt(@FO;^v@4@mtv-vAbX6fpf_g09Cyy{+)Bu;GgXc;U{-I@Tlga@c&Q0I&?dx8t z-m6}m+SqedbL%_Z@|}Cx1?nZlnCqhE#O3OyUB;}=NJm%u&=qs-l8>x~fo)&MZFt8M z+E>%jEj{6HIEIPndpJ@nT2sqH-JWcfCyfthk5Jv7#^sT1_`~#Roxv?UHrMkK>yYh%WwhykhM&EQl^-y5 zfX+pUEc@CVTc6g-XRv(>d%c#3U%xA;ZnG+wb}ZWwVL$jLhIb9A@T?5G1DfAIlWC7V zsY5e+F~&cwg?DtJhQz-7pU{*oV^#G%11eg{E5bhQ?MmMKcqMwyvZ+Vxmq#{jb>nZm zQf-+Od>*zkuAjO7nca7voipSXT6kwu9bcGqOAdV;^#7V}F-uO-Fjm&m>p^8MiCMp4 zAL&)Oy}I*pr<&oa+W(W|n(;>HnpTOmS~xF7U^1?8$Y+2g23r6mm`uRWf`f(yjXt+zGl5 z?Xy+(zU_@Wnnj1@`-LZZ5DRn&Nv9kQV351RM6GDkSW?kC$dt1O|B@#klai1`8%mT|*w4f_%GD0G^=%{0w8%&8(j z;++4r(-+Z$J?DDBf5l!OQsQRBXb*?x!J!(&LH$oX{-3Ssk(n^u)w6d! z(nEBek49mWsCjgTW5sHvL2ZO1NEWg};UayT5B({ahKT5oicTQPCw}EH;Za+k*jf&J z50pkf^RcGo2%#*b`=3)2@Q!qUn;$?nm4}=%;InB(Z%9`t1OeMOb0*OzKjiV5y$B-& zWgyNV?FBCZRwzMVoC#LqJl=?o4?{3RUxbh1i#@x=7G&j_=p{#-3sxeO(404cPGBBv z_~u&35uw2c^3Y4>?gX&Y5V;`ee&xFk)2|?50Y3sadRTe@Z{0nkgDU(`*=I{uxN zAEh0#G%y=rC_CylIIE+KpXEX`#uz&BtAQ?hRui&QV>isD$-6YixJqO=O0;l$>xNtmDXMbiML>E{?h4rAWve}@{E!)rY364?hLkRK9|MBOcP=JaB8uq`} zeGpbuXB`~gj)argJroaF1{F1VkM z(hsq-@XF%{A<=R%=fb;QBewBq)`=HAI4fSLe@cfcNQ}f{u!8)>J;5>CHw&f$uMD3>EM{h{pPNuzOyz%wgLVUv?OoM&*KluiTRQ%mSzyqWG zamJ_t5mEXh$iM|0jVknGV0;HAaa1I@J6$4 z%fcgxWhw*T(>AF`vQ>^!?jNGqJ4A4BityN>j_A$wIaeJR@id0#wl;WUztELI@E>=$ zSDggr0yW{iP?{a)@E_puo4&)}!p#1<{w4g{_w5(T@r6CD2^*FRT;RUz&J*ewn7k{4 z@tK+@a^*j>J_axg@K>?O?hg9Ze&H5IjEn=VbuW1rv584Zme7c%*sJTf{inwRP_!( zb*(mmYuPT8#XZ>0HI3SwrCvOo_LX!kN$hQ2L?8SahVm%%CP(917~iQRx=3z2naCc| zm7pEby(Lu0~R85Z<}EgJH&Fp z&Kjd8`d_2G8(RelgwB6{5CY!6dPDYB7(TEn^d)sdiOWwKb$FYeKf<0hoh+zXe5G}8qauRIAT;gn&4^O+8AMD|w%kPJvwUGr= z3TTV~@q~STpk@!;NtJz67p3Du*Blnuh$??ZU`lu`Q?np}Ys@Vm$yoA;uZokfe2`in zDQK=MD<@y7FG;zaDKIv>Rzn0zh<#+Vu=0C~QRWY%m1L;!z-IFX;ZdKg@kiHNt(s^+ zy}ybl-d@7hAffaB$L$@5RUH#SE;EE&x*x7-+(WorPq2&q$67@U45bt#p{p`7{;J@2 zMIjyxL`k{@qYST#7@Ue2DT=8u_c;DN9Hp&^ldmO>yjlbG3&Y$k(vE$OYqxc$AvFRs6KVsyto5w#cr=4dvN<(iC>(5 z^~UV3AboHJ=!$(Udx7f4?p`2y@dnh2EjH(*{4uIGTG-`Z{gS=qPy@*k7?GX-`riR9 zuYtfa7~Nm(_WurO>71lTF<_qx(A;)!2tmgLszV+}9LhoMFUB2g`ymk`W7k z8z}uTssK*=Pb)??Vj;4D)1N4rusJA3F2Xc+mALW{jB-R`)O%4X76Rgn5n?c6WCNo= zbqwl)grVXkVT1qnhBo(9FUr(;-c@htQPUrAv@oJ!OlT!1l@IUH(<{}K;$UF23ggsk zG)NNMTQx{3luZG-(maceVg4uZ`%(qLEf#Ys)m-zL*f517_NVqZV&|L+vG5n&Ywc9d zEQR*ih4veKrs0#Y@jm~AInu9uayvk&aVn29*6NC_=$Rj+9NPzou5YQY8Q3Y9!x1V; zH}#=h)%Lga<%d$Is&tjSQ7$0MUX2EO`!?{6JHdFszaXFHmtmSRU}k25Pfq?lID#79 zB`1#2@Q**avBFbB>xlq!^ukL*_q{vcerdV+(k-JvZqBqL_^CYQ3jP!6**ik$(?EC& z7!1fmhdGCJ+%moI4c^a zL=l_olSFvNQ$(@Ylf*$T4lP)^zuD#RWtEC`iIX(?YxAfV%wgp4gpf}t2J%>s`awFW zY*z*$OWx(X9hhA99dOg*Pd{krv(jayNAJ(^s9wMN&LEiG{n`n<>Q=uq3I79`yHnIjJcy8a3~qq1vAze z*8+1LudY!J!bY?QQZzt~QJA?%`5!!2Sx5%0# zZAnoZTzYbCB$#i>zW-=lww=du7&g>E1VxT^ZC*-R={N*K&P#%~l;m!S@y22sHZZ7q z?7iz0=>`BIAAIbH=Fuf<7BdQP>SZ0~n#FZObI?6s^ToA72Sqg$)BCQ)n~=iUi014_ zuxxndreFfa0T3h+1oGJ@>73!i1anx<=RMn@?&_m$0W?Jxaqf-(V2kPrK*!vzRwrYV zJzB}`hCN$To`vU}&)m!7CcD#~ZCFp4=xADEy|{JaY;Oc}J!eff4Lh<(#iwz_YV83Q zYBLkv!LNTvX$W-syb?koraHxFfVAlaR~Vge#a0Gj)aBrsua_&UNIjCbz+3`s!hKCY zW#Ht`FH<6~<9fe)q|IMhI{(V#_I60 zg@Tj>WtT8{02v94E0Od+v5G(b_9F7cO%(ZfW*b zvrAIn4{=l>{uv%a&wRme8<6}a^9uc_9b^+h@LslHS-HvT+B9>lK9gAKYuvsflAWD6 z@ugp35)V=M7Ye^za1s~{26y1*L2D^^mtOfc?cb?#7Hk7^f%>uoeWUZPx4H=eQ8jg= z1UFCTIq{M%?ZTzW?ryz*0RzDYW^U3?EB*d_VsLY6Xk{?|@PQ^C)J5Yv{PE@bil*fn3MeErFYT|B!{m_Fgv|+eh5PI#j>Xn4I=Jfp(4}50@9bpYZli#YK0WgSLki(=U%)AO~+K=_~E=-Np+? z9S2YlZR%E5Op3GhpWYWGz^F=)(Vn<v$esRgC0}bNKK3=cd?(q zpgQENopWuJZgv!$ngk+AV|vPTm6&?x=^gDi zrk1A_)LXwidOvGEQp!6VJi9$3XQ;LQ4%u`|-aMV#2xk6#8<2+ZyPJadG5#Gi0Gn>O zwUtiVLpIFr;YtQ{jM~#>ZJT5@k2(6Y)F}8jG4chQ79;raNvvIO+!8vM#_Xv#iJT8h zhpS={)xX0>D$R7%?#iWK-D?{Ky74P7^&&qnD0>@|01ygN{XQ&QWH2nDRT{z)eV59sAKR2 zm&(^QnpaTNgvqTj#Y0RTRLhziDHnyYH;V(pV8=kwr-09bR=kE%#o=D|6O$a{n#0|h zRKO4Rpy{e~9nUNNW3@{2p1HIj^xx$zFP*3uEfb8Un=11;#4TkY{o>W@9i6?Bl@Z~| zWdHSWI*zW!@tI-0FuQ5y=pEXRfIb^UWw!_Zb7pPnz2@$8q%1AR@=U?k@=;&c-o#Mx zXRf-1KF-cSmj8Cd5gitwY)nn^P~P0|CUUVLf%7WwnYw1cLj zEU??{W(i@8Cd!mKu#0?-J`+pdZ%MrK`Fgth3sJ&%7ri1awg2ri_f@uI@X2?aTWy<` zpN6WdL67F^8i#bkKV6eJbG_lFJxn1o{047(!@;jWr+;J2wFmIH&ntQ7-wzLq7A`q_ zYHu`q@PetbAK)LEP7i3nZ)I9tfh`^@-8oP&&QW}azgFhHomvJ~ho9E|MoM*M>t%~W z*h;%6 zr#yd{$5iy^RO4PHO%_VM1FON$b_M%vcX3{09hQX~sAZ>K9Id>bvf1%@??kri?o?&G zf9>-=4`puP7un3>Zl7Q{-eh!B?6qW8Pe0#7{cW}R^qeNaO*_g~Z3cp{3{4A(!szz-cP^0wmsm6!soo=0)d%ghg{`0LnmyGQMbMn-koRl8a zsQi^Uwn+QA?FP_=-tM*@&Ov}weWD$0W&J)A+Y55Kro1<={nWmqd;8e#ep}t#ka%Dd z=40oWzU;O!(R8@sj%bh>&c8)w`b1+cZBw6NdX3?Rpv>j9nUi17Wq5DJ;@mo+W$rZ= zy{!`D3as^#F=Zk>CwGGFA$D|;&I z+)@8wqq33V>QUD-dwDy$#hKBR_VMVVA~Xu^>P9E)UEK@!m6I!>b$6~~YE+jKhkQyJn@WND@*T^AclG(%VI z1@!VXc!tOlbxDi4b{WVsy350OhNQB4$+Y7Qx|4Tiz#TjGPv}Vu->~Y6s@TjSq5a~_ z-1goY@zX~AgM`nOgB-bx5`6ui2;Xlp)-`wzVr=yd%W$XI;KJ8whWFZ2T(Ea<4q zm3q(P>s%*k=#_b`6n%@z=}LRX*G0;yea-}?tt0V6jJNg$VywC2-tjJlkCRe6X*`+O z!Aeh^F4cZjs;^;|I!~wjNUy7g<8ncVHx9i^ov`&i;}58StoOmKn9MU#wWga`Pj&VM z`Np;3D9jVjyW9_Jcfd&rg4&nNQ6ZT5eW&lF8#w|`rNFXMMcz<}J=R}IwizdE*YZn) zUOuvORe-y3m94gHjF~e+YH@>LJ`1M1&h&jZA|ZYZ;*bs3qx3vAY1G53L+o+?>-0PW z+5Nli9mi$bBGp&!06(_=vFPRtU&rEF5^og$1OIF&p7aN#!d& z_tHYvcjo;==Qs!Y#uI(0=+O0!JIgC_3KP!vpg7S2+>6#K>JyBl^@&}QWBt|lx1)XC z8K(niSknTp3vh`zU5#~Z{p z&!4*`;rQAsOY_b@Ol3eP@mBnNn`)ctsGn!?59FX@rF~@#{Q73=j%gx$S0=&z+WOkM zVYYj~`wyFau0wA8)#eYrbZyRiN1K4&1-%@WQ_HwQ>NR6rLERz7qR8R!f=c&j!nd5P zLanF#uTr>|SaOBJYYmm^)}hvt5L`m#k{+v?jiS(^+-opaiRftU;?X&DyP|ewO=9Oy zaK7NRO5!4}Lv4rR))}O8tH!?gWBuD|xx&OERcW?Hy!qAiE75UMX@Ll9+x%xkrVE16TR_NEU4sqvIvq9l_Auc&sM^pcRY_T zAE`%eGqTJr_h1HX!M7EzA>cZBH%!3-RH$ta6C)49;I&WA5CqDy%J=t1+y;bNzj|>Gg$Q{ ziNqy@_>)sxrGra_R!@`?6cz}UROja`5$;idw}b>m@d?g{C{0RALA zl7L?r*8f9Jf2YGU`~^-FpOS9mv~aK) zAp1V{IiX8L_M)~Ibx(1QLFy{lm+L}BEfK7^Sq z`CZ{ld`H-K#CO1V;tSNrrvd*95>h_hEp@`Wq!`zt;P5EMtg{jBY&{d2Q&toxT&)o4 z@`W7pg|ooGg(5a)?)P5I z-t=-MHzGR|dEdvL!3|!G-nNSIH@G`q<9D>Tv_&90p*_V)mzu|#($e0RUY7mpCmWC+ ziMGtydmH=jAwjmKwg^l9w!{i*O!bGZ2b*pcH_Oh6f~9Tdi$Huc#mw68Iv;2?F096V z-@)a5Q+CW{vmSyDRp=ye^lqr@I~}Tu0o?1ja2`?IAqHEPo0b&Y9%5WD+jq~a9^Ib2 z#ck|uhJvw0x5WB<7rjxK-6ubpmmKk3@kdS< zHpvMdlHO4~eAd0Oc=-?b0;`!fF%LUtI+^QNV6G!qFX*o&PaRXCUK^wqAO+6-0kx%T$af6jkm6<*r820T=EV)tHqy3#+b zpM3cheinA(e~KjuRVzhB6f!G!{VkG8E=o9~NiI+*kN&I3SMl$+9i=4YQK{&-V#vIb zK>t8l9<)NopW+NEayb zIaDe`yTtqXXl(oW(84@NX4FcOR@TKD^6?EfIW%(B%9i3e#T5dridO=ysu`2 zSij&6Xhvb>h3Z!1SWy5BtT;@>i;!Fc!ougL7k@VgL0h9hPBS&bUO6=5II88VH}LHMcMlkrIo3WO!MGuKQPqp^UAH$1_}Yd^ZRQ1n5nE{NY96 z)vfRiT#H#$6>v!ENr>wC8!KvS2)Lmr@~s^)*tCB$iQ*8HEn44W@jY@XN()XP0H7G3vTw4h7lNMz z!y*8wAvO=u$kX%;Vvig_us8Ql9@MjR53-lAqJBXVX?<)7MT~d=u999!8l{refq7F{ zX}|j{kGT6W;R7OSdg#cVc!#`so@X9+ zGML9N54lKz86cK*(Zjeie7`U7X@o3UFn?fw;8j;SJ;WmjI!G;`>D|8IxONIVBqPM1 zaY7O#b#~2P5;Ro-tl){}FC;rGI|;50ijA6I47Xnnr0he&INj8O`Bu-dfdL{n4^S3y1MBdv`NfBP8w3M!4)bFNL@v#JBieZi4e4 z-2r4LtJrEXj(+PNWM>5Nr3;Qe!VzRAuh=Rsl0JWk?xX1<1Fy`CUrvk}w-mG7#F=}B zL@)rv;M!quvk?*h`)AWur(`WpR|G{ThQbRTar`1|D3TM6UWDe0jJ5DbWAW5F1LBQ$ z@wf2ljbR9WUJ8Ls>&=9=pGJh4H{lCDktls}tehlLxi9Rrf(AHA9ZoZ>{QKWsf90~H zri|sluU|-BoJ23us~wolu=XElA5Sx8)QIyZVP~2P=wWA(r__Uhhl;+|XhiO2c!$$Aojf0b%HOVRaWo z*Rs29{OTm(Mzdj0bL(pS>4u-bVjdGYmt}X=_@fQIQw_a`>V4Q_9zE@=#p0WVXlX}L#IOYzcQJYl+_1}@?jYLdr-O0VT*R}81D~`q4 zaAyO`jq-~{5zU!$vFL0P^6+VUub5aY4nguTwk?T~WlOq|zSO10#AYISnq$R3IAw zrUkgAF|U%C)+tS!7P97KI195&1h`ELXB_?9nJ^w$^d}dh7-hi>b4;S#*O`tfmC-9; z>T5;#a4}zlmWIv^>}bBq^*$B^KSe*m6Ns0@KRPBBjbB7kE9K+b6||wO?2&0~ukofbLK`(44*skv)okh?el+i53t8e6J=hdGpuk{C9aEri z($hCCb_)p&yh|b2ICzM<2PM zHEhm{QJPL6lL@Vq(pN^K&0mEN{u$;I#Pn$RW={n{BTbP{ zY2DV8jqbYIhPT=}xB~kozah6H(g|{)`IWCl99<+zHuXZW^Aer=mX-$vGIWGRfUvO0 zBBE)Kp>J7k4ji>Io&}Y@d`x4vp6+5c!+0VGx-bJMv~tYhjK#_XHUY{u1kdo_MPME% z%f~dV8Ct$OCcCv|Z1rPalL9|sbHqOUHv58%5y?3^meHGbaLffB2fFtHsuszy_G7~r zF?KRyN>+I_z26gulPChn#V3fOXXw2eD ze>CBJ$<3UZyJqX0JJD%Ot(fF%j9pL1pF~)Ump8)}%}@7-_lH-SPHSW}oc=4CnHH1bbZxZe%`li@gl&T^|p^JXhI`igF zU{)9lx_C2>79w3xUwnP8h4kt7Lp$cJVC_FB9)C%@MEl4=qvdSs-1o?7s7w~voh@2hju&BssA>O12I|Yc=Yj8><$tmycpQygH8OD` zxaw}3)nYIGhDpMwlUbl*8$KKM1%o@zh3^C!4qbGWy&jNjxMgw!2JU(3I3{^!jy~$kBl6SS~_AG znt;0s+R*Nqrw4^jWv@f5s&4OLdA{4~r`L^I%6h}@&)10DASe97nYmu)=<);u@q<;aLT+7iym!g;c+QLDqbAJ#08+iO1>yB*khsTZI zyYBkg`bKmjHNWmm*pOvQ)|0N|Y;4xLM~yQloN7r7K01Eo;#c|`aYVKw>FsXC=t%t8 zob8;#Cw%vT78I4hC2!7U1lX|t6Q(XC{=CrIW)Lb=1l^mU+M&9WXf7p`X#`bV>G1BQD%42Afg6F()Io7mN zUyBoH0yCQx3mb6P`ysDOF{X6)AQ3Nbn0lrZK3J77h~^ zh52Z6^GohIIRi7s((gs6lIh1%Phnppyn@G%a9(9A;=U8U3z&~O@3k#xcM@;r)bByt z{iu$J+QTY1Ld&wX$WrA!+yB%Q!88ld(@sEh^e1>dMo%3tboR z-HW{vKc>DL2Ti*c5t|d7pusupXS;Rum7kEz>XDC0ltgB+jED*09=({S8R7^HWQWMG zMcZL(rJ`JeGP8Yv;F~(mEw?SG3OCKBB+DbZGb%_ZG~(S#~E5pLTx- zAS|HxihbLj`h1&xUjZ_FE56+|&V9G|Q+sY}Z-058kh<~3pEci!Mrheav8w>?@Bk&@ zg4>zfn)|s~PWVfzukPW+UbbA{V zAc=-~N=1dEK4-Wk5?!;`*bf;F+y2|1Dd`#ey&&B}i87N}QWEU8mXx`?&$=+?gld~{ zT@vfIvVDLGGX>=6|1t~9C^RHyl7>l_l0iMCY*378XoFT6NF7)?sN(soUN?g%8&vIqg3bt7SB$0|nPxfDiWW{? zv*E|`X{jkfAFdrZZ)(!DK&Q-Mr_^JgkWXbnPiMfJu=7RMszlPWl2b{0gXoR~Q(f|A zY!78lUt8}rqih-<9n?D&^0eQ}g+=oWF{=#umKpgk)PA4bV3Gr8pzYK25RqfPp#}96 z=Qm?|Lu-`ITAb$OI@5!7tRQ}}1b|a5_aR#9@vz(~Heu(5hF67x{nzZPLhWYD8OJU;`Ti+=gif43){@*})qPQ0xJJvO4ey~6**S_P-H>~2OeeY>1i z)OGxYcWOo+a?kFjQ<}Lr&z20{cHb7C-o%icx1b;Tl)|7dYcWcKZglZWNB__TJ|E+nN$E zuSaQ6=3j1jF4q#yTAu_}i6lEEt*w}PFU^}M3@66ie6trU|(Ed};u zVXhI-qE0a-Wx0@Xub0UsKkLMnEs;)F0z;Z!Q$k(br{P@sdtm^O2fMJ4CB(O|0SGa| zUx3iGE{WKaN+x2S)dKoLWd3y}$Bg3HU*O*}uLbo+<%sHmVOT@l`}NfX=PJFKtlkaXrpS2;92dT5wL}`DYUYLE2pd4vw;TdMYtyH zZ4N)w$P|aGmL|cMkdJkYl;mt}^ZP%iD96Z|-OYWpEKRNH_95deO)u~hwAP;*hiO{6 zqs@JTpVQvveo2<*=1`;R>>+@2Wy>+WGC4PcEsA~EVj`t zWgppxTCt^W&X&5793Tfk-rDOTIY<(M@hHTSg4zuaw6m~o)H^GhvVaK)yw&v;bYuNE1+wKhco%{~;K3MQn`IGz!t+nI14h{vS zavpZF6XVnYZLN8l69;Ra$~oC-Y|~w~>6&eNHrw=6C&fvjdQK`Vdn#M@M8}0?Pj&pT z?5VKq0kk%IJCU=u)0_;aJ~-Car*bBDvXc#qpUM_rb5?f+XLZ-r1NA_1_<5!{ex50g zGwR~?XZkbBg7yEBV)b^t9j&$d4fVJBTgdO$yV35^d&uG3JIT3sl5_9k{X_gisE&W6 z9~PW_KqLPh{`;stdx9eWO#e*E^uOSLg&O#0`)7lG)jtQc^$=O?AzJ$9`Il2G_7lxH zhtK+n%BY`+Wk2DtpJ)g_F`8mH!%uRCpY;|G!gD;~J%QGG2tViiDbD%xu|H7k4{|u? zFUeaDuaLn0z|Z-ADc%Ne131@zg` zjC$}HR&Z=KWEy8fx||J}%vpdw&I0st7GQ|807IMwn8sOvY3V)F zdr~TA@}-13g*%bqe7DF#1>77N1<~ zX+rOW=7#2l-V1#YnjiWwv?#P9v?laPXlrO&XnW}E(2meIpiG&M2wgsA|g^6 z5jhkoN{SQ_F{Qjnc>yURMMR`2QbeE;BO+3YNRd)Xkz%BX@jTBxJ8J!LexGx$vafwJ znKf(He9trY+_TpN=UOpUOmq5)m13oHJ?7j2vX|^d>s_9CD=_okNHcF=XQ1pSuX6^` z{5!}QEa~G6!_0dd&7==H!?B8(Nb}L_&I7b&*yGgD%yP(?DG$@E@dV8pA2{=1@z2Lh zu|R$#o1CZQ8P{>1CL^NAS?U(MrOrn8d>X?yyBD|@IKQJYyv(V0JGd7+uWGH0^P2Xx z@4T)<9XdO7T*sZAx2S=&9tqUeg~ z2jZ7B?u`~(qPwEI#P;a!=x*_PbWe1T*b&_q-6wXkH7s_K9XwzBF?uR`N*s%xiI#}J zMN6Zl;=^cJv{IakR!6JFndtdwt@u~8E_zXX61^O~EIx}~iRz^jZHZo$iq8s56E#E) z(u?*)P129fM4!luqECxmc?m1CWtZY>if@qLEWWY$M%lCYrsA9B6|B9My^DtxkC0av zk1QT7`xRFe-z{$_{!#Id>4=g92!by8<_iEgXQ)K=X|U#7dMy}GA5se9{w6lR02(1UfQ9-&9; zYCT?0(lvUzo~3K`e7#67(aY((TCdX^b-muEcj-pGkHS^xgLKaky63n)rO)VQqfBVh zrd2-VB2%gxO*_-Ul$$Q5yXj^6nEqy<+G~cGVP>SMGGomIGnss+^7G9MGaJvJWaiTI zXPE{3bW=yqo~4(YrFx@TVb)NL>&+&$!E7B~jL%reNC~u56 zj-EWpK9_R1(VOT^(T(0TZ>IOSH_uz>E%uh_N^h07*4yB1_O?=fsl4dQ4sW-&*E^t> zc!#}XToc|&v)ya*&iT^!DV-Diq@VRm{I-6X--*g-pWf$R=6CaZ`n~;reuY2Suk=Uw zo&3={>sR~Z{Yice$KqG(tlH{N_h)%m`nBq$Ki^;EFY%ZAtLaM8U*~W1>-}wX&n~~w z-{&7Rll>#!0D8u8|CA~9&-l%OqOx16tAj8|2d#qApk2^`Yc?nkx&+;WUR=6*W6&q) zPxV+G45aUnU|29Rs0zkXs0qR3V5&FVFAHW+{frJ~2Xli3L0zykSP`rV)(4w{E#BT> zyWSTx1bc%0!J*)2a3VMzoDH4Ogz>OYSNq3xZ5a8DVH>1*dDuSesCG~d91A0&EeK?N4T3}>P~Gx8SV`agondp;Ys>5nWN!3v)^;f3EehqqszR$ zW_wJ={8%!Ujg`dO#>!%yVwd^Vv2L-RvEF)htY54mHaJ!p8xb2FtB#G2O^VgTrpIRK z%2+MeUTl7BQEW+Ud2DrTU5r{mY-6lmPYUDS%y5i8XjV{3E{Sc6agS*Xx|>$9#@N2t z!Pt@5@z^P|Gjd9G@DW5u;umpRLQ{bIq3cf_PngX?#U|O?-WPQ+x}3w#OUddvr;B zfBaDVX#7O{bo^|>Ntl>S#Lb~ZA(_5y673Tm6P*)XY0TIgZwVyii=XT_HD$VLAIt$7G(CCP5q%L=zUqrM;R-oOb! z-U|?t^&x`R!-TZvuOt5-AUs<+!fgi}fiP>}=Djqb;V+lrE*smEjA|gRJ`4Z9lIzxe)uT3e+N8+XN`cn0rC71_zv*7{IedWyKaa3_rRmTH<0&gl*S^s{|kBN z^CQCT1$+v~$_e5A6L>R__uqtk0`3&>J1E1AD8n?azJ;fJ4_F7RLaN>a9)Zu}D4BbK zjquq4?1&iFBQ`u;K8C08Ub%2>{i62O58<8%tOeeQyLhiwXj`)hxcO{|kiUgHLP%CP z2>A?3fO`y2SL0^T9&9qy-m^$gN>Kc0eiC^rFlFIK2F z`4VG1C^Y%pgM3v26Yy_le|IHvhh8jxh^y$u+TwRmstE7ih`UhBZaJ_PKCc1)htXRl zPSIT%b{m@p)RDIeKDMT8U+xT?z`c*!JmVht42OF)+-UjkQs7;P?Hpef74Si85y+85 z?-popVkoYjfd4!h5WWkaB=AnI-4&s&e3n--if^IL*BarCD8VFl#fQqXpH>s79eH>7p0-EgnJHfF%WgF zY^~Wb(2kI&;dXG>CGc4Y_lLkw;qy=2i*eOG1Y{jEk1d?L<-pUx-ve#=e;vs00TL1= z<37hot&U6P81P>32j9lkDe%bvJ>2zEAZka>0e&Bd*;eoysD)0#=TRVQd4$XF>EMxe zx5M)n`^%x5qXrJ2%L|(+*z-w`JHtu~8Zq_^r zi8kzFjB;y$GZC|$J6M0hb-^K75hL7-fc&fELTy7xl!I&Iw{st>b%a7L^wU73ie`3> z2l2}bFb4Abe}v0w6CO`EotSyW4EUpzTz&_Oko+cDAu)3~ai%26ecCxUI2F#EMi~&qYp+Gu7!RR)`hOC9+h0%PE)Nmff9x*n93TAEPn;dif-c(}U$| z8q3GXb#k3ER<5U6`X2d`eAyW%Uy=3BecIKo^MLlKrA*R+4x9&dOvjwbI-wKJk9A6? zoGCh|bIwonRr)HYrtrTD|J!+}aAo02XDWWh*=A=q z|BS5jW=`g$)0m@w&Re-C7dd-!@8%9TZ|C029dh0UH~yzqm$tgpIRIAt&tR$#7Clz< znDZW(>O)1Z6uslTU-WL#L8rOs{i64UD*8*&UxY6DYtdhYDf)ZS--XA!kHRl%ilhiy z_ix=_v?`rbI!6>aG)K$6U;TFO6Tv_2R zaO>Qq?h4sVcaO05QVN#1Yuxp8Z`$4DZgID}4elOyzkA3%>YiZUQ8^Tz@}rE3t3nm2 zHu8*W@Agq0RcAR^byYp+?<-YbH9#&=gXl9<4R>d#QEH5vmW^thny99zX=)~geO%2` z3)NydUM-_?IipsowajvG`sv=jdA8$ou7lE7t(utakhA!2%XzqU%z98+R8Zd1x{SFG zcZTjpWl|x>>z=wdmF@`LkNF2VpBWIjO^x}S1*z6GviDb*kgxC{?ooY^p0Z6J0Si*8 z7OSoL4Bgc%>s$B`Q|c}??Myp4n*Q(LE~T2;WXk0((?#u-XH0j!uZ0`2%!nSLrKzG) zXm-1su`Mel^#I0*k)jh}CET!s<)iSK0>sQJe*kwC+(RtFht;OSjBajpTIPPNwJ5f; zgo7DfjQDo+m$3WoI*RPPk>r2pXHA5+H_s;=3jYs)!x2ALV)8B^#&P*$_+yTj-PxU= ztEM0g`{8fnY12HGbAg=R&pfy6%;D98?6&r(gq82|jsT6G9D~BTMO}e=uVD*PVeEEm z@vM7*uoCQ?p+4rjXl7#D^fsi;UVRr=8^kceZ#FL`pIdSDZp83t^JabuLiR(db|XBj zZFd^tEJyeexLRd%4g4W&*>MOts+sIaiK}iQV(SCp3s_2GsbSNTFT({i68* zxd+%9L@qGzsuyv!9m=G?Q%&xFVV!JOO4p(#oJB5boNdS#p}d!UH0ErL8qt;T8Hy)j z&8bnp`eOKCg&-#ayNIrYQxWG+KD&n8A0xlFBfPaIzK`_J64QYk!>vf+V7RXZjz@S{ z1M)^Z@h13OiriU_0I5>wFQy#jX6OF?`IK-xC1_W+AAJ`-WoSP(FC%c*&CSyYA3|&! zxQ?jad4)NSQ|0~wc{%af2F?-6?e^wYT<_>jHz4M#Y&nR>3IB!Of%TiYgWVc^$J$ly z7`{tBzYEsF}e zrxF%GHQ5*xGtJXAWM@owOXYmG9a$Kw-Ey~!+uiNOR)w`GXs#Veb_CfKWJkcVaA&x) z>F!EdwgugJmh74uvM>g-bwOzw%oc{6 zuiB{gY+sNivx)ZuX$J4=jwDN?#+}S1!}bQJpKJ~)7qU6psG;&Wr=RW}u7*=7ZDb4N z3+uz$9u$`Tb|=t`zRaCW&sareL3RW^fouh}mP%+5l}Uvhtu{~`>tvMgft*j#ZWG{AS$XcPY*+ivTC6{R5ovf2omNVRPvLUcv!zIQ2 zgDn?I#Zun6(cQ>m=}F;c({q->f~irXD7RH^mF`Dnx{0kBwYgS8E-7$%1e(Bjx9lw}CGKM*{6S+S(4VGTe1QtaOxJty@;gGD{fm zEwf~KMOw)+E&nVPCjd9V^SF*jzsYA3otA6xN%{ z@*8$d3X4YVgu5-=C_@FyNU;iDD6BVKyGG_ZWJZ?}>}#H z7i&y~r{q}_%iy6-6<8EyYt!0LcJ15?Xl(>ooq4VRb*HSo_C5F%zzufAu9P*OIbj}{ z@m`>{{wxn**VKOo1_%#UP<;fnsRbXUz}qN3tsxW`B+E!B@LK8!VrYQ-mq65}i*&l+ zkQMkJ%a^kG=;T?WJd->eZrfVGCn#$V+WG~n;5GxnY0zAHh^}riRph=?tQKpHjM%AsC{a`I;h@L zhtv`EfjX-Gu1>3uXk_?Uol*Z%XVoX_ocdHXtIxDkcwgcDg%1=ySoq_@pA z%r)fR$nDO(nQP3wmD`hhJGVFYPHtbxrjpGi^(9+Mww7#b{f*Y0|DzlEk8b2Yx{?2{ zbt5$In4$mq{$KZ{naGT4`CH!dRhG-zj9=FCkX^V{U#51DUDy+9o?~SFNvL^}{|7x! z3yZb!b3M;2eUScV7HhOVMfZ%Rd(Ke!X1$zwEWOdB^C8#KwIj@Gv9?Dw<<&hM)G=nU z^oTEbEK|kLXO$1dbzIk)$rRrbGnJoiW>Ac?b-hviR5O=wwOK%U>22!FQnj14K4v|| zzKNAS6n6vVeuvpZId1#8-siMAYr3<>$FyUWkESluqVmzBy{@{Nbw0Yr>+22h2C=$_ zo?L66OF3+0tqc_%3i zP2M@v#arcb-8B0AFR6RBXnoX4zmtEN_Wf?U6Kj3U8owXa=`p`TchcSRs-MwX`W067 zqni90UB-GJzt*2m_vF<+W|+Uu>zY^ljLxflB-QYA|Db=wKd$Hdr~ETqv#j`Gtq)4K zkrhAwK7Bk$2kp$*paZLZyis~1D}RDM-T;b=u2LP1*5iZz6n-F;&c0xXw?i-TlT>R* zKGy=(vKGi&6-?FJf*HYVecZ2SEs(!NZ4K&lnN?(*fs1*ZEjQ8!)&Kkv4#4=)}X=bXzJ+xD|OqjFKdacekiD81(7#V*RzT! zoaPN+6_I}=oEOXp7n;f8Vr~`G2ab62tg0wz6>bPO==o-LxY@J{W_T0Bt@;$DIjuK_ z6RCWtbSbtS;clxlG84jMx;iKiPf}|=OwViz&zUtb8MgN)d4*<3%vVh9`JIumh>= zQ~0+m@;sx}6REMl>F~cDoa_w5IT&HA-lreX?xI%%F9)^-T6K?AZQKCt0iV0!w%U+m zK&uC_x*qI}TZMvz9>o1GAoLgN1-P#VLXV(};hqZ|3pBVFyZ!2E_+U3azoG(TYxlh+ z)Glskpj{neJ(OP=+jS&XPWjdIF(9-6`E_9mF(Zv;00+UfVG~0PK&eKLJ{$!yn-84fi#O4f-#I zXGy5Q+;0H~!{4e)9tK_jgt|oCjue8mx5~9VmtgnO?OqGY#I?0)RbbE!S^W;JEO;jd zJh-Yu80@vUb|=QJeJz6mwm@381{w-#fwp|ueG|9;{EELTeCER47VcYtc8BIA^p}59 z`x34TpZm~vegJ>F8xIwUM1QyX2=}`vnYZD-iWM%Z4DLID-SbHPF4Q6t;oXN&N(htJ zKz#)41=K*O=kl7DE{wVx!WROechj~8t?~{!K1w-1i4|fc1t8>BFsL)3gzgab}y;6uxTJ@0C$Utl3S`Ak_ zpyjT>fk~uCLSf+!hZ`l9*Ni|(;bLb|fmu}OFB1DzR`(${0Kvma@Nic1D97Sn@Kw?> zWSzNB$xjha8PJY1&^G3|G0Rs~;I6$u%q$Y~kyYHdmaVk=vYX+)2gn)@)|xT$j*C!8 zkBdD;Wp_+%|NRU6?XxZfg1u8!K)VBE+uJ{ZO~7Z+6Fn>oFqE9d956(8{yI_Z|!rNtgJAZTV51{eSqDmqjIy{>ikV^ldn6c& z;@RwF*)C#vwrjS#Sjk+ISi@YCc%I+pBGxk3Bwol4&kh&s`28#5xBSi(u|9iGcAR*L z-~BCK&OVTxB3{YXWNXB$*=gBnVrzCzc8>T1vrl4Mwjuk5c#YpmB3{q#$^J#`%zltP zF8-7~kv%Qm&oN6^pZApFZU`D~jga<+<+Svs};IRnlYLNhWf0 zazB^J+!MKZvLH7td2#N&+zI)O+=sc3 z`6$_k`6zh}^HH)d^HK6z-WQbpcwbOn$NPe^f6?Zm zdU-vcjg=Kee<<21Z!BskdP5H3^RRMg(OX4t%kT5AB*?hi-RNpLgMUv&{)|tg z$~n=1=q6dqr%>hmXh?L6e3H+f%BP}Rqub;{K6xsiVfIYcF?%L|$?Tb2%qin4z%zgk>lG z?F`Ziuo82@N-Xu_bbSS%KlFcS8>(N`G=Q|B$C&{uwb#>jYEy&V)5=Zh@s z&~mQm%V&!CG!gm!`+1_#EoX`Hj6x?qKSZIH(Dms!MYN7`y58St4*B(zhpGNH&MET? zrq18zZ?o)z*~09C*>AZ8Gudza;w(|iIU;`@pCO|3nP8wjLFA1xW0^UyXNZ_HV7`D; z&*z7HKUm?H@EIb%EEtBfP$Rr^!6sjNGyTCq|6q$>4fbGIu*a+q_WSi;oGqf%O|@w= zRm>Im&3w`bJVBh90zVmcZaG)P9Dte8a*BxZcgTyvao#F(*6U2`_$kZ=1jCpO@J5C6 zs7Bh8|H5#wH_tD#OaPxO@_X8|Mcx5k?R$OsT#`BtSSNolul4q>(uq9~h1a_uBJDrsc#D|25g07@ry(jnAN#QWc*~cg&?4 z*&Zv2FQ9fmHC{)b1z}geC!ay0w%(@Y9Fn&-zMa~-35M`#BzqRgTl~doq(t27L9Ty0 zjg;u&w+$D@&e+pP{_?~iijjN6l*CXkYB`M*@0FMsY)?#y`H5+XnN(8K6OV_h@+Xoa zK9j_KBC*yxm)MZl>`hE;O^gYf5<93SCi#;RyAwSUdlLr|hZDoSu8Cublkxuet0{3V zDU*JDpm#Evj2-a@C$naxUm4CbXQ>y}CQH1+L|?xmz988)Sr+q?of0#Xmzj~tZi#Wh zuw>6Ml@QSG&AVw-OP!UiVyU+rNUG?)hbn*YL_fab%?i$m(qySHC1j# zrn;oM)0j}3>Xqt~>YuDkR;C7~hNOn2MkWTCI?B!TuqjoQ8k?Gsnw*-Nn&EX$%}(}9 z&Gnb37MK~Sy3|s?*4q$I$Nbca)EX+G@n%zMeQHx`3-#%m)b><^cP_OjwLf(zC`}zr zoru+@PA3+p&Qh4UX~%3z8*fTF9`sKarlWM5bbA^F52QP$JEyxQ>(f2bSEl=>wx_~4fdoDdtLp(>0{}WrXk&wK35=VlpWb!wHW!R27+p{u>?s&uFv+hhsPVQIOs8>yT1%>~U{(u5rtM1Fo`1k9L_P$x>o%w# zv{g>p^?_AmV#nQJ#iOx$usRI8i?2(dMziY=?2hO4W>$#~ro`Zsj(Q$GU>)q5#-6g! z_Fk*rv}_spIgOQ&1Y4lM7)i?~S-rwcxWVg4%if)8t|Wh`9IRGQg7cGIkOr&9d>nUy z`IDC+{FLT_HNdA0 zuoq&sTu3`S{pWC7?!@vR&^<`2KE+8A%P=XhToP+&X}J@yS$PG+GWcLcuBJ90CAVGu z+jY5pu3Z-*FWPEEEW6nj2*y-deFoS~tA~(Sn@Vs<5)6vjgVi( zf}b;%pFoZ@SU}74IZ0Ltp>=R(9>2c17=RPJ)G{!l1Vrs95qG7#L;s zG2FJ6e+0DJBXF6zJrFFBLHl&a04-~ano_o3gQ3w#vsw*=ri$ACSPJ<8N&`Lg-#Aq8 zQpPeOV4F1JQI;jLJ;nCH3BX^$-?A^3NoAQ#o`JADx@FKMa;i=Op^s5k`GnRh-f==F z&w?n7JnDX2MISLJ2MKP&p8eBkQI^ZJtbbm043(8e`CI;8qC73rpJzwx2xH6EGFv^_ zM;f#xaGCP0=Is>bVb+kVAF~ctSk4<-F3XbI^GkWXbu`At{0UV%3fca01;^%2g!^G2 z+MYfCEPnEX!z0 zi~w>i$`(&?W4Ma>Lfr{}q(9FhcZ3^dCDGcg7Epqr%%5LCdy}@MrQx0pwE8*AURx#h z?}6BDP*(9~=S|CrCWf-C~aG|2x)$2pNNo{ zp{x>7uHlfzj`P?>QI^GQk9e>LVo$KUYd(90u40_g*nhJ-Y|1i-hv08_&%o2m8n`V> ziZ-V0j>qvzLswkDpZ*%^30>Y|d+%dCI!OIY^0jr3TGRaPd+a^%~E~ow)01s*%tr!ig~pF>bA* zl~laV6MJK=bgx7hwvfu4-aw>B{xY7+$UQStRep~{12ssUzhp+CO}J%Dl4<16J8|TP z5YO-HyAZ~=6JyxY0NQ_X9m7uH6nj3!h33~mk}DP7^4DMd^@WqKZ`L4@U!KVOit324 zs-9C{SKknC_jmb^@A4nt%`*5Bsa8Ct%&RV$!W+936~Hbne} ztCzsMlZkux|Ma5ub{|?N#673QJw4){3nKkxk@}jhYAbasN!h9O@D9>j{b;GjZ)Vvs zK26Ujj9TP%LOgw?$Zw(EMo-r}#Q%tSw_N1erLWQs5q_<11;g%$y8BiXTjYI4)P9G4 zj{NtKKP~;r&7QQ>RDr@W)Qzl-D+$Uv57Y=z1i5G9+hqFlRR&!N7BR8 z^X4w~D&gejVG&=aSBbU~f95gK{x#w*`gMtWZ}Q8f4`t6>R-SoSJ!R}9cAdVH%ChuJ7cC9NtR$pYT zPFE9-p02kxX|vc%7ik6g(1N$Fg=Q@nbjP*1@Yc0K=*~i+LDc5eLKnso&Mx$)ow4>l zk6mezBYEvoPuWA%t@dzz7qRJ#PR7@*V!cEgZr9k8-tNK68lYaXm)J`+&sd`6+nq+8 zy;AQM_jKtetpSNYuWdKbcKfJ$gwS@QiLrK(g0S|H?753Mb7IaoPP{qd%%zW5Q=M|X zkiB=A$Mh}E6#a~S+G*4e_xbT64_;22vC-*J_ucBf3o{V0?JgxAw6=;G?l!yiUCu6J znnLVBu#XlUJ7$y@Qe`6kCFFqhjQW%`St8#&LMpE#ybkg<#QX~3haq1f zDL(^wKVoJh&G$)aKSazf$S*+NlY|>3Vnk5NF2vji`8CLokkmQ;J&-x1{4L@KAf_Ag z-yv7v>Q51#fi%w{JRW7x5XSd0uRz$NQnDWys|fxw=AC2;zF$>*0Vg;be@Ua|nGvS~ zpVELD@E$Uq*R(&tv+jm`5T&{b`FoV42J)M@dK{A1A;@0?3ZP(3t8Rg0JmooD1;(Kw zjS5sn^aaVU;OX3J=aG`v*jR7yJ~N$BA*{7@tRKieD$)R#QGt4>kD;6gQL1HV!_9~} zjhI&O+Jj_^dpKZG3qiWu(wcBFX}@#s7KOOV?UKLv6GQhpEd zSU)5lh8QPew2M);7D&cwfn}?VOQHp|7`Z)-+&Uot1@eB{->zb1sAJ8nVr|Jdh=%+b z+m(MrBSPzkUGcm=2O6%>T7^70kpy<8tw4AjrPr|rR{j-|@d#ddvUb%`8l{<-c=F2_ zad$}Tq{N?=vKr~J64s#S;wdCwm(Gir$5Hj9TnfvNpvAt8TK@!BPvGAD z2!9503FIuq|CyvQJ#|*3hc@cHxQeG3(@CcLBW4~XkH=xS7aF2|4)O&&JwSXV!gz{o zBc@B9F42BLO1Ty>tZ7(XE6nx6Dp!ZSL%4IZxF?lKYttEx0`isY{sd~EzM9%6u5Lkj{u%N&sKxUrGs>wiLi~EjTEr9}{Ar{diSTMj z)KRQ?DUFA#FQFY?Lk#9souBdmu12`Zs3MOe#*|s-fi<%~EtL@sU}20s>1 z4K^sy>R$DL z`b+U0@{;;n^=0v8^E>qw@kR1Su_k&=?NQG~U&@TmjLAHnnVR{eBun4=FY+U?LMjH* z@)7Af(!Ytj32B)KM&ZNKZlMX4z*`;xqp%8$!bgFu|k1N~}3I08#k`JYc*6c3}^(3sW+&3&khE7I{l5O&r6NA^%AJ zMam|2VJc5PCBK>)3SQxGxkv6v-2--EiTo!;O5IBsOX@)|3ZDd{@M*$WQuW{!K0_Ew zsu8@xCM~7imii?4gDv0>ex4AO)B>;wzW^3tD_DeI1dDJXScG2!i*QjUlgXyOoVhJC zAhm@2X{0*97TiYuG*bT)oWbqj4DJAD@F(O?BejeCX{3He{xnj1z#05id|O&(c#-IEc;Vp74;VtqLgty3bgty2~5#A!t0PpV~vR}=< zC^v)C_jPdkzDZb${B5xLHh|6dZ(#Fn1e@=>VDoi@&G$X9`8I*g_afMQo5AM$K6&|& zw~&_)`6Y1leglr)QE>Er3y$6~^70|SeA}hG{_^p>0eJ%zjnEjyAv8vD35`)aLSq!4 z&=@7iE6saG2?>W$?jsyVd4O;jrHpVG<->%-C}RnSQO4!X@24o^`;F`OF=cPRNBYfF zehrS>o8Y)zmBj37Z2EiqDc20uu#CXS8U2kyqsSO)lo+K(nNe;`G^&hRV~WveOf#B| z*~UC$fw9PFGdhgr#wuftvEJx5wiw%uUB+IK`ham*q&{Yx6e-Ub=ft&(#uZaCb<;5; zGtV4g4m69+Vde;PlsVR{Fe}Y!bFx`)PBo{CR5Q&v=6ti&Tx_#{^&E{5Xo3+#0BU10T4p~R76V_>~ z#p3cXtRs#p>|-G z*rkSLm)Yg^M7zqaHFbN6-DppY`53j-S!rH zyK&OqW$#6uCfNr>omPsx4_o6!jfPN->|<7geNxnFztv)&vCoP6)Yumd)xKg_j^yY@ zf5&kmC(jw+3>2jt%4HNiJliR@i;OmBm@~pyrA#fsqdY7 zXR0&ZnJId^R@C!~tvYj@`A(}DIg6ckXPL7?Xuv$t2bFdp`cQXPo2|}TbH31lDb5CG zle5*?;jDIcJNul2)*xi@wXZ*E{E}A?88nl5^FS zMZIc7|8e~atQOaBJ!`2On*-f^x4<3b4snOOBi%8^0(ZPS!5k=hRrIwhyOUhctr2-P zxOHZp+u$}?W84|m5_gt6*KKhZx=XBU?ozkYU1_Usm%Gm0=x#QLiM~AGZZmavr@P19 z?;diGihgNyPq?Su9`}N{^RoD@bFbNlJ=L?kz`o+;to@=TyS@JQDyzXOwCcPfqsSZT zm5^3=r8Hu^GOyg5XmyfScvW7lQ|w5_a%YY=#cTAYdClHzZ=ScnTjaGF_`l}77?E6 zPq$)!ra#A@@3;Dk{dRwuKiyy9ulCpa8~jcFR&%ny!)*0;`}_QZ{t^GUf66~Ap6Q6T zH~jPdCI4z5o2%_(f#FO3@xTjWt0u@7J$o%Eu)D3z&UB%1bG$|?KN#d342A^5Nt3<) zVth3OBi#wXm|#3Gz3k-4DVzx3;&-Rv;+(N^FoIX z21|maL1(bixDs>)>#PD}ff)rGtqH+qF;+T*ZDxJ2GuRXCch36tcB9c8914!QCxR2A zRMUdf_B>IOfwn5rt_*t2$-xD)RiwWhTnl!FYG_$4VG!oR{$ZipW1qAe%@OX=u*e9g z_pSY+PRq^eaHzXE92%Cmv&0-z5ERhQJ`%y|IJe(L-Sxus^E{3(?6#GEf=*m{U zGc}wRHXEut+$axc2Xn)D;ev3HQ5&{-=fVzmP`Er?C3L2d+ACaRuX09&>y0&Gw^0&q z3Acy4>~rDXuua5lcjkz@4ul7c^>&ppEj(;Bg~vpSli`{0oK+WI46nGuBPr4&CyJsx zv(jDXtZ)ZK1EPU;cT^k=i$+AFqEUetjTLiZi?c$cs<4cxGOCUyNA>n9(aX!Mm0^+9 z5>1V!M>Ea&(Hs$;AGNxhqdD$y-wAr6#ZkL4#cGO{MJuA!(OQxFhG>H^G&t%{wig)v zqfNmCF^6`VGp%*eR`pn48m}q`xynW1Hk(rR0l&Q(oWg0R~nHlcU%&g4ZOiNglS(sVk zugxsYbUJfP-HOe#nU(%9b8)6CxIou3>oOaSiJ8q_k@)i3nc3qk%k0k_vKET>Vx!fW zIhr|Pj<6Sqw%Ka8Wlm>$G8c^Hnakm#%rz_DKW?;z=j^J?iC7hHyh?Min7?+$mM#9` zAkJAUeVTJVaAn?(Cg!u7TDFM$?z}RAb zT*5-yhLqr;B>t=zf8n(#GZ&co#kO~>c#{?Er{H0c@xJNEhq0bn2!nqie+uE}AbE9K zK$7_y@~en{5wZso*j&OQc@*+)$ZAMnAI!K^@{yi75wO_HGGdr9QG$|VO@7Oc>mf)} z1s)R^bIFctZUJ!95*xk>c?VLy&ZT0HgGm1x@?lK*cBJ_*B(uo|BFv@5)0vgYoHAuh z@BY5^x%@B42Rs+K3=#+?pZlOZ%5{Y74>=6+%mV@v$EO=8192DQM9f*hs+jl1$rkZ1 z5|#(!3$2j#kYF|`^^}J9&L#V1xh{+`0hdX3=m8Z?cDWBjn}Cy|u+}jq*uhUn%oyaX zBL;Y}coww)a1D7o(tz!#v?3*Qj?kyc9ztMD$!=<9Ta=+xi~;~cBA~2XrE`ec1X$mCiDXH zTT@pN&lvaDA!A5xtEtGd4Qbe$>31NXK{@ZoRp`IU+J!kSVL}08RYoEG*O0OWSD{Pl z4_E_{rWNIU9xUg#;&*d75%V%y4_Ktab?3LtMo1v4i5EnSVtOHnJTY&ps}OS+?y835 z_sDzD0PrQ2sSZjo$(pM{&1^El-FC?U_-we(6(H2i)Khdk9SE|R=bNQII~iLOxg^kk#ewWX z489Q9>mz$yOsrW4(N_GX#=A^ikGsAN$yoMp5MB#89_e`oFwma(0!ciJ!iuK+Z>0Pj z%2|YZVGJr3;#s%9+nX;X7P7=A0`Pq$gS!Sp_QSJyUOj`DCn4t|pU)sX677$x@}Kdn z%eab~CalSB)5!F>R0>&WHQ}a==?PIbmlP za+ry$_h8)NU7z?wz_$c5PvwEvD#X1rP)Gho#f;kL4FM^)LgMWpPek}x$ZklygXG5{ zn~@K`GZS9w>xkcjGT<%4oMq)9q{Nq`?4eB_Mhxs3>O92kM9NCU@>NLIMz)clkJwh1@Z#|4r%^@k1UcG!RlU;}y|Y(N8H1A0GfK!acdDuNAY zFl<1@umRl(8_*EgfbN0~Xc%ljcf$r$2^-KP*nl2^4X6q>phsZ?s)h}y1~#C_VFP*s zHlWF{0X+#DP#tVQPr(K>1uc(bR$n|&3$*)!nHzD713>2;0vVV&1hO_wf!L;GiNXfJ_7 z`xnjAJn6C)XtDGrn6%em_mN=tNx{}5!`7p~)}zAKqY;xfrDyUp15!p@5SON0*lUVl zuNe$`O)=~>cfwvX1ooN_lD#H1jO;b3yWz9pBkKv#(^& zrM80yyB9pzUxEjF5IoqIz=J)Mr{wid{fewDsn@}My#(&-U%-964DRcj;J#iVYfI{{ zWNne}fZu|_@LNy}zXf-~Z^00%WYndLjfV6vqbXfMehbp|#M#siUH69AbN%d7Fg5ie zV~;*mJ7MhCrx=GsD;3fSsKnJ|o+kT2BtHeBWkd@TJ5!%`gPExxHYe)Gh?}XO>+^DG z(pj+*mqWBd1)qL;-FLx?TTebU_lheMFya%@?|H;l^pF`d&WoR4-$X1niH%I-)Ni?}OT9y~sL( zvraDwUFo@*DXEwCoo;H6)Qjvf`jne}5ZF!PiL=v3gcg?GU`gtY#F6YfOhN0c)+jR6Bj^38${p=TAT{G)wgo@+u(4M43Cn zs!Y?q|FL>G&N;1iC%g6OcKxKinS2TuGu)ZU38#x)5t}~3-UN(w?pnPNXPj;|y4)Rh zle^p9=N@#A7@m8abk99S9t6mDfO}QD=E>wi!1KJ=*eSGeo0qRo^9tOtW~s5%-QW%K zhG?g~;ois_-U6J9ULAQ0@Md_k($(xIfcyh^ouaM_yp>)T=^^<8Aa4NV2>^Zo4yASP zDEt8IHQU_Q^h~eEDNMI}7re{qdheRA-q`o=7x_c&L$ur9FEcu6ufJbqbkR>RWq`lfNd>+Ig(hx7)9nPU(@G zkcM$uW;;ECm=fumBP8>IzDTF_F}^B4isz1lyn-}k2m{AZ z-`hL7?<<`#6M1CsNvUs5Is|9wyq}&cKhe8L$gC`KPIgQdf@|B}TO-1E&>Br{#&Z`* zN2R`<`aqWCrzlUgM0~NP-B(g2BeI`$%m zyQoHpZW1j2>^Bk zxiWP^$ceo#N{L@iIWI+f&B{}UMVQy@rRbyb)SE&A+vNS7jGZR?@-I_ws)S>u@`ylJM?ph?G-^*^U+g1@XLLP5K2gpg6t7l6-x$-o4f%{f z`o~Br-$wXh$Wrk&-?yiEJT!AM;<193A4C`{Xyp#%Kbn}YB8FP;!@V1Yyq`)du2GE| z&?e2P2SxgQ(5Nxciqg~vgzQ2K+}S%$A_SGv-wQbet@k|c{VZYz;;sqMr6UO6Emi`w z-}lcFdFh~h?(Kb6qLaSp>Q`~s{Yd{l#E(gI>2?u*KWjVm=LymZLd!B7lorv#uZ6@VR zGn?Fq9>h!{KTB_-#P>icG;J8dPoiJWBR%6h&B=%$X8b46cLOjRKa9CzGV1Twl~jIh6}@HTH&QhJKZdsb0BZUHq_+_MENb*wl%JwhpGV5UJa1zx6k|I8Ud*=%&Q5x~3LHpzH zL+{*6Vfo|eokq-=F2WD0f&F?ZdMw?h?TU?&wb9))4IO7Q2|s9h&Xbs*`C+7FuZ7G~fl zdz+*_3kxtf^$BW2#zp0FJiQKbu-q-tX^QkF!ndMoqb@TMeh3-}J96T2svaf$6h`7o zjM<^sdC~-pW&9SFMDeXi`8Dr(+zIp{L4vCrb3qK>6H9t;(ox;$LSO z-KD*Zdsn4AiM}MM9VS8o2UecKy%#aoYH=3g)410``;=qO-wqEkOEGF{@uvF>zQDhZ z^dEs7hxkt*{1i#$d+56tB+;kJzoszO-s+IZwo6(u9(9?5l9c1S{aI)uSPJq)v~53> z@D0q0jAK8J(hj0|kMU5cKpJri9@>WqlMQS5JAQ<@7J+?Vc#|B8xIWAkTp2!Tl)pIn zNy;8Ye3eRydS3lg=ELtY`rl>rzsuoOSfqJ3=3suAteAzG)3IML=QY8`9tb+&x7-QO-W7J(^WVwc`v$rDH3 zT4GPL8gB69i6w73_HwJ!US(F;YwY#bTxQ7=N8UbQAGVKCo$QnL8BwQBQKLFhr}6eV zs*!zB{I5pjcSV%5!I7*bj_x>y>O_V`{CK0kGtem}KD@bFlyWGSQS@*#vEz+J;Ko-w zllvI)%!6kZyiww;a8^5OnelE^IXjGt_I75yn~rmYc<#BpWW;5+JqiP-f|~$ri*qAjP>3s zC-T;a_9^z(8%18Xw*^f3?KEP%UEjSnk&u?!;}kwi#3C|IBW8iv5As zAivn?CU(0~=#TQpig7vHJmpsyrGBLtgKd7b+vGMGwZwFHHi@)>(Dy51jOlKJsrysK zD2x2*{!B5(c9_l$_PaUNUuG`8ng8zZF!Q{e2qz49b1ie={qx=~|B`u}`0t{kUJ#qr zLB81<6a<5an@9=bInTof*{8p5`)gLvqoRKvqH;rd`! z*iGzn(N8D+)^L~E8txT;2gF^6!((n8bI!wyUUPWG92QCDOn(LXUi?M6HOOA?mD3+F z(S^PiP;JGp!5SXr5g(n{=;0ag(Z@y=eGGJFoeM3hb@HM)#5<1`+e4iZQM+G%oo~KD z%&=|Irf6%l!>NpRNBg3KMhWrAqf;W)+338Od#k)-Mpbkvx@t9v5j{O4XN-(TeDO>{ zW{^{y8Il<;%3K-DC9Zg8d}e}}r8@eU;$e=s;$pUKWwy9|&Ky9T@bDP%#WO22U72-a zmR{y8^H;canT_JDzD>-|I`PDbB~I+{%xU{1vBKS*R)aeub2)P@R%0u3+FcR{VMm-J z26$W)91?Tw(x^2a8kfYSaamj*PqaqHRdH=RMWiZ;8?7#(8!c8{JWc#Hi?P0w_}uY= zcv0MDR>U3d(Rg{hDqa(>kGqYF@fPQJygl9(#C~hM*DH+=m;?M)yDdH(AB#`MXUL;} zlxKCtSM0rX2Dd#st7n~T6wEM>XY;ZHvIE7NBl4QX+h%6A*uR<`=C8<($d1a6%~oV9 zo!#-?;Iwl!JeL`ntv0K}UAQ(mTc4epot~YU>B-K?&d;_QqHbPo zH_Mxrn=9UUUf69$xt83*+>-44@R(T{o-~i=mYS`(PXDUeo?DsgGS}wT;Xgl0#^251*&LI-u=K#H=X0bj%V(3;vEGHS@s5?fLW$?b4d_pM48LH z0{Md#$S3}YCsI2wYpe@tz|2y2q0C@CYb}syZC-P0&ms(5htJm5!4TA84PzgOD%hs% zCt61ie5$#IF``5Nb#VL>Z!pXYX4V_C)bBw|Eo%$XFmn?~RiBp_qz7xh&r1wqpkufC zhk>5_4DsOCYd=Q2K`Yqz1#|Qg8wLFt)_|3dJ?$nw zg27MMVOP|!2ZDV)r!i{M(-4Dx)S1c7%zE%})2OfJAm;zDcQs&ERpol`wb%YRfAe$p zIdcxx_WIxJ{l2x{rQw$G z0oqMj<)R6TYejTC)*z1Ez6C2H_G`wj#*f4Jm{kS7OoqO z6^rc{Z@z(hWh6xT$QoE#sjU~{DU!mY@WIFL9mZ)i&NZdJ(%_tg3dgqFl?{X9nEp?>QDA#D?|SE=pD3jIzae ztDb;A)*zeJ&@&)en`Z|hV!+B-5AVJlI}q-gi&V*I`9su81yZ#bmfnxD%&`U`<+6jS z5I(=fYIM92l!0(^j|#;BQxq$xN$DR zL4;A=Mao^&slEylt=D-8_fCZt8E<*-VT9Zc|0aa&k36F7=^NpX5kuoWC@j^&=n~+4 z8iAJDJ&c|OJBjx;Mw>@)ucYCtXb;$LsU zJ{Vzqym5TQ?Bo6AZ$oS{Hok~>=D|Gz{T(|hJhUqxZ)E>_@W-4@WA735IF?5c@&)|r zz~^nmj9G_^_q%%&B-Yfs;~^F90-v%TAQJsZ-cVA%c(eF;NBDRz=wBhuCm=`Su8ok` z-RL40&MLP1Y3$F)u{^=uh?wz~cfO9hG6;`-C+;8M(;pILt5?8%7bNyW=nZgx7~y5t z*S8^;Ax`Xg5C)+fLlp9ukLOC8;W_?!hA71OTf})iLLx8DQTRNFr=S&is5@`Aqy#Zz zCymz;K9eD*Ah`kQjJ>$)R{2odGSfCxd6k0-CUMYp0qiQ<* zWe31R`1Lq!;DQt|KQ#Mu`Xv;ul_CrW35=DQxg9eVp5!@VU<|# zfwiog)!n+$`Y>!`pM+KH7VERtSgY2WWYt+;v1V8^ty$J=tKRyiHOIQwnu~KXx4`=K z94ubHvyM{-ckR`xM7>9qsw-7Tb(JbpSF3V$jq0S{t2(RqsV?eT)m6P;byL@=?&^Bg zLw!K?R5z$z>Vs^dQhilF^vy7JoPO#UwvCGP~TAt)pynR z)FSnLwOBo%mZ%@7rRqVoO#M(TR}ZNb>S47~{Yb4+KUS;NBWjIWtA3)^see}+)JFBV z+N^%Ao={uWlWLpVp?;x$qmHW+>QAa!z2WTToe8evYS(uoZp=-%d2T-ML~zsYN0SdF z9|^-S6K2B>VQE+!ejywm-WJ{--Vsg+zZgyozZ6ai|1F#x-Wg5_?+T}ecZbu$d&24A zm&3a7E8&drtKrP>YvHW$>*4J18)1F;&2Ub5Z#Xx+FPs;CD|{ea%J!>pMfh;IGTabu z3?BmsrRP3q^?bMObLMpBjm(+Mo0+qjw=(B4 z|B-3Qyq&eOc2;HM+2U+@_Cwiz+3#f+Wfx~3$S%n~lzlk6Ci`giC)st`pJq2@AJ1;i zZpm)X?#w=&-IaYN+mL-WyF0rl`+W9=?7r-a+5Op@cdsEge4B;nohfcbIU+;F51K*EY{OTQB<|r^*@OR8z03btX8IooUVtXSOq!+zXsV z&QfOuU8|jS&PHd8v%_g{_BxHuA-d-X-E)lYIZ5H0DfC%wIZL&t;|RIPIi{03t4npc z?xMTvUb?TY)C1`|e;cBQ>5;ldkJaP#L<%*9)1as8nH1k4O2JZ!bFQxE6zF*r>q2LR zUhHht%k)ZTuU@0q|5bkQVyDXO=!|eXxm}%Fw+DE^`@4fE#%U5CrG6uGgj4Sjmhd(_ z;ZCzKg;QRrEL!=(ouzFV!zm}0+oa2R*N9%`o}ypRI1_SvN8E#yiwV4X+1V(2NOZZ^ z)9vU^b*jBS&M~jT?dT1ldwNqS>?z?qd(~X#-Wa;0R>wKdboT__W8xgT*r}Hkb|ZZU zXSmy&_m@!l)$$$_s*#P}F{;@uy52kK%=Vh878{*u%pXppv|6??8SGb;Epcz8R?IAxA_yD1-Vl*bt!hH#FrLmSA}bcQryV9RVa6(v16sHGlO@nxPvJDql13b3YU6Rqg2nG zg8o#OQ`?`l>GtN+HYuHzx-_WMUHrK0U2#*v{eDfbgxX(u>kbxwjXviu#QVyC1^W;VGhB5^W~5 zsG_y}J8KEQi057b`2nN~QrAFM!{@h5uR$G%SBje`Hs;>W?5X_w}L0^F{v%y_;y7 z+Ut@ap|Lgk^mE8p9ctESkx&AH6M)}b%$UtS`kPUDKV=z&xc8GRz58+3T=Q(tf~y#UygfeHP2@BxZzuMvR1i~vXQD`IGe|52xM9+X#)UxAI=<9x(+ zNt_R#4F^2QMh_)S$iKR{7hwnovV0NcP>Aq1K>jQIr6iw$ zTn$+a32O==F?OQ{34?L`^|aaY#ChQcxH|!jC3$F{ihu4Qa!ViQ!!yCe0;RnOs;f-ORu!7S3(XfB`v2Z~6 zkKw@ZY78>e8Q{m#Hu-@$~blMJFw~z!l~7SQ)>yQ zPIk^x_-4X@miC-=gt3wcxtjdOT!2@5F>1iKf$b41J&n^ajd1H^ihDLaaV{a%>6`+> zu*z@vod!MK+u|%>L`yX{8~g7Y3Cl93Jw_pp zF<$jfQppis^_r<(PV3|DDZ;hQzU7_0IHN)iueN7a@F!9Ylfce_cNMp$Hu^Ci=EUH8Och^rqb#a?5F&84-V@3peZ*Ou`zeF7Ggex^&OYw z^c0i``+Pf2PhcU#jo4=Qi`W(nr>DdZYnb8j6zc@^*@8&Txk+L{9RPn|2E*Nn`KaNu zy8*u<<;JS;Da_b_g%nV$Qw8@&AbY}R5G1gWBlB{ZPXo28H2g7-SAq$FSDk-`gylpS z{hYCom>C=PPq7oiI?1-+ z__&0GM4Q*5Vd;opWxd!_$k8rH;I|yxAly6Qh7H-U$^zjVoF8-i~q@ z2cKS$-O(xp^9X`MD^h}>qHse~qqahdcHmk_piSo!kmwz*U|_T;!RNxqXSntxezZn4 z5KjbdG$$zmjT?qUU~j`uw*j(=k*BP`5dJ=M3cS>UXr08GD!mNlsi7S+>$$|D1^h4T zZnQjC?5@y!Il!WF3cN7cnUl()7cje^ft=N?kmblbkeEV!Dd>aLU69f%MZ@(K$e%+l zK;EIlQqTg>(^+EV(Y zwjyEbarW@b*Soq#(KVWIG}}q})5rX8{aoZNZCdCZkkD9Cdr4B;si8NMZP{Vp%Xs}V zUcZdj|897l`k+&FNn2?v{#QPLX9c0m)p{$%OE}YMd?(T*oEYeNVk6DrOs9&F z<_NZs5^5xDsb6DsNij~7_$c*T7->>|PG0IXWy%ZNO68>FQG_g4Teb|lQi9D8eF|12Z?IE@gbU`Q(SoF$a=Om}bewz#7% z&b8;PXvK!^P`{Mw?}$^2lat2?P9%)9-mi3K3rcjR5l$qeNSJknKbGPgpRWoRf_BoJBeQJ+2{YYlm_tCKGPl6YOzj=mUfc_Y=y$Jvd0{c^YBr6%@zR zpvh^xkeAP!!OVQ_iAb3q8R@KhF)yE<80q77Wp2L6V7jUzBU*X+a5gf36E40Pkwxvf z`1~mpLqlXQ^4~Yos0T(41;u)7Me*3cvBCB1g4M`aBZSYGXcc z3wjl-D)WELKk1#%Gf6>Df|4+cmK`UCgC35i2nlFaSo=KWMQnSbp9v6K&;|a`6#0U%M2GW~^%0BvGRxnvR8n7}y}6Tp%sNxCzzIeZ z6b$>NAVI?$;-d`FYaCcW?Rjtu8qKiuhk_qx6T7{9J&!`^Sh;^;ieGPs4=vRw3K*&PyKeDF3Poxt+OY6}ax4Lh+dn64uvESD};G8!EP z`o1?5@t|jUsCN(6ckeBfC)$SoI#Ba5+=Vg`hHlyAAg6Sg_32|FC*W7qvWIa`)~m`Y zQMU;`vK|6!V2uraGJd@WAw}aSwtuv37pouCui*nNiYHn=Sw|xEE_F8%d550Il{#7r zpC7=DJ|ebN1&pjZLzdxJjAmwK3Gj@9?#{(HW6SCUS;GMfy%&Ro-or(0YS`Lcl)Uq2 z$lZ__g^X?u{YBl2_)+ifui!5lzX;^F;giGQgsBJ>vDKC_RV&ivw^!MDmKQ;7o$0Eu z29SF&T{$~1TAyx4+|Y+9S%rcYr+)^Y5=i8gp2@5=hvA|X4*AR%Tae%a`p$@86=5tYVMYe-8Jhj?3HJN-0hLbBL}?)$;O%Wi z0=~tm;&f0cl=Eswzi*_(YmSsLUpJ$IpcC_T`=yKu{8BDcK?c4T8RN$zwS*xicqNPw zP(ytP8_e*!QR>!3W_y~@!d&vz%-_w3!K>g>jjZ;&P@SHjc5%=>8qno0W-DZ59hGd< zo96D%F?>h%dKHmIubSck!*{Sbax&8Fp7P2f$Nb}wv%zw1E5i28NHLnEc2OP8a$Ad* zdflSs-eBSWj`oelnfn`T!)M$x6po$~9pZKNU#B|^AGm)yIzDKM^o$JVRuP>TG)1RG zr_(oTQOpL;^%tGT?TJ~z{VCC9sK0XZT^U{D52SKE)5;J|p*Ar~cyK1NI(nE|z+P$( zJE)D7M_-E`;C4tpC;b{~mzBaE9z93(Q5sV*#n{z55)1r+{Pb8}WJh$Qd(f2_B-roujP;InidK3Rv3_1O*54Z(nd=`Q#73XkAa6Cbz8#z|s>h_) z=*L?*$o)0KJsymryE;=j42#tT!|55b{H-}Aa(|7qJ#R2Qe|ceFLl#gVze{$NnFCbo@m{ZZj77q0TyiCD+jDQbB=sl^`+iepq-%vm1M@u+ts zUJ$8@7jZkJo>>{)9jA6kr8GWX7BBOTQMk@=O})CAde4yP^x%vir#OzryHShn8Slyb z=FDx*%;tn&b-bENt8;veyFXqV43AHU^^TTOj{5ucksaIZZo}lrmib_1`RmV3*<}x38yn*7~8}#Qg3no#? z4~sW?-C`;Hb|}Y;PWcVIBIZYrZBBSztyde=MJ6*-I+agJA{(!c9AU=vM3;DDqI;s3 zzlM3zgE_GciGlt+>N{o3mmVxn4DlvV3?qY4{=(RXG5<7#bzL(g29?6S!5UUQA%=LlBmDooy<8**C4NrF4y7da78 z?8>m@DWG(kwX?)L*jQf6a4+JKbN_^`)}WpJke`4*=E{b1S8RTm*BU*7AYNd2bDkCE zxaKrDSuG3hUgsvrBFHa6g1^~`KzE31pzJ;iH=p~>=#*s*B)GJxd^pVO zaDy$Fa1qN&mNdU-cUQ>wLB`?#ez?Ju?11Uh84qbg3YY55aD#=~c@R=8xHmzHcHI0Pu?iU8oRtT>r^E&$0}lwhs+?nH&VUlUfz{CFq$aE?HRmk7 zg;aq7+5um$FlRgYkiwSzd&qksfnCi>fN#Q$_26RvGv|4Vef@bzX*19@IKZw(>m=Ao zY8loQv$GHDlo{YyW+$@3Ej5Vy0mwl#&iDfq=`nCEvB-SVh!>(CLfHo#v`%0|Ek02Kfkk>(?tQ_CFp&cX+JCBvA3er;Csh~7<@+Hnd2 z`LjHX^hmE42K{buZ-PY19h8{9#-yBmQgCCm)DoNUyFU!s0qHr7=kA4k6h1>CrA^9e zfEu{b(-l@-C}?>U^gHI1IcR)@Ur0_NR`Vf6n;@E2jC;brZa8`H?$DxxMx5p(Rau22 zPrnvnpl30=q@iCnXH7_W(dWuoDjHrHKSf)Lu}ipkjaIr4_lo`zT4{yVKStk&ekZ(4 zvZKhHIxBgU)i$D$7Y)=R_+WK~Fb$g(U{Y41RY#qQ-oh%yF5jHqBBw3k6!Y^to!6M~ z-r!bVT-Kbl^5V)Ug7Mp&ONLS0Sh#mkPqr!M! zxD6C~FI^+Rh&vgKxa;W7cg=|#j5yDVYk?PcWn>NAwH}PPn<6{uu1m1u2F-1GadVuw zkppd5alK%{rFd~8hbc63;|^tZ+=vHu+$Dw|_e2nlo)UK4STt4;D`NKBi*w(`Y9saS znQvo@VoRCxww3WVa!xpJneR4sGEy09j-8EL%xW7?GNY~V*>axZy_mx`J}}yqIc%f- z;v+dn@tWw+_}J+1`1pv&e74c4%xD`u5w8!T%xW8Li7$?Aj80-^TS{d#)+fFuHXyz} zRuw6YZ;FkHZw;!L<2KeazOOyUZ9>J05twljs_pWNuq#wvBj+LD6xEp^4#% zQHjxsam;0#W3r9zNz7;V+Qjn2s`&K8+W6_jhWPQsX1Z@%Yz1@JGJmZw*CtLS&Lmob zv(X?=NAsAqHd@TAwXw3ivb@gGK~#T3^SVWc2h9;LuP4>iRA#Zw8<01cS!-JvYh&y3 zrbWk5x%58IT)QYblv+|eZ)s2z8&y8N2_vD9Bi zF#l}OnYm|~5tsRA$!}i%!l0V@XM@SYK+8RbS!jiSmioy63Rgw<9iZ=qi8gV2{^|IT zJe_}z`eQ*bpg_eB7X*n@^sR)MX$y*(kD1!;RO(l?`F(?if{v7yp47vd3p#Nx17mHE zycq?(sgG@C=GuZm1-0U53x+Ib;s4JKR_S*Xk<}+t)!E)xSE!aSD zEoQFTSOc@w21^U}N2Uu~ZT>)Es->E-sBbQdj4WsgP8MqF(VGgR^nYX!E$A-{w)qPS z%Mx9qOA0#&0}8w4mqzmndnWEL>=RT|@9bAtk)Xa-I3RwwaByK&e0<@E!s=+Jpr~+6 zbXJbdHvcfS=S_t(f~rK%cwt%6OUB~|DD88S$%xg4zqYxsImcidsl0&0 zb_ny=#@8oj@|CPl&SU=Cyk_CBjSdZ(3(NS5PfV`KA0Hhme6~S@@Y%+86ikgZGpB8= zBKcZ!P5c1!+A^PQsFG`PT(;3k%x246w(p(ksJl={3w&%S^TDox)LDFrOJ} z!yaJ^bJV6!CweC*ChljRT4AXzDyH&l2~QPuq^k`-ZQfF5sHIk3lvu?)wS^@`qmvVb zskUfpQC)toqFJ$u$U+(scNNVknqPE((USayG`dtqw&vFqEiW9y9JNIoiZ&N*E7}$7 zQ?#dOe{wwK;dG>LVo%Y*qNbvw1)U;R(TSo{MQ5lMJ0|)?YKmHd5gDC{#`k3kBF8gD znUYLdrgNrSre~&4WK*UhlFbar49-+#Mr5ipV=}dw37N@xRTOesW=3XqGMky3S&&(j zS(>Q|i)lRGlUk5jk(=cU6P?*-0<`4-GYYe);ar9F+7%>#-2(Pw%vfvK6ri#3fGb^~ zQPJKgAR2c%LW(vIS|+n|04UY$9+CC#Tj3*k3V6dUZ5c%|W0ivgJ~xA2Z)bm3R(Q;1 zw{Wxy6Mzq-rof)0hu~Myr$IL*norsNV%BE>Wt+3Y&T)7_b8-Tqy#(=KRg*$ldCPFG ztaQH`{@rlb=US?)+(~zWTcMLN=QX{69K8=dQN;W>-{riCydc*KTxUj0vjTAfX)4Ku zXu1ULilz=4Ft-j8nAeL zY`rKAqo^om;7RNohO=S)Qf104O0S-cGOPbUjT`I zA*V{(i@?7vy&Kv#uN=S17$TY<(Wr~{0eWdA{%GwUYDA-)H8i)vQ)f6Zq{Tu@FZw9e z7bOfWmV=SUegLH{+M(_UiClZ42}e)&Fm5RHIN|EBF`_DrNMaGNg+KfXJY9O?48+-| zWvgX)K^@b};6|>615WHX9!gkrP{QmY967>mWB(cBBKD z*%)1oy$oZ8=-!1_P?&zjx?#V7r!*oqj6yz2N!C+(pKqCJ8GDCl3(XqY%9fdy(b${4 zj$-4JT0>8iRh6>B()kX?QuGcF76Dmbt9o(#O0<{Vkdm*#{S~Bb6W-LXa~M}_C>S}- z$#17Hy3WFzD1){t_6umg-Mip_3}K!`T}WS!!T&Kxyg}Tp$dTwU^WeV)5;{zUUaY|B zEgE=N#wBUbNUg?OKs&e#@z~qZ2A)N!ioIzd((p6HChsQnXa#+{NV z6~UxdbunL>eBHQ34?)4JdPAj&=J%%B&Kju5iN3sskIdFLOHO1N?MZbuCO?vg21y z#}%eD>f@Y?hj8x?;g8w1*wf8f4A&w|7XAq1{DE7(jXJf_)9u%|71=T~L~C)S*I-s7 zD=1vdqs$(7yl0)4(YwV8BQ_(9D)w%SUALiLcD{X@+}q%O2K@+aUiO?T%=bL$Yk$VC z6>$F%;je>F8GJBm+24iF5tQ&ZA?p$HYhV=~fixp+#y)F7u0$BT86C|3&G{GiBF;I8 zN01T5Ks_2^K8L$7e=~d3r{d}NApMvNc?tN)*n)9S&a-e4vn#VBl&4uMFC#5Vo^S`f z(QKSSV0LJH8s}}^%%_+-pT*kuNr+SG4!NWE-AIdPdL*5pI0+MNbiH*@TtU}9h?5{8 zI0OqGJh(%G2MrM1f_u;~xCD2H;1Jy1W$?k>-3OP!2bfvjcdPdMwtl-+_db2Pr@QL@ zal3C%pL3q4?<`CpCqyJLj)b0vb(bBQZmbTf-!nrowQr7BPXhL`N@IWNIv=biALC5qk(I@Cse=T-0(8DxMup|{bc*8o3fuHZ z$VJ7SEq>`czMJ5k@{2yfI7>32xpB~%a<|dTd7xUIUgNKLxTw*8sa7H#|K?Hg6uEeg z*>&+mc0L^3uDyhOFw%-+M2Gcso#&4Soo=motXMe}Y+lq^! zvo#G+7Qz`1_3#ZCE7Tt{YjrDLx0{v~`EGMDx=&|F&+=4Iw_f%-1p&17G4@*F)%8R5 zpZ5iym2Nuy#e6qZ)0zmUral7#s4iH z%HDc*8UYK)ZaHocUle@4Q_Uzr7olNdWq#$cN8*PTFP1*Kz`V&r{#%`w6tQBsWbp@!1)|GF;67B!WpkY+tHx7x&?A4>|tOrdSBd@8jGC&&C!~slY{E zq@cq$cQ@)s#j}$IWHXaRfZ`kl@tlneU+PYd7ZXoEXrZeql=ukw$fyX1$bo4?x1-Ws z_G)6sKuaHhnmPlY^q#hpnm2G9|7ZKo;ClT2Y4yQ~B&Bc97dSTnoYf1qJRpAYNp4;0 zY*O-m&dGWFRSir^Y4Uj1>)oGmtram~_O4ocAhoXs?(68E3*Mu(@#Vk`Z7r{u8b{CA zZ;pQ5|8wv;llS`Q6B0N1#f#+e#|rJXx@l}9XcgZ{MbL+%v3P0r=t+A`)q1i(MU)Os zyeKmCAtFs2^GZiii`Xm8LaCKMh!;POyk6s@nA7y4Z#ev;vO*+wkW;$7qH#yDJ0 zR;U8Au0l;N6Y-}Ef0U=DQHwdb$Ysox@*pcIkU?T(iE%eK%4ByX$`+&t;^qE#^~qv6 zI*yHGT}IMmj#hsHuf36A(uMW+`?`0|h-RX6gX-L3U4t`6NP-v;^1==R>x=*%P$_(Q zfn7el4ss@WN8NV+d7D$Oy3nH_pZ{qV{P1x^t z;ex<(``(+jt?WnxpQAD!;&u)~vHOmIgAb6LQy*;iAM=fO8&8=*?vWtrq?s?A%xYM;reX0RZ{*%{{`?YwsD>gH5rLDHar=Uy*IDBNOwemVEX}1QnAmyx zPLo14XK$LL&yo<%b2%xqMN}7kdXQsoMrF+)E3nvo5-c^85^p-SI;8LaJfjpUhp1RF9lrQOuA}-X=udtAKg(o8AUX7l%b-!k3)2IMbF!17#p;7H-3+p z8wjR5{D2oZF;ZnpI_{x*v#v1jAI83MH^C__Pbm}Ep2_ZVL9r%O5MBry*B|fHDEfhd zq#tq2YDzXk*C~ATS#o^EhIsU%!&(o724BwUO@}1SkUXM9zGl;L50lqzRjXQg&qoxS z{C++EC?LLv{)PaD<(#QVW#u1!bEhfNt0cDWq4$3)@RErkmp~)j5EhXejAnm!i(?JJ z&XOzOu64l{509r4Z^r%@IP@Jtp(fQOOG%wLbU)0)6)7T1Bmv}hibn`fY?q^aL?czi z7{-hf_V|(3{}l^wk9#h9O{q&BErD9Zk6ikkad%isO__Pv6@pbt|2qZDBNl_v!}d9P)bR((JD|T5Ec-K72R7pIl{;fwfZq2NgaPrN1kCG;h# zzc}Pv`?V3_N3{O=0XPUCWJaXXfMj*boX>(jLN#bD?_021&|0iVlCnEzni202GW=!z zK>>_{cgo_J9SF}T?u71=KN$x(tJTq_-tqW<{fV^XGLkr;E_93f4uJ?E6iF5dgaA_Z zaE2r)4|J>jM6JOPKo%g=)*Ap-&Px0gbwqXKb);~_ufZL<{?wHk2GH_xV!nFekm!Hu z;FwhX<7IDzAnZ5M-VAe^iP9@*9yy=GzleV3`gH9hdRtWEhc)>Cqk0lf!dnY|!u#|~ zj0@JYuH?<{3uViCp~nTx0#QO>Xjw*hyG96CskI#bc<}SPtBXO7zVK!sY8VL+1sBi& z{8adm3WSMqQ38iffuCM>Lj4Jm%uISMtG{;#;BpDw zF%>=DhU}cIrWxcL5>ZJG?m+?~i%uOe7PEnhg!vlJh3=+0+S9hAIHRYBhzq!#7hkpgN}Uf+q=5VE5L}zVuJZhxUa4< zBVFa>*}f~B4cf8x77n^i8!7uZ-jV&!KuUAOIz=GZY329qS!KQOFQ)7Asiw<2D=1rM z=X11ev#j{%{g-;kF^8%$Jkxpi42DM8+WEp?fAI;YCBNh1&e<16v9EV_4Qc*Gp>%$c z{c2vdfEQqq&Z}o>$-qR;ICtiHj(m1D{^joY(iTfkta=&n+}`PYT5<78y03oV7^ws#;cTjQ znG#7qR@>?(t)73%>5zoao$aHmcfM43gUTK*q0?i>?k343=JGSe$&A5Rc*@s(tb<<4w+4 zvT>)IeH6416L%VZKCO?cU?6Kb7pe>AaZVSKRWM^*9=(?hC;wPkFy5H;dC_9=T}*Y` z##W0oJWlS0!Y9=i=|<^=R(W%G|J@Y-E#S>=VITSC+$OyuOb(73zsIKUM)2CfN7-n1 z-!#|Z7}#(et9MJKeS4<)qF3|4$9f(*5Ew`Pr$AaxznXeNrvI@mY_^dwAxE-KNv?ui zLSN}Cr@ROb=7R}BF zwjq5OZZTX}AM<*+-V71=!RB&s)x(|P(qm>!9PdYAMjP4%Vz4uXzkhBdd20?uq-K|s^h z2^1vKCm^#h?Y5%4F#B%hE8l016BS>YjTi;pW&UOUVdFctJ2ps_y9&Et@44}P93Nj4 zHZAbyruinl?6S|P7(`63|6QDEML6%Y>mBt|oSO;I99T$tigSw7z}?C5A-tRBLZF{wgQb~tKU36$`tuAd5y;}$1kqg&|K)ptZK0WF{vCaV6ks3 z`*$i$sv`04j3xMo60fNTxX?ekWhpa4jf$AF(z-t)>!^2>T23k1N`ISfE_hmrF1@L2 zUdUK4znzEH#_w2v;gjzmPK{omoTW51ZE&v4TrXCdE~=rqO3`-%YF3V$;MmAE=AU}) zV7J#&gZ4WH(RvO$VIb`w2FCAWjRORflRS&bcn02fDvo`B^arD`s+Yza0bFFN;{VDIA zyh~Kz;8*LaX*=@xYtI<8^aj#IBdYaB2JC}2DAIsnbsW*cW4&lu9c7GOE241iO*=5rdjKgFK-D$WBy z&qn-k_#RosM!Y-u9;>Q=*IF%IDkkzI(EDQ*&J24M&MQT*SX% z!?xm+fJv`(K0Ttz>4;*h0q%z7_4*6gv!zA7rSDyZ-Cg<|eqAZp^uk++@2Sn5Yazv4 z{q@|Yec5vrx`MWZVJB%%Yj0|2YCq7VrWaf}!q>;Qs%fidRo+}#Q`uBmZ(ql|;D{XJs^F^hs`)DKs#3e%WW(y2a{D4>6XWUn8DRBk8GQwPIdmEGO#060 z4$mvir_QI$r>JvKY}3%v$i~Rp$aM4PrroB^ruC++LmgEURb%t2%F5)@LN!aim<<{iiyBnXlQnS*#iO46EKgzY;&I^?y#eOga$Pxc-8Vjjn( z$n+y3$w@w3tw`~pbwM#(VO;Atsc=NzJi6}da)q6S8?#q3!+wqP4{I%DX3M1O5&HcI zX9Iehw)8^gvL2W6lJp;5Wj^k{$<^;vWiKm^5n&Vps%%;;^GC<5zpL817fRSQQ%lR| z(0|pMD#kQ%~P{J3e=vw(`5{nES@gU{*=Ag7M}Yy6!3N zlPe}i#gGl01D)WD`y8v~lyl6T+!A`{Xcs%oklj*1DPR0I%9^kAfs*2tIkOzcskgRJ zrQ*{uS7znDBY;fSuu4t|!K8WwBU2Xr1Q*y0`~$3L8ST!yVG&pv29X?rPVZg1Zg*^IL5(a;b`}8m+Re$_TMD`{34R z?fwW`7(cYX+4s^9;G83|j^Z5rFd$RPx%}5=jK?PGanQt+bo1#$RJ8Zz>L8%Q$Gh5= zdnHF){ee<%JuvPdbtyqR^78829#JvF`K zzaFQ4xT@@VHL! z80a{s&URHvk-Z|x&Sg9}IygEzIw3tI?Z2;*P>Hw9Ebdo7NIOkCPTMcNo%2ZX(De8q zLMEywA}r!1f-X{-ou7@Lt)5+Mz1B{$2!KBwbWo{JE&RNWUmDy0e550=Ms<@?izQVqh!~D84D)FFs?m zM|Ht*tMyR+%>7*da{clfRtQUmp}`sa^8G^nV&VS4`OO3IbMYhb6W|uS-*4WJwi}5w z;MAY!Gjh)t)+x3r;VJ&lAHV1FgtG{L=K0L^nc?%R@cbw2A1Gx=+(`AvjRC;{(C&!t z^zOkgravuz8s+jj@;K5sGCT4)QeS@Cve}Y}L*@Rn{FTiLEwD$sMz01?lTp5Be}jV^ zixvAe7MnO!p^tF8>@w=|)Y0DExJ9gkrbDQMx`V!hwuApa6l%?(D#4S%9KqEg4pQ25 zvU;nU9x=v;vK^a3C-6X3bGbuGG z%OPDY<8V;$y2uE)JPX!lo(uXoqwQghkfx~rRZrq(FQzMIwRE1C%S;pG6})yic1}5{ z7_%N)PO9g%1WW{E_6dTVg`AbanWL?LT?E0p6XSo$N28WaC;svi#n79qn*u=40klE1 zfsg?VQ|Vf#+5~MKJ7+r^JBtd2rjMFbudxqd_7V2cXE79Eje{#DAXAFk*;<#{$lB3b z+u95#!EDlIP?nQ}2)RmsJLswZ33RG$u? z?GNk!+uzdPFXi!%i&K=7g_FPbW39mV9PJeCtXDzCgr)eU3@haZ`W1Xed`mnqekz_8 z-lEI58fJb`e&NNmlga3Px5=|a;g5jt=QXda7XQ|qTIpNOTN5QgF2x5EBbDQoqXsGM zLIBD0lr@T0FK3*+rh}$^%E_}K(JU|adz171HQQGHL}xB;YZaly4Ksl>IX9J!tkYET zzvM~eDXfY_^dNeL1oPqH;nLx>SDD6VGXrO9XLDyIXUSJFSzBjIXNNi_4*?IJnP z0+U{k=IbAuS2`v)+s}Qw=n>wZ%|!TygHv#wtDpL z7W)?4^Sh6_Pa)i(>*X#ZDF+K`{OSA$m~F4+6=^>nUpPjE`q%~f0%3Hk zl`6Bu^*A03je##;;mN+~1`BW{c#y%iWels^eA?M5)9>xaSyMFtm=0`X{m&&jQl7;i zfrVk+kzU{2c}n9vok_)U+wMeKNX> z=7Ous##_TT11voN5!>a=SKM$&BHM?2&F}ro%1@dslf2e@U1;-IbJcLw;GJY-z~w{H zLeYZhjhSe|< zq|88#8JMpT1l@9a^2A)lk1qf;V9_|-eU^TBx#%dwA{ zdU<;(h$-C;KBU}|B?tE$i_A?(p1nfgyhn`?jwbjk+~ZILXZSj0j|YSr)`_~H`?C@? zoh_{Pzm{~vSwip%@#lZP4{a$P2CUjiI9X*@2jVya_UTIu{xm6kZ%0W1 zUlH)6>VB$V3(g{@_P`}F40@IxN<~cV8+ATp4V`dy*LlY=x@|7l!3s8I{r=$jw$f0d zJay`r{t$@uI2!j~sQzO{*insVDmNR<0J87S>VD~#><0K>AU>{P1pUJMYn*45N?qYh zpeY`oY8Qj;rD)zcNCi*3(4XH!$%(}nhwPT~a>wQ6G}Sa3{ML&ifU z!WzOJTHjjVQhMOZvzC|*;=u1xeBjz1C8!@)b-($SOkCMvwIAF?+i_9y)O-0KIsO!&Xk)M0N2x+6?S4IFv(4 zhs(+p_)o=$O~sSY8A!3kCwp|4b{}<6{eq8*5Mco+&w{(#+;x+;IJbh!gws7@+M5L z*2T+vWOnT!!pJd{5%Hpb40pF>fQ$cR9_8*= zZZ?7@E@M%AB5q^O|H5qDU+i_e%k*H0e|cFUZXZ^xt%?g*N_QwwkjPLYh{_)LTTnRc z{t{t$SX#M-SXe61@CZwS9P%!YKv*L~Xm$P=Q(K?HU&-qznA+C=<%}4CcoWJ}tyPBwGUuU6ovn` zNz;>43kyn@I!Y^Emm|{;kZhicNS@YYQ$YU$Q*yDy#%tpD!`(Su>oeJ3rs_sf*gN;?c*CGi7 zYZ1HZGf`IRht0pbLiGG;82Tc@!L=mV{B8{Vy6H842o*)PL^a>BxKnX`p|bJ0EZA-740}+Vwtaho;!+{PYe@3cU5fnkIT`e>@&?!!~Eh?bO zyK!cI8Z66-#QhtI>rSXTFQJC{jZM>rZ7|((@vu5q!h!O@Ec-9EJyPA+qlsBzKFKC{ z+Z54`t-FPzTmA*nq8jz25tWu%=37N3aqbh+^MbL@oDNGdJAEKf+N$;qZRZ;{qClg} zoxE0(tB9`1N4y_nIFrMboZkkF9P+F>d4exT;v+~>dlw>0JaBnMkU;_vyqCBzR{c4) zK(P6gVF*d8X%@SzN-Ay2z9#EvttN?10EIKEZWjs*M}W~=-@@crOkYB-D~%EZR4Poh z?A&WS2sAaoWpC4^r9wdY?3MvUNE` z9Eih`M_o)~+9c%~PU6gTS~(|BOhlc%HN3?GCQ)**#mG9)43RI8E|47l@b>_M_gy(x zXi{Glv~Ad2(i4hIu8Wn)OxR|Xhqh~H)lM547)?$xg^a#zkU}PgN`xa@aV;@!bie0i zF&)Iw=7_%dQRkeSWU&MlS8cWTYHFE{84R=?`0TRiX0rV3%a*WO-c~&=PWV#W^yyB6 z`A$QI@Q}GCax5mcaBH3bM~U$Dom;X{<#f=sk&e7ZH<>m5CTpvOu{|%n8Nha0_-saP zBXz(SUXLb|IGVdHLvg2a9MVL_nQkKOLQtC#HqcP7LcuY??)y_n(=~F+Ow*J~tMq;v9oOeS0C?~8 zAHYS&bs~WpABN^p$vE^`5z3T*)(timk80Zfp?CAy?)LllY00nBIH-i81V3*6r*axh zc_)G6l^5DM)n|BU%yCmg?74)AD~0PsfGhjIVQz-_zYW*;1J88EjBka%Eq&{iz#~ei ziIZBTTXB=L$H5F%#yPz4PovjJ2y5a>ds7kYemT`o3pzpABi?qFk^G zdkyE&b_)jy2T{+z-QPh1OdDWQy!Ajy_YY^yBI4LV=f*zry!qIuegvJ(bLbx%(YpCG zKif;JfH^Bw?E&qAcYk{9r)ykuv43N--j89`2vF214u4-nsHVRrs+Gno9O@yi5NX?c zY-U!!!JN z0SB;PL2-Dsb&hX5;9Xs}nFABU?<099~P{*)Vm z30ZPkaIk94XN=lt3A_CFtDpM$Fj8cDApa@<(}=KuNW&x8DXqqjROU%lzWs!^_zKX&2uYyCfs4hmDN zcuVZHCT#!b^|fMbKuVFF!DQ!w&ry7@Izar52nqpb@;{uyLrEs+_lQFUTpy-?9MWrr zVeME@^C^x%Us9-kh^D!f#w*kqg*2IK>P>^W`t>Ys>&K2Q)8;3+%5%&$#YZcAQ*>Tf z>!GBw;=6~IBPOuI$`#@6~(rBYy{{I6}zZ-dF=NHH|^jv<}kE{Og$~4{B|JOws zO6Sbf0A5b|f6Zq5U|{h8fEd)-?bD6?x?5!U$z7A&jHw6fO##;IbJ#upFH;Z2FLKKP z_nGYvQ2Mg@4UDK~ljdQx{&`;q24VH{NCAqB8fEWuOhQsEAN4T(Y_}cQo-2cajhD?t zu!Y`DU2lG1nAb$4TA1*R0_p{n?SVL8v_g2^M8)tOO!jr3I^K;o$EWQr48q>xN8Iem zIu2y&Uq99CA#2@=f29~&F!)s~vxy29yHx3(?RJ}M(tAcH7w*nM+6a2FWeVEc+}^+X z!FI{9-~$2p{{e0K70`W0cl&Bj90@k0BXdutrFTn^QRGUWi`N`+FH%`oI@gp4OAUEU ztLHzHy*~P`D@T<(&=93LL)s9+o_p&`Z=aiSsM)5smY5H+LKl^soK?8NaA=Kfn#sRG z5gn(`nX+B8L#yWwMAi9oYv!~3g)b=DOxjL_#XND@l`mtyF1RAweOZ7nUZbYejwp4m z*4&PFwY#Z?!j8k%!oDIa%LKec@0J{^)Rx31NDm}PPEb?XfP~N~=p-Djk~aQM#8$ZL0*^9KBeiKR{^qG`J7D0jC|?W6*~K5Heq~Qsy|IFD*jDv!UFc&o>YO9 z&N(RdZgB3`@dQ@oPdvqy`WK72p=*|3GS-2J_D(sd_P?)v5*J|!ru7*Y{v8Kea;g9|dUz!MQ_K zLDA4PK=z5?Md!NDaYWm3+Y{EL##hz&D7xWGSlKET7FHFwGW(Wd((l*is!w}6th%hd ztGW-qSctH=kFYq8u-J&Oc#E(Y{fRvXwWaGL0f&}|xl>kiOBIQ}SnLi&njn=5e_%iK zU(55-Cw6i6h|$>QO~0E_nXAXQs@}dP1UWeb17JqktH=GDE~F1@0`qy`0CsoX5PJ$GPNvbIFV4$otNb_pPHjYD+p=OC7R4 zZe)4HfJR?K`sUD%4dqrFNB2BU_dHj3GX9ng#g@%0l5N=#Z`n|79g}R0U_0)kwm^{e zQ4y<@5vzI;w7>{j#7~`*7az7xh_+5Bw@z@kPDrrifYF9hGV#p_hr|G-189 z+Z~YHF}@0O|DpIEzceRA4|u9vb-{T1RsBUiub{MFB9UNDAm0 zC3|R8b@J4CI5nbZPVWw{Q<`d8>jwX20C^7Z?mhCrM^0^maB7oJw*`FzZ#cPrEc5Oz z_T$;UH~QYnr^PX@1vG`-!IiMWo1EyTNfJWP#4X*TwN8in`STU+Cd{Ezs$Z}`ip==O z`AX;QdM8LMql)@1;VWR19QWvf_leT=T23j$#}T3hG$gloH%g)~e3Ry^P2(=-0ioJ#ZPWZEI=)#U&Z3DSs=#o3+vf!O2ynm4 zTzV#9jq9|01MZm=dORbBRNkSQ?pgAJ^0vk{?{@U|_;%&?SrM94j1}7oUf_O&w@K|p zVUr>g=!?6#`)_wicXf9KcfGA?KrQ3$G$7!#NVak)-)&Eo(&NX2eXJ0~iqNAWUSvhy zD}q+vzJz`SQ>gik9%3Z5tARFaURux2&=W7KBFw=exp%eXbS~F*WyH?3>GO%-D);c| z~LHiK~P}AwCgvXEDjQ!o=zyLLLf*WPYVL--$MnYQLlHe54$aK%$et zqmvZ;ghSYkNKE(9J@<>G^d&WF@B0Ax@1$zf(GA*!L;huPZ|8H{NVy4ZzS@@{|IVYF z3q>oK{qWV)3Q1>*pe~QJwCO1qwIPVy3bis1+q%VVic;u{>`<`C5dUtO1mmZmi7C{! z^mvUt^qU~VDKh96qeEGn3&dC2%(r;qbmPa7!ZY9ag*$EA5U{$0rs%IYNebO;*N{NQ_=wc55lR{gNOe!e9Y&d1!6f5W&3EZ`HsCb2gE1%S&64Xv-@R%AU#z@C5M$HCO>$%dT95Tzs)YIV5B`iAY!9|j^DnUN z4z^zX(&6t>a!p(_YtGc$sNDK@GK#Y=3o&gqqKwNKQrZ9dnD(siON}$kv2W}lMVSoR zbMlbLeif0@f;5QGPL8-hI-Ye6X^r-hD0Ek%eG{Uo8)e#>IYTx2Yy*@IOPk*1AZZi-zI-dMx8(9@=k zb7G2Y7ZzQ^$=Abb7qPsAm9guygrsNkuBrEK2~pUD{lHj^XzOL^EoLpZTTj&zcDpIg zX?SN1|6Nbl63){u^25h>-2nn`&`ojQ_2da)h3>u~56^exiRq~ocn8{*eE2N(Cw2!V zGc3`OODsg)k@?mCD}Zdd!bSH_t=6!ytCW#hIkiGKVsBTuxj9U3nl-S%vw-} z6XvhJg(b9A6Jh-T7AL~0L0)%)*li~f;iweMm@P)x8 zyJu?0=mz0M8DPBqeEqxP>l6rQ{zhN!T-H&ROTN$iP2-rUu%WEPJNaL4SwdNbnnt?3 zmd1~evNA3WpZPDmKcat_&P$n>79EStOYE0umNnYsuavo!bspymmFXu}3zf5%l^++i zl~b0b9#=w3y2^5nt0}+Zlp)&WekedIr?x5iP!v**Yg6!{P^O$yNDF-AZIFAhnB|&x zUeSJ7)s}Hy<#m|dmU>?KblBCFb6!m~g>#JPnM+xKenKNEg)`rG<0vW-JC{G!KHynF zSqwS}z3GGEzLdOragV=w*7+)`Osn7Wh#E{zao@6v>P=3&+=_@AaZF*}?s~=>R-_&l zdZxXy-kj%Lsz8gMtDk3Kr0@#A3GoY~OActNno#JX^s&^j%(3*b><6h2GP=?bn#cG* zZ;t3wh@!cJje4q=$lJO^1`c{j1x626hh~GlNsR{GRv%_X`CvB=ueT(bA2dE%{AFn{ z=e+r<7~Ssqi1D>qoy^a3YA`1SP}-XNCtCcs;xA-(jV^}GAbz`sBcrj5&C39=$L)5( z;u_ibuKk!b_v^8{kJu=@8y?3|%UL|yXWM7{7$N6b@WF_5IDGH&Hhv-AjW4iDGUC&^ z#gW}uUD~i5r2@%}2U_*`iBn?rpc!eIvb${^ly%esWumD3`k;dM#L8tB;bhOp{<=x( zlqbW?`5Sjr9C%mAYBcfMHtg5Se=xRmH+e1L1Ad>3R*W9~V0d3PB+&l)VhJB-`~dDmtPbOL zhvf7pz;l-3Q!B4`ah2B}xQ^#oK6y zh)AAVxpR(0gx~_PsXYBc(Jw4|2Im00bniL_eED1VLkIsfx4h~@05mzteht_ch~DYV z8xw?1lgaMm>J!hCwFJx!>UdCQkX8na@9+CFh11W|@#=^c6_u3Qq^DO|l%(0j>sF+c z6xyWeR%wy*=^sH=y18V^pufJ-=3GH>&2r7yGyA z%X+m?X)j*X>UDOe-VH_VuWu7&KBxgjPTZ`6C;ekH@4l8zaz}+iqxGHjtY-%=moozz>WuA6|XnT zm36oAtB1(2Wjn$*@C0@%FYh1%7pWU$B7I(~!ehcVdBTvdp23F0ISb{tHeGrbbHb0= z5uMC;J69_+nCTk_61Vz^#A}ZeQR-mkR7w43WGxOlytpUv_Td&`vNi z87{AodzOCIcy4x=?O@r!zlen6QiTD-aRy{}OlytE-{*v>41B#xd(ii$eizO$VC*DC z9t<+ZWeuUPAxPTz>N$_}D1Db;4Mh|n=< z`b&Ed;bGFH{gEgF1kW>%>^G?ma_moF<}zJm`>FDIb0g4NQEP=;JEaqyduk())Q&** zhBU;0!&7#6?^nCWc_3vGTG55t;_8(-M}|h#;-fc|H)!=A7FOF9R?!w#Sx@ZwZu2Q^zjj&$HP&SV zYO=v*E4(wUMa7LM9%j7rJQi04Aq!(2la*(U#9i}4cUc(W_J;Oq|}o4PMBg- z{NFr((Qjw!{f4VY6&I%tTHh()#l>6AfYII{ou(3($tn z%LEa#G21O()2j^<-D83rqxC>t*y}ON(}mya)mlS~C@=@|D`gHyvV5T_nAHiB+wC5Fw} z*-rk)j3VTv-c{$K7se-wC($S5CyqNBukg+>nALUht09DbeeHz6Iu$EFLjw1Wh{@Gy(pO}EUC_fmhb7cAp1p39$MbG zFu1(u2C&#+UXgDEWN_}A=!ncqbWFSaSSPvOs}q@>h-x3<1tD0v82i>pwKEMUoU?Mb$w)7X&0{oiURa*G&^~mmMIDr` zo}~fKpl`#S6AU)ZiuMb<&N-d)^)%*KIMsIfqo>^`uSgY0U#6qL4*(f^9#e@rs$ba( z8D1;)CrvJ^?G#IAX%{8I3FvjJkfHcq9u=DLLh5AZV9Auc;N?%0Q#sqwV9aQr8X2qfuBp3V>gaE?x{`_M3}=`UR+4{?_6=rv?90Mynb>&e z`+4ZC)1m*gh1YYg1vkO%lNWF*aucH)KW7*?(?{n&iCl(Wm5uj;z|EI6d?Wp?=XBuh zQF8~lS8MY=aTA)1^L%m7>IWx-@60;Dm*J0%R33{ttT=3=&a-~7tp~ry@e6NIyZB>< z-|gt`WBAo`2tV+t)4~0wS^P0)@Y;cGbb55->YNz32k?D?&O5=qXyJiWo~P3w;4Lxb z!>##a{w(rPBaY93I?l#jhvoCV`C~QQUIacHD*~GaZ9oS&o~PY?&p-Jc*llUD5O`*`?N0>c9Zjbz3I2^4-bfkN5n=4<-=XB zM?u${YxP157OvuZ zJnX<;oSKm$p8LEje!mTJc!UbWZo2{9MqSUOD{zicKk^5|+?UCyJNP&d3RoPxF1Kuc7 zK2A@Zpa;kN>fBwRg^2;zZYB+H9sI8JXfJ38H{o6Uuq_DkXrKx#Pk=R(-+{#2^Ks?@ zKIP0qSzXE0yXS3|i4$|3;*?Dau3UZWb@IJ%Q=tTRuHN51+zpo-@rnP;126d*~k_Ys+D4q&sKq5 zvlpblekKkkJ9Frr?zTF0c?Zf0nX6wM3v3g#(8b=h&S=dTcFvk*_nNx zRqi8ko(UE5aN*e0vpP|#xp_T6d`SAlp-A+qq*91IjuqxAdT-sP5=3i6nY@96N# z6c?Q+mxV;!MCu7yZ;DRjh$o;=oucXq9^wj2p+Y5X_u%!icj&q_jCo+m)o&j<+!s=O z7u}-C4+_p~Wp}LBo$-N9N@on6KY4bXO5Nb zO~YK8EC&&s$6pGae1F+yu(U<7?20cM3E^=-*zJxE^{o^)U!4{{$3tRy_}Rd$0&vHL z962}tKia9zxvw~#$8!3^=u|(|ak5YsM>lfqMX(M3TI?d2Bq zMfNUStjnpLk;1b`@gY-;v)Sir$O9(}2m6rfer_AXUecv%OPLhfFUshG(c<*S%ML&L z2sGMcc^P9^;A1821MTH@%$q%l*(3G1LH6~1kP~LUM!QD&{ns)+Fp4cZtywTYLHv^} zyeBvjBG?n|8^F?lELMj^IhAB-Mwxkt45j+eYdhmEbJv|*H^leykE1(Uzsolev+|Iv zraRWn{eY5Wi|jF!YE0Cl2uo{^K-)#gb!U&%Gk|Jx3Paz2RMfoZI{b|E`IkpJn7b*_ zDJuJG@yF`Sg0Cuvm=y8IE03Vv=NSP%(@Y~Wuombs*$K9y8G6lWcj6@4u5Urc_f8*ogyMzk5$Da`&15I6C-28n)$3X(*aIYOKQ#rwnxnVDJJHNmZLrbi zy$KoFA0NeG6Xz2d2fU-Y@(P)JY*XDe?_>0E{PJ`IX-+*EjU0TbPqmD>KFjZy4cWY% zaq>Aj#D!Clf+A&@fCzj?iT>A;=4 z&n=%_UO%Y!{DJfR1IjN9s80;<_p#+Smfsvcn#bksxOW`)4?5pF+P|=TWu@ppS$=+a z!e3jyy8PP8o1vvfjdRQUm;bRopBrU3&JJ%5>T`vAKsdK@m9#qt{oxpSXGad)JwFa0 z&>nerEiv_0|h66b`Jcvo<&bETjA^m5%(W5t0KvZqoGXMh>=aCxSZDUq4NYyV6sKN(ZO z?5TS^^GXbmC|WwH^kG|!!C;b+w6Eczw4Ma%QNI##{FuvqdwH0T zrzbBbb2m6L29PMFYL9*ThxSEFqK(z==&C!$oWN{^oY_6v@uO=?{l zDHm6+GUJ>Yvl14EvlUua&8-&U`NFEvu8x`E%=mw8{J(G2!dF%!0s%~s z%6TMsf5)gHt+~fWxG=E4I{FBGu8rJ##@)r`udF>`eg&4Lk3&P-Q|^w@$8eh7q^(E; zxHt}(GqLa@C0v~|3R!LA{XJ_dx37GU#^gTzD_%-T`$_qZ!@!{bJaZqNU}`b1?#DOw8B1NRQUDLUJoX=aO9Lc{r#?_^9V*PnVvhE#{#CSVDB~k#$O5D5l0?ESCQK!U`nB`I#Lz9{+M~@$OEH_P(c4|B&Ju-c0JyD`u z7cae^yH~A}{#q0M^Nby+meTIyUaS4~(I;xD9hI-=u641b8Pn%BeTMDJf2HL5f=vw- zFO4*4j%S8s;g=yxa1w}U1~jRXetp#vcdcFp`V!7j7QBFj%-^;0_|Db)z!xANZlNX7 z+SttGUtD!GHocOf4y6V3HGO9`fJ;aR)S;*H5jc#5%cJC_6(cB!%wgMOZQrrA3W4uySU!L?NS$ zGx`g47hRX z8FG=C8hEx31aP5*ejmDDzt#-YC9whx|1eNX84%?Nd zt2ZJ*#K=gs9HmH2KIvG3m$rJQaNKv}uJI&G&haJmcVkY>v&?8vpgtoSo4zxCn0vIn zNF!x$lg#!8`OMDovD7`8EzRYLwA&W#7VPcW))Cg7H>|eK%UaI2-ms~~gcC@*1piwJ&s{$Rr;Ld6|^<9Olfw!Y%I=r4PrOLiiZ(v z)7de0KM=t^WjTp{$bopmffas)!a@o?(<6@fXj*QeNpOHx!m_qfV_(IJ`_KcoG zG;(QV&647l2$I^Mv?N`N!dgO>7iiwb1Hele)5ozKa}TGnPNkxpWGrX{pc$M?tYxWL z=I|gwS+80V=>e1gsur)-AbCdr#E996HUr)GEca@6A#sPb<~(P4sb^n}n^jizwSXjj zje|0CN9wS?xT@itzr!W%Tc8DJ=`9z~!g|R1Px6Wu6U(w@q+T3JRXd_<<3rnuR8qf& zD!>Z8#7Hd>`c|6L+xTheVMnPjYz59c%HyplDSO=qz0#XXIn>OU4SZ6n9onb4jPRjR zW1ecGh@5wS|G{$jCd)Ml4ywV zL+(aLs23>9xQdiGTBC0}%FNZe*!Lrt0LAe;=M*3BGoplE-4@wPgr?dlaU7w6Tk&b@ zw2bXyznLY477bxvq6POyqz>!o*~99cN z_!{(vLRp0pCRQRbC?YG$fPO;8jr_g!#(g!a5+93Bc~-ijXPGl1Pr=?f%k`b=mb4bQ z9c!Hr?_KafudZ5vdfMFhQu<2J3|pS)m7bwK1Ca~h)XE22ID2)&P1_gl>W8~h2<}iH zuh;mnK5P89XlP2gs^^*~ry~Ul+_v(bnpf7grHfopVw7rTVN#I1#7FB3ioY5VUClk# zlW9g0=>Idfwt3QbCKq@@I=kGz_5rJ$nIHVL3e*!ug}Y8kd(P)~439$HWzQX_a@d*2 zet4g`!O^Kahr`k_mP==*%psg<-#S8e>?u8rVl{`cA8E#zkDiw^sh)o4iy8F5lf15p}wme!S=(#iHB_)JQD$MmDsY@zh_-ZHx}ZkKxUxZ zgA2OKh>;e?gF|YcHTW$4SaIkMBe8n3YC?1nwdhGAE3qSuQgB{b@w&1YXN1T00$QSv z+&$nru_LYCI;W9vqcDkOBno1ThI^%uT;P}nj-*J(j8>tlKj~Q_Q|9@^f5(@j^xDQF zHDxtN`0QL;4deU^=i_gr^j;Iiic%{^3#CTnP$vA2v zC&EzQs-=;@`&T^gTbvuk7dZ(pPc6?~c@x`KKR#Tw#HnFHR6E26a(*n;Ji}{cK}oQ5 zq@$di>r|V?8we-NceYFWGuJYEXrBO~KCtsZtT>NgQ0%k;V?PMrT_oyO^{cp~2IIW< zv72Ixn?fCF8XHSpt{!i{?TPtHkJO6v?Ww_`{h^+fAHmKi`eMF&X1GW4Y{xQk)Oy-! znHw{grlx%Z!c0eD<-Sr8SUwA__=mr>we?HlO9dbPr-YcIb7v1sDUCLWT^p~kBi?bO zzRB8O#&9C^^>}A0$2QvQn$D}S_v2XxuV6i|?+Pt`K>DY?vo&+(6z^h(*Ylv>%^NoM zD_()~qJFiQ)CWu}LQe0}H)qw3#+H~5Wu$!-TYU}b5{gBWJMx2p-1FYKK%SL0&QIl^ zc4ylcqreY-;YTE`+;3UA98alzl-i+EPPtf%CGr^A2#+JN@g#}Q7!S-kv9Wts7MQ8A z4IG2fDWQnEMqg4+MnXdASp1O?t?HRiVtHJxH?Tq(TUMIXgq5?jUAYf`3v2!_m*jJIV6;TIK2x%H zwjQoX|3mnd|C!2#o@b-M?R{b*rJb>d;F3ALx**g|yW$hg6KRr?9q%)b3Qve>KPjm{ z>+Q@;)k^6@T2>zSlr2|Uvtz8eLYbv1a-5b@1}UDYoH!wO^Q_uA)3Fo$%hf&iXMP5^ za!zQDh>P648?A7meQYw8u%ULXQe=`9hy-mtG&`DaYPPdH+tso6Lz;$tq1-5 z;LOgFnRDqacqfJ)8m0Yc1=Hs{{_j^PbGSPNpXepy|Mlhhi*I5`E8~*%l$a!YFNkd6 z@iWW&HU|3~DRFN2TJG4P-Rp}xeeeO}jb|mz_(|5fpt{j5B7yk#Z$765?zf3 zBKAvJ_Iv0TQ_pgDuXMFHdJFFVai0s(4`4!7-zea8j|R9aeb7)}jr~mGW^_WSGy;fB z05^NX5--N~GI~b}@&%oVvnZ*F?nX~BlU`rg@5SC8b`}B46~%KS{mP1!@)g?>+pi<7 z`b_!1G)A8Lbz{{kgIF)@5{wO$k)`Z8Msv3x4|;$nNw%}>e*7Gc0$1i8kMkZ*_Z)*& zyvF0u*$I%9z3#W3SDB6yzU`)^ja<=RjK8^bw6ieUOQkN^4hcaa^Qks?ha3lJ+I{Ct`*2#xCrDI3oNu}LyoZRq&6#IpS2+VmWhh^J$bu&jK zah7_H%28#zDVZ73{Puh=d4-(vwsI28tU=B2_w@2<9(&!J(*4y>b?hPMPOTb@W(kmo zt#kfH8%bqXxjU4dKzu4FC+XqBJgfm#u}DsW&b;AMICL`BCtvrl+=I~6 zw3sVvUea|QUBYq)$Z^=>Q$Fo=Rm(&I=TR{qu}}|>S-&5zHRql*qp?0abNx79wAzL2 zUJqy0ywAgWAn)h8zDYkWjJwNgIV>V=qrla|5z~0G+^LORKXgRmnHUElCtgW=<6b+) zO-38{*hE;haKn`^&%F(;)vApfiK}Nfw{b{~Z+{$e{xBVz3F?@V-Ax(S+4B|;O^Yx4 zUyZZ5@`|;@dRtbTU0tM(ysY2qIPYAPyS*+Be&)uHnfr5jeOtqQ=+IZ2w|me+>DDoV zlltCEX(NN~njl3x9W1nM^~|!L+uaoImu-Y9@lpK_?MwIiXMHbzol#r&BO4EZW?qf$ zOLu)fqdnkbe_ROxUp)go9<-5A5>o{Ro>B6%R^!@#)|}$c+p0T7^q=$uW3Rin&Glwu zv!R@Q*Rt3D5)b1j@yN2T!1u0{r|g)74obT|vhNVsMxrI@lae8QU3u5@YJ|`^PybpU zyD`A*W=vEptK}Zz3BB!wyt8fh>+Aaq+1^K6n5)+$2Da*9exiqSq{dp_Id3EqWRJq7 zXq?21gG)wk^R#pm5%72_Nt}#lwsXW zJ3%^D|D{IDdYAvNr6VZ9%D0F9+^w#zY$M-nXljyUV8`9tTYUWk&lorz2@>04q znlVnjq=(XhTFTAkGsYQDu1ysGKnWbq&RugmJ(?GpN|~-{wk)SJ{fv>E-*c-JOfP5b zNy)lEG=g*1j6FwItwO=HGC%F9+jc4~+-ZNLoSg2ej%|fIY2SIEIwjOAuQICAVkI&E zEfd-*s~siwtx`Le3@=(Uki@-pX33i=xTip|FNJ1W7vo$*tMn(>1h=liMrxC5uacWK z!O#&=3JRku0(t+peVZjs*tQh=)UvMdrpJeUI+XizEzer6kqpaMs+}um83<0nsv~KB zo4Qst|BdwRdDA+97TN`VsZvR(oO@`?5FwfS-*SOwYV>oww3#A@uqi)Bmm+65GB$&G zF{q6>M?B(vgx`Bed&;&iGZklTv!69nGNZ*Bew9J(9cidlf7p-k-n1E;&WYt;tVl5l z7F}bXDZg(iJv%HXPI{h^!j6|}ioHC~DeXsl!hdact>;=lmly55L=DA%#)q-x_>AOL z0@|LtEgcQ^KJS3#==j*{yHHSA+NpV(bx2yAImLgi&XgfFnjrYQu4Ze`*-B-UMZabT zL0bzq11t69ba^%N!SZwDYK9v{9gwh!Bjq?_`#I)Y+6IOrD|Pfqd+&9xFN|pbm$krhmQq>$)#lavHKJP2&i7Zv zSMS($*I2TR9c5G-r>HdfRT}%KRz1IdJ%CYlef@^c1LHs6*MVPPOD+*RqlZKb?_2xe z-fh1H;hx}2TRVPHkg<7TA@)f7%zmVT6LAy!Z5v=n{78)@UaUzIDa?*p>$!s6{VB2a z`sPyQqFnB^@-KvVfrr{Qb?X19JEKd{ZIy-2RF;W06|#_^dbO|GE%n+){#!bl(bkRS z&i0xADXm|o)~p`$+EJaHV3ro+lg-qqY^QJ98}g|1nlXv&*>YRokAe1ZexCUg{G}4D zJ-n{4v%eha;QJS>in-Uo)h$=2eOJbBE7+Grb+mK*mO=I!7<)0c7QI#sVwEcgGl!{{ zXs#uVZgmG|`!maim?S=$7=-E1U;GM(?~A!_9yxbpX^Sz^xHHn6uO8aF#69(Z^5lv= z_v|~7TE_a#_5C?w#W@zYmLZMur9qbPpT2gNnBk^Pl}}M zY{TBVeJ3&VpExl3Xf4zWWbvM*b>FW!8WH?5b5(j~`dXW>vtWnlVPv&(o;xGSp-k;` zF)WqhKg+d_BC%=t@TT!KW4hvZk_}zzm-c~O-z)Nd+_p|xp~RL5FN$v^#`=+-)-E>= z`4B3!ZPugPdT_U#)*(9Dd7NA|vGy~20KqgCi8`Cv)3<@$`^hS`QHdO|CM*!@eM7hk^N*@*F5BY-u#Ikn_-$> zYWgI`dDGwBa^Yy=jh2pu(spZwIA_b5!q2sI$qHR1nvpq2WmD5=dNi{>Qm7ECwK9{w zTQ==P0`-vWoi)-Et(IcyI%IlRDYaaC#(l6z*;%Gb0X?s-yZP?Zo(dfkgNc&mI=N?H!4^Zpo6&p3M-%}-5x;skJ4jlD#->5z9?Bc!>@jZ28 zM&W?5~j^NiSd~09&%n`1x zM8yYI4+ult5*<($fTy-XgW|2LS>+HjeRP7d8vY7N82TU9%==@J$<)t$rdV1b(9?W`y`}?*w z3{Q4Ns1uS#u>us_)c&xNRZr7H)Dg+RhH0CauB?zroX5zS|$Wl($eWt=}o- zdz$&S0PkBsX?`ohy>eIAQ2^EXzUSGshA{`@H@v;zmr>~xwRs<=yTbe$sBf~US?*o& zOjL(H_+1!Bjd4q(kdX|(5^In3+_mr1Gh+)GdHeD0T$6qNLa`HHL zTvUJXeV@Iqu~s6TrnsJC>(T5+a@;Lv8R-WvuH9&ruTBLY{y%Sp&br==YxO$Tyxc>&6XD$zH7P9~S+Rpsj6o-4X4_n5!nb(T4zk80y+`8!!Y2)`>qE|8# z7`d{1WK!t}M~oZH`dw@fX@cdF_%gqU!5t&te)*;e*3>QIKbFqiGupakxja(+g&_N3 zl%qS4`;exiv01Om+zCoSLZE8`t?;S@+EEF@$)Ol zwX^Yb{bFxCzP9JuJJQBJw)8&V&Bz$^h;@{-(#yHPxhi&wt!QOu{2oeHX70lg)%mT@ z&l;3aqxlOzeklmt>9M*W>Y-nK57n>HFw=Aoa{lil?0yYAvD?@(iGwG0p4hY&uA|22 zv=M0GWsj}zN~($duDV({?^pWd&SZD+!ofrc^7}}xOZX0?v2$lQ_uv~H&njZ}@Xj+# zW)Gtu&WwJe*_q0(gE>deyao@y+B2ssO44sG~cT8^=b{vlGD?UJkl$qMUjs$HQ%<#W!aCdKa48o`Z|u zp;kVe9T-F5tC*1k9rYPZFK*6-&ZFXqbo-5eTF~=Fj@Y*M1e2qGFWoE8F_tiPZSzOtPHj@|ej-+R1 z0qua=p`n`|K_hD3+aEC!M+X>drit!2)p)+DV`!1EcNNnOj>YZ@L7S%t|mTgbCSt*YmpZS({ z;$_woB1d{qOM5pF_DEJGJ#_Rr5|l9!eG?o?r$~xcd7{6eU`v>48mb4A;V&V0RXi9k7DW_tm%uvKKbTBX4S(+t=Qx3O% zM|yY~ebO;hSkxz$n|F%h5sFD-^+0Z1GO^x_-1av(=i5YmPq?yM`iGN zgLk49uPn|IpWcd*xv`3iZM=od=cyCvuzp8_*jP6?X}=zCC{RlDP7fn)^Jr7HU-^g!A#;3zv}|pWgq` zcA<5k#=|cKD~qY?|5CPVCHm0RXv)4veD$>BiaWne%lsZq6{%s2XyJ>a`btSq{-X0T zvh=onw;ajT1Bg%S8?Vu?M&zi4U2Q~#$OJ8QmJ;*q%MoXzI`cl9xt~%IOc~+x9%#$> zJ4#xx>Llx3}{b>(>10?En~DfyO8ZFrw;FY(uS z!JSp)gJVaH9Pdxhpe3~&`z39C26u>A!u@;pKG83&zwBZQHnohR^(mV@UhSiPo^*>3 zB?M02c22q)Fcwf|VLcgjLg*3OO4z|ax-Fd0&v(q*O8F-(ESa3n2#)Q1M=5WghpSm3 ziM5*k&i;9=E%DVaj&&H$XQxByR(VKDJQepYhKk*}5zaZHxL-^ z@8}Fcz5M!5sI3I0=SqP6%#J?g*iplYuNcXUJ?ET|s6pZ-(sw?zg{82)&8VP*ka0d|3S?zDkj>v_Imv z7^Uuc}DAdZ%PSP+wg<|^P5HzwYk*iXiU^h^KH>W)O~US6?tT~n$kAFRtI{weMK?ZDs=$w_o6 z@@%W7#QSy}3&-D?4@JwpJx436$En>q`@5M!C&8V|_RB*`O2%oV%XTwLQtD9l0?iTK z{$wl^BR!|u+B25np^~1~JFXJpX-jD#N34s(QF440D1DsA{ocZBPl654v5xnY4%V%| zFERJI&{M`%Y8fTBGziAUQ8{W`8I|QvTbpgr>}eN*Q2Eau+k9h9yG-l1yD_y79MzO^ zrkZuTM|+)Ft9(psCH=`+j_hMjnLjcYM-n_v9yy0{5%|VzW=`35rE9NcJBou(t?9N@ zPAQdpex?6f*v?d1_4bk$qYs3Wm_tWkeD-qR(%L}5OKTyKb8U#=?s_}DNn}sUfmm_u z)wU|uoy&r8#$o=)s?F>x4pLWJH5N`h^Ujj?D-t8$LxGghPKqtz%spcj`ptM{HtEPo zu00aZ>}*Vb#W^KIweZMv>1wU4CWg~(KiH*3WvVg19PS>qT%wqTBHTUxsh^S@53DtL ze>rbG)@DzAN3OHG)+IIBL1Zh$Rx-Af*6he=tMQ7+^_L2W-=PUJN5ef=-=v!BuUZ>G z%bU@ThD4=;XXqt0^=qO*+_^Gq40T9y6P3>DOQk6A!X@7#ou!=~#KuvQBbl*w5=|5z zzcWST-m#>9bJZ*nX8kmG2}O$hE^GWGOE^N*#(FYJCgt2S7khf{hwy~#n9eR+B|-~E ze@0Ix{)-gpx^8TKQuWK^97jE0`;zZUc(C#m`@I^tQa8sT%(KnZuXgNHVMH@j&xNOz zmwDC>f9E3{pIh9=9sGZoHp9u-jQt)fskpXn@}xjr@K*xzvq&emS;B4d@Z&>?kcEpYec>SfM2 z@2NW1ZAvgnPfns$(q^0Tu*~uFqy_TcI&Ymr`IzBazf|7W+P6CMN18M}v z8K$G6-b%P2<&rXMl5c;UM8V@nq&Kh+=~7L362k|jifp%g2Mamv& z7reOoY}w?c?B$VqeZ!w$YrnGoV!M4hzoyu`d)JbDub@>G0_9pG{1V8K>u)u^Gw0mi_#(*t$ zz$Qn!vBL=}@tz>xc|{hu(iZ!;phdrl8+q1#(;ilD#KKjc^$=!B&TJd2S-X(6pm=rq zAkd2)PwW88yYAT2#G?HzM0&xlCDNb)66xM|f63KfV#L3YPWdfHzT06*^64!ta3zi1 zUto@1>l?0M0SveXHsI#2eSQN3Y_N*mn+hG-vu|s_bH5k(8bzOXkEy$%C_YbiWbp*u zT)8l^NXbM9&d7^U_;`_-kE(8DkHQR;<-mx)Yfm&goL+Qzq( zdbh69nD=*W$<+qS1h0Ch@;AS7>|Qdc<1b#CI>$$|21;t?saQ#ehdr26I)**^eS^-? zdj6!?KE1hZ#Vf4@rZnnKh|)QGJmzD@(6^w3 z&?*m#eG{keBK}Z zy^fBI@M6ysB-G<%eG&;ho|kykNjdsyk*JQPec6`VA7DR^=oxa~i1mmrYC9(%a-*^y z?wmwD#zx{~nE?*VxXYcRt4uS@Y67nD75uei$7bK5YVGbgHMYYSr_|B@QjmHay(;D0 zW$*898~@6?|Bauf=P8}91Q_4Sj)p|W)Zngr8I3|JyZheKMvr~UFF8^^tIWpfq?-}7 ztV=kPWPZ(hF~7F19jb+kZNP6%ff4V~fgcu^7Qwt9$<<+Ij>a65*HT#{A)2`VEt{6u z-yO=vNpiA3PLkxkwwz~&!ps~q*O^BZIq^8;=wZ8RPp+1YO_=|Q@pZoP$j-K=8Lfdw za||uk#z`J+M_*%c$MHLfGW)#XnsyQsjm^|pQF$}R%+h+IW}#Edrp3}Rz1mauu;i>4 z11YiZ*d}@EcoXAT>9HHyQr3cITj-EZfA%c5$GA8j-~Fg9Hb>z1eHU8}zr7clS}JeL zw_lNFOFE|Jh)8}7LCF=;o-$4G8M$?R9vj|O`PfB34=kza3|?xdua$jq&-(z07X|yg zL)^aBsx4*8FXzv$4wj0Cyx-R-BqcNb>S|4{oewzC&Qh@L1e?H(ci>a_Q7b1HscBs& z(5edcy}IHgB;PX&1k$q~CVPxsoAeE%$VA>`vwuQ$#<8Y(d2=iL%$gdJ4Q5>XRIkX{ z*HmejeMR;u_D8&y*n3jdvE(eRtv=hB&R>P5_-@q#e_0`JDc|3&= z8BHAvIh12%dd6v%zhG9KH}}={GgGwpshbwFx*odn%@1;o#f|^SO;(aK7HlE=Beer~N7}uTu{VO75^I}0>r>b^@M=GV0_l^#+M;ZVgBFNu zc>eFsPAx6t$5JBv%Wk*iv^QBr@9Lr}bNT+Eeafs23}X>;fK=MJyw)yV^Hso0%YEx; zIJI0|t}nbPdu?Hd6SO@yYLTkFW;BU6*-x*rId(lU){LLH1JIaEzj^fMJH8VLp;^8s zc>IVKJ-Hr7R!n-#r4O;0^KMz`?%J@U&h^r(3;G5rK}POdh|FY%y%SHLNP~O{CS}Rv$iPwkZ``fBU zWR0R5Rh*9pmit$}5S2BmXsKZ6z3u0T=&&X78?Cm^0Y*Ftjnm)Qk~5U@OMQI9nD|*D zrur|w{~eh8nk&(3+igr#3h*DuiP`yXuu)6DWqNr#uH3l#9(w%9$Uai*?@js3SZWCO z>}R(`b{DAowEEP)`5unClN!$F`3?kgN9MPD;RgE^31^?KlJA^#@0ym#NY`n7=S5ou z{T6(pkNt*obT|Lm6X#9~_rT~Qd*2zh$2`AsMH|;6oNbij$H{rKzUoLhOo=873}%D9 zb^}gQrC6>~Cb`nhNUBk*>~}OTaV-vMf70PEDqGgQC1^M2I^W@dOQ(i3vC8H9@yH+} zfz=t-UVQhNUpv0EVwdsh+ue*?WSe+2I+h(!>P1EhD=Y3~uYJO?pDpD2Qytk0lPi0s zSWiK|8B0hgSfHDqGunJ;8(GYbtjBl<-mY)N!lMlTSs~~_o?Zi*^U9WXe$|0SVbr$% zqBCcq_#1eEbajeZ~UvCi>}kI@QVb|Txvv0fG0V?L0kD-+R6=J?)!VDH zr?!8O+FZ5+#)U;1Qk!`xxg5Pii#cYDllsjIeH^~3BVYZ>gKt$FKa4{9_5r+5Cn9V9 z9u@e+W28}2}f4EP{+3^ zMiGmxJ}k5UP8sq`pO2UBF|YK+`oK4Yd7&9jU<)T3R!tcti)e4o~?c3JJfamyKe~V z-{Gg!f|aZ4jCmH8zCen<=yTRkd9o!bh=nM=Xy5E|a4uu#J2m}wh`i8qab9B_*p~C7 zt2~Ju+sE*P5v>oS7Gf?iF5_+ycc#RO(#}cBw>!Yoz4IRWSgy)xMfIS4VOwT?GGdeG zL>z^};TW3Br=8;I4u8v8FLeT?J9MC;v#vgs*q95?;mBOmoF2hYY|@T=i#6Q(dYOLb zNSi_nB|F)XBj*ce(X46B>x_|TbPds#M0A~*;i+F=(>h`X$v5V*b4shhH=vCrcc;#k z6&F_<^9|}~$;|pnxwD2c-8ixDnxji>+en`=pa09=mxoz$Rrl4c?N;4e`})@I*ZsQt zb@%l2%=GqV(P$RUXd5wumeC-MG)PEd*b(RhIapv8LBK#hAbgOzgJbaZdILo?pc24+*{Q| zC0?7(SEE0arL27-=1oYO$Wo*4Ql6OJ$~9A6pR78zjYEkNlpzWITyC&r2DjfbRhm%3YhM_d#FQRU-zy|_MUEK$1sI%<62 z!bQFHnyeQ6Z+V)lMtNf;W%OU12;oyyfW1a>E$T65>{R?kXp#7iy-B!%9IRnD!YVMMFuyCPDg`4Y|Q7mv{FSiEa6mdT~}!y(xlfsw|qiu`OnI!z)I2qgNOqqrf9*MFHZ=MbLN|nVNRt&3s?i;xtZ_7T zseOgba7!U2h1SM#)b{gKiJE6kvwO)ikrg4Tl|7+_#)nY$RM~iKIs^4)`}y>a4Vrm9 zUXBZujbDXkrPU?$eor-uOEsv6>B&m!Wh_;16DFxpZ)Q-x3UO@56f`D7+myeqx0UZv z88xycgcQ|2$}OP;x}VCzsaRQ!OI(=$8_(9!D7ZS0zLfL3@b2SvYTHKW;ldi@_aN=U zaw?;%tDIlmntF`8z9Q97yrUAuL)A*uk?{NL>lOU z@aUylt7zE1Pn4hp5Q@boG1OaoC_RFBy!BGrLQG$y7YWbms-VQ=nQ`fGq zSuZL-MqEM4rYbE_xk`#r6mQUqtIVgGMWK%27FutrrzDBi%DdHdl`X7$2$$Ld*5^_? zu?EE%D&L41C`FYjQ!;^+&@sy5l6I>4B=IU5AjD6WT=fd}E!IbTL!&A*vjBUFEV4q_ z)UWjRQ`)Dj_V)f({i>d0riC!Hif3r11kr}~A=IhHM`?@3nb1lS%2ChS(C7oAFJX|5dCI%@%{lP67w?Ztba2uB57T%5UHF+;wBcEz~N0A)eAIsE82-cyo zH|#q_9a>w}c07i6rm-!=_=>ZtjdYC0&*+ZCM;d9vEkr};d$BEwCv=Wl$Xh9FinQVu z%>q+(NUlk~QD!O5SF`q%u3>JdS#e6~EaqpGmeYS~qP$A&5y&dge_X;+Lb_Ns?lbCz zp8KHx4}x_{O?eAC1KW!83bx0o>N;w)Wcv~FYLt1S3>fw&meY(9^>%E^tI&*7b*73T zl)qC)>TH6#o6;y69l=?>WM2r))Kt}0XWT+6%d7mYYKN+=-iT12p>(-EU-_Au8q@`P z-b+1^MdJW84o0(N$cp0C>5Yd-10n``CP~dGr2jcdi$ajfb}Cv3Nhst&NQ%%M*)D~< zD9)mVw$*qJAwU|fB7}pbYCKDg36ZoatVm}l%dAS&7OmADd3}X6)&B(~yNqSXDS8t+ z?juLl_32FN$8kz2mq%lN$Q3oSlB7({iK84Ay(No&5fzgh(dZb-J>@Kv7pkR&bc7_L zjscYS^uTM9EGQp?b_`pB2m(iSl>A~HDx>>Qrh&4dBr|GUM|rfreN0#_+q3FFYf(=p z;G*7)LU-DJKP7Jpb15H7$b+_P$yd2fC5^QQr!`%tUh3I3bdHiH#c|>-(Lwnd+DB_f zR(u@4sLVU{kkUw{p=7@lTBU{li!sTPlCJHGDn-AjYLfh^r5-_5Xqo6&qt2=w<9@%! zDM*8wZBpxt(gn4DAyuk|l99Sx>5D2=;~m&`j2F>ck#3Si(-E{3mQoov)+-C7$lAsH~+#T}RbL4@k(5WMo{1Yip?Fb{sb=TU*y7 z+fW}xdBL-2ejCY=+M^haEQ-n{sJNZHD1OmC)^|~NR5(-BB3-H9)7= zK25*4)K8XBUm%*nIYuoFg%T)E~r+^iD*Y(?YY9Xf_Vk zMOkZ99u>6}Jq*gQ$~=zimD*DliL6v@uV_YKt%Kw_X_mawYSKzImsQmyc~_dLW8sFTjtIz`zQjK*r5fwGHr2x(JvPGgXSCXj9%FU6~1>(SqlbW;AEMj9wq zqERSZDA$G~&-hd@y=A+8*RIkIbss`9N{`86Q`AmlCko45dOU=#bEy(#GFReXutipwM|>bgo^m7iAjhZfXFWuuh!R2c}$@+rJ^ zp$(_2qI9B6lQ&TkN81=D#ag?%ryAuY&qmP`z5jp~^eOc=&0wZ+6*Xg!=8mg6G_Hvj zK|KpeZ?zcbd2Lr!Z_)^k!fKRjnCz-^ijZ#cZ^N~m*2*sfU(|9>?MZF`L z#)N4e3h9;N07ZP%POL@O#BT_4rJl2=8R8^4<2Z+U1=nPiYM#{@nsJGyMfDG!jk6yq zk5p@^dJ0e>Ty;LpcO~S5sEJyS8b&!O>QU7zgq(0b6k#d#RF!I7jVl#WQhknov(9lT zEmUn%v+QbLME!#tR(wZZV(aju!`Fas2EVLYo4Nz*H)w}7OS@d_Y17(sw7a!e?`9{p zlg3H+WN`BKlkYnD$f@U?y6x2au7BzEFTeh+*FSl~jvG(k_=h*W>*jxV^AFDakF)PM z`x|H9fA*7S|K|2@oO|Ss-+#dy?|Q{u58w02dp>o~kIpZh-}NH?(tBR^$M=5nwGY1b zyKngH8$SOQ#F?YSI$3|J;gTP`fjcJ#0p&Si8pp{d5YP`Uj#q>V^1*?w@lo!s;%R9z!vW6Te~{$ z8n~?28SZMh%lgOI4*F=?=KufvKmE}j2m5v}e9g`Ox%r=9pZ+sFjaSlp zsPIWbd<#94{M6%6<8QsyCR}4e`vNFR2Nr$v+uCVuRokomE?n=hHq;J+=3J)z`_2EL zodJ6$A@0KO!*JwvK;g@@SAwPM!JUeV2KF9?=_enGR6*%)=&C=t0x5gYG7mV8Nt_R zPgLjj*ZXi%KZm=NtHagTWO^fN=-FhU7n9cO;Pd#aUWGKdK+P-QoCsQqXW4dtdTxDf zV|Aq{fxxx7{6HhS*Mf9$j-9Z3*qC%R_Hq2Zg;|Uue~oViZso0pri$q z+^yXYe9?w;Hb*M*jg8g2)0UfMZacZti1U5-cB2sL&TjrtyIng9XK3ZzU|5vLqbSNU zIhb%dvvB+|?e*H9Yo7r=YH*Ox4F->QI#JfPK9l#NPTRzm2igtVe+SOv{Z@wgT)#if z*NfHPYvppFw z^)+C9e*dYSZHxBJ!4-kw`HmM~-(F26cU$YMW!#^*M#=M&aZl(?zkR(l&5~JScb;TE z`r>f&C)!_WPidH8?_>K$gu&eg)i04+szTWPt0Z_d%G2c0F#b{&zcP)&?A|!t6~<9? za|oAp7%Q0mPZ-M?I|ZBYhy9psKC3;eeFc!z042u;J{21q!~O;~i;wQaQIy8`^Cw29 z&D?HeIgk0wm!|P^SrjD?*pKP@bx9b?JCZC*%t+{Nz|7#}bEkFY6)a^-UesY3gFoPB zwD~RVhuT-Q8BN>ZLvBMms+(54gnc9hC;^_X3^$d$bm zSmHBC-)Bq^JEld%SSH)GWrITvsK82adw+MKqybuD02KF+As;r4Yo z?DNkDzVBaapNySlP@KWj;2~IW4ekU8?ry=|gS)%Cy9Fn>1}C^I?h@Rc#oZTOVA0F} zz4xx}?$dpn>gn$3o|>ws>Z$JjErJa(0A#kXCc=kYSf_s-{H$=EZn}q|ujtb2Jy_b| zedSqJ2{yv6My7M;oLhiLJk4a^3;qJDTcp>~`Jsp>n;Lr?dW83MRdMyYrE!ecjH{tK zz0l6kc${IDX#+G*3Mbk{PM@}jJ_Yd~I=&8+ou~Wxv!rP5b(}JZ;zP2oi6`*HF5F29 z{083cUs>+=%=Z#nuGaf^=|wQ3{LVDJnseTOl=4L@>| z9OcdD2-uHmxP-rcLmMFA-$qiVKA)e>ZLrdKM-CCA_)T&--2VOZ+BJy@90F1DU4nl)Cy~0vDJ2XptW@g;h(S`-%r8%Sm8#&`PuYcl zhHqq7%(8W#!;<`Nk?4R-9NBr6^FdbuFq?K>w6&Wf+CJ7RzAK)c$B()@Ud`rr0>DQA zi!DBzlBTDnKAPRKJ)5QD&@9WLXp&IsvClFQY{E8SW`0nS&eLpZ7KV_~PxACv5?{pD z#R8uD_Vrb~j+PTJ-#_GHyPKKGjVHW8bPwF?O)DU%hDmh&_b=BA-1cur3-1cR@?qs4yOnry82{GV}{XF=9OJ1F9Dpbo-k|>VCJ7Fba z10#R8izCLzs7){@(;gY-X(#0GOY#HXjwo4$HUhs2f1iAc%K8KGMrU7o9gNF&BBQJ0 zAs=4h@_)6yIwF$_G(x0cprU6UMM5iqUx3S?S|T#fIihe5;;DDUPA>;nT0WBdQF#m7 zDLaFJ+eC@;-Eo=vLciij0uv}>!|;FZ4ZN%09!JG5Qt#y`rxYNI1d%VPDc6N3KMy?cXcl~(W!2`0} z1>qW0Anx3u0CqgN$7tVYp$$>z2$Lb2I?!f)P*A@x4fhJ=H#22^xoOV0Gloj?n z8@_0~@7q`tZrz?ZZWDLo&fGDy>lOo&9W8d!P)B$7k(H832myF;9vA)g> zn-LYu`t6n0d;0_<vD>;2t?rzb?uOCfRk-OBsZhJf{14zJX1a7egjHjDEdm}88tl!qvSrzUov{Yw5DZ? zUVg{!2n2CHu3eVMe&qM!7Wg;R3t&EPGIqD3DepKDla(cndEWe;xff0o+G<1Oc>Zzo zJ6FdjpV~H_%)qE(4lp=>5{^wCFV(U`& zXkF~U`HR0US2272)tIH5w_wSA^(_hZp?RZ zWb~=%1xco_2FP~_2SgTiWk9Z%>s=@MJ^2QAWG3P5^G7|^70aB+hH89b={tO&IKc=z zV)h;VLW3w+q4bi z&QV7)UmKpkDP~TE!m4IIdqBwbBR#*q)d2a^*$mX~r`%^>W+#Mjq}zgNG~R+dC(>y} zD_4A_Nol18hAw}k?^!5qsocG&)yGu96i#jugb43!BX3cSu>74CD!h#ZrR}HfzLceU z5-Jo6e1~-jwORmM=mqVh03aPEVw(vxw1l=(Z!x=Pk`=p$QvMi-yx_ zc+D``-pS=;^Jg$+?F?hL=1X~NdD(%F6+s$msW-BN#`)VVGre&HtgnQ?*f zdm`%=1d}`kv5!j2ctTT)rBpO6@ViPJO=z$*b%ymOW<1KQV@27aNsDs0Vq#}|@DB2) zyoBvh`*P%IKN{pK>ox%>jh~G;)u=!fnU)joW5I1tGf-eE3FuLMBJ1W(=_1>(ZY`oa zKR3&qCg)n5)NoDGg)oL*+#a!-)aMUv~fDMk<{PHzC-A zK9Gb}%Ei4Wt*j;p@%@VDTm3*9_dx3QTn}1wKn%uIB$6UK9I6z*mx-V@_^Ks;3t!lD z=D;GW_pij=E{t>6(vFxR5pEvT#+CfOR2K1P`H752h`xEuL}EnkHTwu;6JOWg*rH|^ zc+<~t9QwW0u%%5igooRw`Y*Uyc42F6uZ+h*XMrO!K81l^@Kxj5@82_RUH~!y^MJ8w zGpnDH({fFhi{Y&RJ*iO@S$sT^0$+l2h`3ttxgcp5ON_UfB$+eNc<_wM`(I-g1nG%P z&{A8?f=MnpfBs^<0wi)a%T23RO% z??Km>nEdo4R>3HhqdlY~nRuv^E|M+Tn^(3V>uOuqKOn>}k8F7`&-(ocZuB*=%KA`h z2YLLpG)E{BDUIfSt|Da-56KF3tV;7ZMf1n=C#1XEE_E8i6Q08E00P*?UfVCSoFvY6 zw)ER(&Tc2!wHv68L$;z&+`j` z{<-xNn@H)g?8smYf;htwfgpMr2$GXVfB3$c#hV+p6nc4~oO_CMsOu< zW24x4E)pd4V_PQfjIgv1I$N$ zEh&##{X-XbyD|MGppC3RYNiTG{zWk*oCLg6p@TEazV`V!wcYHsheQM%x%d zJ-wO%@z$1wnX$g2J7X9l>PKzo&Zqjn_#=}dz50?94qOQmR)77v%FUDf8p8c9O^zCi zk+=!hh3|Me@BnYQ+K0(VTuLQ{@KVfwA1he37S=^H7!U5t$1q75uf78zDow)X6W>Ng>ck(FP)H|4e!`|R$ zYp;QG1|`HY5pnlqtZI+f&w;&Ez|v`AUb$CqlL7FX|hHZx3P(Z&F-v+f_%ACbA@Fq&*)f49cf zH>YlWtZz_{wfo6AQHcI145poK0R!&t?jC)__;<1bZ9JNDl$cnqbnTmIf_a>fNj86V zfP|m~9;>VM8|+rTj((@&VY;1~vE4>sT1S}YQmaNbO_M)DY8l*G`6TMM-x!S-3Lfw% z^O6w>O&Sa{6$wowLoFlbYd&C+#PIHAiT#vyuo`>L{5TF1=2maOP54>QNpE#MS<`ck z(O%1pS;HTUV(c`juP?J3Emh)*W4!Y7w;D&WC5q9YoaZ-quU7l=_=Mw|lBLf~-vGsc zy!Ak+;7Q%MpOpSuJJ0FYmnXXbOlr6t+w=!=Yus8!KlM3=0Y+pa;s@Q!&(+$+0$;=; zgp#jxRZZnjT#$WVY5NwS$xrN>i2&;@uP<$?wKg=GDT<^7wfrp7F5CD&Rp(X8X|;CW z`&Ogf$jtj5=-p5&n0V&N70IRsShE%6oXv{XQUiT-K;@A9&$Fivg{h4g(;5+%a@#zm z{-0vmQZrki*z`Pc3nuR<1?Fw|1Pz(`n44AOC{4WQF^-KtzD>Am3oW*Iw_4p;E9zQu zf6<`bhNq;(#+V#q2qPCDV)kKD`i+8ybw3|>#`Sb9T+m&}9wh`b4X z&l(fGMO=Uc?xu+Pk=-b{IQmRk2k7WOU)@j)_MiI)O|Yb+3IGmrn6vX2aR7y$hY8RNP?AF>r8vwIy#;$ud_*c1DQoOWA$c`1$5@z z9PLId7Us9v6a)=?Cs7he6sk^)4S};?d6W&?dIAr}f1a1ZE3enaxVE)H94@!53u@Rc zo*Z}NkH&xKo+xYISUO{T0K7Q@1Dn9v&g?Pkb8qT@)jK^&fbDlX^H`PGmA zGoEAVnk>$M5v+qkkxwNDfuv4(zYMRTffS8ky2jn@kujv`&yt1 z5w0Fr3zoG|bgW$nGX0V1O5-}o$g*F2LPm9F)KnQ(Y-7wRNh^Je@=?mC7&&>d7{jQE~ZBv0~;nlO=FbbS9Wt_`7 zcst0Kqm-wO@{Go5zuXM5r>`#QhkTNllRdk(FzE<8MwpCgPmVp(nBvq{BHrILBo1ej zpf>LD?_GLegZp5(WA=LLLbrO^Kn(!hV^1gwCS3WtRo;N>pvOBzI|*Kyhf!1eE2Bvh zNu>Mtof!%A*vcBUGn{9r^b%z=lWqF?u#dIvQAA)#AA{jQE0zn$o;cA2CsifX`-mX- zJ8j)bmi7!$?PlNZ^Hs+rU_vaNd2o#JgaodHWIetljfj9o8??+oP9ALB&pUT~KxkzD zC%g9CnN6r7ThgCj;k|WnC-R!<)+fEZ9uICYp4e$+n0TTej;9QTQ+F6&Pf=J;e>kqb z-@5Yt@|`-!U!4-0lvpiBS-|V-&4XfUR~0DEBe|PROOw{rPwgSeni_9)pmvr<6QxKU zFQd%S9Ki}TLR#R=-JV#LFS*PhfM@i;Ud%TX zvp8N6Sk0v`T91`8AWh^s#DwkN!+!I7DDoj>v4+n? zVXP=`d5pL%3pYX)Gr&OeR{q-k*0#GoIhue5vW|C0mV`s0X!-e9V`5&Fb%e15$M7O? zgXgZRI(vCt2(U@< zkMvzBkhhUVuw`h^`KNtpTMqiL_xMLV)J}POyW5h6Vq@20l?t|!IUizGMt@_*rA#G7EWCT9jrU9H09D4gkU{L18wRhy?#LAK+M3~XfjQz(%FMsR-@VCvI;X@M%nLt5Ag?h2>tu6NK&V6swQ-*H^{ z;&MSKwVc}jJ-Uzdx&+aa5%|h1@n%#lRV2&f(lX)s`GEuB=c2VENj=?i;m~iX${%mV zr#Iviq5C5Q0zDjK^X3qlT#GsJ;GqSWH>qX0@;c*-pJo`cup^DAgFrtTE(<&5mh+y? zwc4QCcIh5WTqf8WRVuZ^L z5_z&ikMCz!13q$;=szU>_WlMTq|~b)ahrpyLPT-RRKw~%I#e@6PV+ALLse*`f~!iU z7=;HSWYsUSSk-W+zS-|;bnY`|El(ISx4lz8@s36DH*yX$W~OT&eOYWoUtt_STTh9m z1bqrDMXcC+O81gv(@Z98&%iw7r8MxtI7?^R!u!mA)>AnSpN5sXP*PLek_+vP&O1=A zKJin$=@+{v*h99LntbT~m3wF=pJ`u`<@$(fYkdEyeTtRNOFm;)#u(Q^z+TH)o>#s0 z{JX@CVEYftK1kp7#_xp<4Z#KatxPW3PT>rt&jB<-=n@_^PDH)+cANm7Zj9_#;h$IMc@>DwdF?%4UtGLp$i zC))kXTz|WB8ESPlsvh*yFkSBo;Ii@8j4z?E7XX3>OoSG%gQ4;6e5ijo2buv9nztqF zohfuyImk8nlm;cjOI%AU%)WRA4_^}^|L7OgW0cYwk=Ar~AA&tvA4y%XR?s%d6Otp)R=k=;P1wetGBeVk(V5RtM{g}+ml_pxFSUQiI53Upil zfKaOW8T^S~%&EXjo0m&={UxWW0d7S;Kz=r(IPFrU?a+`R-=zoBKq;??)R z==p9x@?8u9&%0K(X6`QEEeRsR^o#_%9@V8;Y6>s-ugALojm+F{v`aK}8KV|>H0UtX zdb?cfy-TTi>qN(4?|i(Z+ami$;O_bHzUf!T*W&lMRU_`lyy;gRS|N_$ zMY7Rkw+%9zadgOA19o^_6?(h`iYS8T9$|a#6L!F@A-w`71L5eXul?bO*uv!3%G+>H zS`4oRktAL{(7j+c)Q=~jh@fu7jElT$;X$u0-1oy7LeDLpe?|rJ-W@yW+Z3LO^g=C_LjoTDwAFy}AE%UyxsdyauAapj~)p>_q*?!F6q4 z0Sv-S@6|OeHcgj^v-Jv6FWW8*+=t7*^ZqG38TaoZ&?-D(3>GN<*J=M!t8hNJ|F6ye z76cAo5291NHvLEczeWFZF@tTNXShrDzlHz&-~Zp1wom(rWH`K@Gyn1TntDCxh59}i z?uC}|cKLsJ6NbGOq5WT8-h+0Y276hro&GDaFf>}n?0=adKr7|v|K~RT53~O#n*XmY zBL(eSuk(bD<_mq#3t+o`BXRW|@m1}9O(?NZhoML(>6K31b`R;5L1K`P(3TeXefswB za-T%weWo>$MF)-LBk;xh*g0Uuxy!HJ6vDDTl7Y7J$T)KaRS+;ynAh984*`3B1Rsa& z)Svg(jUYswN4a&Kf7FiX^_x8If}a}sg+TH(Jl@5Z1CVq8( zZ21b#nqLKf^zS~4p5Cpxn)p~irf(;2=WjO;5j-9)Zo^2T((fj=tM5K^3wl0=Zgt-R zy?knWjCAtXA?RN3H`5~P5SFvMa)A%Xw21Dz?ATwcs-swq$G`NCVtF6H+ngn_gn~*t ztBe^ff#VFfrFn*PFy8soQpdYW-lk5YyJx!}M56y~f@DHyLK+!lR2Nuw*8QWm?ngXc z<|GviDz1C>vOZOH@2L|v_yI%ugGymAJriddh3V_E9n32dn?&s(W{ zSUcMn+Z5?E1&2Kc1dtrBb>wg_B$m2eiUpLv-Dx~GKPz3$)Ev$S>W+T#_G+n(__-+5 zx;YNHyt7(vF*5uE&Z7qdeOa3G4Q^yU`l+-F^z35rxYPu&+-}lTQrTd!ryb5~DzJ%J z{-sriqeKEx=mn$%GLwo*d3r*&Q!%5}mV`J^7wCDuOEDi+BE8IBxzcj@R3l}4D-*t&&~1-Oa@B2XU{V6-(W=&BrUz1|Toh|BqZ(URkAV zk+$-lgDZ5FM)Q_KV=rxhl2`cdeB1Xm=8mP{1$6Eu;sP-Xx?d%kF?v`f>1{s-zXZK8 zB!2u|z{`K@#sm49Y;S*zYZ>|O1r@xsCVuowvp}RjKsU5o!?5J-~7&z^tb8tTYrl6>Y2TY8Gzl4HeX~m<+GxYdsMVQdO=qPGi1ziTSf&ngfp6B zBZpi+GvuUd>#k-&54QqdKQ;x<7?YX=G7@WQHVg{{&R*V^Xb-0^m-e~ftg0rD$Bpug zE|p$~9M-8wr4L6Pz4xo@0)6>(95!^-+=~L8=nMmEb=x|7l$V>M#FNCFy8Zmn+) z=D_2{9*1+bY%3r}Bq7~&Dy<-&7ZBd^OF?x*U{FLJbwOW5(cYJdUlUrft>s8f47Qvr zj6CTIgPoX;hP_u?kx@~_e(4M=%GRV$-n&7UjlIG~OeCY9JvDQ*7|W~TbF_T?e(z}6 zE^b!?A8|jf7|*@4EA>~o>0sE0$uUMM!kXC0K=&{ zCa|CQT6l(5ciYorhV56UM@ZGX8qM`TUP@&zG8p20BVfVuPWrLs|E9iPZx*r59N<%A ze)CQUIkezmj@+)DIfS!90@3?y8fhNyxK{M_P9W8?Ao6M4fNmQ7(3!U@A4uu)1FCra z9c1_L&J53b32LjJm6?9-cV9!q3Zm3L0x_;!Jn*XF|%&|xb%QA>~7*22oCp>3q)E~4SB zuimV*n73c`Dy+>*TvtV_70~ggYxGj%Y`TM#nHsmPB`^`s-?uwu&d^ZE!06DidIrYh zgF)jtHsX<~>Pu($G-)MnE%Unbi`@hsN_1R!b{yXD3MC5cf2h9z514-B6sday-^3x?OvhSjQRj zx1$j#qUP#R_H}piUn^)$m7WDUS~|$T<>2mnz(ZPiZe?*dxU&vKOvkIztLG7@vnirq zU___0YIOTZv?OjC>V)TCTs<8o*-*DK_p8eMJ-#unE)BQqM{los@4sk~mln*bz9iH{ z7sM=X7%V<3+}yutd@LLaT>UwRY-l@d>)&GX`C1;zR6nX~fW+c&BGaUJdTs*DZf;L z;FpW>!X^gEg35P8-M3i{k7NBgzMDsmh$S_)&6uLM8{3-`daE%}VwN*~)i;vrnK=z7 zbbBMd2HW?WG^amb$XxuJKS8!Cs~V(U&fj;_78)oTR6}5>sSAuC0l#fYDq0?v9>?f& z4As2s98QgIm@O!Iq^p`?z8O3?haG|#LT>EnZq=B)#vTl zd;;}>WV$v4v1v^*CiSS6{ow1LOUWK62wDO0e+6`F3=BqA7MIjD_6~?rSLw6!z7Yr+ za(#Mf=}m?}5&o5VsNJ8~q+*B=yTLy9p7)!+Bhr6J4>^26PxMHz08>AnN7yaR_yffzI_@vmT729`qQBiBUhlY(V8h+7@CZDU^1j;>Ab}L zb*8_0x^Ss=XpdA^NWN=mRJmTHBb^@r+({kvxxKYvtC7}R05w@O-7xBDoc-(|x>_o= zIUYT;*i)_54^SxDhpCF1sGzEP!XFN%{;VY3>L+J3(jx6%f0D9IrE#>X%Rv>+-w^Yn ziJp6Y6MVzePYD)_NKNoa1oQpq5$UVkOn8%XxTMikWLlg)BH9k-y3T)( znxEH)dnZl}P9!PhCsWC896U&I#ipcD>Sw^%b3-HXP|5 zItRH8>COQ93Ebz(*14(r8|Bw`kWdQ&W^jOS`O-*A9%0I4m@@F!D$gXiRgWXPzq{qP^^M&g** zS~!FfHmfQSR zMV6NC1Xbt;7ZZq{WG6>5yexYQ5+^W8ytu-0-wKh!p_v=bZ=h_7y%k$rhQbi~n8v&b z0Nzb$1*XUN0*3aY{`LD#A;D`0@D@B|CYv9uKJSFU4{rO1S{1geDV1!5%~D zHl0xCxQm~hoM&&KZpuyAsYibjuy^=|Plw!l!`YANFjC9LF~Kz)?T{cvPI4KrAb*0d zFc9)OA9T-hX_a~(bsy!pi^N;@cUDaQhMYLUXqWaqO9!(tZ_VO>01;NN*wLMk;OBi2 z_}O(>jx(R?Rz2BTD(qftVkaFxgzdtplzilgUn(O%Gsu#7kxLEM?m|$Q+)&_gr?J83 zo~2P#&nDwp;uFwpANm`%1MJ-it;K?yx8+7O&EFJBth_{LbY#MsFywYp0yKDnsx7QU zjLG_VIefm@{L516HscQ)=+n@bQCML-#eRD1@*T3m)I~E=(ip6Q7Vl?^lGL6gRDjV8 z+9o~=R*E)Oo|Tg6y~D<5+|e&JQHj=>Ex0z~SQpZ-aZ!65nJ?EQ3ibNKuo8SH_^L5( z)Qu$8u>A7p-u^|HmW7ifyFtz}(-fMt+YJ^C9mDyr@YmjkiLjEjb3b}tR?H~$3(Jmk zyhgT%;^%U-my8LgrnJrAOf#By`w>Mjg)JrM2(~1|Ms%E>|EAz09t$K(Un70o;I1ac zDP|?)4MYwej@oi!ZJp0G*vm<@5fERKpCSg7eF66KNLS_}6jJ3C#I!1HHvm_itD(J5 zFXPFQ2k*o2q^IMv8Ww04_QP%TODYg)LpMsEl-CcKKY8RJ|yQZIVIgtOna z3o)Cp!LmNAw-%n}?lUAu#qrczSjeHAwu(dWozeann5;0LurDZ7sRG6PWX%^3Gr9hu zlEQBISDl}LKoT9KntvE8MT1x3IrG}T(&eBrFe6nw9J!`LRd685W=oCp1_XUuC5s^B z4&4Lxia3ENOTfxhDqEmmqi#~W-pf+*8ewSs_LD&L5Ioskk0pN+-m%UR(hE;X4zL9l|>fU1`6hFT=b9td4pO8!Fy?^1k zz}Mo8qT21$>?+F>J?r-!&(J6ZBZ-_WdYP=MrCr zoNf6?GhbHG0m=9wK7zQ(S$H~yCqEi-BvNiZUiXSei(N~LrvO82c3vb+DGp;?iLVw% zqZ^;Qsns}Eh9=czMV;D;Erz7*E~7OXIENf27XXx}$=-OCSlydj_ABr|T? z;T0^C6aqI#9O5?9#gf0G?+rPNnzT!z0QsQB2O=Vnw-kf44$#}f2jtV)I)B!^i zS7@t=i2z7;rE26fZ3TdpK{xzl)W_u>?b5oJIX7R@<)m{E{k-_~%OuV!-C9eNy780J z7SoFI;>E@I;Ud}&LN37n?<&ey|+F>UTH>aDoROb#f@?|@KMatCMa2}fp8#I%nm zz8(URIrKGn+lAtk3azLFwbT?qQMW`oP&`bT-C^>$-4X;}+q!6zBmHyG<9p5Qhc$^c zLM;$MyApL%w~jp8K=Sxi_RDAn&V6q^t2Av}V>&SQGs>RC({C>OEkIhtm1{cN znI6Z&*Oj;x`FFyCEF+GR%kya2%~mnaDsBwBbCLT933G2HInkQyI00YfMuB^|96%Bk zd$EM2J{+?5uzl+7V!dz=bk$#a#B}&|X_5I=%#trsi5{;0Phv-p)6qM6u7X(%c0!|6 z#waVV8G0y;r#$^%@iHV~?P__R?xmytQ{Cb7gD@vnO4egc{;z&>*`cJ z;v_$hFB6$m2U{}nF8|6`39eR~Hp(#D)@kl=zC}^@HJzD6yH_YivL0&Jn74fG<_If< zvz=fl3X+l%Ec`0tLnj@#VK}Zca8${V0t}>hYqx~uF|bIQsZ~BV$V1MW3ZK`Z=Fl39 zi`E#7Yi^z=4t^I63>BG2nJSG_6`zpaN84^YdTn=b3rxm1Jus-vVo?2gInt(VfNvK& zX6ffkXtVK#&p^1QCSAjYN2^sD;m<0CzmC1$^V7dVH1r0U%9ih{i-gLdYBALXcb?Pc zESBPNXvoE>h5BmPyJ94S*}ZNLC|K+W0+b~bhe_#X`u~Ec+Bq~ej5)1ij)=++8_0cM zQAZknYCP;3aTM~~PL+i0d^r*V{9Q`W9t!98T^jaF4K0Jeq?8|SFx|^dS=Jkt*70Oy zVxk(p6wS^_XKfWte7be42`U{OrMlGCqw%}cb}FGY8@%ga7}54$l(-9=m*KN%C#lKR zzc3r-vg9u<&vl%ZSDjGp)blXkGQp`Cfh?Vg$+Ko8jMF-B1tt{QDEGFkdBRQJ%cNQw zo(Lv&%_U76&hYt!pg?eCFbzu_RPA79QNjo?y!=|egY1K6cw1o7A zOHKL_PziYJgaxlpEBALCa}LGaW1z9h744_9`nnm_XTvkCk`4ISGd*REoU83^YUT%M zA8;x!DHM<(j(M}&k|(pdXue84j5OJi*R*|vbd#Kh5aaui^u_S5@reOxb&*i}@-X)s z{yo5sv0aNVlq-9uO}1!?O=KrSx_JDdCZI+<4yJmB$fwk;7{1%fEnJXu;%8L*0ATnD z>T}LjSbBBOk=b+-?-rP!YE@#DX@SHBu1ew=i2a~ofwEI+FxsYbsgOl1aK*+k&f~^O zc`_TNp>})QEhjadpO-Oulx#7h%if$tz^YF3lebBYfALV5BZQQyGqaBmXVUSD=S_VO zH`kyf$Lc9MvvtFmD|_)EyfH(b=w3wb9iQ&8;t8_ve&8SCyB}gwGycihYavoEfPC(? zG)sb`!qu=TNDjcmQYKc4FuoXPZ?qe&ADPbOxZoK|$hx+=*%|?(apW-2ZW40XKHS1$Am5E?5BKOo!imXJEpL@AdW+U(+aG#Y90$2s?+|3&%Ab)#{N zBGyPkqeB783ips0=~*5ton2GOk}LVQdxvxcL`|{2g6hxvNm#?VtFTruY6O)6 zvD=emCb6?^hNo_%g+IjQufJCP)up&El?JhGQ*@TV@->m?E6EFCQ>c+0%Pw22$ey*M zJLWElLfi5o>6*9f#0{H016yb{CN zz{TMrOA*xjsY<;-9U)oHRPP*@q1fFj9Ws*$6nQ6i9G_940^(?{L&0YCt40rrS=M{X zVUuGj9S(a}xz5L&bkkj`KSPuL*a+e-uCqrW3wEz|^|X6r9>`8f4?42$$`aPetxzSK zf>q;_Lw1w+j1x|B5w{oUgS44`^U}e;8pSq1)x4a23$AHdR=S~B7w2N)bXH0T>&q#F z!ekHD#=8wz{lQog{wg@J$lIA_?YE7mCb?X{k%z!TTISboGhhM_QsrVYw>Q|SQPp9Y z+~~i~J?I6={+5O^1f;y2;VS@CaK$10;8906hxzcL+YX>c8Cu$*uE`CMFJOt0o1uQSXOB{$#!KljJP=FnvyJNbP75QwZ}|;CoD&&)Q*E_;?jY z_hLD|Uay(y$OdJJsrBeDH$Uf5toY5sg>A6=iljrj7h>C|`zwZ!Z7IsfrSCSHv7Z!& zity|O4jXQY)T)JpMKT54@Mxb2CJ3EIsen@FIUHx1TAg3+iTsnJ8)%zCmWkMs9#sY= zdA%wk(7ww{73H>k-6z2>#OJh%vC8gU1bv3JCXh(Mr+bidh`^Im6P9|-!4=*7^s#I| zlZ<(-QZ$ChF8qXk6o<2J2mhMEgc4yr#t!M&Zow2N94K7@Yvv|OYb7i<@fDy$9(c}Aq}E)V>}wX@mX zEb$=ZV@1SdK$Y%nD6qoWtNjkvX+OH_mMc+XZq@UPMn;sYFC^vTggp- z3?g)5vQ;2X+gcP(eJl%obdUx;o-F;Vwe4nE9{cnpruDeLk0?ib`O*$!(0qTr${d(ua9KAy8MO z=Qg4_PhZk(^>oZuu3B)-_%MO?qL5L!rvrCyLlJ#&b@h+3XZ7~o;kq>CT(!Q1u!ND1 zZfcf&!eIQ)^khaHoU#uq`okN{b)-jME-j0=0_ON_!e>qjb%|d&@Mklk zshDTNo_rY9m8%TVPWtBYK~agqxKxnq7nE6_;)@35<+|nrAsB}|WruvI66RYchgZ5P zZWX+0{Q@>S+{shq*rf7rI!(Xk3spy8bTx(a+~D5%GYA~EtUVax4?zM$tS#nw{r>!!(1Nba25Gj%=Rh-HIHISk=qFYieIW`^oi8)_FTViw- z>x$O#>(Uikne>uEv}MY*4IxgYts7a_Q$uhHaj^Qjs((g~H$-+#)>(^^Y~ZkY-Y)Ma`+SmX;A=LV zbUR&J3LRkQu>{4gV424sXz{LGo8`BI!ZIrNhb~UmFD38ls7dEza*JsBoN7htybu?l zl@A`7o#I89E_u8VpA^KW|J>Z9U^rIzFiIzZ=fmjd?kT=YBY3?SePVn zhW7-41r9ZdR?n0yCH3MamYT4{y$Y3& zn*mVRqPr>gGG~{hXPzL;|Ixn7_EcZ;<2ie|qIt&eHNoOWj<}UJxhTM1DByi{x(pq( zeKc{B+-G&)coDOM0<`|e4su^t`>u%f=pu0b@}Tuj;c6tq+QNt0dZqeom1$|vBzoI{o>&dybXkIQ2SpFwXeo8-3ahK zBV*pbM_8mUYes-i`u{@Cc2rk!=2@{3NHd~rEi*wbd1>i$_UR%Ol zw^CE)72ETzcoS5O(l`<_`{TT%vr0GqUQ$w>$(Wf`nY}$QZkCreOzpMmBvvID-&Ho6 z=;bWS@Xx=?(7qg3sujAcXD}4SDJD1w?UN1tQ_{b71M+flrRFkboTkt%pLz8pAmuCEKKIH$ zW63IgMXYAsug8MUqE%~wFF+6~$iNbjMtv}5;IuQ&NC=|a*R`$2z)JIHfjkU8w-ZA~AR2CcX3sgTi9YO}|`%>MUZLA-L^4;h%=RGO_T%Gg#&W6PH zyZG%&*G%@jP1v#dHBy&F1ov966Af?)!5hTf=gcBF(ovryDbWAYW?JSDD{AVW;8%_l-1R-7LlbdZ6^)436!4yKLA}oqQ7`TBfY3g!40~hJB*k* zJQW5eY)zjk*jAxI3fi~I^{@0KeOncXW1fjE904U6pYVg*Qo3(i#D(zER z`>M4->(B+X9*fO7WRFT9cCeN__f0^0KKh1ZSPrnqV(H=JX2AJCN04C9W8#iGxApQZ z5qu)$U?(k&FMTe_=no&J&F=P_XCp`SUCvdSUs9U7a0h;OdybNFmXx=w&JeTMk8}`Q z%HppW2T$AZKhM-WtURM`4h0y!XX1nHakO~P{9-p8Io7zPhPRhnf0Pd5HfM*91>b4p zgT;_{N|5zfA>pk3n-XALiC^Lh6hbncFXFp;qgSv zW}X~WYyo8&>2zB@-v^`%&Ta#oyk$d42P{4+w>K@ zv+XLu*hjpJrP@johuS+{D)+{GiMEy6&Roa2gstEgGHNil2XW9cgS)NxX-l5ESZl9% zw3#P?U$ouy6~GEgik)<0e$n){4w(Ymq&Bnav1c$ter!c-$KJh8DSOPBGXr}#~ z0$LKYQs4b`ogu`hb)HK)nKzT4&bAzzeDdm87C$_Wh8~bOTc;&C*6kV}aD_IV*CAnG zX2z~ZWV=lByup8h*ZG|GWc+RteYS~-8hV|SK^ff>noETN%-7|nn$^QzliivgN)%F zO=~^4wAMF0TzgfHt>3;WZ;Jr))^lRrk%w@NakYJ;W%IPiFH(Tqr96A(w4AO>rv)-V zj1i9Jzu%B=Vc&t2-rVcfd;$}-qGi>-n^#f@J&8QYbGg&wgZ6!A)G69je*J%W(4@FTwxws-Qr@$NDAnQIOsWuT@d zq=9r$UU(w3tvoa{6J}jn%qLvRr)fgF)Y`;O>6Xvj1xM*ZDRUiS(DcdwE_rl2rtJ9u zaU)qMEDyGa%%&39U`&iuBa7*k+%k7LM{CSG!43aU6WE7_%r(K2R^Xx>X&dF4V>eP6 zrAN+Cwsk~ZLsOwoN1bD=6I}9aJNkzvcAw1=v_fBCk7T}D5;uJXc@Fg@N4^ixdDO)+ zU>0?h0{yUd+p>yc9OPNf*bfHJv>;Gpwz7rY9v3P8$Pu%#!~q3D)o%TcF;Psm{3>h19}w zlBI;w$^}o0Qd;TJ3XF85RL{YXoKY)UGFyA0KYD@$VF7ZKw;hc7h*xt*2D1+_88sIk zt3UY7aqcMXDW?>1rE7tG+b`v=<5{(Xh){hJ)KO``uhvw3^Z)K4D}JL7=uhzTnFw$- zLm>Bi-4Kj;qC?5#o0wm|uwL&7jh?*MZ-u=iX7|hNnxitJyp7+=vI-;&(gt8UI%c?T&9u*8-y+eBlVR*- z?7?2N!lM?oeKuAaV}s3+Y~J;Ew67h@_&Y`$)>mR$GSz#ojyPJ&($+zyI@gifLD$SkW9uBu`~o%OK7&YO~mi+F#MnQzxYGG0c)XsFwL zcMC5$z?M*C6cP#5Eh#w+vo(!I9i^xfvC&qkmL zV5>eD#gDkcu@Od3ZS&Kx zx-ciyincEsc_c=gG9{Ia$Xb8La?)F@@UuPgksshbT1rQl?@=bO;FfYs^o!l>UdRo$ zoY?**?;6jhQ`5^*aFiJzYjeCE5X|7Q{cE4es4A_jdm*WvbHO`$#Xj^Qsfn+|>JuOM zCZ3*uXxWKX$Phbo(;CS;qL-!;&+Br;EH?ePdqZBJC5$b{?DFbWEuxlgD_F8eYm^Es z3|hYFYu$-L?3h@HcZ9ZI)ko^91;h*t@VZ7B`b_dsj+$1K2(dN-gCfd? zYh@`ZwvGuIa7JqU)o;Mi{|L$vm-oqQM#84i)FM;PRi#6_;2ULprMrOlkCZ1 zpDa(3P@m`Tu^vb-KE!A!yL-V^tyT`vc4*Qu68o6N4(ch5N;6hBxbc3}w#DObM+MF( zTpws_&UY{f6?yk}?|Nq+E;EJff%P5O?6HCp`Sz{3%rX1z1I+kFxy%u}*(||TetDN^ z&V6gWJEz^d%KsL~5xeuSi0or$7dY04y^C54XoU5~rm$C%ckUql?n$(i-h%yc+V2rA zJ`XTA_Wy&)4fB+ve(L#1KtkjqrV^y-Fh{oSJi#$28 zO-rAEr{%V$Y6Nd~=hrv#$wAJ@?HV7zuW^nC56QdpR0Zw1W`LaG&zS|||7Z`|@Lw`q zV+aPEyQ7^qRrs`qd0n&&pGKSP=Hv+={T(|=c^gvnJZJ1l&t2N!z4*}|@&tMpb16_S z@6%wfI-{fnF#xj4|I*|gN7`0w3VN0CpkMBq=f4l|AA+GeyUNiYW|aH~?0!~-o%Xy> zLkpyRW_O1crsvIVW|^iUkK3N3VtLT$h?bW{7b(g8_f324?sY=s+K~Nj!7p3wwvYp+{u%@$X6l-#qo;JCbsQL}KCap4ecX{@`szWIbpNTfv$N z($voh>9u(tGjzkN>Kl0qDR>2IWSSPSP0B5MyV>pPyT9bTJ08e>VNucxwqFm4FXL&X z+)>}7wNNP9gp@Gy02YNdSm%Hm@IS5PNe`X`%X3!gCEsL9<+*PePYL7=x!~EF*aYku zKHOObl5S=y*g+#!e36*+#Iq}`q=xSN_aP)7sexL-fu~N|)1V{RGTI^EW9Cok?meqr z7cGTW*xrl18uIG@KGXtmW)>2=*5jimXkCA`ml^FKt9 z?Bd22QWLpE+u=EK-=1ARiuK3SBePsnUW^uqD11r+`POEDJI|N75=hia{L>a4xkS#8 z17w?i+#%%iA;>TijYc{H^)tp$p$YLC5-EoIJ4VvuVI-U(hqQ=~;4Z!fO6l*M8R4IZ z&MZSTMmy4(UicMX!Z?U$&(%g1Bht+4| zM7!+7MaGSv$2OtzoBddiRL|J)XwEviYJ7XF;$w@@hgO-f+Y9Y9WszgO=H}SJ)RB%o zIoeGgn9~_^K;Cf%gS2vQq<4?2uXg@(v+SEG&@(3cd#kdvv`N9uzM-5s`>rLIH+$i? zTimSI<)p$-1~U(|()ho*W?z4M=n^AW9m&{7XS^>4WyO&g8Fj1BbCX)*y|Mp}_ms@$ z>Sm_9+tKQACU1~feGIdf=F#TJmA3Xrn^}k4wH;?qdJsyC+3C*Lc6Lhn)Q-l6K`-%1 zd9w7hR(VJ>85gbW)q5%!~WgoP(Yp4&$oB^V7M1!JVp(Noig2(jx2 zpo{g57pOrSxeiT?9=K|r#1`=+e8i*i8L@(olrw9R8F}9C;8WLmVgRm@(=&S%c|E#3 zzA%5xdy{z%JkcaGYu>cs+=6&LwevqyjccG=W2k&EOCzEp??r6KJdR_q05deDrI9QN zvlcYcysWy?ah?28TbYab9^cF>1m=`H-F~X*V%%pQ+y4+n4hQgwf9J3#w)b=}FYIVK z{ro@nz67kMwf%cfjYvuwD2XyOt=%Y!kQ5<>h$Kmh22JMFCWJUt=0lRqGZ{0_8IDLv z=FBo2WH^RzJ^kF)&T;PrOOdq%EE00T@1;ly-pU1!2GYfPOdNpcp5h;oD;8~Gv(6|-b~xo}z}H51 zvcq>^5o7jrZq^gbGxjIav#wdK6o&+Vf%C}9(a(L!6 zxDfk+p(ALX1WyGPC1`;-0ZI_ofHLHPLApUhk`7^a5}~*9dL5p44hpkR>@`F_%x7&Y zfaHy|4e2WZmC+i8eG@9-88u9H*lsKE9{7W$0*9bK1?_~W7W>682vI9S zPw*P}2dIR86GyQz191e@rSY&-bS35Q`1f(p=Q(3YSFX=_1FY0UvRg}f9Q7i=K9W0Z0tRX{vJFa3N7^r?NN zwlssHwPloAW}nPj0&kOsX7hz;J1dC@J;i?U#0WMX#66%Xa91x&r6*xgWCZ;X#w*A( z$;8k7`qe>xv2c!vD$T8a4OcOa#5owPgcu9yUBU;NKSEbf31yI*JTKHh zt%E;kcF+f`uE5HK^wW5#4fK$C4h$I@)#wI17mDR>T*JF~vk1kXfv>DSW^IA1;dRqa z3(^;C1`+GUS^{!23a~XG(#Oz)EH`^D7C4Q3M~d-cq`(32DW2p7ITmP#b9xI0EHwCv z#P8r$0g;vY6QA&th~wEOt#r~1AZj4!4Vq2is-P_?g?t~(4Dq~jy6|7SCiD?r2FVNY zg>t*X%u>BH&qx(=QSInC=q=q}gn3)ciaRy<+@^uHqndp8w}-Y}#xswqu-CRqH5?f+`R z=P?lB$#sZ{(26t02af^#!MGLi70fW^TA(p@5g@8WQU;y@YN8G{8h9uafne>?yKWFG z!g?_{5&u}nXV=)9P+&hJs!4dkqFB@hdPSB7%fh9301J|H6-mGas?vkqa= z&|H&6i7{XemoyOCAv`5{2GUWj|EEW7PfIxkJn{Duum#b7O2;=vEQGSKJ=rhXi-gDq z%Ps856V`!*2nSh*_yBEo}r7n@xC40 z+r)CPIb-P*LuNe7;v9&!kp}~$A-Mvo)v1vILRls!(0Qx`-BZjgPe?nSE6vK`36YRv z+6#l7mWVE5mj>c;tcS!c(34DeL65_hB|C?W8E=kbx$))-q8bnYQii%{PXdd(GV6o& z18--77l@cK={Jh4LN`HTsh@=Li1t!%*pwcU2`4{_EHYs)BE|%~r8O743lPY{ zA|}*x?8ry0%6%sL54DqhC)kEWE#zivc0d89<bL`?!k z=$A5$@QfIYkk!fNg!!&_tjjI1;-6pu3?D=FUlC(fdoMX!`+fP4#Z1bMrx6k5SQ@=&GYX4GpTCrXm7h%2yc1s@DG0w2i!BOL&A z!JZT4zLS0?TolTZcL*F-9u3tLG1KC=ESC$Sn9<$c?$K ztuKW3s5ewg?fwg`D)&u!^cab9-^qf(x4=_WD`cOZUyt#Soq{+nY)iZo09F^!6BaK^ zr&tOsGhhrx0Br&51N@0^RwHWxUNwAI;3O!YFQNnM6_piG zfaCyDOmmBQfxiULo3IT2IirrS7l(8jl|eL$#;2^!$vS`rge5`#3bl@z!Z+a#+1k+A z=rwuRC`A!P#L{T2@Mg#sfrrDcqW6psp)v4A2l8m3wa^OI#i7a3I--HNTZ4IQEGO$L z-a=0j!LHGp13*s!>p>YvFlz<+iDn8C#Q1=v(yl^=!t@SaSp4(^DOk=JE!)FIbBx)5 z?eg=1Ym`FYWGVhSoh&T60!-~}5E(FEuuSh}DVaFZ-dpfaS6>^$rVqPVtz7Q*VRpiQy%i~7+Y$Sc;?@VK0G&p z_HscsF-PbtaX9c4S_XZ=?9%Q~Xb;5IA=8i-AOz$ITEHX$5s(6Y zfke}O0-ypRKk9*9h3Ke$P0wh*BE8G#?^6Fyhcf2ZK8SPmzVD3z7BjA4=Rite4ns-o z1qIf_x>sK5f|kNaVdG;?Nva?Z7%d&jF-?*`vWD0}l7Ktch=(vgL@oGlLN73P!u+w{ zWOdLd$tKMe&7+`mSX>#}jMYh&r(hrd+~;3kr3h;6abjf3bohI!az1biS&D>O%Cksw ziIqO04Dma)2Cc-ThRqK{Md6d>$DH8_0_>U~_qYap0R>@;V7Dds0-B!g4a7BgqNG1z zKVr=g`B-a=zd&z>K`fd{8V6Vf4-t_H_=_wrJ7c|qHo&{<$Rh`(@bm=oy@~T^RDwPx z%MRb zDIg*!#4r+lCOt+zCB1b@@J8v^&wed-5U_OIcZ593IdI@!Rg|ZC|L!4ETSAKRReTGc zuh74r4@9lmFY@(;IzT6yWAqhKeB=Tj5$}NtB;6FL26vL~BS{gwPNFr*K3TEQF3>>G zGGqy`9k_@mfzMftjpUh?W765MX2#%Cu+oTLV_YN^;3e$;rdTt6fujYxobU`-fD}PO zpaGGFF`y2SNX-A&<^Q$$e+mCD`+kl6Yl=FE+(nX+A<+>ct3N*0KgN9hs4=v8;*ASf(qs7Tbtz#a6P0GCf%%nZC?GX80r3 zMAlShDr+V)lQoyMkeSOYWG!W_WUXawWR@~3nYGMDW-H@la+#gXUgjWkwCRP_~imgz;#7)vc(naDS=_>J*bdz{Vyd~WwJtRJoUXtDtUr8T{pLDZymvpyu zuXLYuzx0&!w6sY2Ncu$jOr|DNmubi}Wm-Q*B@(q_bQ(T7I{f=@Sv-DQ@&E0-LZah4 z$Hc~1nRXc;H`>ayTU^*ED^vbzM9pVB{}~Y%!hgn%O0Y8R9vc#EW!fVon!n=1<^XhK zM@@U?OZo4HXx4lIr!b$Zs?lb;*Yx-G#A=cph4~cz(nN_^EU(YiQ`Hzc-Af!Ml}W@Z zB5sVTrll%R423gKlAKU3gtJn*(j?a;rHRP3=IAXNCW;ruh(?RTL<#(V2X=7spDL7@ zW@H;7hdP zELgf!x2baa@Gw*Vi128>R#V?DolNccL2@Sg_3Z38C(c>!?BKx31Nl>X{#5?`luP)x z^zVDR^dHhWh1g7)P(1ym3UPfAe_d0e5Q{}+FE*796|L^na)L$MWm`@5&Nd(1rd`3o z5A)LkO5N9KxvZO1VWE|#BiggEy{LJbvt7mDNntN9|5m8SU7De}t={R}=S@r-jHr0#_-aVrStbx6-{az zz%|(Rq5s4;&o0dznX*6mSu4F`m9Ed)f19-VU23x%7g7hl8yqE(@*=WYAs)t4WC*9r z)6&>nR+rOMRp(h;MMX_2<&4=yEgn=F-fJm2p!u>SI{3nR>!9a149AuZ_{4Q($qi&3 zIM=*(Tw9uuI{!3bjkty^T}78?4?7n}7fT0ct}TBwWz{s78E}nK8b16SJn?{;Xh3CL zuibB(B{a8t_;M51pJiw&>&^A#dgOTLberCFWI{r0J6qe(xY0II)Rs+XOq6Zxs0en| zHa0G1cw%Tmye;1qo@sfO<+(MOajP}wY|S~@@R_(k8md?<>&5wS-RYc@Oz-f$iLqnH z{;CNctN*LI6S%sp&$?pqH<^SJ{Uoqb#(qP++FsvbGdagOb81?bVHKez1%K_YV`Db| zsd)U0E8j+@wYc-P@vc7>bx?D?ILzQpp5c{=e@%9@U(z&t?zXCo{hfET&35tg+kLxd zu|<&gqiYY(Zn4RI{n4?@jBghU$BWEHm~S!JS+~*h_r9)jkE4lq{*q3~up28nHSuAj ztz?65#*!B&?pj^&)q65vTSRrQSzY}yLPiB{RL?(os%yCP+^6I^U(C8s?P^-MxLvM6 zqbN`Bg9{5k_vrt$aL`M)P3JpaaqS&iT6FDNWRIR*Tfe#X`MF)>;}~)K-&E?|dDw1; zkz3o=No}4+JzKw8=5((A_BS6^t7{4gl2fS0@=!MbFsB(( z*UzU0qNP8hTk6bz3w+pd9N?q1knR@~!)M~>Hey6XXh=esX{W@5kuh-*3FChN9slXf zIoWYMusJiJbHu45JN=IX@xOw+CtAaO$A|AeasP{k$Y({?ruY3vKJ_TObaFuN^=pzQ zMRnbO#bv>+Jd=+f!xWYJ#q+*+ELVRVmU+>t-?YP%)$iL_uIprJuqU@iw71VFJ+)hx zi;tz7jLEvNdrFU8Th+_X&%8EDKdW8lMT?HlAAWUM98h8$>(RlrCcc4vEqSrcj%QJ z4bL1sGs{AA;1spz-?XYybR2zj@(=k9dY4~*XLcR0vDebNm1yrf^?1XJ*^$;NgNL5l z)n?H0=1qdT-ZyG!660KCXrB_9-g9;2h|qDFCETU#rUZGseih&5v{f~}D|0=ujORj; z65jn1hYWudXeg1@HPICLixPRo-$m4ktHVmxm$7Q5bM=47z6vMhziJ^|Np=6Cd5?1k z4;y*eE$g>Ir5BePocO;*IG%5LuH~8aI|?~j+u8rWLb!jZJAq66opst&mYU6_F5pt< z{UDBwluJ$J+^8vuxRLxHoAT}9Z96eoO$NH!}=Skdn-&thl(9zwmz6LK6~r~ z$<^1-$}aRWX&jP!EU>xJt2yh2_30X}e!ruuN#+MGP5+FGdw!|b&StG2tXUN?$1L-F z!s17(x^?-}f76lrT*Q>G#pb3_F(FrOtCd`j6In$}Pjc&XBj3gAtaC_|+D(7MqP5pT zjvc-?bz}WIqn2e~o7}3;w)F0O@|KQ1v!`iyBdv(_r4>h$FMG#sDA;@;u3M=7r~GTV z`O}}T)Am>vTCgJ`UG=o*^a%zXAD=XCRxe2Cqgzns%9S0tt_pK&TM zAnn-UoAu_+S!Vrg*NaOnuk0QY@hPv7OuV*5c>JZFy$f!s^j|dL{S&{QZ^x?iPCHdr z=k?sG&Kj5NCEah{f1H_x)1h$W5(#r=g|>evp;@Aw-S`5L#)US=}FUbm%-{s!WDTjj zuChtoI_IHOysq4dbI56!q9f{ABWQ8F$T1gZgeYNElN2K+(eV)JpJe(c4J1>DMad32 zs_MR99Bmc`CFN%{b+1Zlm$rHAsX}Ly(bG=O9)HRx`?M@=qsAbeOFmztM>t>Ze0KTl zsGDx4FO1%=i+I!^=5@%8-RfWJM#Qy9d0I8mZT@(pV*h4A5o?2n<`s@Np72C7czdUz zE|ShyUfnv>e|D$tpZlk`+;`9XOz#Q(4*d~lAX@oHTh%|0bO?7imfLCE{0%xqz1w^# zw-r^+-(x-2D({$vZ_H;)n~QG!!*lXNUY>irw9C?*_Bt6G7Oft;(Q?J|m61=IO<9p| zv2w!sQ)!n+e{TFJE}_?N{a(i$$u8Aw)q9cMWLppMkkqB+$2WX?on9Q|UOwPxf$#V< zZ=*lb9xN)&@7R2mLf?>6= z+h@HDE~_h14Q=xxcfmRXT|18VL`5Wo4VPPREs))~@5qqRVezJY{rye5`uA$r z*26(=?aNKT>Fkhe!a z3vRb~rp<*@sh#c~Y{S9`;+a1irj!0Hg!n!(sjl%adtA%umdiQ2wsIFcX9o^Wx7^O@ z`|1D4dEyk3zZg%Pi)fJbk8J$z5C5e)oo6AG5(>ZKM2ii6oyjwVAsHf~q4DfmXgm8tW;1-#93yd84>V#dbL1?{+Vn+`-f zm-#(Po>2YR-%%}Pno5_+cc19G_#7&^cs6-Xukbx{$J}i4;gi9xFQOA(o7BEM)h%n+ z{DkW3sx4Y`^39w-4avyfHFxKb!+OqZGTf54-)LlX*?+|QrPaT+3Fx2t@>Zah_mZb4 zMm%gE^n3rE$!QynZ@#Sec!6vH$9F9oIBTD~Y3|m&_lLcD)xu`pWl33Z%bq(1m6bNss(;{a znZ~S3mtHkG+-~2PL6y%^FJ`)q?%eL`U$;xzOgI}epuJiH)x@%zrSjxg964Fn_{}F8|4dx0Sqgjd?y=$vhv* z4dDhWndiR$vMsXE|Myz^IVACmfRFmGY-V@uz#D%JP`4=hW{_Ka!)fcX@VwGP4>RX% zdE9uxlL0gL7Mk4ftarUMV1jwG24%x5`bMpOJ@x%~7q2-PE!TWqTl5?E-0Mb?mUqRp z^E+Z^99~&ar9S2Sp3^xRm#u?JLzh(jWw2qu={}E|{9ajDFETf8yzAae7S|UZO*6de ze7xv|z`PDYi_d2ix!x8B zE>&xpubC2lcVGkcP_G`wa?gOq?-TcHmwD^^cbao{Z0s5j<7ng88TCdO1r2UL#HM$* z)E#l-%$z3O_Eg=r;Hq(8X4T|7uXBgze*RRa`}1;3NlM+$uZIuQJiU47;$+7O9cC*k zZl^qxrfxo~*EZpcTW6`|Z&LMS!_9eSiIs&`9q%n(b-rS**(m$URYxlt#!fyrH>lcQ zBm8N&NF^s*)?7Za?}jlQ7y5S4S$CtoxBIDqS-S_U>(_%zb9-W$b6-#I{O8^BqwS;i zeSUY|;Dn8bS9p=4sDBH4{iFKgRy*ISnE96LSzcYKFdO)rbKgBjbX@mcGmEtxS!_C~ zL&@G_&F^+HK9cD%d40zzt14Z5D^;f?y>IHYO}FDlkGEi%p9-ZuHm&?(VL|IEUAZ zV>pG%KtV5x^iDn+wL-GYdr3{eM(@9Oz{XC_tQmP*J3Bi^UPM@AcfWJ5a9;=gq4wVf z_dY%1eYXFYz-t;QQSRXZJWv>0B#xcz>kiT0aQ z1|`qYZt}a|*Mi5x%qt@6UkZBrb*}%VYtPgA_k4NtU`F2k`SW(TSoK-{BsQYnIVbDb zx$-lPtqSk&n$hC;owM_jclVuF@kyoM;;51BmwFaf*Wq z8{FIK-mo1P_T9^vS-EhNxch-aW7g{r3Lkpw!-DxH#%^CsFU3#pwA3^4!iivqn>y*& zzN|5woc#IvimtZ{;%skB4o=yBV?(mjb0?o_%jk7FeI6fHk*>ZRpWo9tA*+dlqW&xG zKlgMqs9O7Y$)t`xH?|Hr_NJsP-mOcoqxLR7b0r5mkF?Q#I<1fAy~~A>27w{s_;ca8 zLrPmEN$%Mj#Fef!eRb@Uj%I0SOx?3i!)Fi5$)DORD40u_pOsa%&-Y`U^pH1Bs*VQJ zy4|Q~eEG|ct4&UhsvhjuCrIap`TniGDicPZY+xMsGUKD)i8TGpBd&TkjGRxa>lt($ zx3O!%JChkh%IB?U6c=%7%fvl?QgcS7fNdIms)NxOE#7DHgkt^5-_2?^-nt7t31=y1OE-$LIAi zPX{+F=+LX%@NwH!GZ(qZ?RUGB9vkD-v3&UGNq;1|L>;IvuRJ#IuhExRWf_>gcrqfq zoqx}pi}x3pj4N}pS-xJ;UnJe8awz?A#GU;bFDBlOj5*Z0D7vhpSEBg3!L2Ao-FH#V z`&N$gPizse@8-%kbKOq88F@RbIf=ejFq8>T;cRVOm_SgK)XL%+mXPRERohs4`hjhYzU(Mn@n!zc51 ztRHmSdv!wHgU{!u6uUj_-eE-h-Up_CvNO8A@a??n`Qqw}rxLzY`^3d)Z%pJ)I&7Oz z=KEE1z)g48(|x0U4^8m0+TN?1_0fj1F^7kLY;h<)>fBt7!kyv(@4myY$Cpkq8RQ|J z8)!IxRK1mXtt!F|TeaBVw#U-wznb+O71^~xxAm|0Y&r8deMs-$i~sOR+8xkWV|#?y zBYD!QkkRp9H4i-RG{P<7OVQCz-pR?Chl)P5uiT?|c-O@aYvg^V9Xu>Ks>q$FZ>*P| z5&QMg?mt=?rPrC~RKDNmPaHn9a16J+Q*ZCXIZLkHHGBH1WoI|@tcM8+gCZ}l z=unlMBe$P?u;|Ie!);?dF8}uW%*J-J+Nlqlay>TGzh}k^MbsJQpW@B`^6utgn>>RYgT4Q4u}7vnr7@GpPR~X5!@N?C2bb3w#0j_Y2%u z&P@o=$i#A6Uj8+|SHKn?H2DvSL~V|Q$2hf`-woO#!`(mMu?$X1#U(7n_#@QEB0iEF zO-`K^ld7#UT}8je({q06vNSbSkjHwG^|Q*wQJv%#aUz4 z-o5EIY>rDom92KbKwXFM^Ce~e2eJ-)svf(yPpR%7U)Mg!F!*dRct)YaqjZgkb#6-s z_meAR^*Du0qb3HM`JbK9e+=^|M_?7=?wpZw2)16WINaZ&tT+3`)qlLS`>1J+wjP*~ zlj{_aw@b5?yviz5r)Y!K#||M}>L;ZNB)0O@r(EhIF13iTv32l~RnvFm4=MI`U&Ey?{%;@(za|_%_|Nk{y=wim|Cp}kIc^%wH?HWK zl%Moh`10X(8d%hA6bE{1J$~BG(~^}NwYr&Z>pV4aX=}f6udl4k9l3FJ^WvxV z3KkBC*mcg*cfEPt2G%nUt?203O*6YHQDtvjv7}p}-Ln-dBwmpdwztZ@Ii$kAZ_bt_ zSJn1gWTiR!TLy%_iu;hG>Qvo6y-ldD*PrWsU3a()U3PBF3>~{WKJyxX309H+u6@(@ z-PBP|m6tYl-_a>}rh{^!O)>OKb4%+hEp$41HeIiGciW?J+x>TZs%*LBk-PNW z35!c1*)#g}s|;UWbbdu-&&@0I`ec2rdTx+4{8^h)4SBtRolUzBJ|>RnWsuo3Q*GTs zvst35Cl=D_2VCB6DGG1@{=(y=1j~8%#SwS39!P?(J;;xatd1Yr(cwd&!xxc9Qn!U$cPe?Rh zZ=JNVVCk^(HhNnOa^s@wb|2;0Ub5rVwZZ$eEoa&os-7G>r(DPNZi3dmY)_Hse*pjh z|NjF3P)h>@6aWSQ2mk;8Appq06aEUp0s!>s1^^ZS004Jya%3-NZ*FvRFJ^CUbTuw- zWOQ_9%$;k@ZpU@k=W*g(ZJ&$J{rbM|dC%47*p3~?PU0j^oBKVaQ6LE<&RwF8-PD96 zY9T^GAdyN?DpXVy0j2Pv1wmAyilRh^anoa^s;|L(W`_c#CXSKjg7_kHcF=l=Uhh```Zf(;t8GyRZDsA8XG)^>c0W^>;k= zf9kVOKK038c>3@Dr*`LW|A*FI z^4Fhv`X`@#?z`=-j?Yg~{+Z8x@|ioo`R;$&@%?Cj|Jvt!``P?_#{-*W(=X$>BH~&5F1Ef6tv+Z|#t<-p+UF{D~wy#;FeR=bj z+nw!2?aTe@)LeeQ+-tnPQ)u&o7r$V`zc05tzVX;^{M7&KdB>>u!9VuKA8k*xo#(zS z4vqWy3kE;Ye!2hMX&t3@W~2y{`@w!+>NhRY+8xV%f4^VR@0Toj{9f$$3;TU&NqcF( z&kWqY-0oS@_$B?oQiprL-xroN>eCK&Ufl0Zzn{0HKfm8E==Yt=FX_koefN?^8On3h zR?@g>Cv~_f6IhgeS-%f2ZJ%xS{m4>}GT_Y(j**Mw^fH`_)YxUfjpwM8DtN?|1ckuisbt{qcT3*zY$j_uHrY6U&|V{i)n``44WT zy?#p1`z`m>|l=SP?JU$&&v9=L-C{W!hA1fTns^7QxO^8U(=_<}#}@J|28 zBb{IFyOx`@GfO#eIKH$W+`yf&r!Vi@hy!^UGt%h`bOzth;T=nP-pK<``tgRP4zvVk z>Hrh^Fpl6&8OHUa%kPW&y}Oa;ex=_R`u+Khyhwd$a(LtTLZ?snduOS0Y9p=qJ+zTl zj6dzbKS%m~x!*`mZqn{u${p+XOE&T{^a0-4em}glNgg-lUfu7b8*%3MBMV&m2#@j0 ze1X=a!#j*4@4$o};QXhTeEP^c?Q%mO#sL`IPjBpN>T?G^&-?--G)X$R@XYUxjpI$9 zq4EFiPJjPj-O!3Lho6`)j5W{nm7BT582=zQGHNU#8+qfIAK;zeJkR-axgREf``#}9 z|KklGeQEjSp7Va#^dIE@;?^_keC{8F2fyFVoa6W3Z{%%m<}q^N{6?N<&T{WeH*;XV z?^$l!f;;m98S&so zoPTK}9)D>guYYwZ_Y2GKpV>Iyfrm`{dmCjtG^XC4+lUuqhFk_NeWITSH_8F((I0Sp z)ke92e5DNY6S+?RNoP$-d)$HEozGC1-sH+AS2`STb@UUJj7+_Vo)z=U?ld7hy+dEmoZ#xuX~+1NkmN_&AF z{8{rDTj=qN8`nwt&$y;N#+`K5qm+m57;|9IC;CiZX(Q_g<3!u(|A8ePxZ!>3!iT`k zddpao508Tjd;reWg;uPcAKr*pXhiudo0>n2H|yO1ML@d06B~5~`b|5mui(X4a04?p zZKF%jXZitd=o$2fe((z&!dvhgZ9!-7hi0TRKJ*tFWnH5TJVD=h&iFC@&@gKrI6)s^ zz*pf7bU)T&(wPIv0~YPjH~J6W^c{Ynuha!L<#`V6$)`Q)eP$yKsrSRIjkJ$G0*n`K z?AzT-8aUpyv46ZH_mB^Nr{90O-+#Z~zufPC*zccT%5#6V>&(yZzxTGiZb$96qunAp z(XEEvZV{a9Z>M_N*>0nqZKr$w>2A-Q>}5}Po8Uk%NB+T{cC6b}K=@qyLVMA3f6y%l zt{0E%McuyOFZX?2N9R7-A@J{1e`>wfhx+~5et)>%pJ^X&pJ*TJY0tE`x3{!+b-VD* z?XB$t-O_wdzizZQw8z^=I-DQrDSPen?XPsp?vb|BZM5Bv?K|6h+xz?9x3$;zzwhjI z-`-*D_uR9i#D(7e)%Iw&4j&!Y_5Svu_VR8?KGsX@kF>}7{n~zC?bq2};zo!6GwqeV z?gu))|DeNrV{he6?cM$TZ5@wKbd28Gp6s-HqQAejr+;`{PjvjRcYNQ}@%{_#i@lB0 z-C};Io$FA()N%Pzr`*%+Z?<3RH1s;LTu1wLZloOP*Vp><+4id)!Z+GKX}{iK-P5l= zvj*)y)&AQUiBtWV@nI~Ebr}9V-rGFY=>!}u#_w=1b7qt~-t!LkQj7?rajvJG=qaD; zy*k``e5hl4xaXbixexZf?DP^J>(n^hVV@e%Pxj|)?R+1DLmi9V(dxbZ_i!(Hq~E6o zj!Y3q2ed8nsc?rEf)87&?exb5`o;K1`(FAt3l zbqo*pT8H}c2fM9ItF#4#W20SKgt|vNjN^m4M+VIfjsAln@8kjP-a-5GKh}Tm8_(1_ z+^?gf_Q3&{Qq{%f?vatp6BlK?4lOy19FV~P_&`csFt7*01dqVvi7}*} zu!0x(`y|F(ym5(R@MTOWb97LKmO=~a*?xLP-Ft^LvggSwO!F@OLN<*tB3+7#g{=iQ zdz_vIM{~?I$6i+3Ma|HDZYM2-9`IJxlOC3|2f`88lnWOWErC-lIzB)WPfI2BWRv^M zO&xetirG^}A{4QVI50Bds_NZ=rM=)+6!14Sml&_0+ysGDwZf|R> zfKl8Xs-}({l<`g})~!HT=SZc{q3}vC4t2>ZCRL|;ci`qTGbB0dR%(1Hz3#cC80F$y zo}5|a6>YZR)l)FN9j~UvHAc6?BX!cErFc6q(U%KuVJ$$eB2AGBNKG{aMhD5w6C8{b z1q$Vm1N0AW%govBYk}(+{U-m+fbVLilx5{pdY>55o>lF^E<5>qf6u)>>OD9xxzy8N z-q*5+`}1Cx;-`nqJJIDk<4ik9_45Nm;DQITKe7g11{$TEA>g1?bbR1*Y0IxXOS|2?J^p%s*Ml)#UalvEyN6^ndu1w}_- z)n<)xWL$-ipteGPSiR2;$|=wJ3ts35l;qu3c9xXPx6DF%BBiU3`~@as1T?gov|pHj z72~QjrEN;1@xiZWJ9S-OctcB3lSHdwM$$VqL;CRAQFpheJ<@sTQs*DE;!9nJ+wUC3 z`+mR9^|Z_5+Uw)F*IT&Oe=iSiyVidXFS(aSjcffc^y6kl0h`Qca=_U%a?cK}$yMN! z1LvLgnlXxx^y`5kGcLA2+I8lmy#?mR+5XPh($Dh?55iTzhubo?@N*=$xvq3_K&^c| zfo1dqD72Q+e5%i1_}@9g%%hLT7oJo~!0%8Syr9B~F^iZjaN31-_ZSn} zhhI*&*NstDXJ>Yv8GUDzulDg^uAJ$%2(sy7ud&-Na?z1b4P6>7c(>PtPfvGRp6%2G z6W~2G=zgNTs+Yu?JJT=qTqFT$dqXnAJtv1Qjt+gc=Rx&j{r&Xl6SMJDf1_n`k-TSm znUh^I!NJ%Sj5Yj79eC};kOgSbI|H*59e=DKWU=j?X=vJ5Av{4tpkvWc0-IEfsR3r- z3ICsK=>4>h{lW@yZj@4IXTFIa76iFs4;?541Z*2-3eRUo&%r`?jLFV`1MR3syR^?( z@_TjQz}PY_K(l>p=F?;JC{HTs-~n`QFoFW4Qt$f6gM!HNv}n(1%l<+wO7kWrSU_+y zbfbshgOw*-%3{+W9}w)xiN<;-{(FNrpa3Z?047{O5+_zoXh*5DgNvvG zchYNncCgC_>_$cdo}ixAplwh>{OOI-lB;!pW4P9%ZD0 zkQoJ9IXdWlcJSiPz)tR?-HbOH!A`%gj6TpFC8V>Q>NB(MzGc2S`pkUz=J>$hv4X11 zEP6`a^9?=4TaAUkVSbv{@^E@Xt{7-(y6rBx_lY5pD=Jz(S* zA9z{`;n=36Inc>^(9pRilvBgXr;WXV9kXBi*mB?kRB0|3fgxotFPPB=oPsxIf3yc= zY-_QnC(sya0ZiziRAP34n=LTUfd_Sjf}C=M#DG*4A?IlkewfDu7?Ic z%YkSGN)V_A&!8V5|D-o~P#-FQCpgeL@`(OR4ORejSNkWwo#`#Jir(L)^8;OOJlJL7 zef=FD$c_GVb-Z2edDnZHYyG;fKM`qhr9V9|R@_Uy*4WxV_fKn zSH`N%#dEIv`%V4p9h0j=FVJ(LX5-q7zQx+1*0F8%sY$VhqLpc%)QYL?$NJ`asAfhh z(PE$lwbVe>B%xNcKJ}^CcMY=)2+%MRdeaKh|a1D;pwOv{Yae z)FRLe^n^hLdxn*9pkeVKW28jE_A*_42{{z4xh}P;*vx?}&7_|GhJv^|@Y7Rf9Vn#k zk32KYzA^fiV%|sJk}9#fnWbyJEI0sJdqN&z+_2D*Afy3X?vlTXPr2au((d$NaiMoY zDYZ)R)uPWB6^*oWY(YsID$qmUpj6p9*a&K-`l)PH+deh{DXhvViPuvrORWg0u684C z7Y3a8`qaeXLdF0%(CO%qLi!T#9jgoYNF6A5e(1LG2{QK?c{nV;VC|vAS-LAw-29)j8T)@BkW^dN*SSOU&hJE1XI24YiST|+k39^^= z_jd3cK_$?7EY?@#h$WP&v#h$c)~&s}^>S4ZhmyK$SZn$3#mqT2{<0#S>{`zCrpFFx zj&xCCV1Y8i@oXU3qz4>BJ4#+=H|fax_}f_du*1>Aj&zL@$%m%6Gh_#{#ho7XF*I;w zHM%c)A5f2WU6wK@yB@^u6%eq7!2zEjs}2$vFN>4_vN{2>RC%b5lT>$~j(1oGJ5*=1 z{ClaX|AE^*4ZRsTho))o?VlP5@(-%vNy8!lGE`#EiB+fkFOv}n?Dfg1A)kE!( z#@^4BF%C)uAmaaET|+*e8b04g7klYs*NCwI@khWxS|Ug&SD6z-^QV=|?d5~A?m)`r zXzkGYo}LSBxUmMIC;BD%?&i5$2p?VUyp0!@@zk40UmhO%|M8y33g~{UwmPM>vnl01 zC9=f5P%KzhL~_A}@zZleZ%z)M4jij}0cW1<{eXhZEvRvNc#E*3Xos1>?k#QO893gb zp>ptmy0otKugwBgz?$o!QB&N&l#8CoJ$MlG{oy~x2Z6pIUqNlXW6S~U6n&ELh#qI{ zSIXiccK1NvfOb5XX#1zXm&V*;7a07(6AR4t>;EUOJnrlZ}Czmf{MpG zXVD_u>W&3)g-0ED3989Q?zl78)Ew=r9#$j-eV~MVfF`8&vpe|b{1_#9lsR^B;W~P% z2m681ql07T;_A{H_a7eatD9SMgGj|Uz9$fK^ zPY)O`>+=A*8N9RXV9-94g6Ue_>2+L?#PpZ45)&~J6L z^YyXbDqJRC#6qD(W;s$meT?>=5w-u=;7Ga9Lv10tvG#`k{i2rEv8A+N9Gk>DI8L=T z1+p9<2Bkj*o?I5&RjW-aLFySe@u%RQSN_nBv`~w1HzwoC8(v_>nz>I|WQG{wMX*l%A^LuOBkfsVs7 zdE$;7ro6kEKw{PAU3%-SIWqJXAw#B<%CZDkWU8%G2ENoCI3Q|E>|&9q|An90~GkAS1wB0W3_E|osTYBIM|{2Ix5Av+V_mD zHZPRs4X?CXfNQDTq|R#85v#vkuGbbT0J}l|i>oW?tSahiNG2s1PteFHJ2^ypx{5J^ zdYF~7)&^5{GQnG2N347YGkUHc8SjiM2e7lJ&f2f$!CYVlSzB%tFUE=<%R|OG$mL2v zEe!V@CjE*WqDn*NSUPk=roXhWxp z-|}f~Gf_KU+@$Gg2xgXnU!+v@4c0Pecxfs{mE)tw^yQu%=#-AG6d17==WJ2^E(4<@;OrLW+v9vmqm+#Bj7+pQCk(UAWh)xU^E@ z>yB1czHhmOH~j<^oyWL2+KyGIs{Fg>=15aoIc;ms3Taw@UEh#*Qk61LtaSEVYF@_C z#QP=P#GLV0SN6F$Rq2&^5RJd6Q=^mF7&{}mwxm}ednv}n8)2N;5$eac8Ji$|ouO^> z1HUO2KBkVhLH!gPP@ou7Vs12QNS4JCPk*F6iVHI+B5`Ew39XxyV z*cb!755#4leNyVupcg$5XQM^*s2R6FEn3kY@mJvMEq=kx;F)l+dt2Ok#@uUY0T?@_2cpR(^;tY^i_~@WCJ{Nr+YO;wA zdU<=O-58~e3b7|hvm5QvhBdBNS1XN~hy*BaeI%J&i)WgamH6r(&i>R;Dr6U)84;gI z=B08ZQb&FwH!Dk(u6>QnDK*ZdxEjRw9E-j58Tm|^uwMCU^{N!}oi{T^Ggi?FA{SC` zUiH8#yao+%eWGn);^I7-Y3q(^K-&`rV`5$nfp`DO(Lj zK!|)+qp}C)S4v^N-tEja&l7FmdGv&tCuf=~U)2)#tP1NGecsli1dR2##2Pu&bzO3@ zQ<_m0lC_dig*aYQpUVH zuJplGJsho}DwK8FuW<#1JI4 zcxc9A)(qBga=E8>YhL)&S#@{QvJ4I7cd3*2Ts7w?gIt*BL1tAn_Tklj^(O_QfZ6rhRGD zO2xu`)&#eEZq0C3=1Q*H?V6WXA0L6wX=Tl^wmy%*8a`#_c%)k|dCJ&aU|eWKZ;__I z+8s}+L5#SNSkp`5)4F?W*}Hc|PrK-C)Pet^WjCq)&!u_F>XLr^VRPJa=(1WuQgw!aUPycSu2N5~zUAmq>{Fwk-WZyp zl+t5QZob`tP6zt@Hde1>UuLGO!@BqQQ|NVkaqx!{;Y54I`05y1ve$!`)ji7W6}bze ze_Y?N)Wtf|V{D8(+-kI&9#gRMuioH%W#JwXyQbNh((9{NQ%`*S?%*pn&?A0W_Xzc_ zi)s9IW?q2bd{BPN8b-`bdw?0o?#-goz`^yRwq zAFsGkvp|MIS|;G*m(Rd+7Zt3qIb8jqjI@*fdVb-E?BwxR`h&YT*js+3v7Da$2Pm$W zkUl95oxP5PIeI4io`^L3wSI$SJfWlHw;uEYTDcEm?d*`dGvO#w*ReGQQQy2$K(6px zUrNa5!P0i!u`<`F2l-JvrD)=RvUkEIU&xC>3dcXD9xG!V8i|wXXdBu(5{SjsZl zf0XZ~g0m*lQLdKn^5vU*9YzlUC-cwU4y2oV7E(f95C<%P1E)Bl6P~*u4N_ODqp; z_X6by=O?l!&?#llq_jIP`q&aH5{l{ZiJfRXl(WO{)lD(}z=$jJ-4&9%GB+H7jDL-K zH0j_Q$>1(ps4vdZP@EsZ)0M$}UBA92UF(<9uC>5|7vWZWY?xTr)F6!A#57s9yP6pPW>a9`KPhjJt6yv>*1Ed^H*8cIK+HI=(sQ zq~kEpM!EjL_-llWqY%nRUr>K=&zJUHiTsAay6zK(x*4Igtv>9It2xRa{4|j=juWM{ zMY0c#6CT$C5lCF&Aq;3NBh<30*0yG!5fgU~`9?Juz=FJ)#5 z_-*y!cBMHgM)MiVb#cK^E#L25{NAXpQ_~j?-|r$eGm=ApSZEi0E2CYjaQHE;$&G4H zNbICKw_=qVZ&TyKJNA-MBBaToGroSouPv%>|59GgQY}UBwow{hgcG74Auy=~MVxcRq0;IIa7eo~_3_Z<(b~Iqj5x zv+!BNj<;$%b^DrUp&mfSF0sMRQ*jF=$}^O?U2`OqgmbJ@+PQ$sDo6#6<~B@1onr{Wxv8Kw|-ZUc3Ozn#KftvR=HE!Xz0L&jx>$9lr^v75YEGi`=$ zPEmMo)n-xHHG#JEB2i1p?hdTln;PP7MWvmx)LC@0y=?49?zBhRL+#=5_s00H!mp$EN1Yp^MfPEL+SP%*acEquvv`{x zSY+ne4v|>(b0an#UQ$;gm0ErUj|4L&PX7wNr7O*E_3s_==fKZN55yvR5*^s*yAJ1BUnC`B=Y|6!%~P|uA^INa>O9-?%S(9MdE0U& zQA6Rzc?BqJsziENxKznoSE-T*`IXDfopA}ol+M$3JB3BCnXDr)C?DYZgeGvHL<#+%Gs(<;^iNCbq=-HHokBHS8NXW9#5+=5ANa< zDWTuR^ADI`TGm8=_;m%nGP=MRWzQloS^|3L>jO(m8x!G465^X!3ZoD_p(1)Av82R? zFdseX0*T_+Zl2t6VL(Ar_|=Ht#K}ngs*KS?*3fU!$CUv_Vx4Eu6 zB-tBH9d)%p2@JKQHMG*ww$n?BtoH%e?XR^wa!4colbOy*25YFbPS3J5`(45>n>yIr z>Vfvs@l3qI+IG)3`?OtleQ*s`W{n`&u778Jvviw+p(V}uaGMwCDFtryyC_v7DNVAg zW&f#(eNTSYCAEaWXE%grW6<<6S}r^E{H;CQ{OT(c2QhNZDJ#Z^ph(A1bS>q%ii6bN z^f=@c*j6~fr?OszSbtNuqU7f} zFw@0SC2Q1YGpA^!JScSr0awK~m#4@Yp#-B*{mA^7d40wPI(rfjt08nZ8qbxBZ%IAZ z7%9&e8~Y>Qlb-k@(jIu$q2rI_&&hkNmwIW?iIMHJDTHX}thC5@B(rC05cR0$$k}V2 z+2L1IM#ZV?MIUf|&)87Hy*|@e4b>LZBS_PurEM8|OP{p+BYr2tO5=CAtSqiDtd?pc zYN@O~uKa$JMsNJq2G~TOs(HA#fkjHsJO|FxT%b8`o~D)^8o$_d<<5DZdWSp(+Am9` zQ@qN4Gs792;z+9ditg&NOYN6pdbK^zg%JUJ^bF6q;wfM1gDLgk5b!j@)V)u2q3jsd zP9pRLN9e$tHEE5ONh8`45`$&+745ncdsg?t#xkefN9Z$@QybIM`F;fJ(jH zn5+0dJ4DPP zqlD!hHEm^&Gcxiub0e!xJUS(d%DT%cm9-*!b)eqJe?-~E>cgBvNyR-cIjdtx>GdgJE&hMv%Z8Of>SZ`0B)T5A@Ya5<+V9bEgMxH$K%PPtA#X`&yAs>N6;lRG3SNAVMapG598>qsa&$1jA#Bzft;zUl|u`j#OODL z`mBBPj74WUQi&dT8Z$U(g*k6??nrj_SVxSNP!?GtU!E$R5>KI5JUiyx+Gv@5v}EiH zSje}o7}Cs`1n0T_r$j&mD}D5GtGZ@RS~MELbwuv)BqqOabcBJ4b>(iCvR?`-A+sx) zC<9{v+}+iF)@!C6?rwsS2gXgfCfmN=HNAA=>{=$4IorhkdXANCNqfJ$6?5Z2j9y5) zV2~9uUQ}m?aych%IEIP-sb07zVrf?}d+oTni|hQB_vGU27>8!Y9x!f7r}B&XwL#=* zGTn2d0RslDs9;K$ZPB<0 z=zhILH_+pr8FR2bHt7GF1^-&XjYf}sTE0uTkDPT}Gqo-am{)J&`*3@7FC1OHe-E;4%BeGp}Pw7X779 zQD>$KDLD~COb+z9tz_o=1?d3IfqYm0eM!^0!yCi54@U;Y$c6um#t%Qn3rpGf;MG9L zMZ1ZY%2~zQNuN)8yZ88cmnVz-qju)9!BNe@%H+mjVG#7`Je`a^f}OoNx9MZ3XM~k zmxKRdN8KGUttIvC zoM95%9ggrDNuxW#LCsoEG`?_q!VJOo%Sl1Tu5(tqx`LXqQDXGLoj6a6V#I{2U6Dv> zBLK;jvikMtxhrq{42dpd2UXtzX~6?a*Vq{K`@~O)oaeoE+}@^q{C zGPM2S0jRC+#!2k>kBrq$8+-|C%#b|FI*AV3+th_;pWp&)X=Z==~s22cOz1T8-(jvV#O6}=bQxXu}1aXy=8 z=XvrOSkgmJ^N$|xnKvifg;7Gi$?t)oiW~%ufU-OCJpV@h_a^$Oun*) z!mf>e*P$0cnJZ8F{kU;XLV%h^2PmQaABY@V_&fdxpvtpK4OdRjDuXYHwx~0d?0LTQ z&7#!3e^c5<(X+ivf&6((81LtWwxN~4n{7T39;G6-vIya*u->K!&gU~@=pnT~G z3CwG)@x-4q()xz!DLspKmZX5624q#h*7Yq{%lV%``>lGBv5f%AXRcw#AD zt9zW!fB2ui!Se;gZQR2a|KPt()#_@wgunha0aRCr8|YxRbvf}O7FCHvEKZG zO#gNuBHc{$T`rryZYOn)e44+PTSr8}wQ7fA!{0>oVbzA}>sYN^rmgMRqMF`?wa!@O z<(`>dYVhS1@ycGdz80_TZ!cr<3)}NF9E;Fo=09?b`O*`j#ir-jwJF%AFSmZ%nLZ}A z&A0Q5T{K_e;=0t16D#2VZIE-OjTA_4&NuC!wOy9e^@~RtG3aSJa)c-{WqYt?ehG=W zADfh1eR`JjG`;Y-=R`5~dbqZG;W%u^0%B#UbE|vg)cip!@@Npo&YKug&|~ zr%*JeKe2VHjm)aZ1*MamSN{tir*S1}B6_tj^n5wv=I_F_r~-YS!~CtdvW)&b3-#f} zTC#lhGAb%Mm+$aM^i^yh|)iz<^(DKhK|0Gtpxne;BhN__?#E{0bk}YEz@&m$~5u${|0+%QJJB z!OnU2SIa|!oX8$QtsONx6&NJJHlP`Fdl{C6@*!gT%!C9^H@3n{JVU)w<}YK=5LDK?C&6E~}M`XM~A zvPaeyM`Oh*y)MRP9gC+Gtkr@SR==}ag8!2O+L!bZ>wIOL8K8wlUy-O_$Bv1b8uHuk zkh4x{&t`^*KWUx;k#z2mOP>B+|qu zC3R#V{*BBSG&cPqP=b_8{V5#p&a&s}sab)2<|5_7p?=S_JJLCsPP+{%*w3stoWZMH z7SEa+Ml2cYf!&JdUTsRtnA;snZFW5i`s;v$*O8K}J?F-VWH%MB2bA3#&#^iXvX_ncFuYb@4O_sxBHo}5+Nb-bKWHE!{{ zg|5Cn%@2@S+g^4N+7!Og}cI6mgCm4q`te{*317EBX8?HRld~flr`A)d2?mb zVnNQjj}t7?YDKw-m0m7#{9jurpO_2hQeq{P!tB_ma!j|#72H(Rn(JdX>qh2 z^@T+0=nsrPKzP83ZlIRoJj{A2O&v#Ny);s~%iZ#9=wZwc&&Glpp&}o;JEwl&nGfM6 zH3-Kvsm7q(JN~C1{HYHt9yxJxH!X9cc-Q?kN1pv9_xsfQpxEyC8+*!H_Ov7(adSLh z8mZ_*kz(vsOAq?7vy5rk{FXQ0RW5n)Re0mOH)7EqY+K-#%6!XRJQNMkRlDS0WrU|0I+y{=YLQVL`&sA>C=x3%77?J+fR zFzflO#+7&5aL2*Elou&u6Uml*;K|#8FIAN>^A`ZhSR*1U`b`)$&hr{%?)LgNuiMJ4 zqI6nG)J$Nlul1V0E@i*gE7`hq^yJEkuCgwf{`oe415glX&o4)6wJC^Y!)>qEa;Nfa zowL5?di7n4-0CRJ`DsrbKibMnEp^tpR&uEk+hC?cMt-e*)z?BZtjr|tnMTFcSuaeh z-0)bUlU#%Wv267Mw*xhjSz^=FTP6cnCP+L^ume;uyJE?<_cFQCsp(W2l?->giQ zO`o@G%q=EW$x%8$XQ_K5Z3U zEgcRjxZbWyNvYCh*Qu{M=K8vYV(qjTShWI{W|37(xWQSyudpc^SN~n-W_r|ElosV^ ziL+6trKu~cYF)LoPq~6qbo+`WRw~;c>+ye@^Z%Lebj{^@zb}vf6P^EY@M^ob{Ez8Z z#?yY=>%V*b>0;ym``qg{C+}Yx>0IVtZ2WKcmqxk$C5PY3Bj)7t$m6~|Zd$YS{>U?T zf28n4d*oahf4Qu?H!vfO5|>8}{>OIy*Y~Ua`RaHghcwy&Q~Gpm^pAd53zvGz#qmGA z(^4?ziFe?UMs42qdpV+(puw4b6A47D#eTamVoi1jRK8`~Z=4Xp^IqdiZoZA9^j?3y z(Esj^I3@mH0;5!@15Dyki1Z-R3Has^qe8S1{w-kg-8i{By*+ZMLkpM2-wO+U_)0(D zhSMq-(;FhEXo)Bqs7qa<>A-$>vbn@R0*ry1X51d8z{iJL<+_lpfYs(xpVf6TAl zh_dl?48JR9bnsz%N~{0VgE38~23_#mVKHElTx@vrXq!@a-I$Mz9 z&wj&02gmrob&3U27Mgp4sg@>EA6oe(7?jF)Xwh_|?^4^S#Qc9mt~656a#~~Y0I0u5 zYUPS=drry!fg5k*ti>yNl_hCWn<%BVcs(Ul`K1=GrJNN{81u@IHsTY|O3ezYZ@^Ql zB4Z=ly|68{b{z-C*z+Car+Hi-XTE_o_?8u12nxkFj zwbna(Fr>Mzy9Val`&nbi&Qea1<}bZaD~nwp__kFM?xhJkg2aHGU*KfjiG?U$p?+*J z-t)hx*IJsn$z{Y}Rv3K^Wtr7gROiY*`5(3m$u;Y=)qXH$Z^V||xv+*ljJ0M{I+JUx zdfv9q?j_aa{=Db9wMNxU`K0AL{#&u2WHdSI1nNwS!Z#^mek*0Gobz&s7|WD$p%l+$ znKKUVbxFNw)M*cy<$7Fa5ZLp1=BPK8tjq3zr(-#y=4F&GEPNA6YKe%0E0-G+zd~g82<&zBbTym7iQ+c{W}^IC^+PExnzdm5@}1`S>IVf>? z$l#yyk@Fg<|39sn-|S28Z05wr{%Xrpxaj}p^|j2y`SKKFRBPe$wxJ%Ct^vz{ITR)z0WCBM4IB zb&}e->zWioD(s9#g!4R@OJHjm>5@E>P~-RBIHKi|JRO%rJWsU(YH4UW?qNP?oY$)~ zU{3DnM2mo-#B{WTJ{>@l?N;9u&O6DGE1a=*0TIzsy+g4__aCFjSPw#78ZFb6f}4Gx zEDbi)qQS2^==f5Q+P502k=h&C$vPottDdeqH&D`uUY(r8Djfp~O*YRP7zLVRZ1TzG?Pak=C{NJwAh*kmpf*n7q*~&N-X-TFq*EH?KVhu1b~+}+IF7Pn09Va& zROru8{e!h-C1FJ_=>T6xN7|O?Kk%%Aix-g?nvuK3*d`aLyijBG(mg{~Yx_e#=C^&clS2SQ^WL(Z?98WjyJ4jbCdN7tYRW zLJgbm9GfUhj-Ti;R?imCj!oPb$h}tFy|rXuHxpV2?z5D5^WbxW=dLRd>Lsv4%NQ$> zyQnlqCL=gD_;a@>7)8%5 ze7oowylm^nPQjeDq&3`C$D|&KV@J84K!kPzBi=$g2RzX(c!ZmYhZP=9{KMC}bM%XU zq=n=R7*8)PxCnj0$Or!^v;drOog`v#ASeB_&~iIT&*3xXH&F2|MiGf;@Uo(RI4()T zPEA-CNb0Yp_ZfLnC*NTX=pb9hT7>-c@WOWsO#}X5^l(nvMjtZz0G}<|x;^pc!m|xI z%?P0A5nF_DV%_iwV}vyUqHG=d&is=}^bw3ZJ^(HQ)UUt*yZ!k9_AW&)Ux{2hZ|KY_-^_gSLy4DM=# zf0z4mzzezwUm;bIvXsYQ9-t}Du}cVCS@P!sQ}ubDAb)P_`TQFUF_YVbtAk;k|r z>2totIw0fKqpVMH#rvDvz{J>syr*Ag=Gc_pVrGhm4TCqOOKUdN$FiO$5)N~O>75;02o6f`t`w zh!J9>4McOs>=^n^p2`F&gYGpW(BXHbsoyfXC6Cv8(v#&L$7oIDW0ZwhjSg->$$=3eOgM`8wm2cLk?a6vi~_&D&HjM* zFxnhAJFGFZ20keAD{9iDHzZPy%1y4qd}5;Bs&;bOI47x#(Ck?7f?21MF6t!0rryq=obzd{eyO2iWA#LQiCxu zP!hP$wxhM9R7M8Xc|qi0ObJG81-3J)2Q4u5N&=?f@$^I-J9fG{6+E0@SZ8^R+`-u5 zv=23*FN_sRI|*2d@#Nej*QC;_(ZuL&>gUvZ`o&4=T;tXdqaAE6fvwcFtDBSR&FmfP z0WKYjt-1zzT&Lz0q!PwqTstIGSmI`Q(PMDDcJLQ`lbVL=z+A=4-HwzE@FS>9(_hYJ z;EM8=v4>RVvK{Ih(YC_|fL2QFnzE&8AhH2wtr_(LZ%_uScLFI+(w<}{pE@1%IAC}; zk#wqU;?6Du8Ze|c?&1z`jhZGjL3o`Y|FCa#T6ht-GC46dPRK;c555QbKs_2?1N@xP zHuMg)MCfxEOGd%~z4L9BLn%ftJ#{pWl=82o1Rkjr*1g%|nuz*8s>8@g4 zu|B-Rk$^r0TarDXzDwJh*QzJ7RfLOTHe+!hT86$0sFQj~AqsDPk%F_}#1+Q*WW<$} zcTuNJ^=tmB*Bex(UKzH7`>46vfst1j!w5Wl$_SFDT#b)f)r-dUZyj_#4kZmLW7focjJtlUlTY!{Z=XuDJ8iVo6%e};fbd4 z`+5!t@eEW)slpgljKxYHBom`XF?JFopJ+T-`f%vI(L}EXzcWIuf=6BhTIq@z)nx#8 zY6r}#5|!`LE5ImPjLf2ut++#i@`_`l@`$@F^_tj0yHM8^G7mA9{6~2bwJOFQ(+I7M z*GvjUp7h(tpgUj?csl&Ys!KA0k!qK~W=g@T@L2kGsdsRmlW~KP$cK53dYO+A%}H;> z3ScRAA)r;@35*fNJo5&2MxksxM`Dzz)LRDKPx%UTSEWWAvEmHgDpD|cy2k8RSx9)6 zjdg=-u~KzP6RTm^CXzi8uN_o@qVQYjB5}4|F8>!Jr>uq}xdz zdV~3e9n^#QM0lzwi{T>yzXNnB1>q%Rqz;tJojIMP7HByO$vf;9S6FceG@?J^IlOPs zwtL3Tq&3htsC7V-p?8|LE<9u!0Z+N(lO@K2ODzf{Mz%YCBDK@A{;yA|2F`MnD;+N> z{%lSaW03gcAd7a4;J}!NT*U~TiyGOiq48uW6B*r=eMU~|j7Zc9sWn2&L&QgTOzHcB z_6c6l5zD$G(1+CI9ps0HQ&&)^x0PN?dN`5r9M|ACNN5}UlRHM&@V3$_W4ZLGYRmXd zA+}CEqFmXkvQ?izAJyKdhnDZtzo~jI*63;uv_!QIon7NhFYa({BWGT?Qx6nMZ~pNU zX8NE(st&7Klg{KEKdFwS^NAQ~&Cj`Bo%aF9x2AqQU4g?gH?KOSzRmlb2}OH3^OoYSCU8D#kef^4EGwZ5X{H(`p{1OcBa`xC zCUTQhGak>8o~NEVCg$d9gS2gG`%yn+o%pBkANY&4z*s4cBiH-l9|=z|Q$kw63=Gzl z;|W%!;|-+_Cv$VNH zCZIleK@jQ>z5DhaNI(|K-2 z5T#bDzEkSL`t3dH+4>DS`VQ$Cq>ZhbG~c0@2Xc?noQYrE<2B>_a&^6Wh3Q|UzDYfs zYoX~wr4~;=R(Hhp_&)V)c-ECyaIJQ#FQxPkTGIQp+W|S~4B*xrKlS6IJle6e{>XpH zd3KH9g!=a+BZ)B_7`u}Z#QXrX3+`t~zv+=`Lz>33s!t$4)?#x1v~p9tO`kv}!G7E&r&rCG@Mj&m=89SaVS;Gi@#sAD!>A2_^#mx9 zk?j;}`T#dT$iL+@#6?H%()bFxo_bcwMSmh`mumBrSkyD2P3pV~zavNqtY1OX)fnkW z5?Y8KgZtxhFI|I?_JFP8dh|2`8)X{o&2qw9K^4GU&X`ln28J^-D5wS=Xh<)5Ctv|^ z)`epQT%lda9KdSe4!>cEdbn1CcR3@VUqJzNfYLAn#D4P&Gm2BRIB*VrqF)fGf?iq+Q0?qkKxy(G*&@rZ^++(t4wB05t&`ykeYW*f3lJ0?MVG z&RHACd|H|GaR8rb71O^)-zZwc_{o3>z9;}c72|Z%J42YWW?ea~9uo3v_McV`c^fHF zBX46I8L=B$6(XmR3NUX)tfIV7+6VTc&1q~TNDI|mU<2zabLbUmoHJk-eVm{Q@x?DFyvN zt2Lq^>@wts%vLQ}XR9p+1N--reRE!6~;=iJ=w-b!uWLA&=bYDuZ-%_3iWyk18|L9MqZ9 zd&ITW>p;1no^0qPM_ty?*`^7}M8ta9SiyU*DNl+;Py;`$3x zC}E*GJn^1+6+WqqqFu2(c9-J>9>Un#plFN~Js9A2jHIRC@&czP#wi?9nM+@hj=m;c zC8?P3#H3s!eKW#=oMEI zu}x}evz53vX<#olM_*+H{bpRx%=ujM6Rwoi_3d0A$QIz7a8pkp6Jv(aUPdkCk+f`5 zJWu@~X@We_veKA$>9{KQR&IxKP(HtYMf@Dc1jR9414f*grj($69`_-UG$vq58H64p zIO7d|1C)}GS~2ihul=O>ta0UrV zdl)v2D>a9M~?S@lT_X?5z8UvQ<$(d#96NGS3QPpQjJX8km@|+bU`^l{6HiLXj_0k z`GInV<0WYS<9T4=auo+`POdT6XOvA`pGA)q&QpDi+O+c7RD)7Et=2U#nqLS@ZEeN} zAGZloeTKM8odKRp)f3b*&|mC%BB#^+QjCY5S^}i>aqENo%T!B6VhnN|NiY0{8UNw? z&?DD6lVc%PE$k_34V-t?-DdR0&`-qu5Gp~(J^HE*E2Y9}!pJ~lC1s;@&MKfSoS^{i zuwU?`>0T-D1WGzsm*5SI#fQ=0+E?szW**`tdCU075j(Lvjk+CVGr#tY_jFf{&LD#W zkeg9r*G66cne>49H&vXNA+adSZXWlNF|#+zSoEyihKD<3i(=#s`f*Ha=l|&iIb;ed8y_&y9aD{>gaEc-%D2ve`11 z%rnh%&8M0#G{0ef)7)=<%ls?zznkAR51Nl!&#<0nea-p{>#yw(*dMm{>bvjmp`@qx#ce`|M`x8Ua?o| zE7L2BE2pfyW9813|GBcUb8&TXb?53It-W>a&b1G$eRb^{Yx~!}{nXFA@2*FF|B| zA%SRlwlO<7yC4ubQ z7v8b({)JC2{K>+f2}HlK@Pn?q38HTtgXm`yi2icLSaDaTR@ws5(^vjr#>Y24wsFtKM>jsQ@!^d(Z}c~Mzy8x--~a1RKmO~-AAkHGAOF?kUwr)D z$3K4fmxurE@Xrtb^zh#v{@&s59{%gY-#UEF;oRZ)aOTj)pQ<^wk!c>ey=_MfzW&;I`YUHfPEPw%hqukJ7G&pcRpF#hIy_kD8ThxUDN z-)r{WvhQX4UfMqR*?#Lwt+%vJYwc<6YOS=kwdPw>t$1RXIU)R6_+a?z@MYl@;d8_1 zgqMaFh0h3|?w;qK>z?VJ;qG-$ckXeP5+3kxfBz5v?%T5Mf{QLa>&#ZWec5)oK7GP0 zngwIk*kTsU9Ab!>`O$A=#>Uz58;`DYa^2wlPo2HxLi6+H@O5uCrtVvn0`GhE)McMH zy|b>B!`zvlHyxZgICGP;v#ydeTv|sjEnM2Tv~X$T(m49mz@>>x1DEF9ndTDz3}g6j zfB#?q86Oor;YRs)h8x2}a_t#`kr{m zY^)h4823wgrx^p|WTBjsjDy3!F`jKa)yRrQjsM@3C$~xqUu4`c@(0wCHsBw6jwLn! zgGk%^Ma%IGqdvF3=Fe5v4uAEl!Dl`bf5ZQ>At$-R($~uJmO? zYWSG6w=TB94(XLK=y=siZEdx`zN=fWcypcZ`mX-kYOTJjJExBZWA}T0Z!qNrq3io@ zDauFrLN?0p@&ew9b8)tSJJoZ&b3Rn4HS?ufsnRR88r52(d2O*)t~7JSYNgq%)S45& zeUE*QaQpu7N5)OYpG&Wefrl#VeiyY3ZmP?lJ1Kt;GzQZ%{f67zJv*~|Lf*;9JLinv z83>3fp5N=E51AytD=$9aPVAnU-Q9E>{h8^3yc5Wn{5#_v(sN_b?E;`(^vLh`>ow08 z;CkI&4Ly}s^t%1s?Sku68=>b*=NeVp$+zcnj$4|LOWzBdCC|=XlF3WQYwb#<-SB-s zm(9e1@7L!Gg?YISa#HKU@L}Uj!4JWKez&`;->=m?FKO*yC9vHf=h#8$+n#TkPU!o# zlgs4-$8ob6QGo?hE;ep9u7rL(SYNC9H(VTsxL+Lp+SqR3J0HfN;!C4@0z<;xbG%`HN+VyW z6_(3WmDmeI+m$hwmXvCKxtOc^LEzY~03ly)=JHiHm;lEJ1P(V#i(rRIo5nynw?0^_ z%2m%_TYpaAhkLVuf5v&4R;#%C#InFfpwo6=a+7z}<(D7r*++%H7=xs($M0N6&=LR@ zia4{}Xik(Sr6k8IXA8xAK3niZ_aggTCsV$Ds?nOQ=Djcwv~_}DqETrEvH6_*-ki4y zleV$dTKW^~=J^Ps{r+?{pDz@nY$$G!1;vI}J#)HUo37O2`fO!- zYO%MxvmpI+UCXf?cV<@{OHcDs_l?q1;9kN@j#e@lb??S%)08s(;=*b+miAx%!OrOo zC!YyCKP;bcMlNS=73#<&<)9DXlW8w{o_CZ>4iYYPbnttM7xjL>Q7x2unV=DsCnk!;M(9^#d8ZgvgG}T6T(y$TRs*jD zJV0xH=XemUKLM7>IYb4B!}D6jIA1H2iiK9CG0|vFwx{Bi#4~_g*e}FupQ6r<-3;W*h`LQTQg>L>uKFEDMKF5>6`|^=9K9V@g_}kOT}Nj8aKh_q;PF+jG-?&J{r$ zdS0batyk)Wx)6QK-nzZJ>RR?Tt6W*`ENrcp?P?2Udoq5v%J>13jutiQVKwPSQio96 z(LvOzPs=yUwQOt(rmqTfvusPy-HU_i$#&B#Shi;g>nwd}aqHx27b>VYXi3J27&I9>pE7)n~_rOX&c~nrlzc! zSvkp3yu9}9Z+SJ(0O{Ai-h;fcpy^1@KWemDujh}@<+xRfEFNJt>b#1BE!9*G}x9)k*aSMUS+I%+ev`Z6{ z<)A1y7i8UxK*IM#v|DcR^1z?0H|n|E{M6*cjPLh7K;ljTm~EHCY@$IJ#!E!zFG~IW zlvPl!`p1{gYpa=hl*zjCCx{f(L)WRDU%>#B1HbIr&nOf_5U^`Il~SM3f2NF%Z2|7< z!=H*4{Fe!@km9S-QE*NX)9dJQ75FY~s7h0wm5qyqAQy*)Vw}wdq3Dl!zgSy}@}_4+ zxusgs&*gR&A}g0e$t5ZIyiv&>OMyT}sq3sou4`3{?0G0Nmx;q5m&p|indmYzAECT_ zyfc@xqJkVzQ!Yx{9EdLesPVi+&k}Gv`uESvWPran3*-)oMjnZ-W%NaJ>vsEn{Q8?N zelN7*qVMM$ZaL%Z9ly9E%4OU_tz5E-xyT&9kkUI+`s+m-W9i+IZYaNl)$xmdU}j66 zpQ}5iz+D`_cz-q@x&B7BiAa@-g&!J$z;OZtnFrVd9PHI zR?H9oNi@tA(Io-bM2D;%)hXq)F8DeSy66eU_)B3Ni<+7oLst|T5x_Y1!Z65YL;1^Q z9nqNOFMu8*<-c+GE8`L4&&2-7O8I_WD9ulfkF2bOtE<(u9pTRP`>(miz4kgsj@Ml$ z?=<9{n~ksXo%%pjRez8i8#^TT&Q@{^uDRO1_F5-7N`LQ`cfKw4_^72S6|4`wJwF#L z^uE0C%6YfF%#6gr|g_^>s$9OGyv%Zf#40&+T@LXA1`6>y5`i3)0#3D=FN8K zR(G7TC>G4ly<6ptIk74)5YCiKtO8&W&boM(D9=P zI5|K3XR&>LV>DjJX#eZST%Y5*fe%9V102)yXzcFpv2)ZgH~d@UT&O*NUutzr|uUg&E?cdw5g*uNk5-*3E3 z=dxupn7nho@nhp7qj&0pvb}n8oV@yK@49Q=!25)0H-Q+%T?^oPRjV znV0~`UtMd=-n;LCd!sM>;p|j-7Fdk+TR#>&0R%-a6V12&gN57Xy)Cca+MMeMRU~;{ zEd9GP$s_W!P!flfI_Qg9pZGV12@=Oc=_+jGbFa!-&F%n(TGo~9Wt7{Fn(LH^k zk?pmn7OYmIHZA$5j29X|Fg}sw_qp^Hb_^L;#}U&q zz92R`Fe`>Wm{O>B9Q^-!=$LL81tO`kzLguV;0ha>w3BOOEk6v;J_h%z#-+wjrT2jT zh_-&(xT93=zAgSOGqN&f&(R`+^yoxuRP2ZO#~VyEtlM$_^AN`0-? zU*Bgse%#zLZJK9S#McX$go|3ht{Igr0m1~|>aI&Qf z%BIz5NPXqukBz?*?zaG(=%XgKEjjHcb=5_2km~EO!MnZdyK!mV&c<67>{ffq-|NNG z?}~F1S7vPAi+B0CFeqn>6V2)76@e9O**odfcAhDEX=KKBHjE}}W{{2it>@OuE#U$U z(U*Q^d|qIJHBQi${a&p<=yl~63exEJXZ5=>x!QY0HG4rZs=T03xHyh07l)b9%9^45 zE;DOoGR|dknhjySl!ZV4!uUpF6+(yitLO)c5-nVS=+$pu%SDBgpBd$=ca`&^l&R^?W~C~0uc&IXZLT5H>_Fi1FjeRxD@_bUc= zeu~zZ0KD#1{aR92A9Zm&q{f;Ifzgu{vvkT{!!ojptzF;vlvu445UA8rAw@OlfOX~l=u}|uU$Dl_{>-xYn zIUXH(;78InwBuQsAo6^x=s3=`ZlV0b;JP3eW=h$3!kW+LUvyFN20xp-HhxCVJ!8hV z3RYDFT4ZLo$>;|=PArt`_QceA!9rF5lEFN1W%wh3@`J`Iq32fG*Gwk}BWGL13&LPx(rKO~kSY%`AN>vEMB^0T zRzi?f!i!Clqx@I8JyBDod8F0epqq^0U_HqwZ`20^|0dJ7i=Mf|HXXa*nReHU^IM&u zu+0tKMbmtZpDVxCivrV}a--6u8CWIPTnv|-9e1}pyHMZetQKM^AiKj6+ICM2O{ZYz z?aZ3koox$Sv6h4%#zv|k(b2PznV z>j@JY0MhTCY`S*jOwODX>;=6AT# zwAhI)?>!Ac0=xFR_6H|IyXX`z-);96{0J)n>cdBb<2@)E>2BjR(LdG)fEe+ejZ63s z0C^OEBOaIxeos3fjl`YTnSB|tLVnGT3IkyX(&Qj}aXu>k=Ik!Zvx>gCP>gHaO+Tue zQ!ngw96NO8yKi31ITJ0bbe8MIc4Nj7#Bghkz$_Ml62S?M*g$sP%11z!b0^|L)h^kS z9nY+oQ_a28VK$g<0JhW8nm^DmRCjc$>8|KF4ZWCubZxc5|`w z^w7&h_KmrT`K=YZxP7ISHOmgLUF4{Hw&R3nTW5Ehj#V*hC9@IuVYy^ubV|dAMcVB* zu$ouot1p^nz1k7@j@k+A>VbBXv+93z-I?j)>_pK#r?R7K&1`Qwwj*%$Orb#cekOXpV_PHp+bjQOpmTlDhJEoX%?Gr;auksjYMzG)!xrchfSrS}q^H4;TtAc@#V zRaPu;sS0q`{k?7=%-wN=lLNc#nWuS?7ep7L@;$FReb1}*oHTG^H`o!yX2qQ=W`y+P zwwKNNYoT|kG{McC=A7m2bTiIQ7jr=?!^6e_>G_&5F!mUy8fQS>C-p+2NxcvcG2DGMZS~S zdy;+Xp4(2?y?2Mb+TXtXqR8t6?n*1nxw%;0PQl#FpXNVY z$j!VjT#u}hu&wL!zUlc{yYTFZl9l(ead1m5UoME$D!g$MM|2OICNi+Fqrd4zrjrQyb*2>hfmup^G z$_mqUOVPZ!D~$7dT7DF_UEvIqZj_r@^>T&2Dbk|k%h=_YFoaI!ogxvvcGh=oJD6zM zE7SJ$Hesk{X1W=gW#Om47tK1t#Qry1Um7FXb)8ptU*3KDR(o|-byxK|Jv}qsHT#e= zLvlF78FIL<8j7++M++rV6h+chv{*uDN!9{cumU9(ZHu;rz_1k=b{qsYq##fLBbG(| z2qeHjA}fw!Cz1R!znF90tLmANgvL}?*YfJ!bI;}~B# z7BMdgLYCdWQx)Ptjt61V(q zr~arLtk9(cuUh62_b|jepKZoQTfc*!)B}{m{ISv4t~!;6<{W5_$@ON>VXuPnDLVEZ zx_1eW?BR1odCZ>@z&5!78?U+$y?$Uy+O_&JCbJ`$4?Yzyp%SX0DD*T2d~y%eJX(c?0cbEV(EEe^neaIxjppLht(2|67Up#ICS@tPC z_iH{`d&pVr3MnXGhcdjj@72jZ|ekr$sZEx?0lak?KT8BeB=@3MC?{!kNAo);TqG&KtP%f z>vOWKUAl771jVIQ?nT?lvhc>6{NO74WXYnzM3UJ0!g@4Mgy)$qNLdc)bJ_~R7R!~l z!T?4&s0T|_2U3R|F(%f!yuZ=m9mpFotnD9QZC4uiHXdv|+IS0UvD(iT3!0~h+ROGq zv~v1g99EgrhF^fK)nSm`pj+a_Hfu zSO@fD!09$GOP5~+Hx`$S^Y7b6W?|UDgY?iA-9Eg&@c|hNZDj~(DInIzi5JE!^t@bB zD`QQqjs!78##~chHsJvddC5~@0AvFk9bb_MK-GxEBHD^Xm`6Omokut?fz#zbZoFPq zDz4Wls*Juj`*HSr#)W@Oi{PCy@SYI^hdQ6U7ucxJ(0dE;-hXU-3%E*EWvNl3veHe7 z2W)$Fl@jtG_oIh4RFpsEdG_cP>&N#KleLPr@ULkQ@A?F!2#fhZGAFOK!e(065&mD} ze>VQ2f!`oqF^~PNt36K5g*D0z_~XAumxE*4Q@vAQg0DG0c)L4pJkviY{p??`L&3@L z8)_S}v3eip4pt-&0Rt89M!MnC7muhT_?dY_!e{)Qj`g&%T3a>3j2{N4B?zI>Q+Us8|WPvPbyLp9EwJSm=hNIZafzy0P< z;8Wj#m7anqyo`BZAzCX2pel^mTRd2uQl6DnmdFs6)#Umc_NI%X##{!>&UOii^F&cn zGS6?475caggMic`{L_NRqZA|;P=Bu|;kC7rC;f!9Z0))8<3Qr_Ffih6Lc$n>8XhP9DZV!+^rLVT8x z3;qG}Jbow4CUCps*rNifGZ&Gw$wckWva-a5O#q4JtAQ2n*3eW7&I&0(&UccP!)$N; zBsn@&O%}rR*mtlIJ*z z`5MzF`V9W6Qs|t4eqC65Qd#$TnuAPDP-EPw4ZEWhCVa*B( z7j>2aOD>9XRROSCL{+lGK%Oiz)4sml!#B+fcT&Af$Wh_KKgX zl+OUNEnBYi$-s-=8i~`=3vVIX%V_avbHcb1%k9^<>1IOMqdrY>uE?E2W*Az9#YN#2 z%=4sTQ-&}P7{LvVLJ7`iG*?ansg|d5Kop3lXhoBZ45Id!wXD&9323Rcs%q81D1Qoh zJgYnmCGxyZQN;}S&LkRjBXwKTv!2V8dSaA$)|mwHC9SaH#T zsE#vr-`e388{P4&VF@UQ@tGWAhxXRR@X|F+W41BMtTD}`$`C(>Gx=V{@ruLPpRepQ z9#1fj?w{AF5;x#lxF3~h+h>2*$Wn74z)U==v1g7Og6U$Wgv{Fj>kd5lVlFeHU>@A->J%5BR=x8hy_^(Mz(EThmQCe$foCrd^{bom2J+92sDMj zl_w&WST5d(Sv*zDsrPpLxa5VOK53|DR!et#v&WY2QlubR@H+vVXmGm2GQZ-b1$gq` z!@hJO%62hoO9-%Xq5qJ2V+FCSQ58W6W$v7pLISKE`>Vr@F~*lCc1il)lykvS?3MY^ z?v7<~2sRfa>C??Ay)$=&2FdY5dTTokkL?)>#AJ0-Q;-Monh&TGT+3-$e6A3o2;ZMK z`Px~~oC@+mW!DYx_a8R?O9Q`g0_$S?08@E+6>9aV*S-Wyc`jvsNW<01DYkJm0hCK6 z_gts*OQ($$Rr5=bGfLT#K^(ebb+^rnw(14VONHh9?7r`m4%&3YfSSj{mn?IRa1fFN z7JLDFQUAAAN#9TCHlrukPQmP_}1(PfiM-VYLy@aX-K z$0C-kv#hrE?=S`bm<*4_Bu)mxILdeO2&kRKn zj}Pe}JXWt^Cj8(;OPFm4Koa&?>o8-Mh3?%k>+VX3(j;J$%x5vpMgqu-*nnI4aL7^> zoKTQ;)docNt1(Zc{7|#adRIJ>(;`_^a&R?x&E*cTx<+|3M4-L`oT$-nKN;Py1*39` zs6Bt;0^vr2gZ9~Y4NKm!xy$FG7bh8+vIG59mJU<8gu4OBOOj&&r?lB*c@9)JP@!}$ zWj#K13w^NpG9k2;9dkmW*{Z1MXq4a~7Ca|Ex0SUONQTIm;tgxVO-vMV>QTORQ zBaU{#!^c%nR_D8)uoo<>0^fyE4_8#U#<#Bo152?0q-;7U31K4MVVl8_2O)r+5E6|#%cnr7XZX#LG)B%A=Bg>mf#NW3KQ zuD3zbROW?t!J7|zIZ4|av2ikN&)4@);OztO^`mo+g{R(|j?5{7pw9+y{3-VfU2JYOZNo$_czyo-id zP0_FNH~)Lp|G{3&Tjw2I?61}ObXGMvs)$69H1Al!Z&lWaIXf~tvWr_#|BR=*d$YyQ z5jfc4-ReLpA1_|=WrR{eXWIAA(2)YYq3%J{0gA)y65Y{Wu(vEPvI7L97bSRU4-hy% zC!p`+0I%nP2Vno0;~d9);f6BG{mj11Zjsm&+ciR$v~?u4-CS0zyy17=gN|EEh#*b1DxbYmAQh$bDUKX)y%_>z(2>pCC)Z3pu*l$ z*tx2k2=fGk29pwq&m6^mj@H3$?_&$FJGEaKQXk}yLIksGn6`n}VPIODiMIvrv|KMUN1AY%6 zjM97JT*Nc!eX!{BjXM>|SlnB|f_e{PBse9Pmqm|JY%;;Rk=>#mz;EJOuVZt6HKRoc z?o~)rqj>eq6onu}!Cd0))vxty(>0Y0^SbM-01Lcem(z`2GWWL*0{RBKyjK=ydk9!^k=ceFd-4(Xm;z@Cmxsayz8ucC?vUT6* z#dCQ~^R`DFn55su-uli#y?dmU96$W-JW5~7AuU9&G)umGn|@jx72?QyT#_<>o)`WO zSOVs)dT;9P+WfXk26alP*D*tPyv}lPm2;Z*x8V+1<<8$Aw@N>H$+%*J45V`_WKDYc zW9n7J%&f&qlo`UeRwH)m4&0x}Na&BYo4mXdlOm75 z{BLwP;H9qeZOv{DHO}LF*>oWxD)v%`1fFG$H&y?MBKn<3##(dG}lY3(XGb4&qhJc%duiWUfli4C1;3P z0im;^wE}?mwzYwUh>KatJD~R*3q2~eB(KtTn~kn28pyP5A0Q+_<_rVW;COe2 z7cR|i>iXk8-N2ggT>$Zr7vXj90;)H~(gxX)EHQ{8sdCR2K*wZhgyug|?A zlB`dO=A1FTa-||41omjSjDs^;>j&sS4A!PtF?YHK{+cNvHDF0Gs3#eiL~ zO?jLi$x|V@_T{aF#sy0caBL+Z{j`6eq9z^DT4ilOdJZAM+Y1o0q0crCQDE}ICkp)8 z=h{>Et&0taS!sPE?$#e^l{~)>d&OD&dccyX2#S;!zW%~kCgsul{$wLiruRz{s#;cF-2RmdlDRDOmh@Ko}e|gi z9)4f100BuaFy`w&>khq|)A8910S@hh-GcdM@9%$&gEd6X4wtik>y#;4QQQOPE!%@N zGVPj(f%p+FQ8VhYoh|G6=af~-`e%iWk1@Alj5(wf|NR!|agQnuS_+6BjkEvc8z0=@ zd7`ude*3~BPGqgj;-DMLvLR5Tfd*b7xbX4>Xb{fmT8KNfWi9b^LKQWDl;u-{D+b)h z_}ObdPeu6tb~g2SWN7ZECK7`%sG|XM*BgKfml_W=9&J2XGrSrTZo*3(A(=~;d1vKk z5OZ2#1?j^LH{7lMSm)1zqu*A2t39fok*voKEhSrjynaY@lLr(*HgdGA%`eB zpu}*-0XWa1iSQb6gwLd8d9z?Iti}pfN5+?Rdpu61AxnQKsSSV*>W(m0?ONWB!vwF> zVtF>qcrgTQgg;!6Bn%F9N{aMq?AQ%-Jf>=T*aLE>&xsEO&3xOqB(5Y^MEeq69uL`$4qP?G;o0gezGA$|wNM zG3jueb;FszbJCMSuCA*uCI#!R0okG~|z_`4-3@ z0r$EY?7lWeZy2^N*mfu5OF&st+KgT+L?+VTQmeAjkX3HLH7x-fyQ6VW^r7WuyeFOSP4XsuBU*{Ik`71}){ZXan_SaadBH)d=~BkJ8NGL% zPJH?nr&@XLBoTsogHgouK|4rTdC<@hk0P&{7h7H4g}jl|j`v#-RDTLkX|)Z6PqPgm zyL_Cw=KCb&fpQ&rCMAH}?T%dP(nwlU;pq>n32n?GS8&cq&WlAr6Q+meLyh(Bmvy15 zF4t8Bfbn(GhhCs=%qvw4y{`kz+s#-`P#J<=1y^vI>s?=vg4f+ zCDffe+-HBH*2O2e|8ld-mM>e9uqgkHp~CMYs};w08u-n7lg5F@9z?=zjYg?5wa;pC z(Z7h<&Q#Tc#j8Kj8S3katKU@~k?K2Fv62d#dro^=yc0N}g`F2P>9fubUpXz#J?mbQ zQTaS8?P=vDhfHkGO6Q;SroaI`Ca4TZBvdyx)K|unYu`>u622bkSa=@>cGi;OWKevnN(XZ zf<;ilz$OD4I4{L&Bk(05%e5V>+O2Q&ZXsI7dLWhQEe{Diw$&5uf+^nY{+Xw;tmy=5 ziOp^zqJa!}9JA(8pi9IL%)gI$%H7LFix2LWGy}5Q1k;AfJP>K8y?azXl_aA}x&At} zCJ!W4{wK15Ch^!!(^Aq1m>6W1wBqC6X(SLKw>2I@Mvk5#rJU9|qK*)}k>#R@Qq`U& zH%v{`hAwWXn#?OwH<=k8{d46M2Zc5xLStzNyoe(c_=Ya!{C|+go%hVGs##Fo`n>(ROg#A#v$Hx~fsFut>IO4;~Mu;JAKqMUl zFi!vi88rVwBK$V7%1HfYU9%IYy3H$#hES9ulB`wORNU)bE1sbFoL+hCn+2<5Mzq_-U55Suh>vhD`ZIWCo@zwXp?6vV4XJ^CIWuRQ4rYE>K? z7KMRgp6T*s0cuxff*K-I$atbHp3|Q;ky8J>o3q~SE&^p0y@q@(ttz%yi98P~_Z^LQ z)=`BNqoVV$km?RvV_r`|E1+-8^Lbs2K%2#e zX)>+a+^`|lh}w`<-{!OlV(XISc56h31&?I})YKPCJa8+|7)!Z)JH>&1DggN<7o_f^>RR5de& zRzg79!%@-wlGJoRpz31n{121{m7!|;%NrEJzD6ps%DcNiclz?9pkIYiDaj>;h|!*p zffOU8J1xSJA-~hWP>pdQ2Y8tc{e#0Tfrl6$cGJNy0_$B z!rbTt>wzq!n~sO@;dImRP{@LNw9g(2NX&yPCw_AMm zcCc9Khh5XY*rUCVTGyhXtR^9Gg8qA;sO|uMc&);zMcS)Hq(t|+-rPXkR!D^QCi6l2 z>k(Ol8ft=yC@ieCupf4_FbctTp9&KMZpAmLwV4Ipl|-#opzF;E8MZhH4bi3hPqOG(OV!c;nUUQ-O0`Fvp}yN>mDU5uao981Tf!lwxJB+DD5L9k4QK zT`2tp_ZOGb$eWou;2oe|jx#9#*;BL-+H|N{ZE`>8tSDy>6d`D;75P|D^33->TZ%Qu!sbRfR(NvPwX9zJRD_61FlW5R1 z-WO;)r{AzD0)%9-5eWmq#uNOqt~FG^kiHN0OHuSS1Ua{@gahx{WPpv_IC%iCDeg4A zI1tKF99bps4DeQBSW>+xf=5{tY3c+-*VojC<0kLjVMs#y;cEd;zZkK|Bs{Kh;79ZR z*Q{|Tc=kJBml_2Ez%B;u^?ZR>hTM3~2(aCsXCZaa-vF~E9%sG}RpIP9Aj@(xr@tSI zX+rxDraFNefCgbfu&TarN?oYxL{ZLK>o{5vYc|{f`N~<*^*Sqi!?njsyo65>h&7}h z1Sb~B_7v1Fe7uVw2Zi^D1Vj~OF{N<39RMQeyFy2O=^69Z&0*!*@nc+IX^cWJQ8SoAycpq{(Z4?X-jiYnZ7S69OgKrEeeSpvlI4lQ4@6`Gv5uk zinW*Py716mEs`pVSLs7E?euyfWmek?xo`~#zhE9j+fN6g$J$=z0=t~>&D^k%MeC&75hNmc!9Q2Zh=V&~r0p4S5@~&3 z0oe2=z&g8o-1s!I1-O_!sw{WLquLAcN-ow%P$&y9rVLhUXa{iAONcAXSar2LPojwFJY=rA1`##vKZ2Lu z+jx89gLM^WG4nj08yiV9c;-Hc?$3bC-B3=jm?hQxI*a*E%yAsvd*h`5^)2A;UXA06 zWcPz$X_t8RQ3Igmfr3a_J~YZ|f#u_6$r3rCz_A0oqVl}v)_AMn$DqY%S6OeR1Hxr6 zdc+$?9`=EOXm$G1?l4zT1wI_%wOH2CRbZ3g+ElhsL`<;8vxr5g0TdY~|X9HLs|Gt<6U9SW|EN{6E zcSN&&BIBJq3F97h_Z?dD>A>l+EVVpy>h;6M4;%lovE0}MN!W&QO^=LgaWn!mC`=A& zH3M1I0>YF&&W2cw(c7jwJ+i}s{MV%K-3)N-IjlGAoS(J&nlmxzwJ`Tqg7TC=C@;Q&1H%ZEPQTaqc zTR9g>EtjFqrSKnQ&V`sbE78Cbq`b=oo=1yf#bfc_2)2SW`xu0bK%8ucge~1-NiHo{ z%b!UANrWxTrN+(%crJgn(I`+T@@h%z*_g?gSyWncX!sAj!x6}2c4q6}rkFrNmD6ta z2pxmZn=I3-;RPDR4^1p-r91{E?t(4Y1U`|%<}B$TT)!Lk<^paNO>BQD>`9B17hM5< zcDfw#-o4=Oh@J~-fpuw~v1vk*R8CS0e1|tSeG%poAiAdhc4BHUUTpj&U}UVoxNfT5 z&EO7Fmlbv{0kdHysL;&Msx11SR4@fT(9FKrGiQzx>yG|0_9QHNsp(4QmV*%mLfX5V z?RQ31%!9cFZ}z)j_n71E!m_8kBUpjB0C)-d z6=r3d5Z<(fJDbw^oCjFZ&#g z0v0PyGj|dZ;x73e4WB61T-bZwncCdnXacEnxs|#YF$B8R)%0AR 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