From c596a444833c790e828e52bd6934b239ef6b6a53 Mon Sep 17 00:00:00 2001 From: liweiyi <190785909@qq.com> Date: Thu, 16 May 2024 22:23:10 +0800 Subject: [PATCH] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0=EF=BC=9Av1?= =?UTF-8?q?.4.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/docker-deploy.sh | 4 +- .../src/main/resources/application-dev.yml | 67 +++--- .../src/main/resources/application-prod.yml | 6 +- .../contentcore/config/CMSConfig.java | 33 ++- .../properties/CMSPublishProperties.java | 9 +- .../controller/CatalogController.java | 8 +- .../controller/CoreController.java | 4 +- .../controller/DashboardController.java | 54 +++++ .../controller/PublishLogController.java | 15 +- .../controller/TemplateController.java | 2 - .../contentcore/core/IPageWidget.java | 16 +- .../vo/CmsConfigurationDashboardVO.java | 30 +++ .../job/SitePublishJobHandler.java | 19 +- .../ImageWatermarkArgsProperty.java | 1 - .../publish/CmsStaticizeService.java | 24 ++ .../contentcore/publish/IPublishStrategy.java | 27 +++ .../contentcore/publish/IStaticizeType.java | 15 ++ .../staticize/CatalogStaticizeType.java | 167 ++++++++++++++ .../staticize/ContentStaticizeType.java | 208 ++++++++++++++++++ .../publish/staticize/SiteStaticizeType.java | 95 ++++++++ .../strategies/PublishTaskReceiver.java | 80 +++++++ .../strategies/RedisSetPublishStrategy.java | 99 +++++++++ .../RedisStreamPublishStrategy.java | 120 ++++++++++ .../strategies/ThreadPoolPublishStrategy.java | 75 +++++++ .../contentcore/service/ICatalogService.java | 4 +- .../service/impl/CatalogServiceImpl.java | 4 +- .../service/impl/DynamicPageService.java | 2 +- .../service/impl/PublishServiceImpl.java | 21 +- .../service/impl/TemplateServiceImpl.java | 1 - .../template/tag/CmsIncludeTag.java | 34 ++- .../contentcore/util/TemplateUtils.java | 10 + .../core/impl/MemberDynamicPageInitData.java | 6 +- .../service/impl/DynamicPageServiceImpl.java | 2 +- .../cms/member/CmsMemberConstants.java | 10 + .../impl/AccountBindEmailDynamicPageType.java | 9 +- .../impl/AccountCentreDynamicPageType.java | 9 +- .../AccountContributeDynamicPageType.java | 9 +- .../impl/AccountPasswordDynamicPageType.java | 9 +- .../impl/AccountSettingDynamicPageType.java | 9 +- .../search/impl/SearchDynamicPageType.java | 2 +- .../com/chestnut/common/domain/TreeNode.java | 92 +------- .../common/utils/ChineseSpelling.java | 15 ++ .../chestnut/common/utils/StringUtils.java | 4 - .../common/staticize/FreeMarkerUtils.java | 4 +- .../chestnut/member/core/MemberPrivType.java | 40 ++++ .../member/core/impl/MemberMenuPrivType.java | 20 ++ .../member/privilege/MemberPrivService.java | 29 +++ chestnut-ui/src/api/contentcore/catalog.js | 5 +- chestnut-ui/src/api/contentcore/dashboard.js | 8 + chestnut-ui/src/i18n/lang/en.js | 41 ++++ chestnut-ui/src/i18n/lang/zh-CN.js | 43 +++- chestnut-ui/src/i18n/lang/zh-TW.js | 43 +++- chestnut-ui/src/utils/request.js | 2 +- .../views/cms/components/TagEditor/index.vue | 1 + .../src/views/cms/contentcore/catalogInfo.vue | 8 +- .../views/cms/contentcore/catalogSelector.vue | 42 +++- .../views/cms/contentcore/contentEditor.vue | 6 +- .../src/views/cms/contentcore/contentList.vue | 3 +- .../src/views/cms/contentcore/pageWidget.vue | 2 +- .../system/dashboard/serverInfo/index.vue | 20 +- 60 files changed, 1513 insertions(+), 234 deletions(-) create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/DashboardController.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CmsConfigurationDashboardVO.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/CmsStaticizeService.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IPublishStrategy.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IStaticizeType.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/CatalogStaticizeType.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/ContentStaticizeType.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/PublishTaskReceiver.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisSetPublishStrategy.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisStreamPublishStrategy.java create mode 100644 chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/ThreadPoolPublishStrategy.java create mode 100644 chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/MemberPrivType.java create mode 100644 chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/impl/MemberMenuPrivType.java create mode 100644 chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/privilege/MemberPrivService.java create mode 100644 chestnut-ui/src/api/contentcore/dashboard.js diff --git a/bin/docker-deploy.sh b/bin/docker-deploy.sh index 8ff9fe4a..602a0406 100644 --- a/bin/docker-deploy.sh +++ b/bin/docker-deploy.sh @@ -31,5 +31,5 @@ for NONE_IMAGE_ID in ${NONE_IMAGE_ID_ARR[*]}; do echo ">>>>>delete docker image done: $NONE_IMAGE_ID" done -# 启动容器 -docker-compose up -d \ No newline at end of file +# 启动容器,老版本命令是docker-compose up -d +docker compose up -d \ No newline at end of file diff --git a/chestnut-admin/src/main/resources/application-dev.yml b/chestnut-admin/src/main/resources/application-dev.yml index 40507ead..37d8a131 100644 --- a/chestnut-admin/src/main/resources/application-dev.yml +++ b/chestnut-admin/src/main/resources/application-dev.yml @@ -17,13 +17,18 @@ chestnut: captchaType: math member: uploadPath: 'E:/dev/workspace_chestnut/_xy_member/' + cms: + publish: + pool: + threadNamePrefix: "CMS-PUBLISH-" + queueCapacity: 10000 # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8080 # 开启优雅停机 - shutdown: graceful + shutdown: graceful servlet: # 应用的访问路径 context-path: / @@ -50,12 +55,12 @@ spring: messages: # 国际化资源文件路径 basename: i18n/messages - lifecycle: + lifecycle: # 设置停机缓冲时间,默认:30s - timeout-per-shutdown-phase: 20s + timeout-per-shutdown-phase: 20s # 文件上传 servlet: - multipart: + multipart: # 单个文件大小 max-file-size: 100MB # 设置总上传的文件大小 @@ -84,8 +89,8 @@ spring: password: b18a03 # 连接超时时间 timeout: 10s - lettuce: - pool: + lettuce: + pool: # 连接池中的最小空闲连接 min-idle: 0 # 连接池中的最大空闲连接 @@ -120,7 +125,7 @@ spring: master: type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3308/chestnut_cms?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://127.0.0.1:3308/chestnut_cms?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true username: root password: hello1234 # 从库 @@ -199,24 +204,24 @@ management: logfile: external-file: ./logs/client.log -sa-token: - # token名称 (同时也是cookie名称) - token-name: Authorization - # token前缀 - token-prefix: Bearer - # token有效期,单位s 默认30天, -1代表永不过期 - timeout: 2592000 - # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 - active-timeout: -1 - # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) - is-concurrent: true - # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) - is-share: true - # token风格 - token-style: uuid - # 是否输出操作日志 - is-log: true - +sa-token: + # token名称 (同时也是cookie名称) + token-name: Authorization + # token前缀 + token-prefix: Bearer + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + active-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: true + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: true + # MyBatis配置 mybatis-plus: global-config: @@ -233,15 +238,15 @@ mybatis-plus: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 防止XSS攻击 -xss: +xss: # 过滤开关 enabled: true mode: clean # 过滤链接 - urlPatterns: - - /system/* - - /monitor/* - - /tool/* + urlPatterns: + - /system/* + - /monitor/* + - /tool/* xxl: job: @@ -254,7 +259,7 @@ xxl: ### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。 #address: ### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"; - ip: + ip: ### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口; port: 9968 ### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径; diff --git a/chestnut-admin/src/main/resources/application-prod.yml b/chestnut-admin/src/main/resources/application-prod.yml index 945e71df..076450f8 100644 --- a/chestnut-admin/src/main/resources/application-prod.yml +++ b/chestnut-admin/src/main/resources/application-prod.yml @@ -20,7 +20,11 @@ chestnut: cms: resourceRoot: /home/app/wwwroot_release publish: - consumerCount: 1 + pool: + threadNamePrefix: "CMS-PUBLISH-" + queueCapacity: 10000 + coreSize: 2 + maxSize: 4 # 开发环境配置 server: diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/CMSConfig.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/CMSConfig.java index 5f153d91..200464a6 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/CMSConfig.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/CMSConfig.java @@ -15,27 +15,30 @@ */ package com.chestnut.contentcore.config; -import java.io.File; -import java.io.IOException; -import java.util.Collection; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - import com.chestnut.common.redis.RedisCache; import com.chestnut.common.utils.SpringUtils; import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.file.FileExUtils; import com.chestnut.contentcore.ContentCoreConsts; import com.chestnut.contentcore.config.properties.CMSProperties; +import com.chestnut.contentcore.config.properties.CMSPublishProperties; +import com.chestnut.contentcore.publish.CmsStaticizeService; +import com.chestnut.contentcore.publish.IPublishStrategy; +import com.chestnut.contentcore.publish.strategies.ThreadPoolPublishStrategy; import com.chestnut.system.fixed.config.BackendContext; - import freemarker.cache.FileTemplateLoader; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; /** * CMS配置 @@ -45,7 +48,7 @@ import lombok.extern.slf4j.Slf4j; */ @Slf4j @Configuration -@EnableConfigurationProperties(CMSProperties.class) +@EnableConfigurationProperties({ CMSProperties.class, CMSPublishProperties.class }) public class CMSConfig implements WebMvcConfigurer { public static String CachePrefix = "cms:"; @@ -117,4 +120,10 @@ public class CMSConfig implements WebMvcConfigurer { log.info("Clear redis caches with prefix `{}`", this.properties.getCacheName()); } } + + @Bean + @ConditionalOnMissingBean(IPublishStrategy.class) + public IPublishStrategy publishStrategy(CMSPublishProperties publishProperties, CmsStaticizeService cmsStaticizeService) { + return new ThreadPoolPublishStrategy(publishProperties, cmsStaticizeService); + } } \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSPublishProperties.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSPublishProperties.java index 201c7bc4..0ed3aca7 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSPublishProperties.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSPublishProperties.java @@ -28,9 +28,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; */ @Getter @Setter -@ConfigurationProperties(prefix = "chestnut.cms.publish") +@ConfigurationProperties(prefix = CMSPublishProperties.PREFIX) public class CMSPublishProperties { + public static final String PREFIX = "chestnut.cms.publish"; + /** * 启动时清理发布消息队列 */ @@ -41,6 +43,11 @@ public class CMSPublishProperties { */ private int consumerCount = 2; + /** + * 发布策略 + */ + private String strategy; + private final AsyncProperties.Pool pool = new AsyncProperties.Pool(); private final AsyncProperties.Shutdown shutdown = new AsyncProperties.Shutdown(); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java index 81f51572..4d4a1d34 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CatalogController.java @@ -188,14 +188,18 @@ public class CatalogController extends BaseRestController { */ @Priv(type = AdminUserType.TYPE, value = CmsPrivUtils.PRIV_SITE_VIEW_PLACEHOLDER) @GetMapping("/treeData") - public R treeData() { + public R treeData(@RequestParam(required = false, defaultValue = "false") Boolean disableLink) { CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest()); LoginUser loginUser = StpAdminUtil.getLoginUser(); List catalogs = this.catalogService.lambdaQuery().eq(CmsCatalog::getSiteId, site.getSiteId()) .orderByAsc(CmsCatalog::getSortFlag).list().stream().filter(c -> loginUser .hasPermission(CatalogPrivItem.View.getPermissionKey(c.getCatalogId()))) .toList(); - List> treeData = catalogService.buildCatalogTreeData(catalogs); + List> treeData = catalogService.buildCatalogTreeData(catalogs, (catalog, node) -> { + if (disableLink) { + node.setDisabled(CatalogType_Link.ID.equals(catalog.getCatalogType())); + } + }); return R.ok(Map.of("rows", treeData, "siteName", site.getName())); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java index 4871931b..37996903 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java @@ -144,7 +144,9 @@ public class CoreController extends BaseRestController { // init templateType data to datamode ITemplateType templateType = this.templateService.getTemplateType(SiteTemplateType.TypeId); templateType.initTemplateData(siteId, templateContext); - templateContext.getVariables().put("Request", params); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, params); + // TODO 兼容历史版本,下个大版本移除IncludeRequest模板变量 + templateContext.getVariables().put("IncludeRequest", params); templateContext.getVariables().put("ClientType", ServletUtils.getDeviceType()); // staticize this.staticizeService.process(templateContext, ServletUtils.getResponse().getWriter()); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/DashboardController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/DashboardController.java new file mode 100644 index 00000000..6eac67f7 --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/DashboardController.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022-2024 兮玥(190785909@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.chestnut.contentcore.controller; + +import com.chestnut.common.domain.R; +import com.chestnut.common.security.anno.Priv; +import com.chestnut.common.security.web.BaseRestController; +import com.chestnut.contentcore.config.properties.CMSProperties; +import com.chestnut.contentcore.domain.vo.CmsConfigurationDashboardVO; +import com.chestnut.contentcore.publish.IPublishStrategy; +import com.chestnut.system.security.AdminUserType; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 首页看板数据 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Priv(type = AdminUserType.TYPE) +@RestController +@RequiredArgsConstructor +@RequestMapping("/cms/dashboard/") +public class DashboardController extends BaseRestController { + + private final IPublishStrategy publishStrategy; + + private final CMSProperties properties; + + @GetMapping("/config") + public R getCmsConfiguration() { + CmsConfigurationDashboardVO vo = CmsConfigurationDashboardVO.builder() + .publishStrategy(publishStrategy.getId()) + .resourceRoot(properties.getResourceRoot()) + .build(); + return R.ok(vo); + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishLogController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishLogController.java index d72516f5..82259a4a 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishLogController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/PublishLogController.java @@ -18,11 +18,9 @@ package com.chestnut.contentcore.controller; import com.chestnut.common.domain.R; import com.chestnut.common.security.anno.Priv; import com.chestnut.common.security.web.BaseRestController; -import com.chestnut.contentcore.config.CMSPublishConfig; +import com.chestnut.contentcore.publish.IPublishStrategy; import com.chestnut.system.security.AdminUserType; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.connection.stream.StreamInfo; -import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -40,15 +38,14 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/cms/publish/") public class PublishLogController extends BaseRestController { - private final StringRedisTemplate redisTemplate; + private final IPublishStrategy publishStrategy; /** * 发布队列任务数量 */ @GetMapping("/taskCount") public R getPublishTaskCount() { - StreamInfo.XInfoStream info = redisTemplate.opsForStream().info(CMSPublishConfig.PublishStreamName); - return R.ok(info.streamLength()); + return R.ok(publishStrategy.getTaskCount()); } /** @@ -56,11 +53,7 @@ public class PublishLogController extends BaseRestController { */ @DeleteMapping("/clear") public R clearPublishTask() { - redisTemplate.delete(CMSPublishConfig.PublishStreamName); - try { - redisTemplate.opsForStream().createGroup(CMSPublishConfig.PublishStreamName, CMSPublishConfig.PublishConsumerGroup); - } catch (Exception ignored) { - } + publishStrategy.cleanTasks(); return R.ok(); } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java index e139653d..ab3f9b27 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java @@ -178,8 +178,6 @@ public class TemplateController extends BaseRestController { fileName = FileExUtils.normalizePath(fileName); String[] split = fileName.substring(0, fileName.indexOf(suffix)).split("/"); for (String item : split) { - System.out.println(item); - System.out.println(Pattern.matches("[a-zA-Z0-9_]+", item)); if (StringUtils.isEmpty(item) || !Pattern.matches("^[a-zA-Z0-9_]+$", item)) { return false; } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IPageWidget.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IPageWidget.java index ba3b95e2..dec3e20e 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IPageWidget.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/core/IPageWidget.java @@ -40,24 +40,24 @@ public interface IPageWidget { /** * 页面部件基础数据实例 */ - public CmsPageWidget getPageWidgetEntity(); + CmsPageWidget getPageWidgetEntity(); - public void setPageWidgetEntity(CmsPageWidget cmsPageWdiget); + void setPageWidgetEntity(CmsPageWidget cmsPageWdiget); /** * 操作人 * * @param loginUser */ - public void setOperator(LoginUser loginUser); + void setOperator(LoginUser loginUser); - public LoginUser getOperator(); + LoginUser getOperator(); - public void add(); + void add(); - public void save(); + void save(); - public void delete(); + void delete(); - public void publish() throws TemplateException, IOException; + void publish() throws TemplateException, IOException; } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CmsConfigurationDashboardVO.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CmsConfigurationDashboardVO.java new file mode 100644 index 00000000..897d2e61 --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/domain/vo/CmsConfigurationDashboardVO.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022-2024 兮玥(190785909@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.chestnut.contentcore.domain.vo; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class CmsConfigurationDashboardVO { + + private String publishStrategy; + + private String resourceRoot; +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java index 338153c5..48d3fb29 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/job/SitePublishJobHandler.java @@ -21,9 +21,10 @@ import com.chestnut.contentcore.domain.CmsCatalog; import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.fixed.dict.ContentStatus; -import com.chestnut.contentcore.publish.CatalogPublishTask; -import com.chestnut.contentcore.publish.ContentPublishTask; -import com.chestnut.contentcore.publish.SitePublishTask; +import com.chestnut.contentcore.publish.staticize.CatalogStaticizeType; +import com.chestnut.contentcore.publish.staticize.ContentStaticizeType; +import com.chestnut.contentcore.publish.staticize.SiteStaticizeType; +import com.chestnut.contentcore.publish.IPublishStrategy; import com.chestnut.contentcore.service.ICatalogService; import com.chestnut.contentcore.service.IContentService; import com.chestnut.contentcore.service.ISiteService; @@ -56,11 +57,7 @@ public class SitePublishJobHandler extends IJobHandler implements IScheduledHand private final IContentService contentService; - private final SitePublishTask sitePublisher; - - private final CatalogPublishTask catalogPublisher; - - private final ContentPublishTask contentPublisher; + private final IPublishStrategy publishStrategy; @Override public String getId() { @@ -92,16 +89,16 @@ public class SitePublishJobHandler extends IJobHandler implements IScheduledHand for (int i = 0; i * pageSize < total; i++) { Page page = contentService.page(new Page<>(i, pageSize, false), q); for (CmsContent xContent : page.getRecords()) { - contentPublisher.publish(xContent); + publishStrategy.publish(ContentStaticizeType.TYPE, xContent.getContentId().toString()); } } } // 发布栏目 for (CmsCatalog catalog : catalogList) { - catalogPublisher.publish(catalog); + publishStrategy.publish(CatalogStaticizeType.TYPE, catalog.getCatalogId().toString()); } // 发布站点 - sitePublisher.publish(site); + publishStrategy.publish(SiteStaticizeType.TYPE, site.getSiteId().toString()); } logger.info("Job '{}' completed, cost: {}ms", JOB_NAME, System.currentTimeMillis() - s); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ImageWatermarkArgsProperty.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ImageWatermarkArgsProperty.java index 230d74a1..9bc62773 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ImageWatermarkArgsProperty.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/properties/ImageWatermarkArgsProperty.java @@ -64,7 +64,6 @@ public class ImageWatermarkArgsProperty implements IProperty { public ImageWatermarkArgs getPropValue(Map configProps) { String v = MapUtils.getString(configProps, ID); if (StringUtils.isNotEmpty(v)) { - System.out.println(v); return JacksonUtils.from(v, ImageWatermarkArgs.class); } return defaultValue(); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/CmsStaticizeService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/CmsStaticizeService.java new file mode 100644 index 00000000..ac2d452b --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/CmsStaticizeService.java @@ -0,0 +1,24 @@ +package com.chestnut.contentcore.publish; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * CmsStaticizeService + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Service +@RequiredArgsConstructor +public class CmsStaticizeService { + + private final Map staticizeTypeMap; + + public IStaticizeType getStaticizeType(String type) { + return staticizeTypeMap.get(IStaticizeType.BEAN_PREFIX + type); + } +} + diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IPublishStrategy.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IPublishStrategy.java new file mode 100644 index 00000000..2ffa733e --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IPublishStrategy.java @@ -0,0 +1,27 @@ +package com.chestnut.contentcore.publish; + +/** + * IPublishStrategy + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public interface IPublishStrategy { + + /** + * 发布策略ID + */ + String getId(); + + /** + * 创建发布任务 + * + * @param dataType + * @param dataId + */ + void publish(String dataType, String dataId); + + long getTaskCount(); + + void cleanTasks(); +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IStaticizeType.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IStaticizeType.java new file mode 100644 index 00000000..3f45c7ab --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/IStaticizeType.java @@ -0,0 +1,15 @@ +package com.chestnut.contentcore.publish; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public interface IStaticizeType { + + Logger logger = LoggerFactory.getLogger("publish"); + + String BEAN_PREFIX = "CmsStaticizeType"; + + String getType(); + + void staticize(String dataId); +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/CatalogStaticizeType.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/CatalogStaticizeType.java new file mode 100644 index 00000000..38cd7fae --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/CatalogStaticizeType.java @@ -0,0 +1,167 @@ +package com.chestnut.contentcore.publish.staticize; + +import com.chestnut.common.async.AsyncTaskManager; +import com.chestnut.common.staticize.StaticizeService; +import com.chestnut.common.staticize.core.TemplateContext; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.common.utils.file.FileExUtils; +import com.chestnut.contentcore.core.impl.CatalogType_Link; +import com.chestnut.contentcore.core.impl.PublishPipeProp_DefaultListTemplate; +import com.chestnut.contentcore.core.impl.PublishPipeProp_IndexTemplate; +import com.chestnut.contentcore.core.impl.PublishPipeProp_ListTemplate; +import com.chestnut.contentcore.domain.CmsCatalog; +import com.chestnut.contentcore.domain.CmsPublishPipe; +import com.chestnut.contentcore.domain.CmsSite; +import com.chestnut.contentcore.properties.MaxPageOnContentPublishProperty; +import com.chestnut.contentcore.publish.IStaticizeType; +import com.chestnut.contentcore.service.ICatalogService; +import com.chestnut.contentcore.service.IPublishPipeService; +import com.chestnut.contentcore.service.ISiteService; +import com.chestnut.contentcore.service.ITemplateService; +import com.chestnut.contentcore.template.ITemplateType; +import com.chestnut.contentcore.template.impl.CatalogTemplateType; +import com.chestnut.contentcore.util.SiteUtils; +import com.chestnut.contentcore.util.TemplateUtils; +import freemarker.template.TemplateException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** + * CatalogStaticizeType + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@RequiredArgsConstructor +@Component(IStaticizeType.BEAN_PREFIX + CatalogStaticizeType.TYPE) +public class CatalogStaticizeType implements IStaticizeType { + + public static final String TYPE = "catalog"; + + private final ISiteService siteService; + + private final ICatalogService catalogService; + + private final IPublishPipeService publishPipeService; + + private final ITemplateService templateService; + + private final StaticizeService staticizeService; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void staticize(String dataId) { + Long catalogId = Long.valueOf(dataId); + if (IdUtils.validate(catalogId)) { + CmsCatalog catalog = this.catalogService.getCatalog(catalogId); + if (Objects.nonNull(catalog)) { + this.catalogStaticize(catalog); + } + } + } + + public void catalogStaticize(CmsCatalog catalog) { + CmsSite site = this.siteService.getSite(catalog.getSiteId()); + int maxPage = MaxPageOnContentPublishProperty.getValue(site.getConfigProps()); + this.catalogStaticize(catalog, maxPage); + } + + public void catalogStaticize(CmsCatalog catalog, int pageMax) { + if (!catalog.isStaticize() || !catalog.isVisible() || CatalogType_Link.ID.equals(catalog.getCatalogType())) { + return; + } + List publishPipes = this.publishPipeService.getPublishPipes(catalog.getSiteId()); + for (CmsPublishPipe pp : publishPipes) { + this.doCatalogStaticize(catalog, pp.getCode(), pageMax); + } + } + + private void doCatalogStaticize(CmsCatalog catalog, String publishPipeCode, int pageMax) { + CmsSite site = this.siteService.getSite(catalog.getSiteId()); + if (!catalog.isStaticize()) { + logger.warn("【{}】未启用静态化的栏目跳过静态化:{}", publishPipeCode, catalog.getName()); + return; + } + if (!catalog.isVisible()) { + logger.warn("【{}】不可见状态的栏目跳过静态化:{}", publishPipeCode, catalog.getName()); + return; + } + if (CatalogType_Link.ID.equals(catalog.getCatalogType())) { + logger.warn("【{}】链接类型栏目跳过静态化:{}", publishPipeCode, catalog.getName()); + return; + } + String indexTemplate = PublishPipeProp_IndexTemplate.getValue(publishPipeCode, catalog.getPublishPipeProps()); + String listTemplate = PublishPipeProp_ListTemplate.getValue(publishPipeCode, catalog.getPublishPipeProps()); + if (StringUtils.isEmpty(listTemplate)) { + listTemplate = PublishPipeProp_DefaultListTemplate.getValue(publishPipeCode, site.getPublishPipeProps()); // 取站点默认模板 + } + File indexTemplateFile = this.templateService.findTemplateFile(site, indexTemplate, publishPipeCode); + File listTemplateFile = this.templateService.findTemplateFile(site, listTemplate, publishPipeCode); + if (indexTemplateFile == null && listTemplateFile == null) { + logger.warn(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}]栏目首页模板和列表页模板未配置或不存在:{1}", + publishPipeCode, catalog.getCatalogId() + "#" + catalog.getName()))); + return; + } + String siteRoot = SiteUtils.getSiteRoot(site, publishPipeCode); + String dirPath = siteRoot + catalog.getPath(); + FileExUtils.mkdirs(dirPath); + String staticSuffix = site.getStaticSuffix(publishPipeCode); // 静态化文件类型 + + // 发布栏目首页 + long s = System.currentTimeMillis(); + if (Objects.nonNull(indexTemplateFile)) { + try { + String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, indexTemplate); + TemplateContext templateContext = new TemplateContext(templateKey, false, publishPipeCode); + templateContext.setDirectory(dirPath); + templateContext.setFirstFileName("index" + StringUtils.DOT + staticSuffix); + // init template variables + TemplateUtils.initGlobalVariables(site, templateContext); + // init templateType variables + ITemplateType templateType = templateService.getTemplateType(CatalogTemplateType.TypeId); + templateType.initTemplateData(catalog.getCatalogId(), templateContext); + // staticize + this.staticizeService.process(templateContext); + logger.debug("[{}]栏目首页模板解析:{},耗时:{}ms", publishPipeCode, catalog.getCatalogId() + "#" + catalog.getName(), (System.currentTimeMillis() - s)); + } catch (IOException | TemplateException e) { + logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}]栏目首页解析失败:{1}", + publishPipeCode, catalog.getCatalogId() + "#" + catalog.getName())), e); + } + } + // 发布栏目列表页 + if (Objects.nonNull(listTemplateFile)) { + s = System.currentTimeMillis(); + try { + String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, listTemplate); + TemplateContext templateContext = new TemplateContext(templateKey, false, publishPipeCode); + templateContext.setMaxPageNo(pageMax); + templateContext.setDirectory(dirPath); + String name = Objects.nonNull(indexTemplateFile) ? "list" : "index"; + templateContext.setFirstFileName(name + StringUtils.DOT + staticSuffix); + templateContext.setOtherFileName( + name + "_" + TemplateContext.PlaceHolder_PageNo + StringUtils.DOT + staticSuffix); + // init template variables + TemplateUtils.initGlobalVariables(site, templateContext); + // init templateType variables + ITemplateType templateType = templateService.getTemplateType(CatalogTemplateType.TypeId); + templateType.initTemplateData(catalog.getCatalogId(), templateContext); + // staticize + this.staticizeService.process(templateContext); + logger.debug("[{}]栏目列表模板解析:{},耗时:{}ms", publishPipeCode, catalog.getCatalogId() + "#" + catalog.getName(), (System.currentTimeMillis() - s)); + } catch (Exception e1) { + logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}]栏目列表页解析失败:{1}", + publishPipeCode, catalog.getCatalogId() + "#" + catalog.getName())), e1); + } + } + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/ContentStaticizeType.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/ContentStaticizeType.java new file mode 100644 index 00000000..c231047a --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/ContentStaticizeType.java @@ -0,0 +1,208 @@ +package com.chestnut.contentcore.publish.staticize; + +import com.chestnut.common.async.AsyncTaskManager; +import com.chestnut.common.staticize.StaticizeService; +import com.chestnut.common.staticize.core.TemplateContext; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.contentcore.core.IPublishPipeProp; +import com.chestnut.contentcore.core.impl.PublishPipeProp_ContentTemplate; +import com.chestnut.contentcore.domain.CmsCatalog; +import com.chestnut.contentcore.domain.CmsContent; +import com.chestnut.contentcore.domain.CmsPublishPipe; +import com.chestnut.contentcore.domain.CmsSite; +import com.chestnut.contentcore.publish.IStaticizeType; +import com.chestnut.contentcore.service.*; +import com.chestnut.contentcore.template.ITemplateType; +import com.chestnut.contentcore.template.impl.ContentTemplateType; +import com.chestnut.contentcore.util.ContentUtils; +import com.chestnut.contentcore.util.SiteUtils; +import com.chestnut.contentcore.util.TemplateUtils; +import freemarker.template.TemplateException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** + * ContentStaticizeType + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@RequiredArgsConstructor +@Component(IStaticizeType.BEAN_PREFIX + ContentStaticizeType.TYPE) +public class ContentStaticizeType implements IStaticizeType { + + public static final String TYPE = "content"; + + private final ISiteService siteService; + + private final ICatalogService catalogService; + + private final IContentService contentService; + + private final IPublishPipeService publishPipeService; + + private final ITemplateService templateService; + + private final StaticizeService staticizeService; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void staticize(String dataId) { + Long contentId = Long.valueOf(dataId); + if (IdUtils.validate(contentId)) { + CmsContent content = this.contentService.getById(contentId); + if (Objects.nonNull(content)) { + this.contentStaticize(content); + } + } + } + + public void contentStaticize(CmsContent cmsContent) { + List publishPipes = publishPipeService.getPublishPipes(cmsContent.getSiteId()); + // 发布内容 + for (CmsPublishPipe pp : publishPipes) { + doContentStaticize(cmsContent, pp.getCode()); + // 内容扩展模板静态化 + doContentExStaticize(cmsContent, pp.getCode()); + } + } + + private void doContentStaticize(CmsContent content, String publishPipeCode) { + CmsSite site = this.siteService.getSite(content.getSiteId()); + CmsCatalog catalog = this.catalogService.getCatalog(content.getCatalogId()); + if (!catalog.isStaticize()) { + logger.warn("[ {} ]栏目设置不静态化[ {}#{} ]:{}", publishPipeCode, site.getName(), + catalog.getName(), content.getTitle()); + return; // 不静态化直接跳过 + } + if (content.isLinkContent()) { + logger.warn("[ {} ]标题内容不需要静态化[ {}#{} ]:{}", publishPipeCode, site.getName(), + catalog.getName(), content.getTitle()); + return; // 标题内容不需要静态化 + } + final String detailTemplate = getDetailTemplate(site, catalog, content, publishPipeCode); + File templateFile = this.templateService.findTemplateFile(site, detailTemplate, publishPipeCode); + if (templateFile == null) { + logger.warn(AsyncTaskManager.addErrMessage( + StringUtils.messageFormat("[ {0} ]内容模板未设置或文件不存在[ {1}#{2} ]:{3}", + publishPipeCode, site.getName(), catalog.getName(), content.getTitle()))); + return; + } + try { + long s = System.currentTimeMillis(); + // 自定义模板上下文 + String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, detailTemplate); + TemplateContext templateContext = new TemplateContext(templateKey, false, publishPipeCode); + // init template datamode + TemplateUtils.initGlobalVariables(site, templateContext); + // init templateType data to datamode + ITemplateType templateType = this.templateService.getTemplateType(ContentTemplateType.TypeId); + templateType.initTemplateData(content.getContentId(), templateContext); + // 静态化文件地址 + this.setContentStaticPath(site, catalog, content, templateContext); + // 静态化 + this.staticizeService.process(templateContext); + logger.debug("[ {} ]内容详情页模板解析[ {}#{} ]:{},耗时:{}ms", publishPipeCode, site.getName(), + catalog.getName(), content.getTitle(), (System.currentTimeMillis() - s)); + } catch (TemplateException | IOException e) { + logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}]内容详情页解析失败:[{1}]{2}", + publishPipeCode, catalog.getName(), content.getTitle())), e); + } + } + + private void setContentStaticPath(CmsSite site, CmsCatalog catalog, CmsContent content, TemplateContext context) { + String siteRoot = SiteUtils.getSiteRoot(site, context.getPublishPipeCode()); + if (StringUtils.isNotBlank(content.getStaticPath())) { + String dir = ""; + String filename = content.getStaticPath(); + if (filename.indexOf("/") > 0) { + dir = filename.substring(0, filename.lastIndexOf("/") + 1); + filename = filename.substring(filename.lastIndexOf("/") + 1); + } + context.setDirectory(siteRoot + dir); + context.setFirstFileName(filename); + String name = filename.substring(0, filename.lastIndexOf(".")); + String suffix = filename.substring(filename.lastIndexOf(".")); + context.setOtherFileName(name + "_" + TemplateContext.PlaceHolder_PageNo + suffix); + } else { + context.setDirectory(siteRoot + catalog.getPath()); + String suffix = site.getStaticSuffix(context.getPublishPipeCode()); + context.setFirstFileName(content.getContentId() + StringUtils.DOT + suffix); + context.setOtherFileName( + content.getContentId() + "_" + TemplateContext.PlaceHolder_PageNo + StringUtils.DOT + suffix); + } + } + + private void doContentExStaticize(CmsContent content, String publishPipeCode) { + CmsSite site = this.siteService.getSite(content.getSiteId()); + CmsCatalog catalog = this.catalogService.getCatalog(content.getCatalogId()); + if (!catalog.isStaticize()) { + logger.warn("[{}]栏目设置不静态化[{}#{}]:{}", publishPipeCode, site.getName(), catalog.getName(), content.getTitle()); + return; // 不静态化直接跳过 + } + if (content.isLinkContent()) { + logger.warn("[{}]标题内容不需要静态化[ {}#{} ]:{}", publishPipeCode, site.getName(), catalog.getName(), content.getTitle()); + return; // 标题内容不需要静态化 + } + String exTemplate = ContentUtils.getContentExTemplate(content, catalog, publishPipeCode); + if (StringUtils.isEmpty(exTemplate)) { + return; // 未设置扩展模板直接跳过 + } + File templateFile = this.templateService.findTemplateFile(site, exTemplate, publishPipeCode); + if (templateFile == null) { + logger.warn("[{}]内容扩展模板未设置或文件不存在[ {}#{} ]:{}", publishPipeCode, site.getName(), catalog.getName(), content.getTitle()); + return; + } + try { + long s = System.currentTimeMillis(); + // 自定义模板上下文 + String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, exTemplate); + TemplateContext templateContext = new TemplateContext(templateKey, false, publishPipeCode); + // init template datamode + TemplateUtils.initGlobalVariables(site, templateContext); + // init templateType data to datamode + ITemplateType templateType = this.templateService.getTemplateType(ContentTemplateType.TypeId); + templateType.initTemplateData(content.getContentId(), templateContext); + // 静态化文件地址 + String siteRoot = SiteUtils.getSiteRoot(site, publishPipeCode); + templateContext.setDirectory(siteRoot + catalog.getPath()); + String fileName = ContentUtils.getContextExFileName(content.getContentId(), site.getStaticSuffix(publishPipeCode)); + templateContext.setFirstFileName(fileName); + // 静态化 + this.staticizeService.process(templateContext); + logger.debug("[{}]内容扩展模板解析[ {}#{} ]:{},耗时:{}ms", publishPipeCode, site.getName(), + catalog.getName(), content.getTitle(), (System.currentTimeMillis() - s)); + } catch (TemplateException | IOException e) { + logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}] 内容扩展模板解析失败 [{1}#{2}]:{3}", + publishPipeCode, site.getName(), catalog.getName(), content.getTitle())), e); + } + } + + private String getDetailTemplate(CmsSite site, CmsCatalog catalog, CmsContent content, String publishPipeCode) { + String detailTemplate = PublishPipeProp_ContentTemplate.getValue(publishPipeCode, + content.getPublishPipeProps()); + if (StringUtils.isEmpty(detailTemplate)) { + // 无内容独立模板取栏目配置 + detailTemplate = this.publishPipeService.getPublishPipePropValue( + IPublishPipeProp.DetailTemplatePropPrefix + content.getContentType(), publishPipeCode, + catalog.getPublishPipeProps()); + if (StringUtils.isEmpty(detailTemplate)) { + // 无栏目配置去站点默认模板配置 + detailTemplate = this.publishPipeService.getPublishPipePropValue( + IPublishPipeProp.DefaultDetailTemplatePropPrefix + content.getContentType(), publishPipeCode, + site.getPublishPipeProps()); + } + } + return detailTemplate; + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java new file mode 100644 index 00000000..9455a62b --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/staticize/SiteStaticizeType.java @@ -0,0 +1,95 @@ +package com.chestnut.contentcore.publish.staticize; + +import com.chestnut.common.async.AsyncTaskManager; +import com.chestnut.common.staticize.StaticizeService; +import com.chestnut.common.staticize.core.TemplateContext; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.contentcore.domain.CmsSite; +import com.chestnut.contentcore.publish.IStaticizeType; +import com.chestnut.contentcore.service.IPublishPipeService; +import com.chestnut.contentcore.service.ISiteService; +import com.chestnut.contentcore.service.ITemplateService; +import com.chestnut.contentcore.template.ITemplateType; +import com.chestnut.contentcore.template.impl.SiteTemplateType; +import com.chestnut.contentcore.util.SiteUtils; +import com.chestnut.contentcore.util.TemplateUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.util.Objects; + +/** + * SiteStaticizeType + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@RequiredArgsConstructor +@Component(IStaticizeType.BEAN_PREFIX + SiteStaticizeType.TYPE) +public class SiteStaticizeType implements IStaticizeType { + + public static final String TYPE = "site"; + + private final ISiteService siteService; + + private final IPublishPipeService publishPipeService; + + private final ITemplateService templateService; + + private final StaticizeService staticizeService; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void staticize(String dataId) { + Long siteId = Long.valueOf(dataId); + if (IdUtils.validate(siteId)) { + CmsSite site = this.siteService.getSite(siteId); + if (Objects.nonNull(site)) { + this.siteStaticize(site); + } + } + } + + public void siteStaticize(CmsSite site) { + this.publishPipeService.getPublishPipes(site.getSiteId()) + .forEach(pp -> doSiteStaticize(site, pp.getCode())); + } + + private void doSiteStaticize(CmsSite site, String publishPipeCode) { + try { + AsyncTaskManager + .setTaskMessage(StringUtils.messageFormat("[{0}]正在发布站点首页:{1}", publishPipeCode, site.getName())); + + String indexTemplate = site.getIndexTemplate(publishPipeCode); + File templateFile = this.templateService.findTemplateFile(site, indexTemplate, publishPipeCode); + if (Objects.isNull(templateFile)) { + logger.warn(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}]站点首页模板未配置或不存在:{1}", + publishPipeCode, site.getSiteId() + "#" + site.getName()))); + return; + } + // 模板ID = 通道:站点目录:模板文件名 + String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, indexTemplate); + TemplateContext context = new TemplateContext(templateKey, false, publishPipeCode); + // init template datamode + TemplateUtils.initGlobalVariables(site, context); + // init templateType data to datamode + ITemplateType templateType = templateService.getTemplateType(SiteTemplateType.TypeId); + templateType.initTemplateData(site.getSiteId(), context); + + long s = System.currentTimeMillis(); + context.setDirectory(SiteUtils.getSiteRoot(site, publishPipeCode)); + context.setFirstFileName("index" + StringUtils.DOT + site.getStaticSuffix(publishPipeCode)); + this.staticizeService.process(context); + logger.debug("[{}]首页模板解析:{},耗时:{}ms", publishPipeCode, site.getName(), (System.currentTimeMillis() - s)); + } catch (Exception e) { + logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}][{1}]站点首页解析失败:{2}", + publishPipeCode, site.getName(), e.getMessage())), e); + } + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/PublishTaskReceiver.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/PublishTaskReceiver.java new file mode 100644 index 00000000..6743c3ec --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/PublishTaskReceiver.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022-2024 兮玥(190785909@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.chestnut.contentcore.publish.strategies; + +import com.chestnut.contentcore.publish.CmsStaticizeService; +import com.chestnut.contentcore.publish.IStaticizeType; +import com.chestnut.contentcore.publish.strategies.RedisStreamPublishStrategy; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.MapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.stream.Consumer; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.stream.StreamListener; + +import java.util.Map; +import java.util.Objects; + +/** + * 发布任务消费监听 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Slf4j +@RequiredArgsConstructor +public class PublishTaskReceiver implements StreamListener> { + + private final static Logger logger = LoggerFactory.getLogger("publish"); + + private final CmsStaticizeService cmsStaticizeService; + + private final StringRedisTemplate redisTemplate; + + @Setter + @Getter + private Consumer consumer; + + @Override + public void onMessage(MapRecord message) { + String stream = message.getStream(); + if (Objects.nonNull(stream)) { + try { + Map map = message.getValue(); + String type = MapUtils.getString(map, "type"); + + IStaticizeType staticizeType = cmsStaticizeService.getStaticizeType(type); + if (Objects.nonNull(staticizeType)) { + staticizeType.staticize(map.get("id")); + } + } catch(Exception e) { + logger.error("Publish err.", e); + } finally { + redisTemplate.opsForStream().acknowledge( + stream, + RedisStreamPublishStrategy.PublishConsumerGroup, + message.getId().getValue() + ); + redisTemplate.opsForStream().delete(stream, message.getId().getValue()); + } + } + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisSetPublishStrategy.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisSetPublishStrategy.java new file mode 100644 index 00000000..20c21a63 --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisSetPublishStrategy.java @@ -0,0 +1,99 @@ +package com.chestnut.contentcore.publish.strategies; + +import com.chestnut.common.async.AsyncTask; +import com.chestnut.contentcore.config.properties.CMSPublishProperties; +import com.chestnut.contentcore.publish.CmsStaticizeService; +import com.chestnut.contentcore.publish.IPublishStrategy; +import com.chestnut.contentcore.publish.IStaticizeType; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.PeriodicTrigger; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.Objects; + +/** + * 发布策略:Redis Set + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = CMSPublishProperties.PREFIX, name = "strategy", havingValue = RedisSetPublishStrategy.ID) +public class RedisSetPublishStrategy implements IPublishStrategy, CommandLineRunner { + + public static final String ID = "RedisSet"; + + static final String CACHE_NAME = "cms:publish:list"; + + private final StringRedisTemplate redisTemplate; + + private final CMSPublishProperties properties; + + private final CmsStaticizeService cmsStaticizeService; + + private final ThreadPoolTaskScheduler threadPoolTaskScheduler; + + @Override + public String getId() { + return ID; + } + + @Override + public void publish(String dataType, String dataId) { + redisTemplate.opsForSet().add(CACHE_NAME, dataType+"-"+dataId); + } + + @Override + public long getTaskCount() { + Long size = redisTemplate.opsForSet().size(CACHE_NAME); + return Objects.requireNonNullElse(size, 0L); + } + + @Override + public void cleanTasks() { + redisTemplate.opsForSet().remove(CACHE_NAME); + } + + @Override + public void run(String... args) throws Exception { + for (int i = 1; i <= properties.getConsumerCount(); i++) { + // 创建一个3秒间隔执行的定时任务 + PeriodicTrigger periodicTrigger = new PeriodicTrigger(Duration.ofSeconds(3L)); + periodicTrigger.setFixedRate(false); + + AsyncTask task = new AsyncTask() { + + @Override + public void run0() { + String data = null; + do { + try { + data = redisTemplate.opsForSet().pop(CACHE_NAME); + if (Objects.nonNull(data)) { + String[] split = data.split("-"); + String dataType = split[0]; + String dataId = split[1]; + + IStaticizeType staticizeType = cmsStaticizeService.getStaticizeType(dataType); + if (Objects.nonNull(staticizeType)) { + staticizeType.staticize(dataId); + } + } + } catch (Exception e) { + IStaticizeType.logger.error("静态化失败", e); + } + } while(Objects.nonNull(data)); + } + }; + task.setTaskId("cms-publish-" + i); + task.setType("CMS-PUBLISH"); + threadPoolTaskScheduler.schedule(task, periodicTrigger); + } + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisStreamPublishStrategy.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisStreamPublishStrategy.java new file mode 100644 index 00000000..ae6c5548 --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/RedisStreamPublishStrategy.java @@ -0,0 +1,120 @@ +package com.chestnut.contentcore.publish.strategies; + +import com.chestnut.contentcore.config.properties.CMSPublishProperties; +import com.chestnut.contentcore.publish.CmsStaticizeService; +import com.chestnut.contentcore.publish.IPublishStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.stream.*; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.stream.StreamMessageListenerContainer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.Map; + +/** + * 发布策略:Redis Stream + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = CMSPublishProperties.PREFIX, name = "strategy", havingValue = RedisStreamPublishStrategy.ID) +public class RedisStreamPublishStrategy implements IPublishStrategy { + + public static final String ID = "RedisStream"; + + public static final String PublishStreamName = "ChestnutCMSPublishStream"; + + public static final String PublishConsumerGroup = "ChestnutCMSPublishConsumerGroup"; + + private final CMSPublishProperties properties; + + private final StringRedisTemplate redisTemplate; + + private final CmsStaticizeService cmsStaticizeService; + + @Override + public String getId() { + return ID; + } + + @Override + public void publish(String dataType, String dataId) { + MapRecord record = MapRecord.create(PublishStreamName, Map.of( + "type", dataType, + "id", dataId + )); + redisTemplate.opsForStream().add(record); + } + + @Override + public long getTaskCount() { + StreamInfo.XInfoStream info = redisTemplate.opsForStream().info(PublishStreamName); + return info.streamLength(); + } + + @Override + public void cleanTasks() { + try { + redisTemplate.delete(PublishStreamName); + redisTemplate.opsForStream().createGroup(PublishStreamName, PublishConsumerGroup); + } catch (Exception ignored) { + } + } + + @Bean + public StreamMessageListenerContainer> streamMessageListenerContainer() { + // 启动清理消息队列数据 + if (properties.isClearOnStart()) { + redisTemplate.delete(PublishStreamName); + } + // 监听容器配置 + StreamMessageListenerContainer.StreamMessageListenerContainerOptions> streamMessageListenerContainerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions + .builder() + .batchSize(10) // 一次拉取消息数量 + .pollTimeout(Duration.ofSeconds(2)) // 拉取消息超时时间 + .executor(cmsPublishThreadPoolTaskExecutor()) + .build(); + // 创建监听容器 + StreamMessageListenerContainer> container = StreamMessageListenerContainer + .create(redisTemplate.getRequiredConnectionFactory(), streamMessageListenerContainerOptions); + //创建消费者组 + try { + redisTemplate.opsForStream().createGroup(PublishStreamName, PublishConsumerGroup); + } catch (Exception e) { + log.info("消费者组:{} 已存在", PublishConsumerGroup); + } + // 添加消费者 + for (int i = 0; i < properties.getConsumerCount(); i++) { + Consumer consumer = Consumer.from(PublishConsumerGroup, "cms-publish-consumer-" + i); + PublishTaskReceiver publishTaskReceiver = new PublishTaskReceiver(cmsStaticizeService, redisTemplate); + publishTaskReceiver.setConsumer(consumer); + container.receive(consumer, StreamOffset.create(PublishStreamName, ReadOffset.lastConsumed()), publishTaskReceiver); + } + container.start(); + return container; + } + + @Bean + ThreadPoolTaskExecutor cmsPublishThreadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix(properties.getPool().getThreadNamePrefix()); + executor.setCorePoolSize(properties.getPool().getCoreSize()); + executor.setQueueCapacity(properties.getPool().getQueueCapacity()); + executor.setMaxPoolSize(properties.getPool().getMaxSize()); + executor.setKeepAliveSeconds((int) properties.getPool().getKeepAlive().getSeconds()); + executor.setAllowCoreThreadTimeOut(this.properties.getPool().isAllowCoreThreadTimeout()); + executor.setWaitForTasksToCompleteOnShutdown(properties.getShutdown().isAwaitTermination()); + executor.setAwaitTerminationSeconds((int) properties.getShutdown().getAwaitTerminationPeriod().toSeconds()); + log.info("Cms publish task executor initialize: {}", executor.getThreadNamePrefix()); + executor.initialize(); + return executor; + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/ThreadPoolPublishStrategy.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/ThreadPoolPublishStrategy.java new file mode 100644 index 00000000..2edd795f --- /dev/null +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/publish/strategies/ThreadPoolPublishStrategy.java @@ -0,0 +1,75 @@ +package com.chestnut.contentcore.publish.strategies; + +import com.chestnut.contentcore.config.properties.CMSPublishProperties; +import com.chestnut.contentcore.publish.CmsStaticizeService; +import com.chestnut.contentcore.publish.IPublishStrategy; +import com.chestnut.contentcore.publish.IStaticizeType; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 发布策略:ThreadPool + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = CMSPublishProperties.PREFIX, name = "strategy", havingValue = ThreadPoolPublishStrategy.ID) +public class ThreadPoolPublishStrategy implements IPublishStrategy, CommandLineRunner { + + public static final String ID = "ThreadPool"; + + private final CMSPublishProperties properties; + + private final CmsStaticizeService cmsStaticizeService; + + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Override + public String getId() { + return ID; + } + + @Override + public void publish(String dataType, String dataId) { + IStaticizeType staticizeType = cmsStaticizeService.getStaticizeType(dataType); + if (Objects.nonNull(staticizeType)) { + threadPoolTaskExecutor.execute(() -> { + staticizeType.staticize(dataId); + }); + } + } + + @Override + public long getTaskCount() { + // 返回线程池队列信息 + return threadPoolTaskExecutor.getQueueSize(); + } + + @Override + public void cleanTasks() { + // 清空线程池队列 + threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().clear(); + } + + @Override + public void run(String... args) throws Exception { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix(properties.getPool().getThreadNamePrefix()); + executor.setCorePoolSize(properties.getPool().getCoreSize()); + executor.setQueueCapacity(properties.getPool().getQueueCapacity()); + executor.setMaxPoolSize(properties.getPool().getMaxSize()); + executor.setKeepAliveSeconds((int) properties.getPool().getKeepAlive().getSeconds()); + executor.setAllowCoreThreadTimeOut(this.properties.getPool().isAllowCoreThreadTimeout()); + executor.setWaitForTasksToCompleteOnShutdown(properties.getShutdown().isAwaitTermination()); + executor.setAwaitTerminationSeconds((int) properties.getShutdown().getAwaitTerminationPeriod().toSeconds()); + executor.initialize(); + this.threadPoolTaskExecutor = executor; + } +} diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java index 833c004b..e7b20245 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ICatalogService.java @@ -26,6 +26,8 @@ import com.chestnut.contentcore.domain.dto.*; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; public interface ICatalogService extends IService { @@ -52,7 +54,7 @@ public interface ICatalogService extends IService { * @param catalogs * @return */ - List> buildCatalogTreeData(List catalogs); + List> buildCatalogTreeData(List catalogs, BiConsumer> consumer); /** * 校验栏目别名、目录是否重复 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java index 22de4e27..86f7b6aa 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/CatalogServiceImpl.java @@ -63,6 +63,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Collectors; @Service @@ -129,7 +130,7 @@ public class CatalogServiceImpl extends ServiceImpl> buildCatalogTreeData(List catalogs) { + public List> buildCatalogTreeData(List catalogs, BiConsumer> consumer) { if (Objects.isNull(catalogs)) { return List.of(); } @@ -142,6 +143,7 @@ public class CatalogServiceImpl extends ServiceImpl q = new LambdaQueryWrapper() .eq(CmsContent::getCopyId, content.getContentEntity().getContentId()) .eq(CmsContent::getCopyType, ContentCopyType.Mapping); List mappingContents = contentService.list(q); for (CmsContent mappingContent : mappingContents) { - contentPublishTask.publish(mappingContent); + publishStrategy.publish(ContentStaticizeType.TYPE, mappingContent.getContentId().toString()); } } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/TemplateServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/TemplateServiceImpl.java index 2d213054..dc6e1b3d 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/TemplateServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/TemplateServiceImpl.java @@ -112,7 +112,6 @@ public class TemplateServiceImpl extends ServiceImpl t.getPublishPipeCode().equals(pp.getCode()) && t.getPath().equals(path)) .findFirst(); opt.ifPresentOrElse(t -> { - System.out.println("scan template: " + file.getName() + "|" + file.lastModified() + " = " + t.getModifyTime()); if (t.getModifyTime() != file.lastModified()) { try { t.setFilesize(file.length()); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsIncludeTag.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsIncludeTag.java index adf966eb..c7259c4f 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsIncludeTag.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/template/tag/CmsIncludeTag.java @@ -29,10 +29,9 @@ import com.chestnut.contentcore.service.ITemplateService; import com.chestnut.contentcore.util.SiteUtils; import com.chestnut.contentcore.util.TemplateUtils; import freemarker.core.Environment; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import freemarker.template.TemplateModel; +import freemarker.template.*; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.FileUtils; import org.springframework.stereotype.Component; @@ -47,6 +46,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +@Slf4j @RequiredArgsConstructor @Component public class CmsIncludeTag extends AbstractTag { @@ -128,7 +128,11 @@ public class CmsIncludeTag extends AbstractTag { if (context.isPreview()) { Template includeTemplate = env.getTemplateForInclusion(includeTemplateKey, StandardCharsets.UTF_8.displayName(), true); - env.setVariable("IncludeRequest", wrap(env, StringUtils.splitToMap(params, "&", "="))); + Map paramsMap = StringUtils.splitToMap(params, "&", "="); + Map mergeParams = mergeRequestVariable(env, paramsMap); + env.setVariable(TemplateUtils.TemplateVariable_Request, wrap(env, mergeParams)); + // TODO 兼容历史版本,下个大版本移除IncludeRequest模板变量 + env.setVariable("IncludeRequest", wrap(env, mergeParams)); env.include(includeTemplate); } else if (virtual) { // 动态模板 @@ -159,6 +163,23 @@ public class CmsIncludeTag extends AbstractTag { return null; } + private Map mergeRequestVariable(Environment env, Map params) throws TemplateModelException { + TemplateModel variable = env.getVariable(TemplateUtils.TemplateVariable_Request); + if (Objects.nonNull(variable)) { + if (variable instanceof TemplateHashModelEx2 req) { + for (TemplateHashModelEx2.KeyValuePairIterator iterator = req.keyValuePairIterator();iterator.hasNext();) { + TemplateHashModelEx2.KeyValuePair next = iterator.next(); + String key = ((SimpleScalar) next.getKey()).getAsString(); + if (params.containsKey(key)) { + log.warn("<@cms_include> file parameter `{}` conflicts with the Request parameter.", key); + } + params.put(key, ((SimpleScalar) next.getValue()).getAsString()); + } + } + } + return params; + } + /** * 生成包含模板静态化内容 */ @@ -169,7 +190,10 @@ public class CmsIncludeTag extends AbstractTag { env.setOut(writer); Template includeTemplate = env.getTemplateForInclusion(includeTemplateName, StandardCharsets.UTF_8.displayName(), true); - env.setVariable("IncludeRequest", wrap(env, params)); + Map mergeParams = mergeRequestVariable(env, params); + env.setVariable(TemplateUtils.TemplateVariable_Request, wrap(env, mergeParams)); + // TODO 兼容历史版本,下个大版本移除IncludeRequest模板变量 + env.setVariable("IncludeRequest", wrap(env, mergeParams)); env.include(includeTemplate); return writer.getBuffer().toString(); } finally { diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java index 8bbf86bc..5cdd30d2 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java @@ -38,6 +38,16 @@ import java.util.Map; @RequiredArgsConstructor public class TemplateUtils { + /** + * 模板变量:请求参数 + */ + public final static String TemplateVariable_Request = "Request"; + + /** + * 模板变量:<@cms_include>标签file属性请求参数 + */ + public final static String TemplateVariable_IncludeRequest = "IncludeRequest"; + /** * 模板变量:预览模式登录用户token键名 */ diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java index d3b86a22..6cb9e2c8 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/core/impl/MemberDynamicPageInitData.java @@ -16,6 +16,7 @@ package com.chestnut.cms.dynamic.core.impl; import com.chestnut.cms.dynamic.core.IDynamicPageInitData; +import com.chestnut.cms.member.CmsMemberConstants; import com.chestnut.common.security.domain.LoginUser; import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.member.security.StpMemberUtil; @@ -49,8 +50,9 @@ public class MemberDynamicPageInitData implements IDynamicPageInitData { public void initTemplateData(TemplateContext context, Map parameters) { if (StpMemberUtil.isLogin()) { LoginUser loginUser = StpMemberUtil.getLoginUser(); - context.getVariables().put("Member", loginUser.getUser()); - context.getVariables().put("MemberResourcePrefix", MemberUtils.getMemberResourcePrefix(context.isPreview())); + context.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, loginUser.getUser()); + context.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX, + MemberUtils.getMemberResourcePrefix(context.isPreview())); } } } diff --git a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java index 8fe900c3..8836547e 100644 --- a/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java +++ b/chestnut-cms/chestnut-cms-dynamic/src/main/java/com/chestnut/cms/dynamic/service/impl/DynamicPageServiceImpl.java @@ -200,7 +200,7 @@ public class DynamicPageServiceImpl extends ServiceImpl { diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/CmsMemberConstants.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/CmsMemberConstants.java index a06ca63e..8e23d935 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/CmsMemberConstants.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/CmsMemberConstants.java @@ -26,4 +26,14 @@ public interface CmsMemberConstants { String MEMBER_FAVORITES_DATA_TYPE = "cms_content"; String MEMBER_LIKE_DATA_TYPE = "cms_content"; + + /** + * 模板变量:会员信息 + */ + String TEMPLATE_VARIABLE_MEMBER = "Member"; + + /** + * 模板变量:会员资源前缀 + */ + String TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX = "MemberResourcePrefix"; } diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountBindEmailDynamicPageType.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountBindEmailDynamicPageType.java index 7862da13..1d2d5ed7 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountBindEmailDynamicPageType.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountBindEmailDynamicPageType.java @@ -15,10 +15,12 @@ */ package com.chestnut.cms.member.impl; +import com.chestnut.cms.member.CmsMemberConstants; import com.chestnut.cms.member.publishpipe.PublishPipeProp_MemberBindEmailTemplate; import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.common.utils.ServletUtils; import com.chestnut.contentcore.core.IDynamicPageType; +import com.chestnut.contentcore.util.TemplateUtils; import com.chestnut.member.domain.Member; import com.chestnut.member.domain.vo.MemberCache; import com.chestnut.member.service.IMemberService; @@ -95,8 +97,9 @@ public class AccountBindEmailDynamicPageType implements IDynamicPageType { public void initTemplateData(Map parameters, TemplateContext templateContext) { Long memberId = MapUtils.getLong(parameters, "memberId"); Member member = this.memberService.getById(memberId); - templateContext.getVariables().put("Member", member); - templateContext.getVariables().put("MemberResourcePrefix", MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); - templateContext.getVariables().put("Request", parameters); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, member); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX, + MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, parameters); } } diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountCentreDynamicPageType.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountCentreDynamicPageType.java index 564198e1..8082042c 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountCentreDynamicPageType.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountCentreDynamicPageType.java @@ -15,9 +15,11 @@ */ package com.chestnut.cms.member.impl; +import com.chestnut.cms.member.CmsMemberConstants; import com.chestnut.cms.member.publishpipe.PublishPipeProp_AccountCentreTemplate; import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.contentcore.core.IDynamicPageType; +import com.chestnut.contentcore.util.TemplateUtils; import com.chestnut.member.domain.vo.MemberCache; import com.chestnut.member.service.IMemberStatDataService; import com.chestnut.member.util.MemberUtils; @@ -92,9 +94,10 @@ public class AccountCentreDynamicPageType implements IDynamicPageType { Long siteId = MapUtils.getLong(parameters, "sid"); Long memberId = MapUtils.getLong(parameters, "memberId"); MemberCache member = this.memberStatDataService.getMemberCache(memberId); - templateContext.getVariables().put("Member", member); - templateContext.getVariables().put("MemberResourcePrefix", MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); - templateContext.getVariables().put("Request", parameters); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, member); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX, + MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, parameters); templateContext.setPageIndex(MapUtils.getIntValue(parameters, "page", 1)); String link = "account/" + memberId + "?type=" + parameters.get("type"); diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountContributeDynamicPageType.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountContributeDynamicPageType.java index dfff7779..1a1c0716 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountContributeDynamicPageType.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountContributeDynamicPageType.java @@ -17,6 +17,7 @@ package com.chestnut.cms.member.impl; import com.chestnut.article.domain.CmsArticleDetail; import com.chestnut.article.service.IArticleService; +import com.chestnut.cms.member.CmsMemberConstants; import com.chestnut.cms.member.domain.vo.ContributeArticleVO; import com.chestnut.cms.member.publishpipe.PublishPipeProp_MemberContributeTemplate; import com.chestnut.common.staticize.core.TemplateContext; @@ -27,6 +28,7 @@ import com.chestnut.contentcore.core.IDynamicPageType; import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.service.IContentService; import com.chestnut.contentcore.util.InternalUrlUtils; +import com.chestnut.contentcore.util.TemplateUtils; import com.chestnut.member.domain.Member; import com.chestnut.member.domain.vo.MemberCache; import com.chestnut.member.service.IMemberService; @@ -108,9 +110,10 @@ public class AccountContributeDynamicPageType implements IDynamicPageType { public void initTemplateData(Map parameters, TemplateContext templateContext) { Long memberId = MapUtils.getLong(parameters, "memberId"); Member member = this.memberService.getById(memberId); - templateContext.getVariables().put("Member", member); - templateContext.getVariables().put("MemberResourcePrefix", MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); - templateContext.getVariables().put("Request", parameters); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, member); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX, + MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, parameters); Long contentId = MapUtils.getLong(parameters, "cid", 0L); if (IdUtils.validate(contentId)) { diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountPasswordDynamicPageType.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountPasswordDynamicPageType.java index dc47f025..0b9e96a8 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountPasswordDynamicPageType.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountPasswordDynamicPageType.java @@ -15,10 +15,12 @@ */ package com.chestnut.cms.member.impl; +import com.chestnut.cms.member.CmsMemberConstants; import com.chestnut.cms.member.publishpipe.PublishPipeProp_MemberPasswordTemplate; import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.common.utils.ServletUtils; import com.chestnut.contentcore.core.IDynamicPageType; +import com.chestnut.contentcore.util.TemplateUtils; import com.chestnut.member.domain.Member; import com.chestnut.member.domain.vo.MemberCache; import com.chestnut.member.service.IMemberService; @@ -95,8 +97,9 @@ public class AccountPasswordDynamicPageType implements IDynamicPageType { public void initTemplateData(Map parameters, TemplateContext templateContext) { Long memberId = MapUtils.getLong(parameters, "memberId"); Member member = this.memberService.getById(memberId); - templateContext.getVariables().put("Member", member); - templateContext.getVariables().put("MemberResourcePrefix", MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); - templateContext.getVariables().put("Request", ServletUtils.getParameters()); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, member); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX, + MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, ServletUtils.getParameters()); } } diff --git a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountSettingDynamicPageType.java b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountSettingDynamicPageType.java index c7ccf5d9..0a82213e 100644 --- a/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountSettingDynamicPageType.java +++ b/chestnut-cms/chestnut-cms-member/src/main/java/com/chestnut/cms/member/impl/AccountSettingDynamicPageType.java @@ -15,10 +15,12 @@ */ package com.chestnut.cms.member.impl; +import com.chestnut.cms.member.CmsMemberConstants; import com.chestnut.cms.member.publishpipe.PublishPipeProp_MemberSettingTemplate; import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.common.utils.ServletUtils; import com.chestnut.contentcore.core.IDynamicPageType; +import com.chestnut.contentcore.util.TemplateUtils; import com.chestnut.member.domain.Member; import com.chestnut.member.domain.vo.MemberCache; import com.chestnut.member.service.IMemberService; @@ -95,8 +97,9 @@ public class AccountSettingDynamicPageType implements IDynamicPageType { public void initTemplateData(Map parameters, TemplateContext templateContext) { Long memberId = MapUtils.getLong(parameters, "memberId"); Member member = this.memberService.getById(memberId); - templateContext.getVariables().put("Member", member); - templateContext.getVariables().put("MemberResourcePrefix", MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); - templateContext.getVariables().put("Request", parameters); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, member); + templateContext.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER_RESOURCE_PREFIX, + MemberUtils.getMemberResourcePrefix(templateContext.isPreview())); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, parameters); } } diff --git a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/impl/SearchDynamicPageType.java b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/impl/SearchDynamicPageType.java index 197b404a..e4488f42 100644 --- a/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/impl/SearchDynamicPageType.java +++ b/chestnut-cms/chestnut-cms-search/src/main/java/com/chestnut/cms/search/impl/SearchDynamicPageType.java @@ -78,7 +78,7 @@ public class SearchDynamicPageType implements IDynamicPageType { @Override public void initTemplateData(Map parameters, TemplateContext templateContext) { - templateContext.getVariables().put("Request", ServletUtils.getParameters()); + templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, ServletUtils.getParameters()); String link = "_search?q=" + parameters.get("q"); if (templateContext.isPreview()) { link += "&sid=" + parameters.get("sid") + "&pp=" + templateContext.getPublishPipeCode() + "&preview=true"; diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/domain/TreeNode.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/domain/TreeNode.java index 7c613f97..5c9e22cf 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/domain/TreeNode.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/domain/TreeNode.java @@ -15,6 +15,11 @@ */ package com.chestnut.common.domain; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; @@ -22,13 +27,14 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import com.fasterxml.jackson.annotation.JsonInclude; - /** * 树结构节点实体类 */ +@Getter +@Setter public class TreeNode implements Serializable { + @Serial private static final long serialVersionUID = 1L; /** 节点ID */ @@ -48,7 +54,7 @@ public class TreeNode implements Serializable { /** * 是否禁用 */ - private boolean isDisabled = false; + private boolean disabled = false; /** * 是否新节点,会不同颜色显示 @@ -73,10 +79,6 @@ public class TreeNode implements Serializable { /** * 构建树结构 - * - * @param - * @param list - * @return */ public static List> build(List> list) { Map>> mapChildren = list.stream().filter(n -> !n.isRoot) @@ -98,86 +100,10 @@ public class TreeNode implements Serializable { this.isRoot = isRoot; } - public T getId() { - return id; - } - - public void setId(T id) { - this.id = id; - } - - public T getParentId() { - return parentId; - } - - public void setParentId(T parentId) { - this.parentId = parentId; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public List> getChildren() { - return children; - } - - public void setChildren(List> children) { - this.children = children; - } - - public boolean getIsRoot() { - return isRoot; - } - - public void setIsRoot(boolean isRoot) { - this.isRoot = isRoot; - } - - public boolean getIsDisabled() { - return isDisabled; - } - - public void setDisabled(boolean isDisabled) { - this.isDisabled = isDisabled; - } - - public boolean getIsNew() { - return isNew; - } - - public void setNew(boolean isNew) { - this.isNew = isNew; - } - - public boolean getIsDefaultExpanded() { - return isDefaultExpanded; - } - - public void setDefaultExpanded(boolean isDefaultExpanded) { - this.isDefaultExpanded = isDefaultExpanded; - } - public Map getProps() { if (props == null) { props = new HashMap<>(); } return props; } - - public void setProps(Map props) { - this.props = props; - } - - public boolean isChecked() { - return checked; - } - - public void setChecked(boolean checked) { - this.checked = checked; - } } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ChineseSpelling.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ChineseSpelling.java index 87a1d5f0..f9d556c3 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ChineseSpelling.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ChineseSpelling.java @@ -20,12 +20,27 @@ import lombok.extern.slf4j.Slf4j; import java.io.UnsupportedEncodingException; import java.util.LinkedHashMap; import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Slf4j public class ChineseSpelling { private static LinkedHashMap specialFamilyNames = new LinkedHashMap<>(); + public static int countChineseCharactersByRegex(String input) { + // 使用正则表达式匹配中文字符 + String regex = "[\\u4e00-\\u9fa5]"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(input); + + int count = 0; + while (matcher.find()) { + count++; + } + return count; + } + /** * 获得单个汉字的GBK编码 * diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java index b0ed3b1b..5cb92d3d 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/StringUtils.java @@ -20,7 +20,6 @@ import org.springframework.util.AntPathMatcher; import java.text.MessageFormat; import java.util.*; -import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -743,9 +742,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { * @return */ public static Map getPathParameterMap(String path) { - if (Objects.isNull(path) || !path.contains("?")) { - return Map.of(); - } String str = substringAfter(path, "?"); return splitToMap(str, "&", "="); } diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java index 8a150a7c..1c584718 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/FreeMarkerUtils.java @@ -199,12 +199,12 @@ public class FreeMarkerUtils { private static TemplateModel evalTemplateModel(Environment env, String[] names) throws TemplateModelException { TemplateModel model = env.getVariable(names[0]); - if (model == null || !(model instanceof TemplateHashModel)) { + if (!(model instanceof TemplateHashModel)) { throw new TemplateModelException(); } for (int i = 1; i < names.length - 1; i++) { model = ((TemplateHashModel) model).get(names[i]); - if (model == null || !(model instanceof TemplateHashModel)) { + if (!(model instanceof TemplateHashModel)) { throw new TemplateModelException(); } } diff --git a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/MemberPrivType.java b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/MemberPrivType.java new file mode 100644 index 00000000..9f2fdc24 --- /dev/null +++ b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/MemberPrivType.java @@ -0,0 +1,40 @@ +package com.chestnut.member.core; + +import java.util.Set; + +/** + * MemberPrivilegeProvider + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public interface MemberPrivType { + + /** + * 获取指定类型权限拥有者的权限列表 + * + * @param ownerType 权限所有者类型 + * @param owner 权限所有者唯一标识 + * @return 权限列表 + */ + default Set getPrivileges(String ownerType, String owner) { + + return Set.of(); + } + + /** + * 保存权限数据 + */ + default void savePrivileges(String ownerType, String owner, Set privKeys) { + + } + + /** + * 判断指定会员是否拥有指定权限 + * + * @param memberId + * @param privKey + * @return + */ + boolean checkPriv(Long memberId, String privKey); +} diff --git a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/impl/MemberMenuPrivType.java b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/impl/MemberMenuPrivType.java new file mode 100644 index 00000000..a3e70c09 --- /dev/null +++ b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/core/impl/MemberMenuPrivType.java @@ -0,0 +1,20 @@ +package com.chestnut.member.core.impl; + +import com.chestnut.member.core.MemberPrivType; + +import java.util.Set; + +/** + * 会员菜单权限 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public class MemberMenuPrivType implements MemberPrivType { + + + @Override + public boolean checkPriv(Long memberId, String privKey) { + return false; + } +} diff --git a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/privilege/MemberPrivService.java b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/privilege/MemberPrivService.java new file mode 100644 index 00000000..2c734d18 --- /dev/null +++ b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/privilege/MemberPrivService.java @@ -0,0 +1,29 @@ +package com.chestnut.member.privilege; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * MemberPrivService + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Service +@RequiredArgsConstructor +public class MemberPrivService { + + + + /** + * 保存会员权限信息+ + * + * @param ownerType + * @param owner + */ + public void savePrivilege(String ownerType, String owner, String privType, Set privKeys) { + + } +} diff --git a/chestnut-ui/src/api/contentcore/catalog.js b/chestnut-ui/src/api/contentcore/catalog.js index fb94cb26..35bcbcb5 100644 --- a/chestnut-ui/src/api/contentcore/catalog.js +++ b/chestnut-ui/src/api/contentcore/catalog.js @@ -17,10 +17,11 @@ export function getContentTypes() { } // 查询栏目树结构 -export function getCatalogTreeData() { +export function getCatalogTreeData(params) { return request({ url: '/cms/catalog/treeData', - method: 'get' + method: 'get', + params: params }) } diff --git a/chestnut-ui/src/api/contentcore/dashboard.js b/chestnut-ui/src/api/contentcore/dashboard.js new file mode 100644 index 00000000..4c2f0421 --- /dev/null +++ b/chestnut-ui/src/api/contentcore/dashboard.js @@ -0,0 +1,8 @@ +import request from '@/utils/request' + +export function getCmsConfiguration() { + return request({ + url: '/cms/dashboard/config', + method: 'get' + }) +} \ No newline at end of file diff --git a/chestnut-ui/src/i18n/lang/en.js b/chestnut-ui/src/i18n/lang/en.js index 51a5eb66..e9775dab 100644 --- a/chestnut-ui/src/i18n/lang/en.js +++ b/chestnut-ui/src/i18n/lang/en.js @@ -781,6 +781,10 @@ export default { } }, CMS: { + Dashboard: { + PublishStrategy: "Publish Strategy", + ResourceRoot: "Resource Root" + }, ContentCore: { ContentType: "Content Type", CatalogType: "Catalog Type", @@ -1202,6 +1206,43 @@ export default { ScreenshotDialog: "Video Screenshot", SetLogo: "Set Album Cover" }, + Book: { + Basic: "Book Info", + PublicationDate: "Pub. Date", + Publisher: "Publisher", + NumberOfPages: "Num. of pages", + NumberOfWords: "Num. of words", + Producer: "Producer", + OriginalTitle: "Original title", + Translators: "Translators", + Price: "Price", + CurrencyFen: "Fen", + Completed: "Completed", + Intro: "Introduce", + ChapterList: "Chapters", + InputChapterTitle: "Chapter title", + SortAsc: "ASC", + SortDesc: "DESC", + PublishDate: "Publish date", + PublishImmediately: "Publish Immediately", + ChapterTitle: "Chapter title", + Publish: "Publish", + ToPublish: "Scheduled", + Offline: "Offline", + ToPublishDialogTitle: "Scheduled Publish", + PublishSuccess: "Publish Success", + OfflineSuccess: "Offline Success", + ToPublishSuccess: "Scheduled Publish Success", + CloseChapterEditorTip: "Chapter data not saved, are you sure to quit?", + Route: { + EditChapter: "Edit book chapter" + }, + RuleTips: { + Title: "Title cannot be empty.", + Content: "Content cannot be empty.", + PublishDate: "Publish date cannot be empty." + } + }, PageWidget: { Type: "Type", Name: "Name", diff --git a/chestnut-ui/src/i18n/lang/zh-CN.js b/chestnut-ui/src/i18n/lang/zh-CN.js index 64a6be90..565b41f8 100644 --- a/chestnut-ui/src/i18n/lang/zh-CN.js +++ b/chestnut-ui/src/i18n/lang/zh-CN.js @@ -730,7 +730,7 @@ export default { JVMVersion: "Java版本", JVMStartTime: "启动时间", JVMRunTime: "运行时间", - JVMHome: "安装了路径", + JVMHome: "安装路径", ProjectDir: "项目路径", JVMArgs: "运行参数", Disk: "磁盘状态", @@ -781,6 +781,10 @@ export default { } }, CMS: { + Dashboard: { + PublishStrategy: "发布策略", + ResourceRoot: "资源目录" + }, ContentCore: { ContentType: "内容类型", CatalogType: "栏目类型", @@ -1202,6 +1206,43 @@ export default { ScreenshotDialog: "视频封面截图", SetLogo: "设为视频集封面" }, + Book: { + Basic: "图书信息", + PublicationDate: "出版时间", + Publisher: "出版社", + NumberOfPages: "页数", + NumberOfWords: "字数", + Producer: "出品方", + OriginalTitle: "原著名", + Translators: "译者", + Price: "价格", + CurrencyFen: "分", + Completed: "是否完结", + Intro: "介绍", + ChapterList: "章节列表", + InputChapterTitle: "输入章节标题", + SortAsc: "顺序", + SortDesc: "倒序", + PublishDate: "发布时间", + PublishImmediately: "立即发布", + ChapterTitle: "章节标题", + Publish: "发布", + ToPublish: "定时发布", + Offline: "下线", + ToPublishDialogTitle: "定时发布", + PublishSuccess: "发布成功", + OfflineSuccess: "下线成功", + ToPublishSuccess: "定时发布成功", + CloseChapterEditorTip: "章节数据未保存,确认关闭吗?", + Route: { + EditChapter: "编辑章节" + }, + RuleTips: { + Title: "标题不能为空", + Content: "章节内容不能为空", + PublishDate: "发布时间不能为空" + } + }, PageWidget: { Type: "类型", Name: "名称", diff --git a/chestnut-ui/src/i18n/lang/zh-TW.js b/chestnut-ui/src/i18n/lang/zh-TW.js index 8dfdc17b..44c7ced5 100644 --- a/chestnut-ui/src/i18n/lang/zh-TW.js +++ b/chestnut-ui/src/i18n/lang/zh-TW.js @@ -730,7 +730,7 @@ export default { JVMVersion: "Java版本", JVMStartTime: "啟動時間", JVMRunTime: "運行時間", - JVMHome: "安裝了路徑", + JVMHome: "安裝路徑", ProjectDir: "項目路徑", JVMArgs: "運行參數", Disk: "磁碟狀態", @@ -781,6 +781,10 @@ export default { } }, CMS: { + Dashboard: { + PublishStrategy: "發佈策略", + ResourceRoot: "資源目錄" + }, ContentCore: { ContentType: "內容類型", CatalogType: "欄目類型", @@ -1202,6 +1206,43 @@ export default { ScreenshotDialog: "視頻封面截圖", SetLogo: "設為視頻集封面" }, + Book: { + Basic: "圖書信息", + PublicationDate: "出版時間", + Publisher: "出版社", + NumberOfPages: "頁數", + NumberOfWords: "字數", + Producer: "出品方", + OriginalTitle: "原著名", + Translators: "譯者", + Price: "價格", + CurrencyFen: "分", + Completed: "是否完結", + Intro: "介紹", + ChapterList: "章節列表", + InputChapterTitle: "輸入章節標題", + SortAsc: "順序", + SortDesc: "倒序", + PublishDate: "發佈時間", + PublishImmediately: "立即發佈", + ChapterTitle: "章節標題", + Publish: "發佈", + ToPublish: "定時發佈", + Offline: "下線", + ToPublishDialogTitle: "定時發佈", + PublishSuccess: "發佈成功", + OfflineSuccess: "下線成功", + ToPublishSuccess: "定時發佈成功", + CloseChapterEditorTip: "章節數據未保存,確認關閉嗎?", + Route: { + EditChapter: "編輯章節" + }, + RuleTips: { + Title: "標題不能為空", + Content: "章節內容不能為空", + PublishDate: "發佈時間不能為空" + } + }, PageWidget: { Type: "類型", Name: "名稱", diff --git a/chestnut-ui/src/utils/request.js b/chestnut-ui/src/utils/request.js index 3007a631..ba47f163 100644 --- a/chestnut-ui/src/utils/request.js +++ b/chestnut-ui/src/utils/request.js @@ -18,7 +18,7 @@ const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: process.env.VUE_APP_BASE_API, // 超时 - timeout: 10000 + timeout: 60000 }) // request拦截器 diff --git a/chestnut-ui/src/views/cms/components/TagEditor/index.vue b/chestnut-ui/src/views/cms/components/TagEditor/index.vue index 2d0b0b32..a6fd5a05 100644 --- a/chestnut-ui/src/views/cms/components/TagEditor/index.vue +++ b/chestnut-ui/src/views/cms/components/TagEditor/index.vue @@ -76,6 +76,7 @@ export default { watch: { tags(newVal) { this.tagList = newVal; + this.$emit("change", newVal); } }, data () { diff --git a/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue b/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue index d36b25e6..9a755718 100644 --- a/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue +++ b/chestnut-ui/src/views/cms/contentcore/catalogInfo.vue @@ -171,7 +171,7 @@ :command="pp" :name="pp.pipeCode" :label="pp.pipeName"> - 模板配置 + {{ $t('CMS.Catalog.TemplateConfig') }} {{ $t('CMS.Catalog.ApplyToChildren') }} - 其他配置 + {{ $t('CMS.Catalog.OtherConfig') }} @@ -355,6 +356,7 @@ export default { openCatalogSelector: false, catalogSelectorFor: undefined, showCatalogSelectorRootNode: false, + disableLinkCatalog: false, openContentSelector: false, openTemplateSelector: false, // 是否显示模板选择弹窗 propKey: "", // 选择模板时记录变更的模板对应属性Key @@ -502,6 +504,7 @@ export default { this.catalogSelectorFor = "MoveCatalog"; this.openCatalogSelector = true; this.showCatalogSelectorRootNode = true; + this.disableLinkCatalog = false; }, handleCloseProgress() { if (this.progressType == 'Delete' || this.progressType == 'Move') { @@ -540,6 +543,7 @@ export default { } else if (type === 'catalog') { this.openCatalogSelector = true; this.showCatalogSelectorRootNode = false; + this.disableLinkCatalog = true; this.catalogSelectorFor = ""; } }, diff --git a/chestnut-ui/src/views/cms/contentcore/catalogSelector.vue b/chestnut-ui/src/views/cms/contentcore/catalogSelector.vue index c1242315..fac9328d 100644 --- a/chestnut-ui/src/views/cms/contentcore/catalogSelector.vue +++ b/chestnut-ui/src/views/cms/contentcore/catalogSelector.vue @@ -37,7 +37,7 @@ {{ siteName }} + @@ -96,6 +98,12 @@ export default { type: Boolean, default: true, required: false + }, + // 是否不允许选择链接栏目 + disableLink: { + type: Boolean, + default: false, + required: false } }, computed: { @@ -143,7 +151,7 @@ export default { this.selectedCatalogs = []; this.rootSelected = false; this.loading = true; - getCatalogTreeData().then(response => { + getCatalogTreeData({disableLink: this.disableLink}).then(response => { if (response.code == 200) { this.catalogOptions = response.data.rows; this.siteName = response.data.siteName; @@ -155,10 +163,21 @@ export default { if (!value) return true; return data.label.indexOf(value) > -1; }, - handleNodeClick (data) { + setNodeHighlight(node) { + document.querySelectorAll(".cc-current").forEach(item => item.classList.remove("cc-current")); + if (node) { + document.querySelector("#tn-"+node.id).classList.add("cc-current"); + } + }, + handleNodeClick (data, node) { if (!this.multiple) { - this.selectedCatalogs = [{ id: data.id, name: data.label, props: data.props }]; - this.rootSelected = false; + if (!this.disableLink || !data.disabled) { + this.setNodeHighlight(node) + this.selectedCatalogs = [{ id: data.id, name: data.label, props: data.props }]; + this.rootSelected = false; + } else { + this.$refs.tree.setCurrentKey(null) + } } }, handleTreeRootClick(e) { @@ -179,6 +198,7 @@ export default { this.$modal.alertWarning(this.$t('CMS.Catalog.SelectCatalogFirst')); return; } + this.setNodeHighlight() this.$emit("ok", this.selectedCatalogs, this.copyType); }, handleCancel () { @@ -215,10 +235,14 @@ export default { border-radius: 0; padding: 5px; } -.catalog-selector .tree-container .is-current { - background-color: #edf6ff; -} .catalog-selector .tree-container .tree-root:hover { background-color: #F5F7FA; } +.catalog-selector .tree-container .cc-current { + color: #409EFF; +} +.catalog-selector .tree-container .cc-disabled { + color: #C0C4CC; + cursor: not-allowed; +} \ No newline at end of file diff --git a/chestnut-ui/src/views/cms/contentcore/contentEditor.vue b/chestnut-ui/src/views/cms/contentcore/contentEditor.vue index bdab40b4..4619225e 100644 --- a/chestnut-ui/src/views/cms/contentcore/contentEditor.vue +++ b/chestnut-ui/src/views/cms/contentcore/contentEditor.vue @@ -17,7 +17,7 @@ - + @@ -241,6 +241,7 @@ @@ -323,6 +324,7 @@ export default { contentType: this.$route.query.type, opType: !this.$route.query.id || this.$route.query.id == '0' ? 'ADD' : 'UPDATE', openCatalogSelector: false, + disableLinkCatalog: false, catalogSelectorFor: undefined, openContentSelector: false, // 表单参数 @@ -576,6 +578,7 @@ export default { }, handleCatalogChange() { this.openCatalogSelector = true; + this.disableLinkCatalog = false; this.catalogSelectorFor = "change"; }, handleCatalogSelectorOk(catalogs) { @@ -616,6 +619,7 @@ export default { } else if (type === 'catalog') { this.openCatalogSelector = true; this.catalogSelectorFor = 'linkflag'; + this.disableLinkCatalog = true; } }, handleContentSelectorOk(contents) { diff --git a/chestnut-ui/src/views/cms/contentcore/contentList.vue b/chestnut-ui/src/views/cms/contentcore/contentList.vue index 39a0c36b..ae2665d5 100644 --- a/chestnut-ui/src/views/cms/contentcore/contentList.vue +++ b/chestnut-ui/src/views/cms/contentcore/contentList.vue @@ -232,7 +232,7 @@ - +