版本更新:v1.4.5

This commit is contained in:
liweiyi 2024-05-16 22:23:10 +08:00
parent 58c8cea901
commit c596a44483
60 changed files with 1513 additions and 234 deletions

View File

@ -31,5 +31,5 @@ for NONE_IMAGE_ID in ${NONE_IMAGE_ID_ARR[*]}; do
echo ">>>>>delete docker <none> image done: $NONE_IMAGE_ID"
done
# 启动容器
docker-compose up -d
# 启动容器老版本命令是docker-compose up -d
docker compose up -d

View File

@ -17,6 +17,11 @@ chestnut:
captchaType: math
member:
uploadPath: 'E:/dev/workspace_chestnut/_xy_member/'
cms:
publish:
pool:
threadNamePrefix: "CMS-PUBLISH-"
queueCapacity: 10000
# 开发环境配置
server:
@ -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
# 从库

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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<CmsCatalog> 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<TreeNode<String>> treeData = catalogService.buildCatalogTreeData(catalogs);
List<TreeNode<String>> 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()));
}

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<CmsContent> 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);
}

View File

@ -64,7 +64,6 @@ public class ImageWatermarkArgsProperty implements IProperty {
public ImageWatermarkArgs getPropValue(Map<String, String> configProps) {
String v = MapUtils.getString(configProps, ID);
if (StringUtils.isNotEmpty(v)) {
System.out.println(v);
return JacksonUtils.from(v, ImageWatermarkArgs.class);
}
return defaultValue();

View File

@ -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<String, IStaticizeType> staticizeTypeMap;
public IStaticizeType getStaticizeType(String type) {
return staticizeTypeMap.get(IStaticizeType.BEAN_PREFIX + type);
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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<CmsPublishPipe> 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);
}
}
}
}

View File

@ -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<CmsPublishPipe> 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;
}
}

View File

@ -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);
}
}
}

View File

@ -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<String, MapRecord<String, String, String>> {
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<String, String, String> message) {
String stream = message.getStream();
if (Objects.nonNull(stream)) {
try {
Map<String, String> 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());
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<String, String, String> 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<String, MapRecord<String, String, String>> streamMessageListenerContainer() {
// 启动清理消息队列数据
if (properties.isClearOnStart()) {
redisTemplate.delete(PublishStreamName);
}
// 监听容器配置
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> streamMessageListenerContainerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions
.builder()
.batchSize(10) // 一次拉取消息数量
.pollTimeout(Duration.ofSeconds(2)) // 拉取消息超时时间
.executor(cmsPublishThreadPoolTaskExecutor())
.build();
// 创建监听容器
StreamMessageListenerContainer<String, MapRecord<String, String, String>> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<CmsCatalog> {
@ -52,7 +54,7 @@ public interface ICatalogService extends IService<CmsCatalog> {
* @param catalogs
* @return
*/
List<TreeNode<String>> buildCatalogTreeData(List<CmsCatalog> catalogs);
List<TreeNode<String>> buildCatalogTreeData(List<CmsCatalog> catalogs, BiConsumer<CmsCatalog, TreeNode<String>> consumer);
/**
* 校验栏目别名目录是否重复

View File

@ -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<CmsCatalogMapper, CmsCatalog
}
@Override
public List<TreeNode<String>> buildCatalogTreeData(List<CmsCatalog> catalogs) {
public List<TreeNode<String>> buildCatalogTreeData(List<CmsCatalog> catalogs, BiConsumer<CmsCatalog, TreeNode<String>> consumer) {
if (Objects.isNull(catalogs)) {
return List.of();
}
@ -142,6 +143,7 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
c.getLogo() == null ? "" : c.getLogo(), "logoSrc", logoSrc == null ? "" : logoSrc, "description",
c.getDescription() == null ? "" : c.getDescription());
treeNode.setProps(props);
consumer.accept(c, treeNode);
return treeNode;
}).toList();
return TreeNode.build(list);

View File

@ -91,7 +91,7 @@ public class DynamicPageService {
// init template datamode
TemplateUtils.initGlobalVariables(site, templateContext);
// init templateType data to datamode
templateContext.getVariables().put("Request", ServletUtils.getParameters());
templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, ServletUtils.getParameters());
dpt.initTemplateData(parameters, templateContext);
// staticize
this.staticizeService.process(templateContext, response.getWriter());

View File

@ -35,9 +35,10 @@ import com.chestnut.contentcore.enums.ContentCopyType;
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.listener.event.AfterContentPublishEvent;
import com.chestnut.contentcore.properties.MaxPageOnContentPublishProperty;
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.*;
import com.chestnut.contentcore.template.ITemplateType;
import com.chestnut.contentcore.template.impl.CatalogTemplateType;
@ -80,11 +81,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
private final AsyncTaskManager asyncTaskManager;
private final SitePublishTask sitePublishTask;
private final CatalogPublishTask catalogPublishTask;
private final ContentPublishTask contentPublishTask;
private final IPublishStrategy publishStrategy;
private ApplicationContext applicationContext;
@ -191,7 +188,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
}
private void asyncPublishSite(CmsSite site) {
sitePublishTask.publish(site);
publishStrategy.publish(SiteStaticizeType.TYPE, site.getSiteId().toString());
}
@Override
@ -361,7 +358,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
}
public void asyncPublishCatalog(final CmsCatalog catalog) {
catalogPublishTask.publish(catalog);
publishStrategy.publish(CatalogStaticizeType.TYPE, catalog.getCatalogId().toString());
}
@Override
@ -572,14 +569,14 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
if (publishPipeCodes.isEmpty()) {
return;
}
contentPublishTask.publish(content.getContentEntity());
publishStrategy.publish(ContentStaticizeType.TYPE, content.getContentEntity().getContentId().toString());
// 关联内容静态化映射的引用内容
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<CmsContent>()
.eq(CmsContent::getCopyId, content.getContentEntity().getContentId())
.eq(CmsContent::getCopyType, ContentCopyType.Mapping);
List<CmsContent> mappingContents = contentService.list(q);
for (CmsContent mappingContent : mappingContents) {
contentPublishTask.publish(mappingContent);
publishStrategy.publish(ContentStaticizeType.TYPE, mappingContent.getContentId().toString());
}
}

View File

@ -112,7 +112,6 @@ public class TemplateServiceImpl extends ServiceImpl<CmsTemplateMapper, CmsTempl
.filter(t -> 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());

View File

@ -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<String, String> paramsMap = StringUtils.splitToMap(params, "&", "=");
Map<String, String> 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<String, String> mergeRequestVariable(Environment env, Map<String, String> 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<String, String> 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 {

View File

@ -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键名
*/

View File

@ -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<String, String> 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()));
}
}
}

