版本更新到 1.4.2

This commit is contained in:
liweiyi 2024-03-15 22:35:03 +08:00
parent 85cab07588
commit 01adc9f252
187 changed files with 3794 additions and 454 deletions

View File

@ -1,4 +1,4 @@
# ChestnutCMS v1.4.1
# ChestnutCMS v1.4.2
### 系统简介
@ -20,6 +20,8 @@ ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi
游戏站演示地址PC端<http://game.1000mz.com> 移动端:<http://mgame.1000mz.com>
影视站演示地址PC端<http://movie.1000mz.com> 移动端:<http://movie.1000mz.com>
### 开发环境
- OpenJDK 17
- Maven 3.8+

View File

@ -3,7 +3,7 @@
<parent>
<artifactId>chestnut</artifactId>
<groupId>com.chestnut</groupId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@ -5,7 +5,7 @@ chestnut:
# 代号
alias: ChestnutCMS
# 版本
version: 1.4.1
version: 1.4.2
# 版权年份
copyrightYear: 2022-2024
system:

View File

@ -5,7 +5,7 @@ chestnut:
# 代号
alias: ChestnutCMS
# 版本
version: 1.4.1
version: 1.4.2
# 版权年份
copyrightYear: 2022-2024
system:

View File

@ -5,7 +5,7 @@ chestnut:
# 代号
alias: ChestnutCMS
# 版本
version: 1.4.1
version: 1.4.2
# 版权年份
copyrightYear: 2022-2024
system:

View File