View File

@ -200,7 +200,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
// init template datamode
TemplateUtils.initGlobalVariables(site, templateContext);
// init templateType data to datamode
templateContext.getVariables().put("Request", parameters);
templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, parameters);
// 动态页面自定义数据
if (Objects.nonNull(dynamicPage.getInitDataTypes())) {
dynamicPage.getInitDataTypes().forEach(initDataType -> {

View File

@ -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";
}

View File

@ -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<String, String> 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);
}
}

View File

@ -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");

View File

@ -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<String, String> 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)) {

View File

@ -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<String, String> 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());
}
}

View File

@ -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<String, String> 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);
}
}

View File

@ -78,7 +78,7 @@ public class SearchDynamicPageType implements IDynamicPageType {
@Override
public void initTemplateData(Map<String, String> 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";

View File

@ -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<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 节点ID */
@ -48,7 +54,7 @@ public class TreeNode<T> implements Serializable {
/**
* 是否禁用
*/
private boolean isDisabled = false;
private boolean disabled = false;
/**
* 是否新节点会不同颜色显示
@ -73,10 +79,6 @@ public class TreeNode<T> implements Serializable {
/**
* 构建树结构
*
* @param <T>
* @param list
* @return
*/
public static <T> List<TreeNode<T>> build(List<TreeNode<T>> list) {
Map<T, List<TreeNode<T>>> mapChildren = list.stream().filter(n -> !n.isRoot)
@ -98,86 +100,10 @@ public class TreeNode<T> 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<TreeNode<T>> getChildren() {
return children;
}
public void setChildren(List<TreeNode<T>> 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<String, Object> getProps() {
if (props == null) {
props = new HashMap<>();
}
return props;
}
public void setProps(Map<String, Object> props) {
this.props = props;
}
public boolean isChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked = checked;
}
}

View File

@ -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<String, String> 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编码
*

View File

@ -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<String, String> getPathParameterMap(String path) {
if (Objects.isNull(path) || !path.contains("?")) {
return Map.of();
}
String str = substringAfter(path, "?");
return splitToMap(str, "&", "=");
}

View File

@ -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();
}
}

View File

@ -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<String> getPrivileges(String ownerType, String owner) {
return Set.of();
}
/**
* 保存权限数据
*/
default void savePrivileges(String ownerType, String owner, Set<String> privKeys) {
}
/**
* 判断指定会员是否拥有指定权限
*
* @param memberId
* @param privKey
* @return
*/
boolean checkPriv(Long memberId, String privKey);
}

View File

@ -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;
}
}

View File

@ -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<String> privKeys) {
}
}

View File

@ -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
})
}

View File

@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getCmsConfiguration() {
return request({
url: '/cms/dashboard/config',
method: 'get'
})
}

View File

@ -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",

View File

@ -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: "名称",

View File

@ -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: "名稱",

View File

@ -18,7 +18,7 @@ const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
timeout: 60000
})
// request拦截器

View File

@ -76,6 +76,7 @@ export default {
watch: {
tags(newVal) {
this.tagList = newVal;
this.$emit("change", newVal);
}
},
data () {

View File

@ -171,7 +171,7 @@
:command="pp"
:name="pp.pipeCode"
:label="pp.pipeName">
<el-divider content-position="left">模板配置</el-divider>
<el-divider content-position="left">{{ $t('CMS.Catalog.TemplateConfig') }}</el-divider>
<el-form-item :label="$t('CMS.Catalog.IndexTemplate')" prop="indexTemplate">
<el-input v-model="pp.props.indexTemplate">
<el-button
@ -232,7 +232,7 @@
type="primary"
@click="handleApplyToChildren('contentExTemplate')">{{ $t('CMS.Catalog.ApplyToChildren') }}</el-button>
</el-form-item>
<el-divider content-position="left">其他配置</el-divider>
<el-divider content-position="left">{{ $t('CMS.Catalog.OtherConfig') }}</el-divider>
<el-form-item :label="$t('CMS.Site.UEditorCss')">
<el-input v-model="pp.props.ueditorCss">
<el-button
@ -297,6 +297,7 @@
<cms-catalog-selector
:open="openCatalogSelector"
:showRootNode="showCatalogSelectorRootNode"
:disableLink="disableLinkCatalog"
@ok="handleCatalogSelectorOk"
@close="handleCatalogSelectorClose"></cms-catalog-selector>
<!-- 内容选择组件 -->
@ -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 = "";
}
},

View File

@ -37,7 +37,7 @@
<el-button
v-if="showRootNode"
type="text"
:class="'tree-root' + (rootSelected?' is-current':'')"
:class="'tree-root' + (rootSelected?' cc-current':'')"
icon="el-icon-s-home"
@click="handleTreeRootClick">{{ siteName }}</el-button>
<el-tree
@ -51,8 +51,10 @@
node-key="id"
ref="tree"
default-expand-all
highlight-current
@node-click="handleNodeClick">
<template slot-scope="{ node, data }">
<span :id="'tn-'+node.id" :class="node.disabled?'cc-disabled':''">{{ node.label }}</span>
</template>
</el-tree>
</el-scrollbar>
</div>
@ -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) {
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;
}
</style>

View File

@ -17,7 +17,7 @@
</el-col>
</el-row>
<el-row class="art-editor-container" :gutter="10" v-loading="loading">
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-col :span="16">
<el-row>
<el-col class="pr10">
@ -241,6 +241,7 @@
<!-- 栏目选择组件 -->
<cms-catalog-selector
:open="openCatalogSelector"
:disableLink="disableLinkCatalog"
@ok="handleCatalogSelectorOk"
@close="handleCatalogSelectorClose"></cms-catalog-selector>
<!-- 内容选择组件 -->
@ -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) {

View File

@ -232,7 +232,7 @@
<el-table-column
:label="$t('Common.Operation')"
align="center"
width="220"
width="260"
class-name="small-padding fixed-width">
<template slot-scope="scope">
<span class="btn-cell-wrap">
@ -541,7 +541,6 @@ export default {
}
this.isCopy = true;
this.openCatalogSelector = true;
console.log(this.selectedRows.map(item => item.contentId))
},
doCopy(catalogs, copyType) {
const data = {

View File

@ -57,7 +57,7 @@
</template>
</el-table-column>
<el-table-column :label="$t('CMS.PageWidget.Path')" align="left" width="180" prop="path" :show-overflow-tooltip="true" />
<el-table-column :label="$t('Common.Operation')" align="left" width="200" class-name="small-padding fixed-width">
<el-table-column :label="$t('Common.Operation')" align="right" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<span class="btn-cell-wrap">
<el-button

View File

@ -2,7 +2,7 @@
<div class="dashboard-container">
<el-card shadow="hover" class="mb10">
<div slot="header" class="clearfix">
<span>系统信息</span>
<span>{{ $t("Monitor.Server.ApplicationInfo") }}</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<table cellspacing="0" style="width: 100%;">
@ -19,6 +19,14 @@
<td class="el-table__cell is-leaf"><div class="cell attrname">{{ $t('Monitor.Server.JVMRunTime') }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">{{ serverInfo.runTime }}</div></td>
</tr>
<tr>
<td class="el-table__cell is-leaf"><div class="cell attrname">{{ $t('CMS.Dashboard.PublishStrategy') }}</div></td>
<td class="el-table__cell is-leaf" colspan="3"><div class="cell">{{ config.publishStrategy }}</div></td>
</tr>
<tr>
<td class="el-table__cell is-leaf"><div class="cell attrname">{{ $t('CMS.Dashboard.ResourceRoot') }}</div></td>
<td class="el-table__cell is-leaf" colspan="3"><div class="cell">{{ config.resourceRoot }}</div></td>
</tr>
</tbody>
</table>
</div>
@ -28,6 +36,7 @@
</template>
<script>
import { getDashboardServerInfo } from "@/api/monitor/server";
import { getCmsConfiguration } from "@/api/contentcore/dashboard";
export default {
name: "ServerInfoDashboard",
@ -35,17 +44,24 @@ export default {
return {
serverInfo: {
app: {}
}
},
config:{}
};
},
created() {
this.loadServerInfo();
this.loadCmsConfiguration();
},
methods: {
loadServerInfo() {
getDashboardServerInfo().then(response => {
this.serverInfo = response.data;
})
},
loadCmsConfiguration() {
getCmsConfiguration().then(response => {
this.config = response.data;
})
}
}
};