@ -1,3 +1,22 @@
CREATE TABLE `cms_site_visit_log` (
`log_id` bigint NOT NULL,
`site_id` bigint NOT NULL,
`catalog_id` bigint DEFAULT NULL,
`content_id` bigint DEFAULT NULL,
`host` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`uri` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`ip` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`address` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`referer` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`browser` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
`user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`os` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
`device_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`locale` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
`evt_time` datetime NOT NULL,
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `cms_dynamic_page` (
`page_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL COMMENT '站点ID',

View File

@ -0,0 +1,30 @@
CREATE TABLE `search_word` (
`word_id` bigint NOT NULL COMMENT '主键ID',
`word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '搜索词',
`search_total` bigint NOT NULL COMMENT '历史累计搜索次数',
`top_flag` bigint NOT NULL COMMENT '置顶标识',
`top_date` datetime DEFAULT NULL COMMENT '置顶结束时间',
`source` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '来源标识',
`create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后修改人',
`update_time` datetime DEFAULT NULL COMMENT '最后修改时间',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`word_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `search_word_hour_stat` (
`stat_id` bigint NOT NULL AUTO_INCREMENT,
`hour` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`word_id` bigint NOT NULL,
`word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`search_count` bigint NOT NULL,
PRIMARY KEY (`stat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
# 菜单名称路由调整
UPDATE sys_menu SET component = 'search/wordTab', path = 'searchWord' where menu_id = 2053;
ALTER TABLE cms_content ADD COLUMN prop1 VARCHAR(100);
ALTER TABLE cms_content ADD COLUMN prop2 VARCHAR(100);
ALTER TABLE cms_content ADD COLUMN prop3 VARCHAR(255);
ALTER TABLE cms_content ADD COLUMN prop4 VARCHAR(255);

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-advertisement</artifactId>

View File

@ -77,8 +77,17 @@ public class AdLogController extends BaseRestController {
}
@GetMapping("/chart")
public R<?> getLineChartStatDatas(@RequestParam @Min(1) Long advertisementId, @RequestParam Date beginTime, @RequestParam Date endTime) {
List<CmsAdHourStat> list = this.advHourStatMapper.selectHourStat(advertisementId, FORMAT.format(beginTime), FORMAT.format(endTime));
public R<?> getLineChartStatDatas(
@RequestParam @Min(1) Long advertisementId,
@RequestParam Date beginTime,
@RequestParam Date endTime
) {
LambdaQueryWrapper<CmsAdHourStat> q = new LambdaQueryWrapper<CmsAdHourStat>()
.eq(CmsAdHourStat::getAdvertisementId, advertisementId)
.gt(Objects.nonNull(beginTime), CmsAdHourStat::getHour, beginTime)
.gt(Objects.nonNull(endTime), CmsAdHourStat::getHour, endTime)
.orderByAsc(CmsAdHourStat::getHour);
List<CmsAdHourStat> list = this.advHourStatMapper.selectList(q);
if (!list.isEmpty()) {
Map<String, String> map = this.advService.getAdvertisementMap();
list.forEach(l -> l.setAdName(map.get(l.getAdvertisementId().toString())));

View File

@ -25,18 +25,6 @@ import com.chestnut.advertisement.domain.CmsAdHourStat;
public interface CmsAdHourStatMapper extends BaseMapper<CmsAdHourStat> {
@Select("""
<script>
SELECT * FROM `cms_ad_hour_stat`
WHERE advertisement_id = #{advertisementId}
<if test='begin != null'> and hour &gt;= #{begin} </if>
<if test='end != null'> and hour &lt;= #{end} </if>
ORDER BY hour ASC
</script>
""")
public List<CmsAdHourStat> selectHourStat(@Param("advertisementId") Long advertisementId,
@Param("begin") String begin, @Param("end") String end);
@Select("""
<script>
SELECT advertisement_id, sum(click) click, sum(view) view FROM `cms_ad_hour_stat`

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-article</artifactId>

View File

@ -16,7 +16,6 @@
package com.chestnut.article.template.func;
import com.chestnut.article.ArticleUtils;
import com.chestnut.article.service.IArticleService;
import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.staticize.func.AbstractFunc;
@ -41,8 +40,6 @@ public class dealArticleBodyFunction extends AbstractFunc {
private static final String DESC = "{FREEMARKER.FUNC.DESC." + FUNC_NAME + "}";
private final IArticleService articleService;
@Override
public String getFuncName() {
return FUNC_NAME;

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-block</artifactId>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-comment</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-contentcore</artifactId>

View File

@ -16,6 +16,7 @@
package com.chestnut.contentcore.controller;
import com.chestnut.common.domain.R;
import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.staticize.StaticizeService;
@ -24,6 +25,7 @@ import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.ServletUtils;
import com.chestnut.contentcore.core.IInternalDataType;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.vo.DynamicPageTypeVO;
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.service.ITemplateService;
@ -44,6 +46,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -154,6 +157,11 @@ public class CoreController extends BaseRestController {
@Priv(type = AdminUserType.TYPE)
@GetMapping("/cms/dynamicPageTypes")
public R<?> getDynamicPageTypes() {
return R.ok(ContentCoreUtils.getDynamicPageTypes());
List<DynamicPageTypeVO> list = ContentCoreUtils.getDynamicPageTypes().stream()
.map(DynamicPageTypeVO::newInstance).toList();
list.forEach( vo ->
vo.setName(I18nUtils.get(vo.getName()))
);
return R.ok(list);
}
}

View File

@ -100,6 +100,7 @@ public class TemplateController extends BaseRestController {
.filesize(t.getFilesize())
.filesizeName(FileUtils.byteCountToDisplaySize(t.getFilesize()))
.modifyTime(DateUtils.epochMilliToLocalDateTime(t.getModifyTime()))
.remark(t.getRemark())
.build()).toList();
return this.bindDataTable(list, (int) page.getTotal());
}

View File

@ -97,9 +97,9 @@ public class SiteExportContext implements ISiteThemeContext {
* @param dest 目标路径项目资源根目录resourceRoot
*/
public void saveFile(File source, String dest) {
dest = ExportDir + SiteDirPath + dest;
File destFile = new File(SiteUtils.getSiteResourceRoot(site) + dest);
try {
dest = ExportDir + SiteDirPath + dest;
if (source.isDirectory()) {
FileUtils.copyDirectory(source, destFile);
} else {

View File

@ -288,6 +288,26 @@ public class CmsContent extends BaseEntityWithLogicDelete {
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> configProps;
/**
* 备用字段1
*/
private String prop1;
/**
* 备用字段2
*/
private String prop2;
/**
* 备用字段3
*/
private String prop3;
/**
* 备用字段4
*/
private String prop4;
public boolean isLock() {
return YesOrNo.isYes(this.isLock);

View File

@ -0,0 +1,50 @@
/*
* 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 com.chestnut.contentcore.core.IDynamicPageType;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class DynamicPageTypeVO {
private String type;
private String name;
private String desc;
private String requestPath;
private String publishPipeKey;
private List<IDynamicPageType.RequestArg> requestArgs;
public static DynamicPageTypeVO newInstance(IDynamicPageType dynamicPageType) {
DynamicPageTypeVO vo = new DynamicPageTypeVO();
vo.setType(dynamicPageType.getType());
vo.setName(dynamicPageType.getName());
vo.setDesc(dynamicPageType.getDesc());
vo.setRequestPath(dynamicPageType.getRequestPath());
vo.setPublishPipeKey(dynamicPageType.getPublishPipeKey());
vo.setRequestArgs(dynamicPageType.getRequestArgs());
return vo;
}
}

View File

@ -54,4 +54,6 @@ public class TemplateListVO {
* 模板文件最后变更时间
*/
private LocalDateTime modifyTime;
private String remark;
}

View File

@ -36,12 +36,12 @@ public interface ISiteService extends IService<CmsSite> {
* @param siteId
* @return
*/
public boolean checkSiteUnique(String siteName, String sitePath, Long siteId);
boolean checkSiteUnique(String siteName, String sitePath, Long siteId);
/**
* 获取当前站点保存在token中
*/
public CmsSite getCurrentSite(HttpServletRequest request);
CmsSite getCurrentSite(HttpServletRequest request);
/**
* 获取站点数据
@ -49,7 +49,7 @@ public interface ISiteService extends IService<CmsSite> {
* @param siteId
* @return
*/
public CmsSite getSite(Long siteId);
CmsSite getSite(Long siteId);
/**
* 新增站点数据
@ -58,7 +58,7 @@ public interface ISiteService extends IService<CmsSite> {
* @return
* @throws IOException
*/
public CmsSite addSite(SiteDTO dto) throws IOException;
CmsSite addSite(SiteDTO dto) throws IOException;
/**
* 修改站点数据
@ -67,7 +67,7 @@ public interface ISiteService extends IService<CmsSite> {
* @return
* @throws IOException
*/
public CmsSite saveSite(SiteDTO site) throws IOException;
CmsSite saveSite(SiteDTO site) throws IOException;
/**
* 删除站点数据
@ -76,7 +76,7 @@ public interface ISiteService extends IService<CmsSite> {
* @return
* @throws IOException
*/
public void deleteSite(Long siteId) throws IOException;
void deleteSite(Long siteId) throws IOException;
/**
* 保存站点默认模板配置
@ -84,7 +84,7 @@ public interface ISiteService extends IService<CmsSite> {
* @param dto
* @return
*/
public void saveSiteDefaultTemplate(SiteDefaultTemplateDTO dto);
void saveSiteDefaultTemplate(SiteDefaultTemplateDTO dto);
/**
* 清理站点缓存数据

View File

@ -0,0 +1,82 @@
/*
* 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.template.func;
import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.staticize.func.AbstractFunc;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.service.ICatalogService;
import freemarker.core.Environment;
import freemarker.template.SimpleNumber;
import freemarker.template.TemplateModelException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Freemarker模板自定义函数获取内容分页链接
*/
@Component
@RequiredArgsConstructor
public class ContentPageLinkFunction extends AbstractFunc {
private static final String FUNC_NAME = "contentPageLink";
private static final String DESC = "{FREEMARKER.FUNC.DESC." + FUNC_NAME + "}";
private final ICatalogService catalogService;
@Override
public String getFuncName() {
return FUNC_NAME;
}
@Override
public String getDesc() {
return DESC;
}
@Override
public Object exec0(Object... args) throws TemplateModelException {
if (args.length == 0) {
return StringUtils.EMPTY;
}
String link = args[0].toString();
if (args.length == 1) {
return link;
}
int pageNumber = ((SimpleNumber) args[1]).getAsNumber().intValue();
if(pageNumber <= 1) {
return link;
}
TemplateContext context = FreeMarkerUtils.getTemplateContext(Environment.getCurrentEnvironment());
if (context.isPreview()) {
link += "&pi=" + pageNumber;
} else {
int dotIndex = link.lastIndexOf(StringUtils.DOT);
link = link.substring(0, dotIndex) + "_" + pageNumber + link.substring(dotIndex);
}
return link;
}
@Override
public List<FuncArg> getFuncArgs() {
return List.of(new FuncArg("内容链接", FuncArgType.String, true, null),
new FuncArg("页码", FuncArgType.Int, true, null));
}
}

View File

@ -16,6 +16,7 @@
package com.chestnut.contentcore.template.func;
import java.util.List;
import java.util.Objects;
import org.springframework.stereotype.Component;
@ -58,6 +59,9 @@ public class InternalUrlFunction extends AbstractFunc {
}
TemplateContext context = FreeMarkerUtils.getTemplateContext(Environment.getCurrentEnvironment());
SimpleScalar simpleScalar = (SimpleScalar) args[0];
if (Objects.isNull(simpleScalar)) {
return StringUtils.EMPTY;
}
return InternalUrlUtils.getActualUrl(simpleScalar.getAsString(), context.getPublishPipeCode(), context.isPreview());
}

View File

@ -129,7 +129,7 @@ public class CmsContentTag extends AbstractListTag {
TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
Page<CmsContent> pageResult = this.contentService.page(new Page<>(pageIndex, size, page), q);
if (pageIndex > 1 & pageResult.getRecords().size() == 0) {
if (pageIndex > 1 & pageResult.getRecords().isEmpty()) {
throw new TemplateException("内容列表页码超出上限:" + pageIndex, env);
}
List<ContentDTO> list = new ArrayList<>();

View File

@ -73,7 +73,7 @@ public class CmsSitePropertyTag extends AbstractListTag {
.eq(StringUtils.isNotEmpty(code), CmsSiteProperty::getPropCode, code);
q.apply(StringUtils.isNotEmpty(condition), condition);
Page<CmsSiteProperty> pageResult = this.sitePropertyService.page(new Page<>(pageIndex, size, page), q);
if (pageIndex > 1 & pageResult.getRecords().size() == 0) {
if (pageIndex > 1 & pageResult.getRecords().isEmpty()) {
throw new TemplateException("站点自定义属性数据列表页码超出上限:" + pageIndex, env);
}
return TagPageData.of(pageResult.getRecords(), pageResult.getTotal());

View File

@ -106,6 +106,7 @@ FREEMARKER.FUNC.DESC.dynamicPageLink=动态页面链接获取函数,例如:$
FREEMARKER.FUNC.DESC.dict=获取字典数据列表,例如:${dict('YesOrNo', 'Y')}
FREEMARKER.FUNC.DESC.sysConfig=获取系统参数配置值,例如:${sysConfig('SiteApiUrl')}
FREEMARKER.FUNC.DESC.listHtmlInternalUrl=获取html文本中的iurl列表例如${listHtmlInternalUrl(ArticleContent)}
FREEMARKER.FUNC.DESC.contentPageLink=获取内容分页链接,例如:${contentPageLink(content.link, 2)}
FREEMARKER.FUNC.DESC.videoPlayer=将html文本中的视频资源链接替换为<video>视频播放器
FREEMARKER.FUNC.videoPlayer.Arg1.Name=Html文本内容
FREEMARKER.FUNC.videoPlayer.Arg2.Name=视频宽度

View File

@ -106,6 +106,7 @@ FREEMARKER.FUNC.DESC.dynamicPageLink=Use ${dynamicPageLink('Search')} in templat
FREEMARKER.FUNC.DESC.dict=Use ${dict(content.linkFlag, 'YesOrNo')} in template to get the dict data label.
FREEMARKER.FUNC.DESC.sysConfig=Use ${sysConfig('SiteApiUrl')} in template to get the system config value.
FREEMARKER.FUNC.DESC.listHtmlInternalUrl=Use ${listHtmlInternalUrl(ArticleContent)} in template to get the iurl list.
FREEMARKER.FUNC.DESC.contentPageLink=Use ${contentPageLink(content.link, 2)} in template to get the content page link.
FREEMARKER.FUNC.DESC.videoPlayer=Replace html video resource link tag to video tag.
FREEMARKER.FUNC.videoPlayer.Arg1.Name=Html content
FREEMARKER.FUNC.videoPlayer.Arg2.Name=Width

View File

@ -106,6 +106,7 @@ FREEMARKER.FUNC.DESC.dynamicPageLink=動態頁面連結獲取函數,例如:$
FREEMARKER.FUNC.DESC.dict=獲取字典數據列表,例如:${dict('YesOrNo', 'Y')}
FREEMARKER.FUNC.DESC.sysConfig=獲取系統參數配置值,例如:${sysConfig('SiteApiUrl')}
FREEMARKER.FUNC.DESC.listHtmlInternalUrl=獲取html文本中的iurl列表例如${listHtmlInternalUrl(ArticleContent)}
FREEMARKER.FUNC.DESC.contentPageLink=獲取內容分頁鏈接,例如:${contentPageLink(content.link, 2)}
FREEMARKER.FUNC.DESC.videoPlayer=將html文本中的視頻資源連結替換為<video>視頻播放器
FREEMARKER.FUNC.videoPlayer.Arg1.Name=Html文本內容
FREEMARKER.FUNC.videoPlayer.Arg2.Name=視頻寬度

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-customform</artifactId>

View File

@ -0,0 +1,216 @@
/*
* 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.customform;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.common.utils.JacksonUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.ICoreDataHandler;
import com.chestnut.contentcore.core.SiteExportContext;
import com.chestnut.contentcore.core.SiteImportContext;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.customform.domain.CmsCustomForm;
import com.chestnut.customform.domain.CmsCustomFormData;
import com.chestnut.customform.mapper.CustomFormDataMapper;
import com.chestnut.customform.service.ICustomFormService;
import com.chestnut.exmodel.MetaControlType_CmsImage;
import com.chestnut.exmodel.MetaControlType_UEditor;
import com.chestnut.xmodel.core.MetaModel;
import com.chestnut.xmodel.domain.XModel;
import com.chestnut.xmodel.domain.XModelField;
import com.chestnut.xmodel.service.IModelFieldService;
import com.chestnut.xmodel.service.IModelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
/**
* 自定义表单内容核心数据处理器
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CustomFormCoreDataHandler implements ICoreDataHandler {
private static final String XMODEL_TABLE_SUFFIX = "_cms_custom_form";
private final ICustomFormService customFormService;
private final CustomFormDataMapper customFormDataMapper;
private final IModelService modelService;
private final IModelFieldService modelFieldService;
@Override
public void onSiteExport(SiteExportContext context) {
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导出自定义表单数据");
// 自定义表单数据导出
List<CmsCustomForm> customForms = customFormService.lambdaQuery()
.eq(CmsCustomForm::getSiteId, context.getSite().getSiteId())
.list();
context.saveData(CmsCustomForm.TABLE_NAME, JacksonUtils.to(customForms));
// 自定义表单关联扩展模型
List<Long> modelIds = customForms.stream().map(CmsCustomForm::getModelId).toList();
if (modelIds.isEmpty()) {
return;
}
List<XModel> modelList = this.modelService.lambdaQuery().in(XModel::getModelId, modelIds).list();
context.saveData(XModel.TABLE_NAME + XMODEL_TABLE_SUFFIX, JacksonUtils.to(modelList));
// 扩展模型字段
List<XModelField> fieldList = this.modelFieldService.lambdaQuery()
.in(XModelField::getModelId, modelIds).list();
context.saveData(XModelField.TABLE_NAME + XMODEL_TABLE_SUFFIX, JacksonUtils.to(fieldList));
// 扩展模型数据
int pageSize = 200;
int fileIndex = 1;
for (XModel model : modelList) {
int pageIndex = 1;
while (true) {
LambdaQueryWrapper<CmsCustomFormData> q = new LambdaQueryWrapper<CmsCustomFormData>()
.eq(CmsCustomFormData::getModelId, model.getModelId());
Page<CmsCustomFormData> page = customFormDataMapper.selectPage(new Page<>(pageIndex, pageSize, false), q);
if (page.getRecords().isEmpty()) {
break;
}
pageIndex++;
context.saveData(CmsCustomFormData.TABLE_NAME, JacksonUtils.to(page.getRecords()), fileIndex);
fileIndex++;
}
}
}
@Override
public void onSiteImport(SiteImportContext context) {
// CmsCustomForm
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入自定义表单");
Map<Long, Long> customFormIdMapping = new HashMap<>();
List<File> files = context.readDataFiles(CmsCustomForm.TABLE_NAME);
files.forEach(f -> {
List<CmsCustomForm> list = JacksonUtils.fromList(f, CmsCustomForm.class);
for (CmsCustomForm data : list) {
try {
Long oldFormId = data.getFormId();
data.setFormId(IdUtils.getSnowflakeId());
data.setModelId(data.getFormId());
data.setSiteId(context.getSite().getSiteId());
data.createBy(context.getOperator());
customFormService.save(data);
customFormIdMapping.put(oldFormId, data.getFormId());
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入自定义表单失败:" + data.getName()
+ "[" + data.getModelId() + "]");
log.error("Import custom form failed: {}", data.getCode(), e);
}
}
});
if (files.isEmpty()) {
return;
}
// XModel
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入自定义表单元数据模型");
files = context.readDataFiles(XModel.TABLE_NAME + XMODEL_TABLE_SUFFIX);
files.forEach(f -> {
List<XModel> list = JacksonUtils.fromList(f, XModel.class);
for (XModel data : list) {
try {
data.setModelId(customFormIdMapping.get(data.getModelId()));
data.setCode(data.getModelId().toString());
data.setOwnerId(context.getSite().getSiteId().toString());
data.createBy(context.getOperator());
modelService.save(data);
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入自定义表单元数据模型失败:" + data.getName()
+ "[" + data.getModelId() + "]");
log.error("Import custom form xmodel failed: {}", data.getCode(), e);
}
}
});
// XModelField
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入自定义表单模型字段");
files = context.readDataFiles(XModelField.TABLE_NAME + XMODEL_TABLE_SUFFIX);
files.forEach(f -> {
List<XModelField> list = JacksonUtils.fromList(f, XModelField.class);
for (XModelField data : list) {
try {
data.setFieldId(IdUtils.getSnowflakeId());
data.setModelId(customFormIdMapping.get(data.getModelId()));
data.createBy(context.getOperator());
modelFieldService.save(data);
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入自定义表单模型字段失败:" + data.getName()
+ "[" + data.getFieldId() + "]");
log.error("Import custom form xmodel field failed: {}", data.getCode(), e);
}
}
});
// CmsCustomFormData
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入自定义表单数据");
files = context.readDataFiles(CmsCustomFormData.TABLE_NAME);
files.forEach(f -> {
List<CmsCustomFormData> list = JacksonUtils.fromList(f, CmsCustomFormData.class);
for (CmsCustomFormData data : list) {
try {
data.setDataId(IdUtils.getSnowflakeId());
data.setModelId(customFormIdMapping.get(data.getModelId()));
// 处理图片和富文本内部链接
MetaModel model = modelService.getMetaModel(data.getModelId());
model.getFields().forEach(field -> {
if (MetaControlType_CmsImage.TYPE.equals(field.getControlType())) {
String fieldValue = data.getStringFieldValue(field.getFieldName());
data.setFieldValue(field.getFieldName(), context.dealInternalUrl(fieldValue));
} else if (MetaControlType_UEditor.TYPE.equals(field.getControlType())) {
String fieldValue = data.getStringFieldValue(field.getFieldName());
if (StringUtils.isNotEmpty(fieldValue)) {
// 替换正文内部资源地址
StringBuilder html = new StringBuilder();
int index = 0;
Matcher matcher = InternalUrlUtils.InternalUrlTagPattern.matcher(fieldValue);
while (matcher.find()) {
String tagStr = matcher.group();
String iurl = matcher.group(1);
String newIurl = context.dealInternalUrl(iurl);
tagStr = StringUtils.replaceEx(tagStr, iurl, newIurl);
html.append(fieldValue, index, matcher.start()).append(tagStr);
index = matcher.end();
}
html.append(fieldValue.substring(index));
data.setFieldValue(field.getFieldName(), html.toString());
}
}
});
customFormDataMapper.insert(data);
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入自定义及表单数据失败:" + data.getModelId() + "-" + data.getDataId());
log.error("Import custom form data failed: {} - {}", data.getModelId(), data.getDataId(), e);
}
}
});
}
}

View File

@ -51,7 +51,7 @@ public class CmsCustomForm extends BaseEntity {
private Long siteId;
/**
* 关联元数据模型ID
* 关联元数据模型ID与主键form_id一致
*/
private Long modelId;

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-dynamic</artifactId>

View File

@ -18,6 +18,7 @@ package com.chestnut.cms.dynamic.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.cms.dynamic.core.IDynamicPageInitData;
import com.chestnut.cms.dynamic.domain.CmsDynamicPage;
import com.chestnut.cms.dynamic.domain.vo.DynamicPageInitDataTypeVO;
import com.chestnut.cms.dynamic.service.IDynamicPageService;
import com.chestnut.common.domain.R;
import com.chestnut.common.exception.CommonErrorCode;
@ -78,7 +79,9 @@ public class DynamicPageController extends BaseRestController {
@GetMapping("/init_data_types")
public R<?> getInitDataTypes() {
return R.ok(initDataTypes);
List<DynamicPageInitDataTypeVO> list = initDataTypes.stream()
.map(DynamicPageInitDataTypeVO::newInstance).toList();
return R.ok(list);
}
@GetMapping("/{pageId}")

View File

@ -52,7 +52,7 @@ public class DynamicPageFrontController extends BaseRestController {
public void handleDynamicPageRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, String> parameters = ServletUtils.getParameters();
String requestURI = request.getRequestURI();
System.out.println("dynamicPage: " + requestURI);
Long siteId = MapUtils.getLong(parameters, "sid", 0L);
String publishPipeCode = parameters.get("pp");
boolean preview = MapUtils.getBoolean(parameters, "preview", false);

View File

@ -17,6 +17,8 @@ package com.chestnut.cms.dynamic.core;
import com.chestnut.common.staticize.core.TemplateContext;
import java.util.Map;
/**
* 动态页面初始化数据接口
*
@ -27,7 +29,7 @@ public interface IDynamicPageInitData {
String BEAN_PREFIX = "DynamicPageInitData.";
void initTemplateData(TemplateContext context);
void initTemplateData(TemplateContext context, Map<String, String> parameters);
String getType();

View File

@ -22,27 +22,31 @@ import com.chestnut.member.security.StpMemberUtil;
import com.chestnut.member.util.MemberUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* MemberDynamicPageInitData
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component
@Component(IDynamicPageInitData.BEAN_PREFIX + MemberDynamicPageInitData.TYPE)
public class MemberDynamicPageInitData implements IDynamicPageInitData {
public static final String TYPE = "Member";
@Override
public String getType() {
return "Member";
return TYPE;
}
@Override
public String getName() {
return "登录会员";
return "{DYNAMIC_PAGE_INIT_DATA." + TYPE + "}";
}
@Override
public void initTemplateData(TemplateContext context) {
public void initTemplateData(TemplateContext context, Map<String, String> parameters) {
if (StpMemberUtil.isLogin()) {
LoginUser loginUser = StpMemberUtil.getLoginUser();
context.getVariables().put("Member", loginUser.getUser());

View File

@ -0,0 +1,51 @@
/*
* 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.cms.dynamic.core.impl;
import com.chestnut.cms.dynamic.core.IDynamicPageInitData;
import com.chestnut.common.staticize.core.TemplateContext;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* PaginationDynamicPageInitData
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IDynamicPageInitData.BEAN_PREFIX + PaginationDynamicPageInitData.TYPE)
public class PaginationDynamicPageInitData implements IDynamicPageInitData {
public static final String TYPE = "Pagination";
@Override
public String getType() {
return TYPE;
}
@Override
public String getName() {
return "{DYNAMIC_PAGE_INIT_DATA." + TYPE + "}";
}
@Override
public void initTemplateData(TemplateContext context, Map<String, String> parameters) {
int pageIndex = MapUtils.getIntValue(parameters, "pi", 1);
context.setPageIndex(pageIndex);
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.cms.dynamic.domain.vo;
import com.chestnut.cms.dynamic.core.IDynamicPageInitData;
import com.chestnut.common.i18n.I18nUtils;
import lombok.Getter;
import lombok.Setter;
/**
* 自定义动态页面初始化数据类型VO
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class DynamicPageInitDataTypeVO {
/**
* 类型唯一标识
*/
private String type;
/**
* 名称
*/
private String name;
public static DynamicPageInitDataTypeVO newInstance(IDynamicPageInitData dynamicPageInitData) {
DynamicPageInitDataTypeVO vo = new DynamicPageInitDataTypeVO();
vo.setType(dynamicPageInitData.getType());
vo.setName(I18nUtils.get(dynamicPageInitData.getName()));
return vo;
}
}

View File

@ -25,8 +25,6 @@ import java.util.Map;
public interface IDynamicPageService extends IService<CmsDynamicPage> {
String getDynamicPagePath(Long siteId, String code);
void addDynamicPage(CmsDynamicPage dynamicPage);
void saveDynamicPage(CmsDynamicPage dynamicPage);

View File

@ -0,0 +1,76 @@
/*
* 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.cms.dynamic.service.impl;
import com.chestnut.cms.dynamic.core.IDynamicPageInitData;
import com.chestnut.cms.dynamic.domain.CmsDynamicPage;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.core.IDynamicPageType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicPageHelper {
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "dynamic_page:";
private final RedisCache redisCache;
private final Map<String, IDynamicPageInitData> dynamicPageInitDataMap;
private final Map<String, IDynamicPageType> dynamicPageTypeMap;
public IDynamicPageInitData getDynamicPageInitData(String type) {
return dynamicPageInitDataMap.get(IDynamicPageInitData.BEAN_PREFIX + type);
}
public String getDynamicPagePath(Long siteId, String code) {
IDynamicPageType dynamicPageType = this.dynamicPageTypeMap.get(IDynamicPageType.BEAN_PREFIX + code);
if (Objects.nonNull(dynamicPageType)) {
return dynamicPageType.getRequestPath();
}
CmsDynamicPage dynamicPage = getDynamicPage(siteId, code);
if (Objects.nonNull(dynamicPage)) {
return dynamicPage.getPath();
}
return null;
}
public void clearCache(CmsDynamicPage dynamicPage) {
this.redisCache.deleteObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getPath());
this.redisCache.deleteObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getCode());
}
public void updateCache(CmsDynamicPage dynamicPage) {
this.redisCache.setCacheObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getPath(), dynamicPage);
this.redisCache.setCacheObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getCode(), dynamicPage);
}
public CmsDynamicPage getDynamicPage(Long siteId, String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return redisCache.getCacheObject(CACHE_PREFIX + siteId + ":" + path);
}
}

View File

@ -24,15 +24,12 @@ import com.chestnut.cms.dynamic.mapper.CmsDynamicPageMapper;
import com.chestnut.cms.dynamic.service.IDynamicPageService;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.exception.GlobalException;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.common.staticize.StaticizeService;
import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.common.utils.ServletUtils;
import com.chestnut.common.utils.SpringUtils;
import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.core.IDynamicPageType;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.service.ITemplateService;
@ -67,33 +64,17 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
private final StaticizeService staticizeService;
private final RedisCache redisCache;
private final Map<String, IDynamicPageInitData> dynamicPageInitDataMap;
private final List<RequestMappingHandlerMapping> allRequestMapping;
private final DynamicPageRequestMappingHandlerMapping dynamicPageRequestMapping;
private final Map<String, IDynamicPageType> dynamicPageTypeMap;
private final DynamicPageHelper dynamicPageHelper;
@Override
public String getDynamicPagePath(Long siteId, String code) {
IDynamicPageType dynamicPageType = this.dynamicPageTypeMap.get(IDynamicPageType.BEAN_PREFIX + code);
if (Objects.nonNull(dynamicPageType)) {
return dynamicPageType.getRequestPath();
}
CmsDynamicPage dynamicPage = getDynamicPage(siteId, code);
if (Objects.nonNull(dynamicPage)) {
return dynamicPage.getPath();
}
return null;
}
@Override
public void addDynamicPage(CmsDynamicPage dynamicPage) {
if (!dynamicPage.getPath().startsWith("/")) {
dynamicPage.setPath("/" + dynamicPage.getPath());
if (dynamicPage.getPath().startsWith("/")) {
dynamicPage.setPath(dynamicPage.getPath().substring(1));
}
this.checkDynamicPage(dynamicPage);
@ -102,7 +83,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
this.registerDynamicPageMapping(dynamicPage);
this.updateCache(dynamicPage);
dynamicPageHelper.updateCache(dynamicPage);
}
@Override
@ -119,7 +100,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
dbDynamicPage.setTemplates(dynamicPage.getTemplates());
this.updateById(dbDynamicPage);
this.updateCache(dbDynamicPage);
dynamicPageHelper.updateCache(dbDynamicPage);
}
@Override
@ -129,24 +110,10 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
dynamicPages.forEach(dynamicPage -> {
this.unregisterDynamicPageMapping(dynamicPage);
this.clearCache(dynamicPage);
dynamicPageHelper.clearCache(dynamicPage);
});
}
private void clearCache(CmsDynamicPage dynamicPage) {
this.redisCache.deleteObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getPath());
this.redisCache.deleteObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getCode());
}
private void updateCache(CmsDynamicPage dynamicPage) {
this.redisCache.setCacheObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getPath(), dynamicPage);
this.redisCache.setCacheObject(CACHE_PREFIX + dynamicPage.getSiteId() + ":" + dynamicPage.getCode(), dynamicPage);
}
private CmsDynamicPage getDynamicPage(Long siteId, String path) {
return redisCache.getCacheObject(CACHE_PREFIX + siteId + ":" + path);
}
private void checkDynamicPage(CmsDynamicPage dynamicPage) {
Long count = this.lambdaQuery()
.and(wrapper -> wrapper.eq(CmsDynamicPage::getPath, dynamicPage.getPath())
@ -201,7 +168,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
public void run(String... args) throws Exception {
// 初始化DynamicPageRequestMapping
this.list().forEach(dynamicPage -> {
this.updateCache(dynamicPage);
dynamicPageHelper.updateCache(dynamicPage);
this.registerDynamicPageMapping(dynamicPage);
});
@ -218,7 +185,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
this.catchException("/", response, new RuntimeException("Site not found: " + siteId));
return;
}
CmsDynamicPage dynamicPage = this.getDynamicPage(siteId, requestURI);
CmsDynamicPage dynamicPage = dynamicPageHelper.getDynamicPage(siteId, requestURI);
String template = dynamicPage.getTemplates().get(publishPipeCode);
File templateFile = this.templateService.findTemplateFile(site, template, publishPipeCode);
if (Objects.isNull(templateFile) || !templateFile.exists()) {
@ -236,13 +203,13 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
// init template datamode
TemplateUtils.initGlobalVariables(site, templateContext);
// init templateType data to datamode
templateContext.getVariables().put("Request", ServletUtils.getParameters());
templateContext.getVariables().put("Request", parameters);
// 动态页面自定义数据
if (Objects.nonNull(dynamicPage.getInitDataTypes())) {
dynamicPage.getInitDataTypes().forEach(initDataType -> {
IDynamicPageInitData initData = dynamicPageInitDataMap.get(IDynamicPageInitData.BEAN_PREFIX + initDataType);
IDynamicPageInitData initData = dynamicPageHelper.getDynamicPageInitData(initDataType);
if (Objects.nonNull(initData)) {
initData.initTemplateData(templateContext);
initData.initTemplateData(templateContext, parameters);
}
});
}

View File

@ -0,0 +1,95 @@
/*
* 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.cms.dynamic.template.func;
import com.chestnut.cms.dynamic.service.impl.DynamicPageHelper;
import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.staticize.func.AbstractFunc;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.util.TemplateUtils;
import freemarker.core.Environment;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateModelException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Freemarker模板自定义函数获取自定义动态模板链接
*/
@Component
@RequiredArgsConstructor
public class CustomDynamicPageLinkFunction extends AbstractFunc {
static final String FUNC_NAME = "customDynamicPageLink";
private static final String DESC = "{FREEMARKER.FUNC.DESC." + FUNC_NAME + "}";
private final DynamicPageHelper dynamicPageHelper;
@Override
public String getFuncName() {
return FUNC_NAME;
}
@Override
public String getDesc() {
return DESC;
}
@Override
public Object exec0(Object... args) throws TemplateModelException {
if (args.length < 1) {
return StringUtils.EMPTY;
}
String code = args[0].toString(); // 自动动态模板编码
Environment env = Environment.getCurrentEnvironment();
TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
long siteId = FreeMarkerUtils.evalLongVariable(env, "Site.siteId");
String path = this.dynamicPageHelper.getDynamicPagePath(siteId, code);
if (StringUtils.isEmpty(path)) {
throw new TemplateModelException("Unknown dynamic page code: " + code);
}
if (context.isPreview()) {
path += "?preview=true";
path = TemplateUtils.appendTokenParameter(path, Environment.getCurrentEnvironment());
}
if (args.length == 2) {
String queryString = args[1].toString();
path += (path.contains("?") ? "&" : "?") + queryString;
}
boolean ignoreBaseArg = true;
if (args.length == 3) {
ignoreBaseArg = ((TemplateBooleanModel) args[2]).getAsBoolean();
}
if (context.isPreview() || !ignoreBaseArg) {
path += (path.contains("?") ? "&" : "?") + "sid=" + siteId + "&pp=" + context.getPublishPipeCode();
}
return path;
}
@Override
public List<FuncArg> getFuncArgs() {
return List.of(
new FuncArg("动态页面编码", FuncArgType.String, true, null),
new FuncArg("自定义参数", FuncArgType.String, false, null),
new FuncArg("忽略sid/pp参数", FuncArgType.String, false, "默认true")
);
}
}

View File

@ -0,0 +1,4 @@
DYNAMIC_PAGE_INIT_DATA.Member=登录会员
DYNAMIC_PAGE_INIT_DATA.Pagination=分页参数
FREEMARKER.FUNC.DESC.customDynamicPageLink=获取自定义动态模板页面链接,例如:${customDynamicPageLink('Test','a=1&b=2',true)}

View File

@ -0,0 +1,4 @@
DYNAMIC_PAGE_INIT_DATA.Member=Member
DYNAMIC_PAGE_INIT_DATA.Pagination=Pagination
FREEMARKER.FUNC.DESC.customDynamicPageLink=Get custom dynamic page link, eg: ${customDynamicPageLink('Test','a=1&b=2',true)}

View File

@ -0,0 +1,4 @@
DYNAMIC_PAGE_INIT_DATA.Member=登錄會員
DYNAMIC_PAGE_INIT_DATA.Pagination=分頁參數
FREEMARKER.FUNC.DESC.customDynamicPageLink=獲取自定義動態模板頁面鏈接,例如:${customDynamicPageLink('Test','a=1&b=2',true)}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-exmodel</artifactId>

View File

@ -59,6 +59,8 @@ import java.util.regex.Matcher;
@RequiredArgsConstructor
public class EXModelCoreDataHandler implements ICoreDataHandler {
private static final String XMODEL_TABLE_SUFFIX = "_cms_exmodel";
private final ExtendModelMapper modelDataMapper;
private final IModelService modelService;
@ -88,11 +90,11 @@ public class EXModelCoreDataHandler implements ICoreDataHandler {
// 扩展模型
List<XModel> modelList = this.modelService.lambdaQuery().in(XModel::getModelId, modelIdStrings).list();
context.saveData(XModel.TABLE_NAME, JacksonUtils.to(modelList));
context.saveData(XModel.TABLE_NAME + XMODEL_TABLE_SUFFIX, JacksonUtils.to(modelList));
// 扩展模型字段
List<XModelField> fieldList = this.modelFieldService.lambdaQuery()
.in(XModelField::getModelId, modelIdStrings).list();
context.saveData(XModelField.TABLE_NAME, JacksonUtils.to(fieldList));
context.saveData(XModelField.TABLE_NAME + XMODEL_TABLE_SUFFIX, JacksonUtils.to(fieldList));
// 扩展模型数据
int pageSize = 200;
int fileIndex = 1;
@ -117,7 +119,7 @@ public class EXModelCoreDataHandler implements ICoreDataHandler {
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入扩展模型");
// XModel
Map<Long, Long> modelIdMapping = new HashMap<>();
List<File> files = context.readDataFiles(XModel.TABLE_NAME);
List<File> files = context.readDataFiles(XModel.TABLE_NAME + XMODEL_TABLE_SUFFIX);
files.forEach(f -> {
List<XModel> list = JacksonUtils.fromList(f, XModel.class);
for (XModel data : list) {
@ -141,7 +143,7 @@ public class EXModelCoreDataHandler implements ICoreDataHandler {
});
// XModelField
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入扩展模型字段");
files = context.readDataFiles(XModelField.TABLE_NAME);
files = context.readDataFiles(XModelField.TABLE_NAME + XMODEL_TABLE_SUFFIX);
files.forEach(f -> {
List<XModelField> list = JacksonUtils.fromList(f, XModelField.class);
for (XModelField data : list) {
@ -198,7 +200,7 @@ public class EXModelCoreDataHandler implements ICoreDataHandler {
});
modelDataMapper.insert(data);
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入扩展模型字段失败:" + data.getModelId()
AsyncTaskManager.addErrMessage("导入扩展模型数据失败:" + data.getModelId()
+ data.getDataType() + "-" + data.getDataId());
log.error("Import xmodel data failed: {} - {}", data.getModelId(), data.getDataId(), e);
}

View File

@ -52,9 +52,9 @@ public class MetaControlType_CmsImage implements IMetaControlType {
String imagePath = value.toString();
if (InternalUrlUtils.isInternalUrl(imagePath)) {
String previewUrl = InternalUrlUtils.getActualPreviewUrl(imagePath);
fieldData.setValueSrc(previewUrl);
fieldData.setValueObj(previewUrl);
} else {
fieldData.setValueSrc(imagePath);
fieldData.setValueObj(imagePath);
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.exmodel;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.service.IContentService;
import com.chestnut.xmodel.core.IMetaControlType;
import com.chestnut.xmodel.dto.XModelFieldDataDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* 元数据模型字段控件类型内容选择
*
* @author 兮玥
* @email 190785909@qq.com
*/
@RequiredArgsConstructor
@Component(IMetaControlType.BEAN_PREFIX + MetaControlType_ContentSelect.TYPE)
public class MetaControlType_ContentSelect implements IMetaControlType {
public static final String TYPE = "CMSContentSelect";
private final IContentService contentService;
@Override
public String getType() {
return TYPE;
}
@Override
public String getName() {
return "{META.CONTROL_TYPE." + TYPE + "}";
}
@Override
public void parseFieldValue(XModelFieldDataDTO fieldData) {
Object value = fieldData.getValue();
if (Objects.isNull(value) || StringUtils.isEmpty(value.toString())) {
fieldData.setValueObj(Map.of());
return;
}
Optional<CmsContent> content = contentService.lambdaQuery()
.select(List.of(CmsContent::getContentId, CmsContent::getTitle))
.eq(CmsContent::getContentId, value).oneOpt();
if (content.isEmpty()) {
fieldData.setValue(StringUtils.EMPTY);
fieldData.setValueObj(Map.of());
return;
}
fieldData.setValueObj(Map.of(
"contentId", content.get().getContentId(),
"title", content.get().getTitle()
));
}
}

View File

@ -11,4 +11,5 @@ DICT.ExtendModelDataType.catalog=栏目
DICT.ExtendModelDataType.content=内容
META.CONTROL_TYPE.CMSImage=图片上传
META.CONTROL_TYPE.UEditor=富文本编辑器
META.CONTROL_TYPE.UEditor=富文本编辑器
META.CONTROL_TYPE.CMSContentSelect=内容选择

View File

@ -11,4 +11,5 @@ DICT.ExtendModelDataType.catalog=Catalog
DICT.ExtendModelDataType.content=Content
META.CONTROL_TYPE.CMSImage=Image Upload
META.CONTROL_TYPE.UEditor=Rich Text Editor
META.CONTROL_TYPE.UEditor=Rich Text Editor
META.CONTROL_TYPE.CMSContentSelect=Content Selector

View File

@ -12,3 +12,4 @@ DICT.ExtendModelDataType.content=內容
META.CONTROL_TYPE.CMSImage=圖片上傳
META.CONTROL_TYPE.UEditor=富文本編輯器
META.CONTROL_TYPE.CMSContentSelect=內容選擇

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-image</artifactId>

View File

@ -0,0 +1,97 @@
/*
* 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.cms.image;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.cms.image.domain.CmsImage;
import com.chestnut.cms.image.service.IImageService;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.common.utils.JacksonUtils;
import com.chestnut.contentcore.core.ICoreDataHandler;
import com.chestnut.contentcore.core.SiteExportContext;
import com.chestnut.contentcore.core.SiteImportContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
/**
* 图集内容扩展内容核心数据处理器
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ImageCoreDataHandler implements ICoreDataHandler {
private final IImageService imageService;
@Override
public void onSiteExport(SiteExportContext context) {
// cms_image
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导出图集内容数据");
int pageSize = 200;
long offset = 0;
int fileIndex = 1;
while (true) {
LambdaQueryWrapper<CmsImage> q = new LambdaQueryWrapper<CmsImage>()
.eq(CmsImage::getSiteId, context.getSite().getSiteId())
.gt(CmsImage::getImageId, offset)
.orderByAsc(CmsImage::getImageId);
Page<CmsImage> page = imageService.page(new Page<>(1, pageSize, false), q);
if (!page.getRecords().isEmpty()) {
context.saveData(CmsImage.TABLE_NAME, JacksonUtils.to(page.getRecords()), fileIndex);
if (page.getRecords().size() < pageSize) {
break;
}
offset = page.getRecords().get(page.getRecords().size() - 1).getContentId();
fileIndex++;
} else {
break;
}
}
}
@Override
public void onSiteImport(SiteImportContext context) {
// cms_image
AsyncTaskManager.setTaskTenPercentProgressInfo("正在导入图集内容数据");
List<File> files = context.readDataFiles(CmsImage.TABLE_NAME);
files.forEach(f -> {
List<CmsImage> list = JacksonUtils.fromList(f, CmsImage.class);
for (CmsImage data : list) {
try {
data.setImageId(IdUtils.getSnowflakeId());
data.setSiteId(context.getSite().getSiteId());
data.setContentId(context.getContentIdMap().get(data.getContentId()));
data.createBy(context.getOperator());
data.setPath(context.dealInternalUrl(data.getPath()));
imageService.save(data);
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入图集内容数据失败:" + data.getTitle()
+ "[" + data.getImageId() + "]");
log.error("Import cms_image failed: {}", data.getImageId(), e);
}
}
});
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-link</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-media</artifactId>

View File

@ -77,7 +77,7 @@ public class CmsVideoTag extends AbstractListTag {
q.orderByAsc(CmsVideo::getSortFlag);
Page<CmsVideo> pageResult = this.videoService.page(new Page<>(pageIndex, size, page), q);
if (pageIndex > 1 & pageResult.getRecords().size() == 0) {
if (pageIndex > 1 & pageResult.getRecords().isEmpty()) {
throw new TemplateException("内容列表页码超出上限:" + pageIndex, env);
}
TemplateContext context = FreeMarkerUtils.getTemplateContext(env);

View File

@ -6,7 +6,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-member</artifactId>

View File

@ -63,7 +63,7 @@ public class AccountBindEmailDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "会员绑定邮箱页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -59,7 +59,7 @@ public class AccountCentreDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "个人中心页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -76,7 +76,7 @@ public class AccountContributeDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "会员投稿页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -47,7 +47,7 @@ public class AccountForgetPasswordDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "会员找回密码页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -47,7 +47,7 @@ public class AccountLoginDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "会员登录页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -58,12 +58,12 @@ public class AccountPasswordDynamicPageType implements IDynamicPageType {
@Override
public String getType() {
return TYPE;
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override
public String getName() {
return "会员修改密码页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -47,7 +47,7 @@ public class AccountRegisterDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "会员注册页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -63,7 +63,7 @@ public class AccountSettingDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "会员设置页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -9,3 +9,13 @@ FREEMARKER.TAG.DESC.cms_member_follow=获取关注/粉丝数据列表,内嵌<#
# freemarker模板函数
FREEMARKER.FUNC.DESC.accountUrl=会员中心动态页面地址,例如:${accountUrl('comment')}
# 动态模板
DYNAMIC_PAGE_TYPE.NAME.AccountBindEmail=会员绑定邮箱页
DYNAMIC_PAGE_TYPE.NAME.AccountCentre=会员个人中心页
DYNAMIC_PAGE_TYPE.NAME.AccountContribute=会员投稿页
DYNAMIC_PAGE_TYPE.NAME.AccountForgetPassword=会员找回密码页
DYNAMIC_PAGE_TYPE.NAME.AccountLogin=会员登录页
DYNAMIC_PAGE_TYPE.NAME.AccountPassword=会员修改密码页
DYNAMIC_PAGE_TYPE.NAME.AccountRegister=会员注册页
DYNAMIC_PAGE_TYPE.NAME.AccountSetting=会员设置页

View File

@ -9,3 +9,13 @@ FREEMARKER.TAG.DESC.cms_member_follow=Fetch member follow/follower list, use <#l
# freemarker模板函数
FREEMARKER.FUNC.DESC.accountUrl=Account centre page url, eg: ${accountUrl('comment')}.
# 动态模板
DYNAMIC_PAGE_TYPE.NAME.AccountBindEmail=Account binding email page
DYNAMIC_PAGE_TYPE.NAME.AccountCentre=Account centre page
DYNAMIC_PAGE_TYPE.NAME.AccountContribute=Account contribute page
DYNAMIC_PAGE_TYPE.NAME.AccountForgetPassword=Account forget password page
DYNAMIC_PAGE_TYPE.NAME.AccountLogin=Account login page
DYNAMIC_PAGE_TYPE.NAME.AccountPassword=Account modify password page
DYNAMIC_PAGE_TYPE.NAME.AccountRegister=Account register page
DYNAMIC_PAGE_TYPE.NAME.AccountSetting=Account setting page

View File

@ -9,3 +9,13 @@ FREEMARKER.TAG.DESC.cms_member_follow=獲取關注/粉絲數據列表,內嵌<#
# freemarker模板函數
FREEMARKER.FUNC.DESC.accountUrl=會員中心動態頁面地址,例如:${accountUrl('comment')}
# 动态模板
DYNAMIC_PAGE_TYPE.NAME.AccountBindEmail=會員綁定郵箱頁
DYNAMIC_PAGE_TYPE.NAME.AccountCentre=會員個人中心頁
DYNAMIC_PAGE_TYPE.NAME.AccountContribute=會員投稿頁
DYNAMIC_PAGE_TYPE.NAME.AccountForgetPassword=會員找回密碼頁
DYNAMIC_PAGE_TYPE.NAME.AccountLogin=會員登錄頁
DYNAMIC_PAGE_TYPE.NAME.AccountPassword=會員修改密碼頁
DYNAMIC_PAGE_TYPE.NAME.AccountRegister=會員註冊頁
DYNAMIC_PAGE_TYPE.NAME.AccountSetting=會員設置頁

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-search</artifactId>

View File

@ -0,0 +1,31 @@
/*
* 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.cms.search;
/**
* CmsSearchConstants
*
* @author 兮玥
* @email 190785909@qq.com
*/
public interface CmsSearchConstants {
String SEARCH_SOURCE_PREFIX = "cms:";
static String generateSearchSource(Long siteId) {
return SEARCH_SOURCE_PREFIX + siteId;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.cms.search.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.cms.search.CmsSearchConstants;
import com.chestnut.common.domain.R;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.security.web.PageRequest;
import com.chestnut.common.utils.ServletUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.search.domain.SearchLog;
import com.chestnut.search.domain.SearchWord;
import com.chestnut.search.service.ISearchLogService;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController
@RequestMapping("/cms/search/log")
public class CMSSearchLogController extends BaseRestController {
private final ISiteService siteService;
private final ISearchLogService searchLogService;
@Priv(type = AdminUserType.TYPE)
@GetMapping
public R<?> getPageList(@RequestParam(value = "query", required = false) String query) {
PageRequest pr = this.getPageRequest();
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
Page<SearchLog> page = this.searchLogService.lambdaQuery()
.eq(SearchLog::getSource, CmsSearchConstants.generateSearchSource(site.getSiteId()))
.like(StringUtils.isNotEmpty(query), SearchLog::getWord, query)
.orderByDesc(SearchLog::getLogId)
.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true));
return this.bindDataTable(page);
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.cms.search.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.cms.search.CmsSearchConstants;
import com.chestnut.common.domain.R;
import com.chestnut.common.log.annotation.Log;
import com.chestnut.common.log.enums.BusinessType;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.security.web.PageRequest;
import com.chestnut.common.utils.ServletUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.search.domain.SearchWord;
import com.chestnut.search.service.ISearchWordService;
import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Priv(type = AdminUserType.TYPE)
@RequiredArgsConstructor
@RestController
@RequestMapping("/cms/search/word")
public class CMSSearchWordStatController extends BaseRestController {
private final ISiteService siteService;
private final ISearchWordService searchWordStatService;
@GetMapping
public R<?> getPageList(@RequestParam(value = "query", required = false) String query) {
PageRequest pr = this.getPageRequest();
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
Page<SearchWord> page = this.searchWordStatService.lambdaQuery()
.eq(SearchWord::getSource, CmsSearchConstants.generateSearchSource(site.getSiteId()))
.like(StringUtils.isNotEmpty(query), SearchWord::getWord, query)
.orderByDesc(SearchWord::getTopFlag, SearchWord::getSearchTotal)
.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true));
return this.bindDataTable(page);
}
@Log(title = "新增搜索词", businessType = BusinessType.INSERT)
@PostMapping
public R<?> addWord(@RequestBody SearchWord wordStat) {
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
wordStat.setSource(CmsSearchConstants.generateSearchSource(site.getSiteId()));
wordStat.createBy(StpAdminUtil.getLoginUser().getUsername());
this.searchWordStatService.addWord(wordStat);
return R.ok();
}
}

View File

@ -19,6 +19,7 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.chestnut.cms.search.CmsSearchConstants;
import com.chestnut.cms.search.es.doc.ESContent;
import com.chestnut.cms.search.vo.ESContentVO;
import com.chestnut.common.domain.R;
@ -153,7 +154,7 @@ public class SearchApiController extends BaseRestController {
c.setCommentCount(cdd.getComments());
});
// 记录搜索日志
this.logService.addSearchLog("site:" + siteId, query, ServletUtils.getRequest());
this.logService.addSearchLog(CmsSearchConstants.generateSearchSource(siteId), query, ServletUtils.getRequest());
return this.bindDataTable(list, Objects.isNull(sr.hits().total()) ? 0 : sr.hits().total().value());
}

View File

@ -58,7 +58,7 @@ public class SearchDynamicPageType implements IDynamicPageType {
@Override
public String getName() {
return "搜索结果页";
return "{DYNAMIC_PAGE_TYPE.NAME." + TYPE + "}";
}
@Override

View File

@ -15,6 +15,7 @@
*/
package com.chestnut.cms.search.properties;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.IProperty;
import com.chestnut.contentcore.util.ConfigPropertyUtils;
import com.chestnut.system.fixed.dict.YesOrNo;
@ -29,6 +30,8 @@ import java.util.Map;
public class EnableIndexProperty implements IProperty {
public final static String ID = "EnableIndex";
private final static String DEFAULT_VALUE = YesOrNo.YES;
static UseType[] UseTypes = new UseType[] { UseType.Site, UseType.Catalog };
@ -49,11 +52,14 @@ public class EnableIndexProperty implements IProperty {
@Override
public String defaultValue() {
return YesOrNo.YES;
return DEFAULT_VALUE;
}
public static String getValue(Map<String, String> firstConfigProps, Map<String, String> secondConfigProps) {
String value = ConfigPropertyUtils.getStringValue(ID, firstConfigProps, secondConfigProps);
if (StringUtils.isEmpty(value)) {
value = DEFAULT_VALUE;
}
return YesOrNo.isYes(value) ? value : YesOrNo.NO;
}
}

View File

@ -29,7 +29,6 @@ import com.chestnut.cms.search.properties.EnableIndexProperty;
import com.chestnut.common.async.AsyncTask;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.DateUtils;
import com.chestnut.contentcore.core.IContent;
import com.chestnut.contentcore.core.IContentType;
import com.chestnut.contentcore.core.impl.InternalDataType_Content;
@ -105,7 +104,7 @@ public class ContentIndexService implements CommandLineRunner {
Assert.isTrue(response.acknowledged(), () -> new RuntimeException("Create Index[cms_content] failed."));
}
public void recreateIndex(CmsSite site) throws IOException {
public void deleteContentIndices(CmsSite site) throws IOException {
boolean exists = esClient.indices().exists(fn -> fn.index(ESContent.INDEX_NAME)).value();
if (exists) {
// 删除站点索引文档数据
@ -119,9 +118,7 @@ public class ContentIndexService implements CommandLineRunner {
.getRecords().stream().map(CmsContent::getContentId).toList();
deleteContentDoc(contentIds);
}
esClient.indices().delete(fn -> fn.index(ESContent.INDEX_NAME));
}
this.createIndex();
}
/**
@ -219,8 +216,8 @@ public class ContentIndexService implements CommandLineRunner {
@Override
public void run0() throws Exception {
// 重建索引
recreateIndex(site);
// 删除内容索引 TODO batchDelete
deleteContentIndices(site);
List<CmsCatalog> catalogs = catalogService.lambdaQuery()
.eq(CmsCatalog::getSiteId, site.getSiteId()).list();

View File

@ -47,6 +47,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
@Deprecated(since = "1.4.2", forRemoval = true)
@Component
@RequiredArgsConstructor
public class CmsRelaContentTag extends AbstractListTag {

View File

@ -17,6 +17,7 @@ package com.chestnut.cms.search.template.tag;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.chestnut.cms.search.es.doc.ESContent;
import com.chestnut.cms.search.vo.ESContentVO;
@ -36,6 +37,7 @@ import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.ehcache.shadow.org.terracotta.context.query.QueryBuilder;
import org.springframework.stereotype.Component;
import java.io.IOException;
@ -64,7 +66,7 @@ public class CmsSearchContentTag extends AbstractListTag {
@Override
public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = super.getTagAttrs();
tagAttrs.add(new TagAttr(ATTR_QUERY, true, TagAttrDataType.STRING, "检索词"));
tagAttrs.add(new TagAttr(ATTR_QUERY, false, TagAttrDataType.STRING, "检索词"));
tagAttrs.add(new TagAttr(ATTR_CATALOG_ID, false, TagAttrDataType.STRING, "栏目ID"));
tagAttrs.add(new TagAttr(ATTR_CONTENT_TYPE, false, TagAttrDataType.STRING, "内容类型"));
tagAttrs.add(new TagAttr(ATTR_MODE, false, TagAttrDataType.STRING, "检索方式",
@ -77,9 +79,9 @@ public class CmsSearchContentTag extends AbstractListTag {
long siteId = FreeMarkerUtils.evalLongVariable(env, "Site.siteId");
String mode = MapUtils.getString(attrs, ATTR_MODE, SearchMode.FullText.name());
String query = MapUtils.getString(attrs, ATTR_QUERY);
if (StringUtils.isEmpty(query)) {
throw new TemplateException("Tag attr `query` cannot be empty.", env);
}
// if (StringUtils.isEmpty(query)) {
// throw new TemplateException("Tag attr `query` cannot be empty.", env);
// }
String contentType = MapUtils.getString(attrs, ATTR_CONTENT_TYPE);
Long catalogId = MapUtils.getLong(attrs, ATTR_CATALOG_ID);
try {
@ -94,25 +96,38 @@ public class CmsSearchContentTag extends AbstractListTag {
if (IdUtils.validate(catalogId)) {
b.must(must -> must.term(tq -> tq.field("catalogId").value(catalogId)));
}
if (SearchMode.isFullText(mode)) {
b.must(must -> must
.multiMatch(match -> match
.analyzer(SearchConsts.IKAnalyzeType_Smart)
.fields("title^10", "fullText^1")
.query(query)
)
);
} else {
b.must(should -> {
if (StringUtils.isNotEmpty(query)) {
if (SearchMode.isFullText(mode)) {
b.must(must -> must
.multiMatch(match -> match
.analyzer(SearchConsts.IKAnalyzeType_Smart)
.fields("title^10", "fullText^1")
.query(query)
)
);
} else if (SearchMode.isTagAnd(mode)) {
String[] keywords = StringUtils.split(query, ",");
for (String keyword : keywords) {
should.constantScore(cs ->
cs.boost(1F).filter(f ->
f.match(m ->
m.field("tags").query(keyword))));
if (StringUtils.isNotEmpty(keyword)) {
b.must(must -> must.match(term -> term.field("tags").query(keyword)));
}
}
return should;
});
} else {
b.must(must -> {
String[] keywords = StringUtils.split(query, ",");
for (String keyword : keywords ) {
if (StringUtils.isNotEmpty(keyword)) {
must.match(m ->
m.field("tags").query(keyword));
// must.constantScore(cs ->
// cs.boost(1F).filter(f ->
// f.match(m ->
// m.field("tags").query(keyword))));
}
}
return must;
});
}
}
return b;
})
@ -184,7 +199,9 @@ public class CmsSearchContentTag extends AbstractListTag {
// 所有站点
FullText("全文检索"),
// 当前站点
Tag("标签检索,多个标签英文逗号隔开");
Tag("标签检索,多个标签英文逗号隔开"),
// 当前站点
TagAnd("标签检索,多个标签英文逗号隔开");
private final String desc;
@ -200,10 +217,15 @@ public class CmsSearchContentTag extends AbstractListTag {
return Tag.name().equalsIgnoreCase(mode);
}
static boolean isTagAnd(String mode) {
return TagAnd.name().equalsIgnoreCase(mode);
}
static List<TagAttrOption> toTagAttrOptions() {
return List.of(
new TagAttrOption(FullText.name(), FullText.desc),
new TagAttrOption(Tag.name(), Tag.desc)
new TagAttrOption(Tag.name(), Tag.desc),
new TagAttrOption(TagAnd.name(), Tag.desc)
);
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.cms.search.template.tag;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.cms.search.CmsSearchConstants;
import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.tag.AbstractListTag;
import com.chestnut.search.domain.SearchWord;
import com.chestnut.search.service.ISearchWordService;
import freemarker.core.Environment;
import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class CmsSearchWordTag extends AbstractListTag {
public final static String TAG_NAME = "cms_search_word";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}";
private final ISearchWordService searchWordService;
@Override
public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException {
long siteId = FreeMarkerUtils.evalLongVariable(env, "Site.siteId");
String source = CmsSearchConstants.generateSearchSource(siteId);
Page<SearchWord> pageResult = this.searchWordService.lambdaQuery()
.eq(SearchWord::getSource, source)
.orderByDesc(SearchWord::getTopFlag, SearchWord::getSearchTotal)
.page(new Page<>(pageIndex, size, page));
return TagPageData.of(pageResult.getRecords(), pageResult.getTotal());
}
@Override
public String getTagName() {
return TAG_NAME;
}
@Override
public String getName() {
return NAME;
}
@Override
public String getDescription() {
return DESC;
}
}

View File

@ -2,5 +2,9 @@ FREEMARKER.TAG.NAME.cms_rela_content=相关内容标签
FREEMARKER.TAG.DESC.cms_rela_content=相关内容标签
FREEMARKER.TAG.NAME.cms_search_content=检索内容标签
FREEMARKER.TAG.DESC.cms_search_content=检索内容标签
FREEMARKER.TAG.NAME.cms_search_word=搜索热词标签
FREEMARKER.TAG.DESC.cms_search_word=搜索热词标签
CONFIG.CMSSearchAnalyzeType=索引分词方式
CONFIG.CMSSearchAnalyzeType=索引分词方式
DYNAMIC_PAGE_TYPE.NAME.Search=搜索结果页

View File

@ -2,5 +2,9 @@ FREEMARKER.TAG.NAME.cms_rela_content=Rela content tag
FREEMARKER.TAG.DESC.cms_rela_content=Rela content tag
FREEMARKER.TAG.NAME.cms_search_content=Search content tag
FREEMARKER.TAG.DESC.cms_search_content=Search content tag
FREEMARKER.TAG.NAME.cms_search_word=Search hot word tag
FREEMARKER.TAG.DESC.cms_search_word=Search hot word tag
CONFIG.CMSSearchAnalyzeType=ES Index Analyze Type
CONFIG.CMSSearchAnalyzeType=ES Index Analyze Type
DYNAMIC_PAGE_TYPE.NAME.Search=Search result page

View File

@ -2,5 +2,9 @@ FREEMARKER.TAG.NAME.cms_rela_content=相關內容標籤
FREEMARKER.TAG.DESC.cms_rela_content=相關內容標籤
FREEMARKER.TAG.NAME.cms_search_content=檢索內容標籤
FREEMARKER.TAG.DESC.cms_search_content=檢索內容標籤
FREEMARKER.TAG.NAME.cms_search_word=檢索詞熱詞標籤
FREEMARKER.TAG.DESC.cms_search_word=檢索詞熱詞標籤
CONFIG.CMSSearchAnalyzeType=索引分詞方式
DYNAMIC_PAGE_TYPE.NAME.Search=搜索結果頁

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-seo</artifactId>

View File

@ -0,0 +1,61 @@
/*
* 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.seo.controller;
import com.chestnut.common.domain.R;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.ServletUtils;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.seo.service.BaiduPushService;
import com.chestnut.system.security.AdminUserType;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* <p>
* 百度收录推送前端控制器
* </p>
*
* @author 兮玥
* @email 190785909@qq.com
*/
@RestController
@RequestMapping("/cms/seo")
@RequiredArgsConstructor
public class SitePushController extends BaseRestController {
private final ISiteService siteService;
private final BaiduPushService baiduPushService;
@Priv(type = AdminUserType.TYPE)
@PostMapping("/baidu_push")
public R<?> generateSitemap(@RequestBody @NotEmpty List<Long> contentIds) {
CmsSite site = siteService.getCurrentSite(ServletUtils.getRequest());
List<BaiduPushService.BaiduPushResult> results = baiduPushService.pushContents(site, contentIds);
return R.ok(results);
}
}

View File

@ -44,7 +44,7 @@ public class SitemapPageType extends FixedDictType {
super(TYPE, "{DICT." + TYPE + "}");
super.addDictData("{DICT." + TYPE + "." + PC + "}", PC, 1);
super.addDictData("{DICT." + TYPE + "." + Mobile + "}", Mobile, 2);
super.addDictData("{DICT." + TYPE + ".pc_mobile}", PC_Mobile, 3);
super.addDictData("{DICT." + TYPE + "." + PC_Mobile + "}", PC_Mobile, 3);
}
public static <T> void decode(List<T> list, Function<T, String> getter, BiConsumer<T, String> setter) {

View File

@ -0,0 +1,67 @@
/*
* 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.seo.properties;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.IProperty;
import com.chestnut.contentcore.util.ConfigPropertyUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 百度收录API秘钥
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IProperty.BEAN_NAME_PREFIX + BaiduPushAccessSecretProperty.ID)
public class BaiduPushAccessSecretProperty implements IProperty {
public final static String ID = "BaiduPushAccessSecret";
private final static String DEFAULT_VALUE = StringUtils.EMPTY;
static UseType[] UseTypes = new UseType[] { UseType.Site };
@Override
public UseType[] getUseTypes() {
return UseTypes;
}
@Override
public String getId() {
return ID;
}
@Override
public String getName() {
return "百度收录API秘钥";
}
@Override
public String defaultValue() {
return DEFAULT_VALUE;
}
public static String getValue(Map<String, String> configProps) {
String value = ConfigPropertyUtils.getStringValue(ID, configProps);
if (StringUtils.isEmpty(value)) {
value = DEFAULT_VALUE;
}
return value;
}
}

View File

@ -0,0 +1,96 @@
/*
* 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.seo.service;
import com.chestnut.common.utils.HttpUtils;
import com.chestnut.common.utils.JacksonUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.CmsPublishPipe;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.IContentService;
import com.chestnut.contentcore.service.IPublishPipeService;
import com.chestnut.seo.properties.BaiduPushAccessSecretProperty;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* BaiduUrlPusher
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Service
@RequiredArgsConstructor
public class BaiduPushService {
private final IContentService contentService;
private final IPublishPipeService publishPipeService;
private static final String API = "http://data.zz.baidu.com/urls?site={0}&token={1}";
public List<BaiduPushResult> pushContents(CmsSite site, List<Long> contentIds) {
String secret = BaiduPushAccessSecretProperty.getValue(site.getConfigProps());
if (StringUtils.isEmpty(secret)) {
return List.of();
}
List<CmsPublishPipe> publishPipes = publishPipeService.getPublishPipes(site.getSiteId());
List<BaiduPushResult> results = new ArrayList<>(publishPipes.size());
publishPipes.forEach(pp -> {
String domain = site.getUrl(pp.getCode());
if (StringUtils.isEmpty(domain)) {
return;
}
if (domain.contains("://")) {
domain = StringUtils.substringAfter(domain, "://");
}
List<CmsContent> list = contentService.lambdaQuery().in(CmsContent::getContentId, contentIds).list();
List<String> urls = list.stream().map(content -> contentService
.getContentLink(content, 1, pp.getCode(), false)).toList();
String apiUrl = StringUtils.messageFormat(API, domain, secret);
String body = StringUtils.join(urls, "\n");
String response = HttpUtils.post(URI.create(apiUrl), body, Map.of("Content-Type", "text/plain"));
BaiduPushResult r = JacksonUtils.from(response, BaiduPushResult.class);
r.setPublishPipeCode(pp.getCode());
results.add(r);
});
return results;
}
@Getter
@Setter
public static class BaiduPushResult {
private String publishPipeCode;
private Integer remain;
private Integer success;
private List<String> not_same_site;
private List<String> not_valid;
}
}

View File

@ -5,4 +5,4 @@ SCHEDULED_TASK.SiteMapJobHandler=站点地图定时更新任务
DICT.CMSSitemapPageType=发布通道页面类型
DICT.CMSSitemapPageType.pc=PC端
DICT.CMSSitemapPageType.mobile=移动端
DICT.CMSSitemapPageType.pc_mobile=自适应
DICT.CMSSitemapPageType.pc,mobile=自适应

View File

@ -5,4 +5,4 @@ SCHEDULED_TASK.SiteMapJobHandler=Sitemap Update Task
DICT.CMSPublishPipePageType=发布通道页面类型
DICT.CMSPublishPipePageType.pc=PC端
DICT.CMSPublishPipePageType.mobile=移动端
DICT.CMSPublishPipePageType.pc_mobile=自适应
DICT.CMSPublishPipePageType.pc,mobile=自适应

View File

@ -5,4 +5,4 @@ SCHEDULED_TASK.SiteMapJobHandler=站點地圖定時更新任務
DICT.CMSSitemapPageType=發布通道頁面類型
DICT.CMSSitemapPageType.pc=PC端
DICT.CMSSitemapPageType.mobile=移動端
DICT.CMSSitemapPageType.pc_mobile=自適應
DICT.CMSSitemapPageType.pc,mobile=自適應

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-stat</artifactId>

View File

@ -31,7 +31,7 @@ public class CmsSiteVisitLog implements Serializable {
public final static String TABLE_NAME = "cms_site_visit_log";
@TableId(value = "log_id", type = IdType.AUTO)
@TableId(value = "log_id", type = IdType.INPUT)
private Long logId;
/**

View File

@ -38,6 +38,8 @@ public class PageViewStatEventHandler implements IStatEventHandler {
public static final String TYPE = "pv";
static final String CACHE_PREFIX = "cms:stat:pv:";
private final AsyncTaskManager asyncTaskManager;
private final CmsSiteVisitLogMapper siteVisitLogMapper;
@ -55,7 +57,7 @@ public class PageViewStatEventHandler implements IStatEventHandler {
public void handle(StatEvent event) {
CmsSiteVisitLog log = parseSiteVisitLog(event);
// 更新Redis数据
this.redisCache.incrCounter("cms:pv:" + log.getUri());
this.redisCache.incrLongCounter(CACHE_PREFIX + log.getUri());
// 更新内容浏览量
if (IdUtils.validate(log.getContentId())) {
contentDynamicDataService.increaseViewCount(log.getContentId());

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-vote</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</parent>
<artifactId>chestnut-cms-word</artifactId>

View File

@ -0,0 +1,129 @@
/*
* 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.cms.word.template.tag;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.common.staticize.enums.TagAttrDataType;
import com.chestnut.common.staticize.tag.AbstractListTag;
import com.chestnut.common.staticize.tag.TagAttr;
import com.chestnut.common.staticize.tag.TagAttrOption;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.word.domain.TagWordGroup;
import com.chestnut.word.service.ITagWordGroupService;
import freemarker.core.Environment;
import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class CmsTagWordGroupTag extends AbstractListTag {
public final static String TAG_NAME = "cms_tag_word_group";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}";
private static final String TAG_ATTR_CODE = "code";
private static final String TAG_ATTR_LEVEL = "level";
private final ITagWordGroupService tagWordGroupService;
@Override
public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = super.getTagAttrs();
tagAttrs.add(new TagAttr(TAG_ATTR_CODE, true, TagAttrDataType.STRING, "TAG词分组编码") );
tagAttrs.add(new TagAttr(TAG_ATTR_LEVEL, false, TagAttrDataType.STRING, "数据获取范围,值为`Root`时忽略属性code",
TagWordGroupTagLevel.toTagAttrOptions(), TagWordGroupTagLevel.Current.name()));
return tagAttrs;
}
@Override
public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException {
String group = MapUtils.getString(attrs, TAG_ATTR_CODE);
Optional<TagWordGroup> opt = tagWordGroupService.lambdaQuery().eq(TagWordGroup::getCode, group).oneOpt();
if (opt.isEmpty()) {
throw new TemplateException("Tag word group not found: " + group, env);
}
String level = MapUtils.getString(attrs, TAG_ATTR_LEVEL);
TagWordGroup parent = opt.get();
LambdaQueryWrapper<TagWordGroup> q = new LambdaQueryWrapper<>();
if (TagWordGroupTagLevel.isCurrent(level)) {
q.eq(TagWordGroup::getParentId, parent.getParentId());
} else if (TagWordGroupTagLevel.isChild(level)) {
q.eq(TagWordGroup::getParentId, parent.getGroupId());
}
String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition);
q.apply(StringUtils.isNotEmpty(condition), condition);
q.orderByAsc(TagWordGroup::getSortFlag);
Page<TagWordGroup> pageResult = this.tagWordGroupService.page(new Page<>(pageIndex, size, page), q);
return TagPageData.of(pageResult.getRecords(), pageResult.getTotal());
}
@Override
public String getTagName() {
return TAG_NAME;
}
@Override
public String getName() {
return NAME;
}
@Override
public String getDescription() {
return DESC;
}
private enum TagWordGroupTagLevel {
Root("所有分组"), Current("同级分组"), Child("子分组");
private final String desc;
TagWordGroupTagLevel(String desc) {
this.desc = desc;
}
static boolean isRoot(String level) {
return Root.name().equalsIgnoreCase(level);
}
static boolean isCurrent(String level) {
return Current.name().equalsIgnoreCase(level);
}
static boolean isChild(String level) {
return Child.name().equalsIgnoreCase(level);
}
static List<TagAttrOption> toTagAttrOptions() {
return List.of(
new TagAttrOption(Root.name(), Root.desc),
new TagAttrOption(Current.name(), Current.desc),
new TagAttrOption(Child.name(), Child.desc)
);
}
}
}

View File

@ -4,4 +4,6 @@ FREEMARKER.FUNC.DESC.replaceSensitiveWord=替换敏感词,例如:${replaceSe
# freemarker模板标签
FREEMARKER.TAG.NAME.cms_tag_word=TAG词列表标签
FREEMARKER.TAG.DESC.cms_tag_word=根据TAG词分组编码获取TAG词列表内嵌<#list DataList as tag>${tag.word}</#list>遍历数据
FREEMARKER.TAG.DESC.cms_tag_word=根据TAG词分组编码获取TAG词列表内嵌<#list DataList as tag>${tag.word}</#list>遍历数据
FREEMARKER.TAG.NAME.cms_tag_word_group=TAG词分组列表标签
FREEMARKER.TAG.DESC.cms_tag_word_group=根据TAG词分组编码获取TAG词分组列表内嵌<#list DataList as group>${group.name}</#list>遍历数据

View File

@ -2,5 +2,7 @@
FREEMARKER.FUNC.DESC.replaceHotWord=Replace hot word in content argument, eg: ${replaceHotWord(content, 'default', '[a href='\{0\}' target='\{2\}']\{1\}[/a]')}
FREEMARKER.FUNC.DESC.replaceSensitiveWord=Replace sensitive word in content argument, eg: ${replaceSensitiveWord(content, 'xxx')}
FREEMARKER.TAG.NAME.cms_link=TAG Word List Tag
FREEMARKER.TAG.DESC.cms_link=Fetch tag-word list, use <#list> in tag like "<#list DataList as tag>${tag.word}</#list>" to walk through the list of tag-words.
FREEMARKER.TAG.NAME.cms_tag_word=TAG Word List Tag
FREEMARKER.TAG.DESC.cms_tag_word=Fetch tag-word list, use <#list> in tag like "<#list DataList as tag>${tag.word}</#list>" to walk through the list of tag-words.
FREEMARKER.TAG.NAME.cms_tag_word_group=TAG Word Group List Tag
FREEMARKER.TAG.DESC.cms_tag_word_group=Fetch tag-word-group list, use <#list> in tag like "<#list DataList as group>${group.name}</#list>" to walk through the list of tag-word-groups.

Some files were not shown because too many files have changed in this diff Show More