版本更新:V1.5.8

This commit is contained in:
liweiyi 2025-09-03 17:23:09 +08:00
parent c847340220
commit a3261f18cb
470 changed files with 8262 additions and 4444 deletions

12
Jenkinsfile vendored
View File

@ -121,20 +121,17 @@ pipeline {
dir('./ChestnutCMS') {
withCredentials([usernamePassword(credentialsId: 'ALIYUN-DOCKER-REGISTRY-LWY', passwordVariable: 'DOCKERPWD', usernameVariable: 'DOCKERUSER')]) {
sh '''
cp -f bin/docker-deploy.sh ${APP_PATH}
cp -f bin/docker-deploy.sh ${APP_PATH}/docker-deploy.sh
cp -f docker/docker-compose_${DEPLOY_ENV}.yml ${APP_PATH}/docker-compose.yml
cd ${APP_PATH}
cp -f ../bin/docker-deploy.sh docker-deploy.sh
sed -i "s/{{DOCKERUSER}}/${DOCKERUSER}/g" docker-deploy.sh
sed -i "s/{{DOCKERPWD}}/${DOCKERPWD}/g" docker-deploy.sh
sed -i "s/{{DOCKER_HUB_URL}}/${DOCKER_HUB_URL}/g" docker-deploy.sh
sed -i "s/{{IMAGE_REPOSITORY}}/${DOCKER_HUB_WORKSPACE}\\/${APP_NAME}/g" docker-deploy.sh
sed -i "s/{{IMAGE_TAG}}/${IMAGE_TAG}/g" docker-deploy.sh
cp -f docker/docker-compose_${DEPLOY_ENV}.yml docker-compose.yml
sed -i "s/{{DOCKER_IMAGE}}/${DOCKER_HUB_URL}\\/${DOCKER_HUB_WORKSPACE}\\/${APP_NAME}:${IMAGE_TAG}/g" docker-compose.yml
'''
sshPublisher(publishers: [sshPublisherDesc(configName: 'GameCluster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
@ -203,7 +200,6 @@ pipeline {
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
sh 'rm -f ui.zip'
}
}
}
}

View File

@ -1,4 +1,4 @@
# ChestnutCMS v1.5.7
# ChestnutCMS v1.5.8
### 系统简介

View File

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

View File

@ -5,7 +5,7 @@ chestnut:
# 代号
alias: ChestnutCMS
# 版本
version: 1.5.7
version: 1.5.8
# 版权年份
copyrightYear: 2022-2025
system:
@ -15,6 +15,8 @@ chestnut:
uploadPath: 'E:/dev/workspace_chestnut/uploadPath'
member:
uploadPath: 'E:/dev/workspace_chestnut/_xy_member/'
image:
type: 'JDK'
cms:
publish:
strategy: ThreadPool
@ -24,7 +26,7 @@ chestnut:
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
# 服务器的HTTP端口默认为9080
port: 9080
# 开启优雅停机
shutdown: graceful

View File

@ -5,7 +5,7 @@ chestnut:
# 代号
alias: ChestnutCMS
# 版本
version: 1.5.7
version: 1.5.8
# 版权年份
copyrightYear: 2022-2025
system:
@ -15,6 +15,8 @@ chestnut:
uploadPath: /home/app/uploadPath
freemarker:
templateLoaderPath: /home/app/statics
image:
type: 'JDK'
cms:
resourceRoot: /home/app/wwwroot_release
publish:

View File

@ -5,7 +5,7 @@ chestnut:
# 代号
alias: ChestnutCMS
# 版本
version: 1.5.7
version: 1.5.8
# 版权年份
copyrightYear: 2022-2025
system:
@ -13,12 +13,17 @@ chestnut:
demoMode: true
# 文件路径 示例( Windows配置D:/chestnut/uploadPathLinux配置 /home/app/uploadPath
uploadPath: /home/app/uploadPath
# 验证码类型 math 数组计算 char 字符验证
captchaType: math
freemarker:
templateLoaderPath: /home/app/statics
image:
type: 'JDK'
cms:
resourceRoot: /home/app/wwwroot_release
publish:
strategy: ThreadPool
pool:
threadNamePrefix: "CMS-PUBLISH-"
queueCapacity: 10000
# 开发环境配置
server:

View File

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

View File

@ -16,6 +16,7 @@
package com.chestnut.advertisement.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.advertisement.domain.CmsAdvertisement;
import com.chestnut.advertisement.service.IAdvertisementService;
import com.chestnut.common.async.AsyncTaskManager;
@ -26,6 +27,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
@ -41,10 +44,21 @@ public class AdvertisementListener {
try {
long total = this.advertisementService
.count(new LambdaQueryWrapper<CmsAdvertisement>().eq(CmsAdvertisement::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除广告数据:" + (i * pageSize) + "/" + total);
this.advertisementService.remove(new LambdaQueryWrapper<CmsAdvertisement>()
.eq(CmsAdvertisement::getSiteId, site.getSiteId()).last("limit " + pageSize));
Page<CmsAdvertisement> advertisements = this.advertisementService.lambdaQuery()
.select(CmsAdvertisement::getAdvertisementId)
.eq(CmsAdvertisement::getSiteId, site.getSiteId())
.gt(CmsAdvertisement::getAdvertisementId, lastId)
.orderByAsc(CmsAdvertisement::getAdvertisementId)
.page(Page.of(1, pageSize, false));
if (!advertisements.getRecords().isEmpty()) {
List<Long> advertisementIds = advertisements.getRecords().stream()
.map(CmsAdvertisement::getAdvertisementId).toList();
this.advertisementService.removeBatchByIds(advertisementIds);
lastId = advertisementIds.get(advertisementIds.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除广告数据错误:" + e.getMessage());

View File

@ -8,9 +8,9 @@ ADVERTISEMENT.TYPE.image=圖片
FREEMARKER.TAG.cms_advertisement.NAME=廣告列表標籤
FREEMARKER.TAG.cms_advertisement.DESC=獲取廣告數據列表,內嵌`<#list DataList as ad>${ad.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_advertisement.code=廣告位編碼
FREEMARKER.TAG.cms_advertisement.type=廣告鏈接類型
FREEMARKER.TAG.cms_advertisement.type.None=原始鏈接
FREEMARKER.TAG.cms_advertisement.type.Stat=統計鏈接
FREEMARKER.TAG.cms_advertisement.type=廣告連結類型
FREEMARKER.TAG.cms_advertisement.type.None=原始連結
FREEMARKER.TAG.cms_advertisement.type.Stat=統計連結
# 統計菜單
STAT.MENU.CmsAdv=廣告數據統計
@ -23,7 +23,7 @@ SCHEDULED_TASK.AdvertisementStatJob=廣告統計任務
SCHEDULED_TASK.AdvertisementPublishJob=廣告定時發布下線任務
MONITORED.CACHE.AD_ID2NAME=廣告名稱
MONITORED.CACHE.AD_ID2URL=廣告跳轉鏈接
MONITORED.CACHE.AD_ID2URL=廣告跳轉連結
CMS.AD.ID=廣告ID
CMS.AD.SPACE_ID=所屬廣告版位頁面部件ID
@ -34,7 +34,7 @@ CMS.AD.KEYWORDS=關鍵詞
CMS.AD.STATE=狀態
CMS.AD.ONLINE_DATE=上線時間
CMS.AD.OFFLINE_DATE=下線時間
CMS.AD.REDIRECT_URL=原始跳轉鏈接
CMS.AD.LINK=實際跳轉鏈接(可設置為中轉地址)
CMS.AD.RESOURCE_PATH=廣告素材源路徑
CMS.AD.RESOURCE_SRC=廣告素材訪問鏈接
CMS.AD.REDIRECT_URL=原始跳轉連結
CMS.AD.LINK=實際跳轉連結(可設置為中轉地址)
CMS.AD.RESOURCE_PATH=廣告素材源路徑
CMS.AD.RESOURCE_SRC=廣告素材訪問地址

View File

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

View File

@ -1,139 +1,139 @@
/*
* Copyright 2022-2025 兮玥(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.article.controller.front;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.article.domain.vo.ArticleApiVO;
import com.chestnut.article.service.IArticleService;
import com.chestnut.common.domain.R;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.fixed.dict.ContentStatus;
import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService;
import com.chestnut.contentcore.template.tag.CmsCatalogTag;
import com.chestnut.contentcore.template.tag.CmsContentTag;
import com.chestnut.contentcore.util.CatalogUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
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;
import java.util.*;
import java.util.stream.Collectors;
/**
* 内容数据API接口
*
* @author 兮玥
* @email 190785909@qq.com
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/cms/article")
public class ArticleApiController extends BaseRestController {
private final ICatalogService catalogService;
private final IContentService contentService;
private final IArticleService articleService;
@GetMapping("/list")
public R<List<ArticleApiVO>> getContentList(
@RequestParam("sid") Long siteId,
@RequestParam(value = "cid", required = false, defaultValue = "0") Long catalogId,
@RequestParam(value = "lv", required = false, defaultValue = "Root") String level,
@RequestParam(value = "attrs", required = false) String hasAttributes,
@RequestParam(value = "no_attrs", required = false) String noAttributes,
@RequestParam(value = "st", required = false, defaultValue = "Recent") String sortType,
@RequestParam(value = "ps", required = false, defaultValue = "16") Integer pageSize,
@RequestParam(value = "pn", required = false, defaultValue = "1") Long pageNumber,
@RequestParam(value = "pp") String publishPipeCode,
@RequestParam(value = "preview", required = false, defaultValue = "false") Boolean preview,
@RequestParam(value = "text", required = false, defaultValue = "false") Boolean text
) {
if (!CmsCatalogTag.CatalogTagLevel.isRoot(level) && !IdUtils.validate(catalogId)) {
return R.fail("The parameter cid is required where lv is `Root`.");
}
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<>();
q.eq(CmsContent::getSiteId, siteId).eq(CmsContent::getStatus, ContentStatus.PUBLISHED);
if (!CmsCatalogTag.CatalogTagLevel.isRoot(level)) {
CmsCatalog catalog = this.catalogService.getCatalog(catalogId);
if (Objects.isNull(catalog)) {
return R.fail("Catalog not found: " + catalogId);
}
if (CmsCatalogTag.CatalogTagLevel.isCurrent(level)) {
q.eq(CmsContent::getCatalogId, catalog.getCatalogId());
} else if (CmsCatalogTag.CatalogTagLevel.isChild(level)) {
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors() + CatalogUtils.ANCESTORS_SPLITER);
} else if (CmsCatalogTag.CatalogTagLevel.isCurrentAndChild(level)) {
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors());
}
}
if (StringUtils.isNotEmpty(hasAttributes)) {
int attrTotal = ContentAttribute.convertInt(hasAttributes.split(","));
q.apply(attrTotal > 0, "attributes&{0}={1}", attrTotal, attrTotal);
}
if (StringUtils.isNotEmpty(noAttributes)) {
String[] contentAttrs = noAttributes.split(",");
int attrTotal = ContentAttribute.convertInt(contentAttrs);
for (String attr : contentAttrs) {
int bit = ContentAttribute.bit(attr);
q.apply(bit > 0, "attributes&{0}<>{1}", attrTotal, bit);
}
}
if (CmsContentTag.SortTagAttr.isRecent(sortType)) {
q.orderByDesc(CmsContent::getPublishDate);
} else {
q.orderByDesc(Arrays.asList(CmsContent::getTopFlag, CmsContent::getSortFlag));
}
Page<CmsContent> pageResult = this.contentService.dao().page(new Page<>(pageNumber, pageSize, false), q);
if (pageResult.getRecords().isEmpty()) {
return R.ok(List.of());
}
List<Long> contentIds = pageResult.getRecords().stream().map(CmsContent::getContentId).toList();
Map<Long, CmsArticleDetail> articleDetails = new HashMap<>();
if (text) {
articleDetails.putAll(this.articleService.dao().listByIds(contentIds)
.stream().collect(Collectors.toMap(CmsArticleDetail::getContentId, c -> c)));
}
List<ArticleApiVO> list = new ArrayList<>();
pageResult.getRecords().forEach(c -> {
ArticleApiVO vo = ArticleApiVO.newInstance(c, null);
CmsCatalog catalog = this.catalogService.getCatalog(c.getCatalogId());
vo.setCatalogName(catalog.getName());
vo.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
vo.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview));
vo.setLogoSrc(InternalUrlUtils.getActualUrl(c.getLogo(), publishPipeCode, preview));
if (text && articleDetails.containsKey(c.getContentId())) {
vo.setContentHtml(articleDetails.get(c.getContentId()).getContentHtml());
}
list.add(vo);
});
return R.ok(list);
}
}
/*
* Copyright 2022-2025 兮玥(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.article.controller.front;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.article.domain.vo.ArticleApiVO;
import com.chestnut.article.service.IArticleService;
import com.chestnut.common.domain.R;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.fixed.dict.ContentStatus;
import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService;
import com.chestnut.contentcore.template.tag.CmsCatalogTag;
import com.chestnut.contentcore.template.tag.CmsContentTag;
import com.chestnut.contentcore.util.CatalogUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
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;
import java.util.*;
import java.util.stream.Collectors;
/**
* 内容数据API接口
*
* @author 兮玥
* @email 190785909@qq.com
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/cms/article")
public class ArticleApiController extends BaseRestController {
private final ICatalogService catalogService;
private final IContentService contentService;
private final IArticleService articleService;
@GetMapping("/list")
public R<List<ArticleApiVO>> getContentList(
@RequestParam("sid") Long siteId,
@RequestParam(value = "cid", required = false, defaultValue = "0") Long catalogId,
@RequestParam(value = "lv", required = false, defaultValue = "Root") String level,
@RequestParam(value = "attrs", required = false) String hasAttributes,
@RequestParam(value = "no_attrs", required = false) String noAttributes,
@RequestParam(value = "st", required = false, defaultValue = "Recent") String sortType,
@RequestParam(value = "ps", required = false, defaultValue = "16") Integer pageSize,
@RequestParam(value = "pn", required = false, defaultValue = "1") Long pageNumber,
@RequestParam(value = "pp") String publishPipeCode,
@RequestParam(value = "preview", required = false, defaultValue = "false") Boolean preview,
@RequestParam(value = "text", required = false, defaultValue = "false") Boolean text
) {
if (!CmsCatalogTag.CatalogTagLevel.isRoot(level) && !IdUtils.validate(catalogId)) {
return R.fail("The parameter cid is required where lv is `Root`.");
}
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<>();
q.eq(CmsContent::getSiteId, siteId).eq(CmsContent::getStatus, ContentStatus.PUBLISHED);
if (!CmsCatalogTag.CatalogTagLevel.isRoot(level)) {
CmsCatalog catalog = this.catalogService.getCatalog(catalogId);
if (Objects.isNull(catalog)) {
return R.fail("Catalog not found: " + catalogId);
}
if (CmsCatalogTag.CatalogTagLevel.isCurrent(level)) {
q.eq(CmsContent::getCatalogId, catalog.getCatalogId());
} else if (CmsCatalogTag.CatalogTagLevel.isChild(level)) {
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors() + CatalogUtils.ANCESTORS_SPLITER);
} else if (CmsCatalogTag.CatalogTagLevel.isCurrentAndChild(level)) {
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors());
}
}
if (StringUtils.isNotEmpty(hasAttributes)) {
int attrTotal = ContentAttribute.convertInt(hasAttributes.split(","));
q.apply(attrTotal > 0, "attributes&{0}={1}", attrTotal, attrTotal);
}
if (StringUtils.isNotEmpty(noAttributes)) {
String[] contentAttrs = noAttributes.split(",");
int attrTotal = ContentAttribute.convertInt(contentAttrs);
for (String attr : contentAttrs) {
int bit = ContentAttribute.bit(attr);
q.apply(bit > 0, "attributes&{0}<>{1}", attrTotal, bit);
}
}
if (CmsContentTag.SortTagAttr.isRecent(sortType)) {
q.orderByDesc(CmsContent::getPublishDate);
} else {
q.orderByDesc(Arrays.asList(CmsContent::getTopFlag, CmsContent::getSortFlag));
}
Page<CmsContent> pageResult = this.contentService.dao().page(new Page<>(pageNumber, pageSize, false), q);
if (pageResult.getRecords().isEmpty()) {
return R.ok(List.of());
}
List<Long> contentIds = pageResult.getRecords().stream().map(CmsContent::getContentId).toList();
Map<Long, CmsArticleDetail> articleDetails = new HashMap<>();
if (text) {
articleDetails.putAll(this.articleService.dao().listByIds(contentIds)
.stream().collect(Collectors.toMap(CmsArticleDetail::getContentId, c -> c)));
}
List<ArticleApiVO> list = new ArrayList<>();
pageResult.getRecords().forEach(c -> {
ArticleApiVO vo = ArticleApiVO.newInstance(c, null);
CmsCatalog catalog = this.catalogService.getCatalog(c.getCatalogId());
vo.setCatalogName(catalog.getName());
vo.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
vo.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview));
vo.setLogoSrc(InternalUrlUtils.getActualUrl(c.getLogo(), publishPipeCode, preview));
if (text && articleDetails.containsKey(c.getContentId())) {
vo.setContentHtml(articleDetails.get(c.getContentId()).getContentHtml());
}
list.add(vo);
});
return R.ok(list);
}
}

View File

@ -10,6 +10,7 @@ FREEMARKER.FUNC.dealArticleBody.DESC=文章正文處理函數,主要用來處
FREEMARKER.FUNC.dealArticleBody.Arg1.Name=文章正文內容
FREEMARKER.FUNC.dealArticleBody.Arg2.Name=文章正文格式
# 固定字典項
ArticleBodyFormat.RichText=富文本
CMS.ARTICLE.FORMAT=文章正文格式

View File

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

View File

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

View File

@ -7,19 +7,19 @@ FREEMARKER.TAG.cms_comment.type=來源類型
CMS.COMMENT.ID=評論ID
CMS.COMMENT.UID=評論用戶ID
CMS.COMMENT.PARENT_ID=父級評論ID
CMS.COMMENT.REPLY_UID=的用戶ID
CMS.COMMENT.REPLY_COUNT=
CMS.COMMENT.REPLY_UID=的用戶ID
CMS.COMMENT.REPLY_COUNT=
CMS.COMMENT.SOURCE_TYPE=來源分類
CMS.COMMENT.SOURCE_ID=來源唯一標識
CMS.COMMENT.SOURCE_TITLE=來源標題
CMS.COMMENT.SOURCE_URL=來源URL
CMS.COMMENT.CONTENT=評論內容
CMS.COMMENT.LIKE_COUNT=
CMS.COMMENT.LIKE_COUNT=
CMS.COMMENT.AUDIT_STATUS=評論審核狀態
CMS.COMMENT.COMMENT_TIME=評論提交時間
CMS.COMMENT.DEL_FLAG=刪除標識
CMS.COMMENT.IP=用戶ID
CMS.COMMENT.IP=用戶IP
CMS.COMMENT.REGION=用戶歸屬地
CMS.COMMENT.CLIENT_TYPE=客戶端類型
CMS.COMMENT.USER_AGENT=UserAgent
CMS.COMMENT.REPLY_LIST=列表
CMS.COMMENT.REPLY_LIST=列表

View File

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

View File

@ -84,8 +84,8 @@ public class CatalogMonitoredCache implements IMonitoredCache<CmsCatalog> {
return redisCache.getCacheObject(cacheKeyByAlias(siteId, alias), CmsCatalog.class, supplier);
}
public void clear(CmsCatalog catalog) {
this.redisCache.deleteObject(cacheKeyById(catalog.getCatalogId()));
this.redisCache.deleteObject(cacheKeyByAlias(catalog.getSiteId(), catalog.getAlias()));
public void clear(Long siteId, Long catalogId, String catalogAlias) {
this.redisCache.deleteObject(cacheKeyById(catalogId));
this.redisCache.deleteObject(cacheKeyByAlias(siteId, catalogAlias));
}
}

View File

@ -28,6 +28,7 @@ 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.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.*;
import com.chestnut.contentcore.core.ICatalogType;
@ -270,7 +271,7 @@ public class CatalogController extends BaseRestController {
throw ContentCoreErrorCode.CATALOG_CANNOT_PUBLISH.exception();
}
AsyncTask task = this.publishService.publishCatalog(catalog, dto.getPublishChild(), dto.getPublishDetail(),
dto.getPublishStatus(), StpAdminUtil.getLoginUser());
dto.getPublishStatus(), Operator.of(StpAdminUtil.getLoginUser()));
return R.ok(task.getTaskId());
}

View File

@ -25,6 +25,7 @@ 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.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.security.web.PageRequest;
import com.chestnut.common.utils.IdUtils;
@ -171,12 +172,11 @@ public class ContentController extends BaseRestController {
@PostMapping
public R<?> addContent(@RequestParam("contentType") String contentType, HttpServletRequest request)
throws IOException {
LoginUser loginUser = StpAdminUtil.getLoginUser();
IContentType ct = ContentCoreUtils.getContentType(contentType);
IContent<?> content = ct.readFrom(request.getInputStream());
content.setOperator(StpAdminUtil.getLoginUser());
PermissionUtils.checkPermission(CatalogPrivItem.AddContent.getPermissionKey(content.getCatalogId()),
content.getOperator());
PermissionUtils.checkPermission(CatalogPrivItem.AddContent.getPermissionKey(content.getCatalogId()), loginUser);
content.setOperator(Operator.of(loginUser));
AsyncTask task = this.contentService.addContent(content);
return R.ok(Map.of("taskId", task.getTaskId()));
@ -187,12 +187,13 @@ public class ContentController extends BaseRestController {
@PutMapping
public R<?> saveContent(@RequestParam("contentType") String contentType, HttpServletRequest request)
throws IOException {
LoginUser loginUser = StpAdminUtil.getLoginUser();
IContentType ct = ContentCoreUtils.getContentType(contentType);
IContent<?> content = ct.readFrom(request.getInputStream());
content.setOperator(StpAdminUtil.getLoginUser());
content.setOperator(Operator.of(loginUser));
PermissionUtils.checkPermission(CatalogPrivItem.EditContent.getPermissionKey(content.getCatalogId()),
content.getOperator());
StpAdminUtil.getLoginUser());
AsyncTask task = this.contentService.saveContent(content);
return R.ok(Map.of("taskId", task.getTaskId()));

View File

@ -89,13 +89,13 @@ public class CoreController extends BaseRestController {
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(dataType);
Assert.notNull(internalDataType, () -> ContentCoreErrorCode.UNSUPPORTED_INTERNAL_DATA_TYPE.exception(dataType));
IInternalDataType.RequestData data = new IInternalDataType.RequestData(dataId, pageIndex, publishPipe,
true, ServletUtils.getParamMap(ServletUtils.getRequest()));
try {
IInternalDataType.RequestData data = new IInternalDataType.RequestData(dataId, pageIndex, publishPipe,
true, ServletUtils.getParamMap(ServletUtils.getRequest()));
String pageData = internalDataType.getPageData(data);
response.getWriter().write(pageData);
internalDataType.processPageData(data, response.getWriter());
} catch (Exception e) {
response.getWriter().write(e.getMessage());
// Ignore
e.printStackTrace(response.getWriter());
}
}

View File

@ -32,6 +32,7 @@ import com.chestnut.common.utils.ServletUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.IResourceType;
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
import com.chestnut.contentcore.core.impl.ResourceType_Image;
import com.chestnut.contentcore.domain.CmsResource;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.dto.ResourceUploadDTO;
@ -118,7 +119,11 @@ public class ResourceController extends BaseRestController {
if (r.getPath().startsWith("http://") || r.getPath().startsWith("https://")) {
r.setSrc(r.getPath());
} else {
resourceService.dealDefaultThumbnail(site, r.getInternalUrl(), r::setSrc);
if (ResourceType_Image.isImage(r.getResourceType())) {
resourceService.dealDefaultThumbnail(site, r.getInternalUrl(), r::setSrc);
} else {
r.setSrc(InternalUrlUtils.getActualPreviewUrl(r.getInternalUrl()));
}
}
});
}
@ -192,7 +197,7 @@ public class ResourceController extends BaseRestController {
return R.ok(resource);
}
@GetMapping("/downlad/{resourceId}")
@GetMapping("/download/{resourceId}")
public void downloadResourceFile(@PathVariable @LongId Long resourceId, HttpServletResponse response) {
CmsResource resource = this.resourceService.getById(resourceId);
Assert.notNull(resource, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("resourceId", resourceId));

View File

@ -26,6 +26,7 @@ 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.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.security.web.PageRequest;
import com.chestnut.common.utils.Assert;
@ -257,7 +258,7 @@ public class SiteController extends BaseRestController {
Assert.notNull(site, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("siteId", dto.getSiteId()));
if (!dto.isPublishIndex()) {
AsyncTask task = publishService.publishAll(site, dto.getContentStatus(), StpAdminUtil.getLoginUser());
AsyncTask task = publishService.publishAll(site, dto.getContentStatus(), Operator.of(StpAdminUtil.getLoginUser()));
return R.ok(task.getTaskId());
}
publishService.publishSiteIndex(site);

View File

@ -29,6 +29,7 @@ import com.chestnut.common.security.web.PageRequest;
import com.chestnut.common.staticize.StaticizeService;
import com.chestnut.common.utils.*;
import com.chestnut.common.utils.file.FileExUtils;
import com.chestnut.common.validation.RegexConsts;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.CmsTemplate;
import com.chestnut.contentcore.domain.dto.TemplateAddDTO;
@ -182,7 +183,7 @@ public class TemplateController extends BaseRestController {
fileName = FileExUtils.normalizePath(fileName);
String[] split = fileName.substring(0, fileName.indexOf(suffix)).split("/");
for (String item : split) {
if (StringUtils.isEmpty(item) || !Pattern.matches("^[a-zA-Z0-9_]+$", item)) {
if (StringUtils.isEmpty(item) || !Pattern.matches(RegexConsts.REGEX_CODE, item)) {
return false;
}
}

View File

@ -16,8 +16,9 @@
package com.chestnut.contentcore.core;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.utils.*;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent;
@ -30,7 +31,6 @@ import com.chestnut.contentcore.listener.event.AfterContentSaveEvent;
import com.chestnut.contentcore.listener.event.BeforeContentSaveEvent;
import com.chestnut.contentcore.listener.event.OnContentCopyEvent;
import com.chestnut.contentcore.listener.event.OnContentMoveEvent;
import com.chestnut.contentcore.perms.CatalogPermissionType;
import com.chestnut.contentcore.properties.PublishedContentEditProperty;
import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService;
@ -40,7 +40,6 @@ import com.chestnut.contentcore.util.ContentCoreUtils;
import com.chestnut.contentcore.util.ContentLogUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.fixed.dict.YesOrNo;
import com.chestnut.system.permission.PermissionUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
@ -73,7 +72,7 @@ public abstract class AbstractContent<T> implements IContent<T> {
@Setter
private Map<String, Object> params;
private LoginUser operator;
private Operator operator;
@Override
public CmsSite getSite() {
@ -122,12 +121,12 @@ public abstract class AbstractContent<T> implements IContent<T> {
}
@Override
public LoginUser getOperator() {
public Operator getOperator() {
return this.operator;
}
@Override
public void setOperator(LoginUser operator) {
public void setOperator(Operator operator) {
this.operator = operator;
}
@ -274,9 +273,6 @@ public abstract class AbstractContent<T> implements IContent<T> {
this.getContentEntity().getTitle())) {
throw ContentCoreErrorCode.TITLE_REPLEAT.exception();
}
// 校验权限
PermissionUtils.checkPermission(CatalogPermissionType.CatalogPrivItem.AddContent.getPermissionKey(toCatalog.getCatalogId()), this.getOperator());
CmsContent newContent = new CmsContent();
BeanUtils.copyProperties(this.getContentEntity(), newContent, "contentId", "template", "staticPath", "topFlag",
"topDate", "isLock", "lockUser");
@ -316,8 +312,6 @@ public abstract class AbstractContent<T> implements IContent<T> {
this.getContentEntity().getTitle())) {
throw ContentCoreErrorCode.TITLE_REPLEAT.exception();
}
// 校验权限
PermissionUtils.checkPermission(CatalogPermissionType.CatalogPrivItem.AddContent.getPermissionKey(toCatalog.getCatalogId()), this.getOperator());
CmsCatalog fromCatalog = this.getCatalogService().getCatalog(content.getCatalogId());
// 重置内容信息
@ -377,12 +371,12 @@ public abstract class AbstractContent<T> implements IContent<T> {
}
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<CmsContent>()
.eq(CmsContent::getCatalogId, next.getCatalogId()).gt(CmsContent::getSortFlag, next.getSortFlag())
.orderByAsc(CmsContent::getSortFlag).last("limit 1");
CmsContent prev = this.getContentService().dao().getOne(q);
if (prev == null) {
.orderByAsc(CmsContent::getSortFlag);
Page<CmsContent> prev = this.getContentService().dao().page(Page.of(1, 1, false), q);
if (prev.getRecords().isEmpty()) {
this.content.setSortFlag(SortUtils.getDefaultSortValue());
} else {
this.content.setSortFlag((next.getSortFlag() + prev.getSortFlag()) / 2);
this.content.setSortFlag((next.getSortFlag() + prev.getRecords().get(0).getSortFlag()) / 2);
}
this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(content);

View File

@ -15,7 +15,7 @@
*/
package com.chestnut.contentcore.core;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.CmsSite;
@ -95,7 +95,7 @@ public interface IContent<T> {
/**
* 获取操作人信息
*/
LoginUser getOperator();
Operator getOperator();
/**
* 获取操作人用户名
@ -111,7 +111,7 @@ public interface IContent<T> {
/**
* 设置操作人信息
*/
void setOperator(LoginUser operator);
void setOperator(Operator operator);
/**
* 复制内容到指定栏目

View File

@ -22,6 +22,7 @@ import lombok.Getter;
import lombok.Setter;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
/**
@ -93,6 +94,8 @@ public interface IInternalDataType {
return StringUtils.EMPTY;
}
default void processPageData(RequestData requestData, Writer writer) throws TemplateException, IOException {}
/**
* 访问链接
*

View File

@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
/**
@ -56,6 +57,14 @@ public class InternalDataType_Catalog implements IInternalDataType {
return this.publishService.getCatalogPageData(catalog, requestData, listFlag);
}
@Override
public void processPageData(RequestData requestData, Writer writer) throws TemplateException, IOException {
CmsCatalog catalog = catalogService.getCatalog(requestData.getDataId());
boolean listFlag = YesOrNo.isYes(requestData.getParams().get("list"));
this.publishService.processCatalogPage(catalog, requestData, listFlag, writer);
}
@Override
public String getLink(InternalURL internalUrl, int pageIndex, String publishPipeCode, boolean isPreview) {
CmsCatalog catalog = catalogService.getCatalog(internalUrl.getId());

View File

@ -27,6 +27,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
/**
@ -58,6 +59,14 @@ public class InternalDataType_Content implements IInternalDataType {
return this.publishService.getContentPageData(content, requestData);
}
@Override
public void processPageData(RequestData requestData, Writer writer) throws TemplateException, IOException {
CmsContent content = contentService.dao().getById(requestData.getDataId());
Assert.notNull(content, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", requestData.getDataId()));
this.publishService.processContentPage(content, requestData, writer);
}
@Override
public String getLink(InternalURL internalUrl, int pageIndex, String publishPipeCode, boolean isPreview) {
CmsContent content = contentService.dao().getById(internalUrl.getId());

View File

@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.Writer;
/**
* 内部数据类型页面组件
@ -53,6 +54,14 @@ public class InternalDataType_PageWidget implements IInternalDataType {
CmsPageWidget pageWidget = pageWidgetService.getById(data.getDataId());
Assert.notNull(pageWidget, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("pageWidgetId", data.getDataId()));
return this.publishService.getPageWidgetPageData(pageWidget, data.getPublishPipeCode(), data.isPreview());
return this.publishService.getPageWidgetPageData(pageWidget, data);
}
@Override
public void processPageData(RequestData data, Writer writer) throws TemplateException, IOException {
CmsPageWidget pageWidget = pageWidgetService.getById(data.getDataId());
Assert.notNull(pageWidget, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("pageWidgetId", data.getDataId()));
this.publishService.processPageWidget(pageWidget, data, writer);
}
}

View File

@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.Writer;
/**
* 内部数据类型站点
@ -54,6 +55,12 @@ public class InternalDataType_Site implements IInternalDataType {
return this.publishService.getSitePageData(site, requestData);
}
@Override
public void processPageData(RequestData requestData, Writer writer) throws TemplateException, IOException {
CmsSite site = siteService.getSite(requestData.getDataId());
this.publishService.processSitePage(site, requestData, writer);
}
@Override
public String getLink(InternalURL internalUrl, int pageIndex, String publishPipeCode, boolean isPreview) {
CmsSite site = siteService.getSite(internalUrl.getId());

View File

@ -15,6 +15,7 @@
*/
package com.chestnut.contentcore.core.impl;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.IPublishPipeProp;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
@ -59,7 +60,10 @@ public class PublishPipeProp_RelativePrefix implements IPublishPipeProp {
public static String getValue(String publishPipeCode, Map<String, Map<String, Object>> publishPipeProps) {
if (Objects.nonNull(publishPipeProps)) {
return MapUtils.getString(publishPipeProps.get(publishPipeCode), KEY);
String v = MapUtils.getString(publishPipeProps.get(publishPipeCode), KEY);
if (StringUtils.isNotBlank(v)) {
return v;
}
}
return DEFAULT_VALUE;
}

View File

@ -65,7 +65,8 @@ public class CmsContentDAO extends BackupServiceImpl<CmsContentMapper, CmsConten
}
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<CmsContent>()
.select(StringUtils.isNotEmpty(columns), columns)
.eq(CmsContent::getSiteId, siteId);
.eq(CmsContent::getSiteId, siteId)
.orderByAsc(CmsContent::getContentId);
return this.page(page, q);
}
@ -83,7 +84,8 @@ public class CmsContentDAO extends BackupServiceImpl<CmsContentMapper, CmsConten
}
return this.getBackupMapper().selectPage(page, new LambdaQueryWrapper<BCmsContent>()
.select(StringUtils.isNotEmpty(columns), columns)
.eq(BCmsContent::getSiteId, siteId));
.eq(BCmsContent::getSiteId, siteId)
.orderByAsc(BCmsContent::getBackupId));
}
public void removeBackupBatchByIds(List<Long> backupIds) {

View File

@ -19,8 +19,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.chestnut.common.db.domain.BaseEntity;
import com.chestnut.common.validation.RegexConsts;
import com.chestnut.system.fixed.dict.EnableOrDisable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
@ -64,7 +64,7 @@ public class CmsPublishPipe extends BaseEntity {
/**
* 编码
*/
@Pattern(regexp = "[A-Za-z0-9_]+")
@Pattern(regexp = RegexConsts.REGEX_CODE)
private String code;
/**

View File

@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.db.domain.BaseEntity;
import com.chestnut.common.validation.RegexConsts;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
@ -55,7 +56,7 @@ public class CmsSiteProperty extends BaseEntity {
private String propName;
@XComment("属性编码")
@Pattern(regexp = "[A-Za-z0-9_]+", message = "{VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR}")
@Pattern(regexp = RegexConsts.REGEX_CODE)
private String propCode;
@XComment("属性值")

View File

@ -16,7 +16,7 @@
package com.chestnut.contentcore.domain.dto;
import com.chestnut.common.security.domain.BaseDTO;
import com.chestnut.common.validation.RegexConsts;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
@ -46,14 +46,14 @@ public class CatalogAddDTO extends BaseDTO {
* 栏目别名
*/
@NotBlank
@Pattern(regexp = "^[A-Za-z0-9_]+$", message = "栏目别名只能使用大小写字母、数字、下划线组合")
@Pattern(regexp = RegexConsts.REGEX_CODE)
private String alias;
/**
* 栏目目录
*/
@NotBlank
@Pattern(regexp = "^[A-Za-z0-9_\\/]+$", message = "栏目路径只能使用大小写字母、数字、下划线组合")
@Pattern(regexp = RegexConsts.REGEX_PATH)
private String path;
/**

View File

@ -16,6 +16,7 @@
package com.chestnut.contentcore.domain.dto;
import com.chestnut.common.security.domain.BaseDTO;
import com.chestnut.common.validation.RegexConsts;
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
import com.chestnut.system.fixed.dict.YesOrNo;
import com.chestnut.system.validator.Dict;
@ -54,14 +55,14 @@ public class CatalogUpdateDTO extends BaseDTO {
* 栏目别名
*/
@NotBlank
@Pattern(regexp = "^[A-Za-z0-9_]+$", message = "栏目别名只能使用大小写字母、数字、下划线组合")
@Pattern(regexp = RegexConsts.REGEX_CODE, message = "栏目别名只能使用大小写字母、数字、下划线组合")
private String alias;
/**
* 栏目目录
*/
@NotBlank
@Pattern(regexp = "^[A-Za-z0-9_\\/]+$", message = "栏目路径只能使用大小写字母、数字、下划线组合")
@Pattern(regexp = RegexConsts.REGEX_PATH, message = "栏目路径只能使用大小写字母、数字、下划线组合")
private String path;
/*

View File

@ -1,3 +1,18 @@
/*
* Copyright 2022-2025 兮玥(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.enums;
import com.chestnut.common.i18n.I18nUtils;

View File

@ -60,8 +60,6 @@ public class ContentCoreListener {
private final IPublishService publishService;
private final AsyncTaskManager asyncTaskManager;
@EventListener
public void beforeSiteDelete(BeforeSiteDeleteEvent event) {
CmsSite site = event.getSite();
@ -74,27 +72,37 @@ public class ContentCoreListener {
// 删除内容数据
try {
long total = this.contentService.dao().countBySiteId(site.getSiteId());
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除内容数据:" + (i * pageSize) + "/" + total);
List<Long> contentIds = this.contentService.dao()
.pageBySiteId(
new Page<>(0, pageSize, false),
site.getSiteId(),
List.of(CmsContent::getContentId)
).getRecords().stream().map(CmsContent::getContentId).toList();
this.contentService.dao().removeBatchByIds(contentIds);
Page<CmsContent> page = this.contentService.dao().lambdaQuery()
.select(CmsContent::getContentId)
.eq(CmsContent::getSiteId, site.getSiteId())
.gt(CmsContent::getContentId, lastId)
.orderByAsc(CmsContent::getContentId)
.page(Page.of(0, pageSize, false));
if (!page.getRecords().isEmpty()) {
List<Long> contentIds = page.getRecords().stream().map(CmsContent::getContentId).toList();
this.contentService.dao().removeBatchByIds(contentIds);
lastId = contentIds.get(contentIds.size() - 1);
}
}
// 删除备份内容数据
total = this.contentService.dao().countBackupBySiteId(site.getSiteId());
lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除内容备份数据:" + (i * pageSize) + "/" + total);
List<Long> backupIds = this.contentService.dao()
.pageBackupBySiteId(
new Page<>(0, pageSize, false),
site.getSiteId(),
List.of(BCmsContent::getContentId)
).getRecords().stream().map(BCmsContent::getBackupId).toList();
this.contentService.dao().removeBackupBatchByIds(backupIds);
Page<BCmsContent> page = this.contentService.dao().getBackupMapper()
.selectPage(Page.of(0, pageSize, false), new LambdaQueryWrapper<BCmsContent>()
.select(BCmsContent::getBackupId)
.eq(BCmsContent::getSiteId, site.getSiteId())
.gt(BCmsContent::getBackupId, lastId)
.orderByAsc(BCmsContent::getBackupId));
if (!page.getRecords().isEmpty()) {
List<Long> backupIds = page.getRecords().stream().map(BCmsContent::getBackupId).toList();
this.contentService.dao().removeBackupBatchByIds(backupIds);
lastId = backupIds.get(backupIds.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除内容错误:" + e.getMessage());
@ -104,10 +112,20 @@ public class ContentCoreListener {
try {
long total = this.resourceService
.count(new LambdaQueryWrapper<CmsResource>().eq(CmsResource::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除资源数据:" + (i * pageSize) + "/" + total);
this.resourceService.remove(new LambdaQueryWrapper<CmsResource>()
.eq(CmsResource::getSiteId, site.getSiteId()).last("limit " + pageSize));
Page<CmsResource> resources = this.resourceService.lambdaQuery()
.select(CmsResource::getResourceId)
.eq(CmsResource::getSiteId, site.getSiteId())
.gt(CmsResource::getResourceId, lastId)
.orderByAsc(CmsResource::getResourceId)
.page(Page.of(0, pageSize, false));
if (!resources.getRecords().isEmpty()) {
List<Long> resourceIds = resources.getRecords().stream().map(CmsResource::getResourceId).toList();
this.resourceService.removeBatchByIds(resourceIds);
lastId = resourceIds.get(resourceIds.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除资源数据错误:" + e.getMessage());
@ -117,10 +135,20 @@ public class ContentCoreListener {
try {
long total = this.catalogService
.count(new LambdaQueryWrapper<CmsCatalog>().eq(CmsCatalog::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除栏目数据:" + (i * pageSize) + "/" + total);
this.catalogService.remove(new LambdaQueryWrapper<CmsCatalog>()
.eq(CmsCatalog::getSiteId, site.getSiteId()).last("limit " + pageSize));
Page<CmsCatalog> catalogs = this.catalogService.lambdaQuery()
.select(CmsCatalog::getCatalogId)
.eq(CmsCatalog::getSiteId, site.getSiteId())
.gt(CmsCatalog::getCatalogId, lastId)
.orderByAsc(CmsCatalog::getCatalogId)
.page(Page.of(0, pageSize, false));
if (!catalogs.getRecords().isEmpty()) {
List<Long> catalogIds = catalogs.getRecords().stream().map(CmsCatalog::getCatalogId).toList();
this.catalogService.removeBatchByIds(catalogIds);
lastId = catalogIds.get(catalogIds.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除栏目数据错误:" + e.getMessage());
@ -130,10 +158,20 @@ public class ContentCoreListener {
try {
long total = this.sitePropertyService
.count(new LambdaQueryWrapper<CmsSiteProperty>().eq(CmsSiteProperty::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除站点扩展属性数据:" + (i * pageSize) + "/" + total);
this.sitePropertyService.remove(new LambdaQueryWrapper<CmsSiteProperty>()
.eq(CmsSiteProperty::getSiteId, site.getSiteId()).last("limit " + pageSize));
Page<CmsSiteProperty> siteProperties = this.sitePropertyService.lambdaQuery()
.select(CmsSiteProperty::getPropertyId)
.eq(CmsSiteProperty::getSiteId, site.getSiteId())
.gt(CmsSiteProperty::getPropertyId, lastId)
.orderByAsc(CmsSiteProperty::getPropertyId)
.page(Page.of(0, pageSize, false));
if (!siteProperties.getRecords().isEmpty()) {
List<Long> ids = siteProperties.getRecords().stream().map(CmsSiteProperty::getPropertyId).toList();
this.sitePropertyService.removeBatchByIds(ids);
lastId = ids.get(ids.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除站点扩展属性错误:" + e.getMessage());
@ -143,10 +181,20 @@ public class ContentCoreListener {
try {
long total = this.templateService
.count(new LambdaQueryWrapper<CmsTemplate>().eq(CmsTemplate::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除模板数据:" + (i * pageSize) + "/" + total);
this.templateService.remove(new LambdaQueryWrapper<CmsTemplate>()
.eq(CmsTemplate::getSiteId, site.getSiteId()).last("limit " + pageSize));
Page<CmsTemplate> templates = this.templateService.lambdaQuery()
.select(CmsTemplate::getTemplateId)
.eq(CmsTemplate::getSiteId, site.getSiteId())
.gt(CmsTemplate::getTemplateId, lastId)
.orderByAsc(CmsTemplate::getTemplateId)
.page(Page.of(0, pageSize, false));
if (!templates.getRecords().isEmpty()) {
List<Long> ids = templates.getRecords().stream().map(CmsTemplate::getTemplateId).toList();
this.templateService.removeBatchByIds(ids);
lastId = ids.get(ids.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除模板数据错误:" + e.getMessage());
@ -156,10 +204,21 @@ public class ContentCoreListener {
try {
long total = this.contentRelaService
.count(new LambdaQueryWrapper<CmsContentRela>().eq(CmsContentRela::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在内容关联表数据:" + (i * pageSize) + "/" + total);
this.contentRelaService.remove(new LambdaQueryWrapper<CmsContentRela>()
.eq(CmsContentRela::getSiteId, site.getSiteId()).last("limit " + pageSize));
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total),
"正在内容关联表数据:" + (i * pageSize) + "/" + total);
Page<CmsContentRela> templates = this.contentRelaService.lambdaQuery()
.select(CmsContentRela::getRelaContentId)
.eq(CmsContentRela::getSiteId, site.getSiteId())
.gt(CmsContentRela::getRelaContentId, lastId)
.orderByAsc(CmsContentRela::getRelaContentId)
.page(Page.of(0, pageSize, false));
if (!templates.getRecords().isEmpty()) {
List<Long> ids = templates.getRecords().stream().map(CmsContentRela::getRelaContentId).toList();
this.contentRelaService.removeBatchByIds(ids);
lastId = ids.get(ids.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除内容关联表数据错误:" + e.getMessage());

View File

@ -17,6 +17,7 @@ package com.chestnut.contentcore.service;
import com.chestnut.common.async.AsyncTask;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.contentcore.core.IContent;
import com.chestnut.contentcore.core.IInternalDataType;
import com.chestnut.contentcore.core.IPageWidget;
@ -27,6 +28,7 @@ import com.chestnut.contentcore.domain.CmsSite;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
public interface IPublishService {
@ -50,7 +52,7 @@ public interface IPublishService {
* @param contentStatus 内容状态
* @return 结果
*/
AsyncTask publishAll(CmsSite site, final String contentStatus, final LoginUser operator);
AsyncTask publishAll(CmsSite site, final String contentStatus, final Operator operator);
/**
* 站点首页页面内容
@ -64,6 +66,8 @@ public interface IPublishService {
String getSitePageData(CmsSite site, IInternalDataType.RequestData requestData)
throws IOException, TemplateException;
void processSitePage(CmsSite site, IInternalDataType.RequestData requestData, Writer writer) throws TemplateException, IOException;
/**
* 获取栏目模板页面内容
*
@ -77,6 +81,9 @@ public interface IPublishService {
String getCatalogPageData(CmsCatalog catalog, IInternalDataType.RequestData requestData, boolean listFlag)
throws IOException, TemplateException;
void processCatalogPage(CmsCatalog catalog, IInternalDataType.RequestData requestData, boolean listFlag, Writer writer)
throws IOException, TemplateException;
/**
* 发布栏目异步任务
*
@ -87,7 +94,7 @@ public interface IPublishService {
* @return 结果
*/
AsyncTask publishCatalog(CmsCatalog catalog, boolean publishChild, boolean publishDetail,
String publishStatus, final LoginUser operator);
String publishStatus, final Operator operator);
/**
* 获取内容模板页面结果
@ -108,6 +115,9 @@ public interface IPublishService {
*/
void asyncStaticizeContent(IContent<?> content);
String processContentPage(CmsContent content, IInternalDataType.RequestData requestData, Writer writer)
throws IOException, TemplateException;
/**
* 发布内容
*
@ -116,7 +126,7 @@ public interface IPublishService {
*/
AsyncTask publishContents(List<CmsContent> contents, LoginUser operator);
void publishContent(CmsContent content, LoginUser operator);
void publishContent(CmsContent content, Operator operator);
/**
* 获取内容扩展模板解析内容
@ -135,13 +145,15 @@ public interface IPublishService {
* 获取页面部件模板解析内容
*
* @param pageWidget 页面部件
* @param publishPipeCode 发布通道编码
* @param isPreview 是否预览
* @param data 请求数据
* @return 结果
* @throws IOException e1
* @throws TemplateException e2
*/
String getPageWidgetPageData(CmsPageWidget pageWidget, String publishPipeCode, boolean isPreview) throws IOException, TemplateException;
String getPageWidgetPageData(CmsPageWidget pageWidget, IInternalDataType.RequestData data) throws IOException, TemplateException;
void processPageWidget(CmsPageWidget pageWidget, IInternalDataType.RequestData data, Writer writer)
throws IOException, TemplateException;
/**
* 页面部件静态化

View File

@ -277,6 +277,7 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
checkRedirectUrl(dto.getCatalogType(), dto.getRedirectUrl());
String oldPath = catalog.getPath();
String oldAlias = catalog.getAlias();
BeanUtils.copyProperties(dto, catalog);
// 发布通道数据处理
Map<String, Map<String, Object>> publishPipeProps = dto.getPublishPipeDatas().stream()
@ -285,7 +286,7 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
catalog.updateBy(dto.getOperator().getUsername());
this.updateById(catalog);
this.clearCache(catalog);
this.clearCache(catalog.getSiteId(), catalog.getCatalogId(), oldAlias);
this.applicationContext.publishEvent(new AfterCatalogSaveEvent(this, catalog, oldPath, dto.getParams()));
return catalog;
}
@ -404,7 +405,11 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
@Override
public void clearCache(CmsCatalog catalog) {
this.catalogCache.clear(catalog);
this.catalogCache.clear(catalog.getSiteId(), catalog.getCatalogId(), catalog.getAlias());
}
private void clearCache(Long siteId, Long catalogId, String alias) {
this.catalogCache.clear(siteId, catalogId, alias);
}
@Override

View File

@ -21,6 +21,7 @@ import com.chestnut.common.async.AsyncTask;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.SpringUtils;
import com.chestnut.common.utils.StringUtils;
@ -40,6 +41,7 @@ import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.fixed.dict.ContentOpType;
import com.chestnut.contentcore.fixed.dict.ContentStatus;
import com.chestnut.contentcore.listener.event.*;
import com.chestnut.contentcore.perms.CatalogPermissionType;
import com.chestnut.contentcore.perms.CatalogPermissionType.CatalogPrivItem;
import com.chestnut.contentcore.properties.RepeatTitleCheckProperty;
import com.chestnut.contentcore.publish.IContentPathRule;
@ -104,20 +106,16 @@ public class ContentServiceImpl implements IContentService {
}
@Override
public void deleteContent(CmsContent cmsContent, LoginUser operator, Map<String, Object> params) {
public void deleteContent(CmsContent cmsContent, LoginUser loginUser, Map<String, Object> params) {
boolean canDelete = ContentStatus.isDraft(cmsContent.getStatus()) || ContentStatus.isOffline(cmsContent.getStatus());
Assert.isTrue(canDelete, ContentCoreErrorCode.DEL_CONTENT_ERR::exception);
IContentType contentType = ContentCoreUtils.getContentType(cmsContent.getContentType());
IContent<?> content = contentType.loadContent(cmsContent);
content.setOperator(operator);
content.setOperator(Operator.of(loginUser));
content.setParams(params);
transactionTemplate.executeWithoutResult(transactionStatus -> deleteContent0(content));
SpringUtils.publishEvent(new AfterContentDeleteEvent(this, content));
}
private void deleteContent0(IContent<?> content) {
content.delete();
// 删除映射内容
List<CmsContent> mappingList = this.dao().lambdaQuery()
.eq(CmsContent::getCopyType, ContentCopyType.Mapping)
@ -126,15 +124,21 @@ public class ContentServiceImpl implements IContentService {
for (CmsContent mappingContent : mappingList) {
log.debug("CC.Content[{}].delete: mapping content delete", content.getContentEntity().getContentId());
try {
deleteContent(mappingContent, content.getOperator(), Map.of(
IContent.PARAM_IS_DELETE_BY_CATALOG, content.getParams().get(IContent.PARAM_IS_DELETE_BY_CATALOG)
));
IContentType mappingContentType = ContentCoreUtils.getContentType(cmsContent.getContentType());
IContent<?> mappingIContent = mappingContentType.loadContent(cmsContent);
mappingIContent.setOperator(Operator.of(loginUser));
mappingIContent.setParams(params);
transactionTemplate.executeWithoutResult(transactionStatus -> deleteContent0(mappingIContent));
SpringUtils.publishEvent(new AfterContentDeleteEvent(this, mappingIContent));
} catch (Exception e) {
AsyncTaskManager.setTaskTenPercentProgressInfo(ContentTips.DELETING_MAPPING_CONTENT.locale(
mappingContent.getTitle(), mappingContent.getContentId()));
}
}
// 删除相关内容
}
private void deleteContent0(IContent<?> content) {
content.delete();
contentRelaService.onContentDelete(content.getContentEntity().getContentId());
// TODO 删除内容历史版本
}
@ -170,13 +174,13 @@ public class ContentServiceImpl implements IContentService {
}
@Override
public void deleteContentsByCatalog(CmsCatalog catalog, boolean includeChild, LoginUser operator) {
public void deleteContentsByCatalog(CmsCatalog catalog, boolean includeChild, LoginUser loginUser) {
long pageSize = 100;
long total = this.dao().lambdaQuery()
.eq(!includeChild, CmsContent::getCatalogId, catalog.getCatalogId())
.likeRight(includeChild, CmsContent::getCatalogAncestors, catalog.getAncestors())
.count();
Operator operator = Operator.of(loginUser);
for (int i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize / total),
"正在栏目删除内容:" + (i * pageSize) + " / " + total);
@ -307,6 +311,8 @@ public class ContentServiceImpl implements IContentService {
CmsContent cmsContent = dao().getById(contentId);
if (Objects.nonNull(cmsContent)) {
for (CmsCatalog catalog : catalogs) {
// 校验权限
PermissionUtils.checkPermission(CatalogPermissionType.CatalogPrivItem.AddContent.getPermissionKey(catalog.getCatalogId()), dto.getOperator());
CmsContent copyContent = copy0(cmsContent, catalog, dto.getCopyType(), dto.getOperator());
SpringUtils.publishEvent(new AfterContentCopyEvent(this, cmsContent, copyContent));
}
@ -321,12 +327,12 @@ public class ContentServiceImpl implements IContentService {
}
private CmsContent copy0(CmsContent cmsContent, CmsCatalog toCatalog, Integer copyType, LoginUser operator) {
private CmsContent copy0(CmsContent cmsContent, CmsCatalog toCatalog, Integer copyType, LoginUser loginUser) {
AsyncTaskManager.setTaskTenPercentProgressInfo(ContentTips.COPYING_CONTENT.locale(AsyncTaskManager.getLocale(),
cmsContent.getTitle(), toCatalog.getName()));
IContentType ct = ContentCoreUtils.getContentType(cmsContent.getContentType());
IContent<?> content = ct.loadContent(cmsContent);
content.setOperator(operator);
content.setOperator(Operator.of(loginUser));
return transactionTemplate.execute(transactionStatus -> content.copyTo(toCatalog, copyType));
}
@ -357,7 +363,9 @@ public class ContentServiceImpl implements IContentService {
}
@Override
public void moveContent(CmsContent cmsContent, CmsCatalog toCatalog, LoginUser operator) {
public void moveContent(CmsContent cmsContent, CmsCatalog toCatalog, LoginUser loginUser) {
// 校验权限
PermissionUtils.checkPermission(CatalogPermissionType.CatalogPrivItem.AddContent.getPermissionKey(toCatalog.getCatalogId()), loginUser);
if (cmsContent.getCatalogId().equals(toCatalog.getCatalogId())) {
log.warn("Cannot move content to source catalog!");
return;
@ -366,7 +374,7 @@ public class ContentServiceImpl implements IContentService {
cmsContent.getTitle(), toCatalog.getName()));
IContentType ct = ContentCoreUtils.getContentType(cmsContent.getContentType());
IContent<?> content = ct.loadContent(cmsContent);
content.setOperator(operator);
content.setOperator(Operator.of(loginUser));
transactionTemplate.executeWithoutResult(transactionStatus -> content.moveTo(toCatalog));
}
@ -376,7 +384,7 @@ public class ContentServiceImpl implements IContentService {
for (CmsContent c : contents) {
IContentType ct = ContentCoreUtils.getContentType(c.getContentType());
IContent<?> content = ct.loadContent(c);
content.setOperator(dto.getOperator());
content.setOperator(Operator.of(dto.getOperator()));
transactionTemplate.executeWithoutResult(transactionStatus -> content.setTop(dto.getTopEndTime()));
SpringUtils.publishEvent(new AfterContentTopSetEvent(this, content));
}
@ -391,7 +399,7 @@ public class ContentServiceImpl implements IContentService {
for (CmsContent c : contents) {
IContentType ct = ContentCoreUtils.getContentType(c.getContentType());
IContent<?> content = ct.loadContent(c);
content.setOperator(operator);
content.setOperator(Operator.of(operator));
transactionTemplate.executeWithoutResult(transactionStatus -> content.cancelTop());
SpringUtils.publishEvent(new AfterContentTopCancelEvent(this, content));
}
@ -420,11 +428,11 @@ public class ContentServiceImpl implements IContentService {
offline0(cmsContent, operator, LocaleContextHolder.getLocale());
}
private void offline0(CmsContent cmsContent, LoginUser operator, Locale locale) {
private void offline0(CmsContent cmsContent, LoginUser loginUser, Locale locale) {
AsyncTaskManager.setTaskTenPercentProgressInfo(ContentTips.OFFLINE_CONTENT.locale(locale, cmsContent.getTitle()));
IContentType ct = ContentCoreUtils.getContentType(cmsContent.getContentType());
IContent<?> content = ct.loadContent(cmsContent);
content.setOperator(operator);
content.setOperator(Operator.of(loginUser));
transactionTemplate.executeWithoutResult(transactionStatus -> content.offline());
// 映射关联内容同步下线
if (!cmsContent.isLinkContent() && !ContentCopyType.isMapping(cmsContent.getCopyType())) {
@ -435,7 +443,7 @@ public class ContentServiceImpl implements IContentService {
for (CmsContent c : mappingList) {
log.debug("CC.Content[{}].offline: mapping content offline", cmsContent.getContentId());
AsyncTaskManager.setTaskTenPercentProgressInfo(ContentTips.OFFLINE_MAPPING_CONTENT.locale(locale, c.getTitle()));
offline0(c, operator, locale);
offline0(c, loginUser, locale);
}
}
SpringUtils.publishEvent(new AfterContentOfflineEvent(this, content));
@ -447,7 +455,7 @@ public class ContentServiceImpl implements IContentService {
CmsContent c = this.dao().getById(dto.getContentId());
IContentType ct = ContentCoreUtils.getContentType(c.getContentType());
IContent<?> content = ct.loadContent(c);
content.setOperator(dto.getOperator());
content.setOperator(Operator.of(dto.getOperator()));
content.sort(dto.getTargetContentId());
}
@ -460,10 +468,10 @@ public class ContentServiceImpl implements IContentService {
}
@Override
public void toPublish(CmsContent cmsContent, LoginUser operator) {
public void toPublish(CmsContent cmsContent, LoginUser loginUser) {
IContentType ct = ContentCoreUtils.getContentType(cmsContent.getContentType());
IContent<?> content = ct.loadContent(cmsContent);
content.setOperator(operator);
content.setOperator(Operator.of(loginUser));
transactionTemplate.executeWithoutResult(transactionStatus -> content.toPublish());
SpringUtils.publishEvent(new AfterContentToPublishEvent(this, content));
}

View File

@ -15,12 +15,12 @@
*/
package com.chestnut.contentcore.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.contentcore.cache.PageWidgetMonitoredCache;
import com.chestnut.contentcore.core.IPageWidget;
import com.chestnut.contentcore.core.IPageWidgetType;
@ -76,11 +76,10 @@ public class PageWidgetServiceImpl extends ServiceImpl<CmsPageWidgetMapper, CmsP
@Override
public boolean checkCodeUnique(Long siteId, String code, Long pageWidgetId) {
LambdaQueryWrapper<CmsPageWidget> q = new LambdaQueryWrapper<CmsPageWidget>().eq(CmsPageWidget::getCode, code)
return this.lambdaQuery().eq(CmsPageWidget::getCode, code)
.eq(CmsPageWidget::getSiteId, siteId)
.ne(pageWidgetId != null && pageWidgetId > 0, CmsPageWidget::getPageWidgetId, pageWidgetId)
.last("limit 1");
return this.count(q) == 0;
.ne(IdUtils.validate(pageWidgetId), CmsPageWidget::getPageWidgetId, pageWidgetId)
.count() == 0;
}
@Override

View File

@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.common.async.AsyncTask;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.staticize.StaticizeService;
import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.utils.Assert;
@ -61,6 +62,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
@Service
@ -92,6 +94,14 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
@Override
public String getSitePageData(CmsSite site, IInternalDataType.RequestData requestData)
throws IOException, TemplateException {
try (StringWriter writer = new StringWriter()) {
processSitePage(site, requestData, writer);
return writer.toString();
}
}
@Override
public void processSitePage(CmsSite site, IInternalDataType.RequestData requestData, Writer writer) throws TemplateException, IOException {
String indexTemplate = site.getIndexTemplate(requestData.getPublishPipeCode());
File templateFile = this.templateService.findTemplateFile(site, indexTemplate, requestData.getPublishPipeCode());
if (Objects.isNull(templateFile)) {
@ -108,9 +118,8 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
templateType.initTemplateData(site.getSiteId(), context);
long s = System.currentTimeMillis();
try (StringWriter writer = new StringWriter()) {
try {
this.staticizeService.process(context, writer);
return writer.toString();
} finally {
logger.debug("[{}]Parse index template: {}\t, cost: {}ms", requestData.getPublishPipeCode(), site.getName(), System.currentTimeMillis() - s);
}
@ -126,7 +135,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
}
@Override
public AsyncTask publishAll(CmsSite site, final String contentStatus, final LoginUser operator) {
public AsyncTask publishAll(CmsSite site, final String contentStatus, final Operator operator) {
AsyncTask asyncTask = new AsyncTask() {
@Override
@ -162,7 +171,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
content.setContentEntity(xContent);
content.setOperator(operator);
Boolean published = transactionTemplate.execute(callback -> content.publish());
if (published) {
if (Boolean.TRUE.equals(published)) {
applicationContext.publishEvent(new AfterContentPublishEvent(contentType, content));
}
this.checkInterrupt();
@ -200,6 +209,15 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
@Override
public String getCatalogPageData(CmsCatalog catalog, IInternalDataType.RequestData requestData, boolean listFlag)
throws IOException, TemplateException {
try (StringWriter writer = new StringWriter()) {
this.processCatalogPage(catalog, requestData, listFlag, writer);
return writer.toString();
}
}
@Override
public void processCatalogPage(CmsCatalog catalog, IInternalDataType.RequestData requestData, boolean listFlag, Writer writer)
throws IOException, TemplateException {
if (CatalogType_Link.ID.equals(catalog.getCatalogType())) {
throw new RuntimeException("链接类型栏目无独立页面:" + catalog.getName());
}
@ -240,9 +258,8 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
templateContext.setFirstFileName(catalogLink);
templateContext.setOtherFileName(TemplateUtils.appendPageIndexParam(catalogLink, TemplateContext.PlaceHolder_PageNo));
}
try (StringWriter writer = new StringWriter()) {
try {
this.staticizeService.process(templateContext, writer);
return writer.toString();
} finally {
logger.debug("[{}]栏目页模板解析:{},耗时:{}ms", requestData.getPublishPipeCode(), catalog.getName(),
(System.currentTimeMillis() - s));
@ -251,7 +268,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
@Override
public AsyncTask publishCatalog(CmsCatalog catalog, boolean publishChild, boolean publishDetail,
final String publishStatus, final LoginUser operator) {
final String publishStatus, final Operator operator) {
List<CmsPublishPipe> publishPipes = publishPipeService.getPublishPipes(catalog.getSiteId());
Assert.isTrue(!publishPipes.isEmpty(), ContentCoreErrorCode.NO_PUBLISHPIPE::exception);
@ -296,7 +313,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
content.setContentEntity(xContent);
content.setOperator(operator);
Boolean published = transactionTemplate.execute(callback -> content.publish());
if (published) {
if (Boolean.TRUE.equals(published)) {
applicationContext.publishEvent(new AfterContentPublishEvent(contentType, content));
}
this.checkInterrupt();
@ -386,23 +403,64 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
templateContext.setFirstFileName(contentLink);
templateContext.setOtherFileName(TemplateUtils.appendPageIndexParam(contentLink, TemplateContext.PlaceHolder_PageNo));
// staticize
this.staticizeService.process(templateContext, writer);
this.processContentPage(content, requestData, writer);
logger.debug("[{}][{}]内容模板解析:{},耗时:{}", requestData.getPublishPipeCode(), contentType.getId(), content.getTitle(),
System.currentTimeMillis() - s);
return writer.toString();
}
}
@Override
public String processContentPage(CmsContent content, IInternalDataType.RequestData requestData, Writer writer)
throws IOException, TemplateException {
CmsSite site = this.siteService.getById(content.getSiteId());
CmsCatalog catalog = this.catalogService.getCatalog(content.getCatalogId());
if (content.isLinkContent()) {
throw new RuntimeException("标题内容:" + content.getTitle() + ",跳转链接:" + content.getRedirectUrl());
}
// 查找模板
final String detailTemplate = getDetailTemplate(site, catalog, content, requestData.getPublishPipeCode());
File templateFile = this.templateService.findTemplateFile(site, detailTemplate, requestData.getPublishPipeCode());
Assert.notNull(templateFile,
() -> ContentCoreErrorCode.TEMPLATE_EMPTY.exception(requestData.getPublishPipeCode(), detailTemplate));
IContentType contentType = ContentCoreUtils.getContentType(content.getContentType());
long s = System.currentTimeMillis();
// 生成静态页面
try {
// 模板ID = 通道:站点目录:模板文件名
String templateKey = SiteUtils.getTemplateKey(site, requestData.getPublishPipeCode(), detailTemplate);
TemplateContext templateContext = new TemplateContext(templateKey, requestData.isPreview(), requestData.getPublishPipeCode());
templateContext.setPageIndex(requestData.getPageIndex());
templateContext.getVariables().put(TemplateUtils.TemplateVariable_Request, Objects.requireNonNullElse(requestData.getParams(), Map.of()));
// 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 contentLink = this.contentService.getContentLink(content, 1, requestData.getPublishPipeCode(), requestData.isPreview());
templateContext.setFirstFileName(contentLink);
templateContext.setOtherFileName(TemplateUtils.appendPageIndexParam(contentLink, TemplateContext.PlaceHolder_PageNo));
// staticize
this.staticizeService.process(templateContext, writer);
return writer.toString();
} finally {
logger.debug("[{}][{}]内容模板解析:{},耗时:{}", requestData.getPublishPipeCode(), contentType.getId(), content.getTitle(),
System.currentTimeMillis() - s);
}
}
/**
* 内容发布
*/
@Override
public AsyncTask publishContents(List<CmsContent> contents, LoginUser operator) {
public AsyncTask publishContents(List<CmsContent> contents, LoginUser loginUser) {
Locale locale = LocaleContextHolder.getLocale();
AsyncTask task = new AsyncTask() {
@Override
public void run0() {
publishContents0(contents, operator, locale);
publishContents0(contents, Operator.of(loginUser), locale);
}
};
task.setType("PublishContents");
@ -411,11 +469,11 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
}
@Override
public void publishContent(CmsContent content, LoginUser operator) {
public void publishContent(CmsContent content, Operator operator) {
publishContents0(List.of(content), operator, null);
}
private void publishContents0(List<CmsContent> contents, LoginUser operator, Locale locale) {
private void publishContents0(List<CmsContent> contents, Operator operator, Locale locale) {
if (contents.isEmpty()) {
return;
}
@ -427,7 +485,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
IContent<?> content = contentType.loadContent(cmsContent);
content.setOperator(operator);
Boolean published = transactionTemplate.execute(callback -> content.publish());
if (published) {
if (Boolean.TRUE.equals(published)) {
applicationContext.publishEvent(new AfterContentPublishEvent(contentType, content));
}
catalogIds.add(cmsContent.getCatalogId());
@ -517,20 +575,29 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
}
@Override
public String getPageWidgetPageData(CmsPageWidget pageWidget, String publishPipeCode, boolean isPreview)
public String getPageWidgetPageData(CmsPageWidget pageWidget, IInternalDataType.RequestData data)
throws IOException, TemplateException {
try (StringWriter writer = new StringWriter()) {
processPageWidget(pageWidget, data, writer);
return writer.toString();
}
}
@Override
public void processPageWidget(CmsPageWidget pageWidget, IInternalDataType.RequestData data, Writer writer)
throws IOException, TemplateException {
CmsSite site = this.siteService.getById(pageWidget.getSiteId());
String template = MapUtils.getString(pageWidget.getTemplates(), publishPipeCode, "");
String template = MapUtils.getString(pageWidget.getTemplates(), data.getPublishPipeCode(), "");
File templateFile = this.templateService.findTemplateFile(site, template,
publishPipeCode);
data.getPublishPipeCode());
Assert.notNull(templateFile, () -> ContentCoreErrorCode.TEMPLATE_EMPTY.exception(template));
// 生成静态页面
try (StringWriter writer = new StringWriter()) {
long s = System.currentTimeMillis();
long s = System.currentTimeMillis();
try {
// 模板ID = 通道:站点目录:模板文件名
String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, template);
TemplateContext templateContext = new TemplateContext(templateKey, isPreview, publishPipeCode);
String templateKey = SiteUtils.getTemplateKey(site, data.getPublishPipeCode(), template);
TemplateContext templateContext = new TemplateContext(templateKey, data.isPreview(), data.getPublishPipeCode());
// init template global variables
TemplateUtils.initGlobalVariables(site, templateContext);
templateContext.getVariables().put(TemplateUtils.TemplateVariable_PageWidget, pageWidget);
@ -539,9 +606,9 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
templateType.initTemplateData(site.getSiteId(), templateContext);
// staticize
this.staticizeService.process(templateContext, writer);
logger.debug("[{}]页面部件【{}#{}】模板解析耗时:{}ms", publishPipeCode, pageWidget.getName(),
} finally {
logger.debug("[{}]页面部件【{}#{}】模板解析耗时:{}ms", data.getPublishPipeCode(), pageWidget.getName(),
pageWidget.getCode(), System.currentTimeMillis() - s);
return writer.toString();
}
}

View File

@ -15,8 +15,7 @@
*/
package com.chestnut.contentcore.template.exception;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.common.i18n.I18nUtils;
import freemarker.core.Environment;
import freemarker.template.TemplateException;
@ -24,7 +23,7 @@ public class CatalogNotFoundException extends TemplateException {
private static final long serialVersionUID = 1L;
public CatalogNotFoundException(String tag, long catalogId, String alias, Environment env) {
super(StringUtils.messageFormat("<@{0}>[id: {1}, alias: {2}]", tag, catalogId, alias), env);
public CatalogNotFoundException(long catalogId, String alias, Environment env) {
super(I18nUtils.parse("FREEMARKER.ERR.CatalogNotFound", env.getLocale(), catalogId, alias), env);
}
}

View File

@ -85,7 +85,7 @@ public class CmsCatalogTag extends AbstractListTag {
}
String level = MapUtils.getString(attrs, ATTR_LEVEL);
if (!CatalogTagLevel.isRoot(level) && Objects.isNull(catalog)) {
throw new CatalogNotFoundException(getTagName(), catalogId, alias, env);
throw new CatalogNotFoundException(catalogId, alias, env);
}
String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition);

View File

@ -25,10 +25,16 @@ import com.chestnut.common.staticize.tag.TagAttr;
import com.chestnut.common.staticize.tag.TagAttrOption;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.vo.TagContentVO;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.fixed.dict.ContentStatus;
import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService;
import com.chestnut.contentcore.template.exception.CatalogNotFoundException;
import com.chestnut.contentcore.util.CatalogUtils;
import com.chestnut.contentcore.util.TemplateUtils;
import freemarker.core.Environment;
import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor;
@ -37,6 +43,7 @@ import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Component
@RequiredArgsConstructor
@ -47,35 +54,82 @@ public class CmsContentClosestTag extends AbstractListTag {
public final static String DESC = "{FREEMARKER.TAG." + TAG_NAME + ".DESC}";
public final static String ATTR_USAGE_CONTENT_ID = "{FREEMARKER.TAG." + TAG_NAME + ".contentId}";
public final static String ATTR_USAGE_TYPE = "{FREEMARKER.TAG." + TAG_NAME + ".type}";
public final static String ATTR_USAGE_SORT = "{FREEMARKER.TAG." + TAG_NAME + ".sort}";
public final static String ATTR_OPTION_TYPE_PREV = "{FREEMARKER.TAG." + TAG_NAME + ".type.Prev}";
public final static String ATTR_OPTION_TYPE_NEXT = "{FREEMARKER.TAG." + TAG_NAME + ".type.Next}";
final static String ATTR_CONTENT_ID = "contentid";
final static String ATTR_SORT = "sort";
final static String ATTR_TYPE = "type";
public final static String ATTR_CATALOG_ID = "catalogid";
public final static String ATTR_CATALOG_ALIAS = "catalogalias";
public final static String ATTR_LEVEL = "level";
public final static String ATTR_SORT = "sort";
public final static String ATTR_HAS_ATTRIBUTE = "hasattribute";
public final static String ATTR_NO_ATTRIBUTE = "noattribute";
public final static String ATTR_STATUS = "status";
public final static String ATTR_TOP_FLAG = "topflag";
private final ICatalogService catalogService;
private final IContentService contentService;
@Override
public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException {
boolean isNext = TypeTagAttr.isNext(attrs.get(ATTR_TYPE));
String sort = attrs.get(ATTR_SORT);
Long contentId = MapUtils.getLong(attrs, ATTR_CONTENT_ID);
CmsContent content = this.contentService.dao().getById(contentId);
Assert.notNull(content, () -> new TemplateException(StringUtils.messageFormat("Tag attr[contentid={0}] data not found.", contentId), env));
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<CmsContent>()
.eq(CmsContent::getCatalogId, content.getCatalogId())
.eq(CmsContent::getStatus, ContentStatus.PUBLISHED);
if (CmsContentTag.SortTagAttr.isRecent(sort)) {
CmsCatalog catalog = null;
long catalogId = MapUtils.getLongValue(attrs, ATTR_CATALOG_ID);
if (catalogId > 0) {
catalog = this.catalogService.getCatalog(catalogId);
}
long siteId = TemplateUtils.evalSiteId(env);
String alias = MapUtils.getString(attrs, ATTR_CATALOG_ALIAS);
if (Objects.isNull(catalog) && StringUtils.isNotEmpty(alias)) {
catalog = this.catalogService.getCatalogByAlias(siteId, alias);
}
String level = MapUtils.getString(attrs, ATTR_LEVEL);
if (!CmsContentTag.LevelTagAttr.isRoot(level) && Objects.isNull(catalog)) {
throw new CatalogNotFoundException(catalogId, alias, env);
}
String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition);
String status = MapUtils.getString(attrs, ATTR_STATUS, ContentStatus.PUBLISHED);
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<>();
q.eq(CmsContent::getSiteId, siteId).eq(!"-1".equals(status), CmsContent::getStatus, status);
if (Objects.nonNull(catalog)) {
if (CmsContentTag.LevelTagAttr.isCurrent(level)) {
q.eq(CmsContent::getCatalogId, catalog.getCatalogId());
} else if (CmsContentTag.LevelTagAttr.isChild(level)) {
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors() + CatalogUtils.ANCESTORS_SPLITER);
} else if (CmsContentTag.LevelTagAttr.isCurrentAndChild(level)) {
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors());
}
}
String hasAttribute = MapUtils.getString(attrs, ATTR_HAS_ATTRIBUTE);
if (StringUtils.isNotEmpty(hasAttribute)) {
int attrTotal = ContentAttribute.convertInt(hasAttribute.split(","));
q.apply(attrTotal > 0, "attributes&{0}={1}", attrTotal, attrTotal);
}
String noAttribute = MapUtils.getString(attrs, ATTR_NO_ATTRIBUTE);
if (StringUtils.isNotEmpty(noAttribute)) {
String[] contentAttrs = noAttribute.split(",");
int attrTotal = ContentAttribute.convertInt(contentAttrs);
for (String attr : contentAttrs) {
int bit = ContentAttribute.bit(attr);
q.apply(bit > 0, "attributes&{0}<>{1}", attrTotal, bit);
}
}
q.apply(StringUtils.isNotEmpty(condition), condition);
String sortType = MapUtils.getString(attrs, ATTR_SORT);
q.orderByDesc(MapUtils.getBooleanValue(attrs, ATTR_TOP_FLAG, true), CmsContent::getTopFlag);
if (CmsContentTag.SortTagAttr.isRecent(sortType)) {
q.gt(isNext, CmsContent::getPublishDate, content.getPublishDate());
q.lt(!isNext, CmsContent::getPublishDate, content.getPublishDate());
q.orderBy(true, isNext, CmsContent::getPublishDate);
} else if(CmsContentTag.SortTagAttr.isViews(sort)) {
} else if(CmsContentTag.SortTagAttr.isViews(sortType)) {
q.gt(isNext, CmsContent::getViewCount, content.getViewCount());
q.lt(!isNext, CmsContent::getViewCount, content.getViewCount());
q.orderBy(true, isNext, CmsContent::getViewCount);
@ -120,11 +174,18 @@ public class CmsContentClosestTag extends AbstractListTag {
@Override
public List<TagAttr> getTagAttrs() {
return List.of(
new TagAttr(ATTR_CONTENT_ID, true, TagAttrDataType.INTEGER, ATTR_USAGE_CONTENT_ID),
new TagAttr(ATTR_TYPE, true, TagAttrDataType.STRING, ATTR_USAGE_TYPE, TypeTagAttr.toTagAttrOptions(), TypeTagAttr.Prev.name()),
new TagAttr(ATTR_SORT, false, TagAttrDataType.STRING, ATTR_USAGE_SORT, CmsContentTag.SortTagAttr.toTagAttrOptions(), CmsContentTag.SortTagAttr.Default.name())
);
List<TagAttr> tagAttrs = super.getTagAttrs();
tagAttrs.add(new TagAttr(ATTR_CONTENT_ID, true, TagAttrDataType.INTEGER, ATTR_USAGE_CONTENT_ID));
tagAttrs.add(new TagAttr(ATTR_TYPE, true, TagAttrDataType.STRING, ATTR_USAGE_TYPE, TypeTagAttr.toTagAttrOptions(), TypeTagAttr.Prev.name()));
tagAttrs.add(new TagAttr(ATTR_CATALOG_ID, false, TagAttrDataType.INTEGER, CmsContentTag.ATTR_USAGE_CATALOG_ID));
tagAttrs.add(new TagAttr(ATTR_CATALOG_ALIAS, false, TagAttrDataType.STRING, CmsContentTag.ATTR_USAGE_CATALOG_ALIAS));
tagAttrs.add(new TagAttr(ATTR_LEVEL, false, TagAttrDataType.STRING, CmsContentTag.ATTR_USAGE_LEVEL, CmsContentTag.LevelTagAttr.toTagAttrOptions(), CmsContentTag.LevelTagAttr.Current.name()));
tagAttrs.add(new TagAttr(ATTR_SORT, false, TagAttrDataType.STRING, CmsContentTag.ATTR_USAGE_SORT, CmsContentTag.SortTagAttr.toTagAttrOptions(), CmsContentTag.SortTagAttr.Default.name()));
tagAttrs.add(new TagAttr(ATTR_HAS_ATTRIBUTE, false, TagAttrDataType.STRING, CmsContentTag.ATTR_USAGE_HAS_ATTRIBUTE));
tagAttrs.add(new TagAttr(ATTR_NO_ATTRIBUTE, false, TagAttrDataType.STRING, CmsContentTag.ATTR_USAGE_NO_ATTRIBUTE));
tagAttrs.add(new TagAttr(ATTR_STATUS, false, TagAttrDataType.STRING, CmsContentTag.ATTR_USAGE_STATUS));
tagAttrs.add(new TagAttr(ATTR_TOP_FLAG, false, TagAttrDataType.BOOLEAN, CmsContentTag.ATTR_USAGE_TOP_FLAG, Boolean.TRUE.toString()));
return tagAttrs;
}
enum TypeTagAttr {

View File

@ -109,7 +109,7 @@ public class CmsContentTag extends AbstractListTag {
}
String level = MapUtils.getString(attrs, ATTR_LEVEL);
if (!LevelTagAttr.isRoot(level) && Objects.isNull(catalog)) {
throw new CatalogNotFoundException(getTagName(), catalogId, alias, env);
throw new CatalogNotFoundException(catalogId, alias, env);
}
String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition);
String status = MapUtils.getString(attrs, ATTR_STATUS, ContentStatus.PUBLISHED);

View File

@ -23,7 +23,10 @@ import com.chestnut.common.staticize.tag.AbstractTag;
import com.chestnut.common.staticize.tag.TagAttr;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.impl.PublishPipeProp_PrefixMode;
import com.chestnut.contentcore.core.impl.PublishPipeProp_RelativePrefix;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.enums.SitePrefixMode;
import com.chestnut.contentcore.properties.EnableSSIProperty;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.service.ITemplateService;
@ -160,7 +163,7 @@ public class CmsIncludeTag extends AbstractTag {
}
}
if (ssi) {
String prefix = SiteUtils.getPublishPipePrefix(site, context.getPublishPipeCode(), context.isPreview());
String prefix = getIncludePathPrefix(context.getPublishPipeCode(), site);
env.getOut().write(StringUtils.messageFormat(SSI_INCLUDE_TAG, prefix + staticFilePath));
} else {
env.getOut().write(staticContent);
@ -169,6 +172,18 @@ public class CmsIncludeTag extends AbstractTag {
return null;
}
public static String getIncludePathPrefix(String publishPipeCode, CmsSite site) {
String prefix = null;
String prefixMode = PublishPipeProp_PrefixMode.getValue(publishPipeCode, site.getPublishPipeProps());
if (SitePrefixMode.isRelative(prefixMode)) {
prefix = PublishPipeProp_RelativePrefix.getValue(publishPipeCode, site.getPublishPipeProps());
}
if (StringUtils.isEmpty(prefix)) {
prefix = "/";
}
return prefix;
}
private Map<String, String> mergeRequestVariable(Environment env, Map<String, String> params) throws TemplateModelException {
TemplateModel variable = env.getVariable(TemplateUtils.TemplateVariable_Request);
if (Objects.nonNull(variable)) {

View File

@ -124,19 +124,19 @@ public class CmsPageWidgetTag extends AbstractTag {
String siteRoot = SiteUtils.getSiteRoot(site, context.getPublishPipeCode());
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(context.getPublishPipeCode()));
String staticFilePath = pw.getPath() + staticFileName;
if (ssi) {
// 读取页面部件静态化内容
String staticContent = templateService.getTemplateStaticContentCache(templateKey);
if (Objects.isNull(staticContent) || !new File(siteRoot + staticFilePath).exists()) {
staticContent = this.processTemplate(env, pw, templateKey);
String staticContent = templateService.getTemplateStaticContentCache(templateKey);
if (Objects.isNull(staticContent) || !new File(siteRoot + staticFilePath).exists()) {
staticContent = this.processTemplate(env, pw, templateKey);
this.templateService.setTemplateStaticContentCache(templateKey, staticContent);
if (ssi) {
FileUtils.writeStringToFile(new File(siteRoot + staticFilePath), staticContent, StandardCharsets.UTF_8);
this.templateService.setTemplateStaticContentCache(templateKey, staticContent);
}
String prefix = SiteUtils.getPublishPipePrefix(site, context.getPublishPipeCode(), context.isPreview());
}
if (ssi) {
String prefix = CmsIncludeTag.getIncludePathPrefix(context.getPublishPipeCode(), site);
env.getOut().write(StringUtils.messageFormat(CmsIncludeTag.SSI_INCLUDE_TAG, prefix + staticFilePath));
} else {
// 非ssi模式无法使用缓存
String staticContent = this.processTemplate(env, pw, templateKey);
env.getOut().write(staticContent);
}
}

View File

@ -88,7 +88,7 @@ public class CatalogUtils {
publishPipeCode, pageIndex);
return BackendContext.getValue() + catalogPath;
}
String prefix = SiteUtils.getPublishPipePrefix(site, publishPipeCode, isPreview);
String prefix = SiteUtils.getPublishPipePrefix(site, publishPipeCode, false);
if (catalog.isStaticize()) {
return prefix + catalog.getPath();
} else {

View File

@ -16,7 +16,7 @@
package com.chestnut.contentcore.util;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.common.utils.SpringUtils;
import com.chestnut.contentcore.domain.CmsContent;
@ -39,7 +39,7 @@ public class ContentLogUtils {
private static final AsyncTaskManager asyncTaskManager = SpringUtils.getBean(AsyncTaskManager.class);
public static void addLog(String opType, CmsContent content, LoginUser operator) {
public static void addLog(String opType, CmsContent content, Operator operator) {
addLog(opType, content, null, operator.getUserType(), operator.getUsername());
}

View File

@ -21,6 +21,7 @@ import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.core.IInternalDataType;
import com.chestnut.contentcore.core.impl.InternalDataType_Site;
import com.chestnut.contentcore.core.impl.PublishPipeProp_PrefixMode;
import com.chestnut.contentcore.core.impl.PublishPipeProp_RelativePrefix;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.enums.SitePrefixMode;
import com.chestnut.system.fixed.config.BackendContext;
@ -48,7 +49,7 @@ public class SiteUtils {
}
String pathMode = PublishPipeProp_PrefixMode.getValue(publishPipeCode, site.getPublishPipeProps());
if (SitePrefixMode.isRelative(pathMode)) {
return "/";
return PublishPipeProp_RelativePrefix.getValue(publishPipeCode, site.getPublishPipeProps());
}
return site.getUrl(publishPipeCode);
}
@ -81,7 +82,7 @@ public class SiteUtils {
}
/**
* 获取站点资源文件访问链接前缀
* 获取站点资源文件访问链接前缀非预览模式为设置资源域名则使用指定发布通道域名
*
* @param site 站点
* @param publishPipeCode 发布通道编码

View File

@ -149,7 +149,6 @@ FREEMARKER.TAG.cms_content_closest.contentId=內容ID
FREEMARKER.TAG.cms_content_closest.type=类型
FREEMARKER.TAG.cms_content_closest.type.Prev=上一篇
FREEMARKER.TAG.cms_content_closest.type.Next=下一篇
FREEMARKER.TAG.cms_content_closest.sort=排序值
FREEMARKER.TAG.cms_content_rela.NAME=相关内容标签
FREEMARKER.TAG.cms_content_rela.DESC=相关内容标签,内嵌`<#list DataList as content>${content.name}</#list>`遍历数据
FREEMARKER.TAG.cms_content_rela.contentId=内容ID
@ -195,6 +194,8 @@ FREEMARKER.FUNC.fileExtractor.DESC=提取html文本中的文件资源链接
FREEMARKER.FUNC.fileExtractor.Arg1.Name=Html文本内容
FREEMARKER.FUNC.fileExtractor.Arg2.Name=文件类型/后缀
FREEMARKER.ERR.CatalogNotFound=栏目数据不存在:[栏目ID: {0}], [栏目别名: {1}]
# 校验规则
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=发布通道配置不能为空
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=属性编码只能使用大小写字母、数字和下划线

View File

@ -149,7 +149,6 @@ FREEMARKER.TAG.cms_content_closest.contentId=Target content id
FREEMARKER.TAG.cms_content_closest.type=Type
FREEMARKER.TAG.cms_content_closest.type.Prev=Prev
FREEMARKER.TAG.cms_content_closest.type.Next=Next
FREEMARKER.TAG.cms_content_closest.sort=Sort Value
FREEMARKER.TAG.cms_content_rela.NAME=Related content tag
FREEMARKER.TAG.cms_content_rela.DESC=Fetch related contents by contentId, use `<#list>` in tag like `<#list DataList as content>${content.title}</#list>` to walk through the list of contents.
FREEMARKER.TAG.cms_content_rela.contentId=Target content id
@ -195,6 +194,8 @@ FREEMARKER.FUNC.fileExtractor.DESC=Get file link list from html text.
FREEMARKER.FUNC.fileExtractor.Arg1.Name=Html text
FREEMARKER.FUNC.fileExtractor.Arg2.Name=File type or suffix
FREEMARKER.ERR.CatalogNotFound=Missing catalog: [id: {0}], [alias: {1}]
# 校验规则
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=The site publish pipe props cannot be empty.
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=The code can only use uppercase/lowercase letters, numbers and underscores.

View File

@ -1,338 +1,339 @@
# 資源類型
CMS.CONTENTCORE.RESOURCE_TYPE.image=圖片
CMS.CONTENTCORE.RESOURCE_TYPE.audio=音頻
CMS.CONTENTCORE.RESOURCE_TYPE.video=視頻
CMS.CONTENTCORE.RESOURCE_TYPE.file=檔案
# 欄目類型
CMS.CONTENTCORE.CATALOG_TYPE.common=普通欄目
CMS.CONTENTCORE.CATALOG_TYPE.link=連結欄目
# 字典數據
DICT.CMSPageWidgetStatus=內容狀態
DICT.CMSPageWidgetStatus.0=初稿
DICT.CMSPageWidgetStatus.30=已發布
DICT.CMSPageWidgetStatus.40=已下線
DICT.CMSPageWidgetStatus.60=重新編輯
DICT.CMSContentStatus=內容狀態
DICT.CMSContentStatus.0=初稿
DICT.CMSContentStatus.20=待發布
DICT.CMSContentStatus.30=已發布
DICT.CMSContentStatus.40=已下線
DICT.CMSContentStatus.60=重新編輯
DICT.CMSContentAttribute=內容屬性
DICT.CMSContentAttribute.image=圖片
DICT.CMSContentAttribute.video=視頻
DICT.CMSContentAttribute.attach=附件
DICT.CMSContentAttribute.hot=熱點
DICT.CMSContentAttribute.recommend=推薦
DICT.CMSStaticSuffix=靜態檔案類型
DICT.CMSStaticSuffix.shtml=shtml
DICT.CMSStaticSuffix.html=html
DICT.CMSStaticSuffix.xml=xml
DICT.CMSStaticSuffix.json=json
DICT.CMSContentOpType=內容操作類型
DICT.CMSContentOpType.ADD=新增
DICT.CMSContentOpType.UPDATE=修改
DICT.CMSContentOpType.DELETE=刪除
DICT.CMSContentOpType.LOCK=鎖定
DICT.CMSContentOpType.UNLOCK=解鎖
DICT.CMSContentOpType.TO_PUBLISH=待發佈
DICT.CMSContentOpType.PUBLISH=發佈
DICT.CMSContentOpType.OFFLINE=下線
DICT.CMSContentOpType.SORT=排序
DICT.CMSContentOpType.TOP=置頂
DICT.CMSContentOpType.CANCEL_TOP=取消置頂
# 固定配置參數
CONFIG.CMSBackendContext=後台訪問地址
CONFIG.CMSModuleEnable=是否開啟CMS功能
CONFIG.CMSTemplateSuffix=模板尾碼名
CONFIG.AllowUploadFileType=CMS檔案上傳類型限制
CONFIG.SiteApiUrl=站點API地址
CONFIG.ResourceUploadAcceptSize=素材庫資源上傳大小限制
# 錯誤資訊
ERRCODE.CMS.CONTENTCORE.NO_SITE=無站點數據,請先去站點管理菜單創建站點
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_PAGE_WIDGET_TYPE=未知頁面部件類型:{0}
ERRCODE.CMS.CONTENTCORE.TEMPLATE_EMPTY=模板未配置或模板檔案不存在
ERRCODE.CMS.CONTENTCORE.TEMPLATE_FILE_NOT_FOUND=模板檔案“{0}”不存在
ERRCODE.CMS.CONTENTCORE.INVALID_TEMPLATE_NAME=模板檔案名只能使用大小寫字母和下劃線,且必須以`{0}`結尾
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_CONTENT_TYPE=未知內容類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_CATALOG_TYPE=未知欄目類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_INTERNAL_DATA_TYPE=未知內部數據類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_TYPE=未知資源類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_DYNAMIC_PAGE_TYPE=未知動態模板類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_CONTENT_PATH_RULE=未知內容詳情頁路徑規則:{0}
ERRCODE.CMS.CONTENTCORE.DEL_CHILD_FIRST=請先刪除子欄目
ERRCODE.CMS.CONTENTCORE.CONFLICT_CATALOG=欄目名稱/別名/目錄重複
ERRCODE.CMS.CONTENTCORE.CATALOG_MAX_TREE_LEVEL=欄目層級超出上限
ERRCODE.CMS.CONTENTCORE.CATALOG_MOVE_TO_SELF_OR_CHILD=欄目不能轉移到自身或子欄目
ERRCODE.CMS.CONTENTCORE.INVALID_PROPERTY=擴展屬性[{0}={1}]校驗失敗
ERRCODE.CMS.CONTENTCORE.EXISTS_SITE_PATH=站點目錄衝突
ERRCODE.CMS.CONTENTCORE.CONTENT_LOCKED=內容已被"{0}"鎖定
ERRCODE.CMS.CONTENTCORE.TITLE_REPLEAT=標題重複
ERRCODE.CMS.CONTENTCORE.CANNOT_EDIT_PUBLISHED_CONTENT=已發布內容不允許編輯,請先下線內容!
ERRCODE.CMS.CONTENTCORE.NO_PUBLISHPIPE=無可用發布通道
ERRCODE.CMS.CONTENTCORE.CATALOG_CANNOT_PUBLISH=欄目發布失敗:欄目不可見/不可靜態化/標題欄目。
ERRCODE.CMS.CONTENTCORE.SITE_FILE_OP_ERR=不能操作非當前站點檔案
ERRCODE.CMS.CONTENTCORE.TEMPLATE_PATH_EXISTS=模板檔案已存在或路徑被佔用
ERRCODE.CMS.CONTENTCORE.NOT_ALLOW_FILE_TYPE=禁止上傳的檔案類型:{0}
ERRCODE.CMS.CONTENTCORE.NOT_EDITABLE_FILE=指定檔案不支援線上編輯
ERRCODE.CMS.CONTENTCORE.FILE_NOT_EXIST=檔案不存在
ERRCODE.CMS.CONTENTCORE.FILE_ALREADY_EXISTS=檔案已存在
ERRCODE.CMS.CONTENTCORE.CATALOG_SORT_VALUE_ZERO=欄目排序值不能為0
ERRCODE.CMS.CONTENTCORE.SITE_EXPORT_TASK_EXISTS=站點導出任務正在進行中
ERRCODE.CMS.CONTENTCORE.DEL_CONTENT_ERR=只能刪除初稿和已下線內容
ERRCODE.CMS.CONTENTCORE.RESOURCE_ACCEPT_SIZE_LIMIT=上傳文件大小超過限制
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_STORAGE=資源存儲方式與當前站點配置不一致
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_IS_EMPTY=被合並欄目不存在
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_NOT_LEAF=被合並欄目不能包含子欄目
ERRCODE.CMS.CONTENTCORE.DENY_LINK_TO_LINK_INTERNAL_DATA=不能链接到链接内容或栏目
# freemarker模板標籤
FREEMARKER.TAG.cms_site.NAME=站點列表標籤
FREEMARKER.TAG.cms_site.DESC=獲取站點數據列表,內嵌`<#list DataList as site>${catalog.site}</#list>`遍曆數據
FREEMARKER.TAG.cms_site.id=站點ID
FREEMARKER.TAG.cms_site.level=數據獲取範圍,值為`Root`時忽略屬性`id`
FREEMARKER.TAG.cms_site.level.Root=所有站點
FREEMARKER.TAG.cms_site.level.Current=同級站點
FREEMARKER.TAG.cms_site.level.Child=子站點
FREEMARKER.TAG.cms_catalog.NAME=欄目列表標籤
FREEMARKER.TAG.cms_catalog.DESC=獲取欄目數據列表,內嵌`<#list DataList as catalog>${catalog.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_catalog.id=欄目ID
FREEMARKER.TAG.cms_catalog.alias=欄目別名
FREEMARKER.TAG.cms_catalog.level=數據獲取範圍,值為`Root`時忽略屬性`id`、`alias`
FREEMARKER.TAG.cms_catalog.level.Root=所有欄目
FREEMARKER.TAG.cms_catalog.level.Current=同級欄目
FREEMARKER.TAG.cms_catalog.level.Child=子欄目
FREEMARKER.TAG.cms_catalog.level.CurrentAndChild=當前欄目及子欄目
FREEMARKER.TAG.cms_catalog.level.Self=當前欄目
FREEMARKER.TAG.cms_content.NAME=內容列表標籤
FREEMARKER.TAG.cms_content.DESC=獲取內容數據列表,內嵌`<#list DataList as content>${content.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_content.catalogId=欄目ID
FREEMARKER.TAG.cms_content.catalogAlias=欄目別名
FREEMARKER.TAG.cms_content.level=數據獲取範圍,值為`Root`時忽略屬性`catalogid`、`catalogalias`
FREEMARKER.TAG.cms_content.level.Root=所有欄目
FREEMARKER.TAG.cms_content.level.Current=當前欄目
FREEMARKER.TAG.cms_content.level.Child=子欄目
FREEMARKER.TAG.cms_content.level.CurrentAndChild=當前欄目及子欄目
FREEMARKER.TAG.cms_content.sort=排序方式
FREEMARKER.TAG.cms_content.sort.Recent=發佈時間降序
FREEMARKER.TAG.cms_content.sort.Views=瀏覽量降序
FREEMARKER.TAG.cms_content.sort.Default=排序字段降序(默認)
FREEMARKER.TAG.cms_content.hasattribute=包含內容屬性,多個屬性英文逗號分隔
FREEMARKER.TAG.cms_content.noattribute=不包含內容屬性,多個屬性英文逗號分隔
FREEMARKER.TAG.cms_content.status=狀態,'-1'表示不限制狀態
FREEMARKER.TAG.cms_content.status.defaultValue=30: 已發佈
FREEMARKER.TAG.cms_content.topflag=是否允許指定
FREEMARKER.TAG.cms_site_property.NAME=站點自定義屬性標籤
FREEMARKER.TAG.cms_site_property.DESC=獲取站點自定義屬性數據列表,內嵌`<#list DataList as prop>${prop.propName}</#list>`遍曆數據
FREEMARKER.TAG.cms_site_property.siteid=站點ID默認從模板變量中獲取`${Site.siteId}`
FREEMARKER.TAG.cms_site_property.code=屬性編碼
FREEMARKER.TAG.cms_include.NAME=模板引用標籤
FREEMARKER.TAG.cms_include.DESC=引用其他模板內容支援ssi引用標籤
FREEMARKER.TAG.cms_include.file=引用模板文件路徑相對模板目錄template/
FREEMARKER.TAG.cms_include.ssi=是否啟用SSI
FREEMARKER.TAG.cms_include.virtual=是否啟用Virtual模式
FREEMARKER.TAG.cms_include.cache=是否啟用緩存
FREEMARKER.TAG.cms_pagewidget.NAME=頁面部件引用標籤
FREEMARKER.TAG.cms_pagewidget.DESC=引用頁面部件內容支援ssi引用標籤
FREEMARKER.TAG.cms_pagewidget.code=頁面部件編碼
FREEMARKER.TAG.cms_pagewidget.ssi=是否啟用SSI
FREEMARKER.TAG.cms_pagewidget_data.NAME=頁面部件數據標籤
FREEMARKER.TAG.cms_pagewidget_data.DESC=頁面部件數據標籤,標籤體內可使用`${Data.name}`獲取數據
FREEMARKER.TAG.cms_pagewidget_data.code=頁面部件編碼
FREEMARKER.TAG.cms_content_closest.NAME=內容前後篇標籤
FREEMARKER.TAG.cms_content_closest.DESC=內容前後篇標籤,僅支援指定內容當前欄目列表,標籤體內可使用`${Data.title}`獲取數據
FREEMARKER.TAG.cms_content_closest.contentId=內容ID
FREEMARKER.TAG.cms_content_closest.type=類型
FREEMARKER.TAG.cms_content_closest.type.Prev=上一篇
FREEMARKER.TAG.cms_content_closest.type.Next=下一篇
FREEMARKER.TAG.cms_content_closest.sort=排序方式
FREEMARKER.TAG.cms_content_rela.NAME=相關內容標籤
FREEMARKER.TAG.cms_content_rela.DESC=相關內容標籤,內嵌`<#list DataList as content>${content.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_content_rela.contentId=內容ID
# freemarker模板函數
FREEMARKER.FUNC.htmlInternalUrl.DESC=將html文本中的內部連結地址“iurl://”解析為正常http(s)訪問地址,例如:`${htmlInternalUrl(ArticleContent)}`
FREEMARKER.FUNC.htmlInternalUrl.Arg1.Name=Html文本內容
FREEMARKER.FUNC.imageSize.DESC=獲得圖片縮放圖函數,不在的縮略圖會自動建立,例如:`${imageSize(content.logo, 300, 300)}`
FREEMARKER.FUNC.imageSize.Arg1.Name=圖片資源內部路徑iurl://
FREEMARKER.FUNC.imageSize.Arg1.Desc=僅支持處理內部資源圖片iurl://
FREEMARKER.FUNC.imageSize.Arg2.Name=寬度
FREEMARKER.FUNC.imageSize.Arg3.Name=高度
FREEMARKER.FUNC.internalUrl.DESC=將內部連結“iurl://”解析為正常http(s)訪問地址,例如:`${internalUrl(content.redirectUrl)}`
FREEMARKER.FUNC.internalUrl.Arg1.Name=內部鏈接iurl://
FREEMARKER.FUNC.siteUrl.DESC=獲取站點指定發布通道訪問連結,例如:`${siteUrl(Site.siteId, 'h5')}`
FREEMARKER.FUNC.siteUrl.Arg1.Name=站點ID
FREEMARKER.FUNC.siteUrl.Arg2.Name=發佈通道編碼
FREEMARKER.FUNC.catalogUrl.DESC=獲取欄目指定發布通道訪問連結,例如:`${catalogUrl(Catalog.catalogId, 'h5')}`
FREEMARKER.FUNC.catalogUrl.Arg1.Name=欄目ID
FREEMARKER.FUNC.catalogUrl.Arg2.Name=發佈通道編碼
FREEMARKER.FUNC.contentUrl.DESC=獲取內容指定發布通道訪問連結,例如:`${contentUrl(Content.contentId, 'h5')}`
FREEMARKER.FUNC.contentUrl.Arg1.Name=內容ID
FREEMARKER.FUNC.contentUrl.Arg2.Name=發佈通道編碼
FREEMARKER.FUNC.dynamicPageLink.DESC=動態頁面連結獲取函數,例如:`${dynamicPageLink('Search')}`
FREEMARKER.FUNC.dynamicPageLink.Arg1.Name=動態頁面類型
FREEMARKER.FUNC.dynamicPageLink.Arg2.Name=忽略`sid/pp`參數
FREEMARKER.FUNC.dict.DESC=獲取字典數據列表,例如:`${dict('YesOrNo', 'Y')}`
FREEMARKER.FUNC.dict.Arg1.Name=字典類型
FREEMARKER.FUNC.dict.Arg2.Name=字典數據值
FREEMARKER.FUNC.dict.Arg3.Name=國際化標識
FREEMARKER.FUNC.sysConfig.DESC=獲取系統參數配置值,例如:`${sysConfig('SiteApiUrl')}`
FREEMARKER.FUNC.sysConfig.Arg1.Name=系統參數鍵名
FREEMARKER.FUNC.listHtmlInternalUrl.DESC=獲取html文本中的iurl列表例如`${listHtmlInternalUrl(ArticleContent)}`
FREEMARKER.FUNC.listHtmlInternalUrl.Arg1.Name=Html文本內容
FREEMARKER.FUNC.contentPageLink.DESC=獲取內容分頁鏈接,例如:`${contentPageLink(content.link, 2)}`
FREEMARKER.FUNC.contentPageLink.Arg1.Name=內容鏈接
FREEMARKER.FUNC.contentPageLink.Arg2.Name=頁碼
FREEMARKER.FUNC.videoPlayer.DESC=將html文本中的視頻資源連結替換為<video>視頻播放器
FREEMARKER.FUNC.videoPlayer.Arg1.Name=Html文本內容
FREEMARKER.FUNC.videoPlayer.Arg2.Name=視頻寬度
FREEMARKER.FUNC.videoPlayer.Arg3.Name=視頻高度
FREEMARKER.FUNC.fileExtractor.DESC=提取html文本中的文件資源鏈接
FREEMARKER.FUNC.fileExtractor.Arg1.Name=Html文本內容
FREEMARKER.FUNC.fileExtractor.Arg2.Name=文件類型/後綴
# 校驗規則
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=發布通道配置不能為空
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=屬性編碼只能使用大小寫字母、數字和下劃線
VALIDATOR.CMS.FILE_NAME.REGEXP_ERR=文件名不能包含斜杠或反斜杠
# 定時任務
SCHEDULED_TASK.RecycleExpireJobHandler=資源回收筒過期內容刪除任務
SCHEDULED_TASK.SitePublishJobHandler=內容定時發布任務
SCHEDULED_TASK.ContentTopCancelJobHandler=內容置頂取消任務
SCHEDULED_TASK.UpdateDynamicDataJobHandler=保存內容動態數據任務
SCHEDULED_TASK.ContentOfflineJobHandler=內容定時下線任務
SCHEDULED_TASK.ResourceChunkClearJobHandler=資源上傳分片文件過期刪除任務
# 缓存监控
MONITORED.CACHE.SITE=站點
MONITORED.CACHE.CATALOG=欄目
MONITORED.CACHE.PAGE_WIDGET=頁面部件
MONITORED.CACHE.TEMPLATE=模板
# 动态模板
DYNAMIC_PAGE_TYPE.ARG.sid=站點ID
DYNAMIC_PAGE_TYPE.ARG.pp=發佈通道編碼
DYNAMIC_PAGE_TYPE.ARG.preview=是否預覽模式
# 内容静态化子目录划分规则
CONTENT_PATH_RULE.IdHash=按/內容ID分片/劃分
CONTENT_PATH_RULE.Year=按/年/劃分
CONTENT_PATH_RULE.Month=按/年/月/劃分
CONTENT_PATH_RULE.Date=按/年/月/日/劃分
CONTENT_PATH_RULE.DateStr=按/年-月-日/劃分
TIP.CMS.CORE.DELETING_MAPPING_CONTENT=正在刪除關聯映射內容:{0}[ID:{1}]
TIP.CMS.CORE.OFFLINE_CONTENT=正在下線內容:{0}
TIP.CMS.CORE.OFFLINE_SUCCESS=下線成功
TIP.CMS.CORE.OFFLINE_MAPPING_CONTENT=正在下線關聯映射內容:{0}[ID:{1}]
TIP.CMS.CORE.PUBLISHING_CONTENT=正在發佈內容:{0}
TIP.CMS.CORE.PUBLISHING_CATALOG=正在發佈欄目:{0}
TIP.CMS.CORE.PUBLISHING_SITE=正在發佈站點:{0}
TIP.CMS.CORE.PUBLISH_SUCCESS=發佈成功
TIP.CMS.CORE.COPYING_CONTENT=正在複製:{0} > {1}
TIP.CMS.CORE.COPY_CONTENT_SUCCESS=復製成功
TIP.CMS.CORE.MOVING_CONTENT=正在移動:{0} > {1}
TIP.CMS.CORE.MOVE_CONTENT_SUCCESS=移動成功
CMS.SITE.ID=站點ID
CMS.SITE.PARENT_ID=父級站點ID
CMS.SITE.NAME=站點名稱
CMS.SITE.DESC=站點描述
CMS.SITE.LOGO=站點LOGO
CMS.SITE.LOGO_SRC=LOGO訪問地址
CMS.SITE.PATH=站點路徑
CMS.SITE.RESOURCE_URL=站點資源域名
CMS.SITE.DEPT_CODE=所屬機構編碼
CMS.SITE.SEO_KEYWORDS=SEO關鍵詞
CMS.SITE.SEO_DESC=SEO描述
CMS.SITE.SEO_TITLE=SEO標題
CMS.SITE.CONFIG_PROPS=擴展配置
CMS.SITE.LINK=鏈接地址
CMS.SITE.PROP.ID=站點屬性ID
CMS.SITE.PROP.SITE_ID=所屬站點ID
CMS.SITE.PROP.NAME=屬性名稱
CMS.SITE.PROP.CODE=屬性編碼
CMS.SITE.PROP.VALUE=屬性值
CMS.CATALOG.ID=欄目ID
CMS.CATALOG.SITE_ID=站點ID
CMS.CATALOG.PARENT_ID=父級欄目ID
CMS.CATALOG.ANCESTORS=祖級欄目IDs
CMS.CATALOG.NAME=欄目名稱
CMS.CATALOG.LOGO=欄目引導圖
CMS.CATALOG.LOGO_SRC=欄目引導圖訪問路徑
CMS.CATALOG.ALIAS=欄目別名
CMS.CATALOG.DESC=欄目簡介
CMS.CATALOG.DEPT_CODE=所屬部門編碼
CMS.CATALOG.TYPE=欄目類型
CMS.CATALOG.PATH=欄目目錄
CMS.CATALOG.REDIRECT_URL=欄目跳轉地址
CMS.CATALOG.TREE_LEVEL=欄目層級
CMS.CATALOG.CHILD_COUNT=子欄目數量
CMS.CATALOG.CONTENT_COUNT=內容數量
CMS.CATALOG.SEO_KEYWORDS=SEO關鍵詞
CMS.CATALOG.SEO_DESC=SEO描述
CMS.CATALOG.SEO_TITLE=SEO標題
CMS.CATALOG.CONFIG_PROPS=擴展配置
CMS.CATALOG.LINK=欄目首頁鏈接
CMS.CATALOG.LIST_LINK=欄目列表頁鏈接(無首頁模板時等同於首頁鏈接)
CMS.CONTENT.ID=內容ID
CMS.CONTENT.SITE_ID=所屬站點ID
CMS.CONTENT.CATALOG_ID=所屬欄目ID
CMS.CONTENT.CATALOG_ANCESTORS=所屬欄目祖級IDs
CMS.CONTENT.TOP_CATALOG=所屬頂級欄目ID
CMS.CONTENT.DEPT_ID=所屬部門ID
CMS.CONTENT.DEPT_CODE=所屬部門編碼
CMS.CONTENT.TYPE=內容類型
CMS.CONTENT.TITLE=標題
CMS.CONTENT.SUB_TITLE=副標題
CMS.CONTENT.SHORT_TITLE=短標題
CMS.CONTENT.TITLE_STYLE=標題樣式
CMS.CONTENT.LOGO=封面圖
CMS.CONTENT.LOGO_SRC=封面圖訪問路徑
CMS.CONTENT.IMAGES=封面圖列表
CMS.CONTENT.SOURCE=來源
CMS.CONTENT.SOURCE_URL=來源URL
CMS.CONTENT.ORIGINAL=是否原創
CMS.CONTENT.AUTHOR=作者
CMS.CONTENT.EDITOR=編輯
CMS.CONTENT.CONTRIBUTOR_ID=投稿用戶ID
CMS.CONTENT.SUMMARY=摘要
CMS.CONTENT.ATTRS=內容屬性標識列表
CMS.CONTENT.STATUS=內容狀態
CMS.CONTENT.LINK_FLAG=是否鏈接內容
CMS.CONTENT.REDIRECT_URL=跳轉鏈接
CMS.CONTENT.TOP_FLAG=置頂標識
CMS.CONTENT.TOP_DATE=置頂結束時間
CMS.CONTENT.KEYWORDS=關鍵詞
CMS.CONTENT.TAGS=TAG詞
CMS.CONTENT.PUBLISH_DATE=發佈時間
CMS.CONTENT.SEO_TITLE=SEO標題
CMS.CONTENT.SEO_KEYWORDS=SEO關鍵詞
CMS.CONTENT.SEO_DESC=SEO描述
CMS.CONTENT.LIKE=點讚數(非實時)
CMS.CONTENT.COMMENT=評論數(非實時)
CMS.CONTENT.FAVORITE=收藏數(非實時)
CMS.CONTENT.VIEW=瀏覽量(非實時)
CMS.CONTENT.PROP1=備用字段1
CMS.CONTENT.PROP2=備用字段2
CMS.CONTENT.PROP3=備用字段3
CMS.CONTENT.PROP4=備用字段4
CMS.CONTENT.LINK=內容鏈接
CMS.PAGE_WIDGET.ID=頁面部件ID
CMS.PAGE_WIDGET.SITE_ID=所屬站點ID
CMS.PAGE_WIDGET.CATALOG_ID=所屬欄目ID
CMS.PAGE_WIDGET.CATALOG_ANCESTORS=所屬欄目祖級IDs
CMS.PAGE_WIDGET.TYPE=類型
CMS.PAGE_WIDGET.NAME=名稱
CMS.PAGE_WIDGET.CODE=編碼
CMS.PAGE_WIDGET.PUBLISH_PIPE=發佈通道
CMS.PAGE_WIDGET.CONTENT_OBJ=擴展數據
# 資源類型
CMS.CONTENTCORE.RESOURCE_TYPE.image=圖片
CMS.CONTENTCORE.RESOURCE_TYPE.audio=音頻
CMS.CONTENTCORE.RESOURCE_TYPE.video=視頻
CMS.CONTENTCORE.RESOURCE_TYPE.file=檔案
# 欄目類型
CMS.CONTENTCORE.CATALOG_TYPE.common=普通欄目
CMS.CONTENTCORE.CATALOG_TYPE.link=連結欄目
# 字典數據
DICT.CMSPageWidgetStatus=內容狀態
DICT.CMSPageWidgetStatus.0=初稿
DICT.CMSPageWidgetStatus.30=已發布
DICT.CMSPageWidgetStatus.40=已下線
DICT.CMSPageWidgetStatus.60=重新編輯
DICT.CMSContentStatus=內容狀態
DICT.CMSContentStatus.0=初稿
DICT.CMSContentStatus.20=待發布
DICT.CMSContentStatus.30=已發布
DICT.CMSContentStatus.40=已下線
DICT.CMSContentStatus.60=重新編輯
DICT.CMSContentAttribute=內容屬性
DICT.CMSContentAttribute.image=圖片
DICT.CMSContentAttribute.video=視頻
DICT.CMSContentAttribute.attach=附件
DICT.CMSContentAttribute.hot=熱點
DICT.CMSContentAttribute.recommend=推薦
DICT.CMSStaticSuffix=靜態檔案類型
DICT.CMSStaticSuffix.shtml=shtml
DICT.CMSStaticSuffix.html=html
DICT.CMSStaticSuffix.xml=xml
DICT.CMSStaticSuffix.json=json
DICT.CMSContentOpType=內容操作類型
DICT.CMSContentOpType.ADD=新建
DICT.CMSContentOpType.UPDATE=修改
DICT.CMSContentOpType.DELETE=刪除
DICT.CMSContentOpType.LOCK=鎖定
DICT.CMSContentOpType.UNLOCK=解鎖
DICT.CMSContentOpType.TO_PUBLISH=待發布
DICT.CMSContentOpType.PUBLISH=發布
DICT.CMSContentOpType.OFFLINE=下線
DICT.CMSContentOpType.SORT=排序
DICT.CMSContentOpType.TOP=置頂
DICT.CMSContentOpType.CANCEL_TOP=取消置頂
# 固定配置參數
CONFIG.CMSBackendContext=後台訪問地址
CONFIG.CMSModuleEnable=是否開啟CMS功能
CONFIG.CMSTemplateSuffix=模板尾碼名
CONFIG.AllowUploadFileType=CMS檔案上傳類型限制
CONFIG.SiteApiUrl=站點API地址
CONFIG.ResourceUploadAcceptSize=素材庫資源上傳大小限制
# 錯誤資訊
ERRCODE.CMS.CONTENTCORE.NO_SITE=無站點數據,請先去站點管理菜單建立站點。
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_PAGE_WIDGET_TYPE=未知頁面部件類型:{0}
ERRCODE.CMS.CONTENTCORE.TEMPLATE_EMPTY=模板未配置或模板檔案不存在
ERRCODE.CMS.CONTENTCORE.TEMPLATE_FILE_NOT_FOUND=模板檔案“{0}”不存在
ERRCODE.CMS.CONTENTCORE.INVALID_TEMPLATE_NAME=模板檔案名只能使用大小寫字母和下劃線,且必須以`{0}`結尾
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_CONTENT_TYPE=未知內容類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_CATALOG_TYPE=未知欄目類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_INTERNAL_DATA_TYPE=未知內部數據類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_TYPE=未知資源類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_DYNAMIC_PAGE_TYPE=未知動態模板類型:{0}
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_CONTENT_PATH_RULE=未知的內容詳情頁路徑規則:{0}
ERRCODE.CMS.CONTENTCORE.DEL_CHILD_FIRST=請先刪除子欄目
ERRCODE.CMS.CONTENTCORE.CONFLICT_CATALOG=欄目名稱/別名/目錄重複
ERRCODE.CMS.CONTENTCORE.CATALOG_MAX_TREE_LEVEL=欄目層級超出上限
ERRCODE.CMS.CONTENTCORE.CATALOG_MOVE_TO_SELF_OR_CHILD=欄目不能轉移到自身或子欄目
ERRCODE.CMS.CONTENTCORE.INVALID_PROPERTY=擴展屬性[{0}={1}]校驗失敗
ERRCODE.CMS.CONTENTCORE.EXISTS_SITE_PATH=站點目錄衝突
ERRCODE.CMS.CONTENTCORE.CONTENT_LOCKED=內容已被"{0}"鎖定
ERRCODE.CMS.CONTENTCORE.TITLE_REPLEAT=標題重複
ERRCODE.CMS.CONTENTCORE.CANNOT_EDIT_PUBLISHED_CONTENT=已發布內容不允許編輯,請先下線內容!
ERRCODE.CMS.CONTENTCORE.NO_PUBLISHPIPE=無可用發布通道
ERRCODE.CMS.CONTENTCORE.CATALOG_CANNOT_PUBLISH=欄目發布失敗:欄目不可見/不可靜態化/標題欄目。
ERRCODE.CMS.CONTENTCORE.SITE_FILE_OP_ERR=不能操作非當前站點檔案
ERRCODE.CMS.CONTENTCORE.TEMPLATE_PATH_EXISTS=模板檔案已存在或路徑被佔用
ERRCODE.CMS.CONTENTCORE.NOT_ALLOW_FILE_TYPE=禁止上傳的檔案類型:{0}
ERRCODE.CMS.CONTENTCORE.NOT_EDITABLE_FILE=指定檔案不支援線上編輯
ERRCODE.CMS.CONTENTCORE.FILE_NOT_EXIST=檔案不存在
ERRCODE.CMS.CONTENTCORE.FILE_ALREADY_EXISTS=檔案已存在
ERRCODE.CMS.CONTENTCORE.CATALOG_SORT_VALUE_ZERO=欄目排序值不能為0
ERRCODE.CMS.CONTENTCORE.SITE_EXPORT_TASK_EXISTS=站點導出任務正在進行中
ERRCODE.CMS.CONTENTCORE.DEL_CONTENT_ERR=只能刪除初稿和已下線內容
ERRCODE.CMS.CONTENTCORE.RESOURCE_ACCEPT_SIZE_LIMIT=上傳檔案大小超過限制
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_STORAGE=資源存儲方式與當前站點配置不一致
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_IS_EMPTY=被合并欄目不存在
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_NOT_LEAF=被合并欄目不能包含子欄目
ERRCODE.CMS.CONTENTCORE.DENY_LINK_TO_LINK_INTERNAL_DATA=不能連結到連結內容或欄目
# freemarker模板標籤
FREEMARKER.TAG.cms_site.NAME=站點列表標籤
FREEMARKER.TAG.cms_site.DESC=獲取站點數據列表,內嵌`<#list DataList as site>${catalog.site}</#list>`遍曆數據
FREEMARKER.TAG.cms_site.id=站點ID
FREEMARKER.TAG.cms_site.level=數據獲取範圍,值為`Root`時忽略屬性`id`
FREEMARKER.TAG.cms_site.level.Root=所有站點
FREEMARKER.TAG.cms_site.level.Current=同級站點
FREEMARKER.TAG.cms_site.level.Child=子站點
FREEMARKER.TAG.cms_catalog.NAME=欄目列表標籤
FREEMARKER.TAG.cms_catalog.DESC=獲取欄目數據列表,內嵌`<#list DataList as catalog>${catalog.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_catalog.id=欄目ID
FREEMARKER.TAG.cms_catalog.alias=欄目別名
FREEMARKER.TAG.cms_catalog.level=數據獲取範圍,值為`Root`時忽略屬性`id`、`alias`
FREEMARKER.TAG.cms_catalog.level.Root=所有欄目
FREEMARKER.TAG.cms_catalog.level.Current=同級欄目
FREEMARKER.TAG.cms_catalog.level.Child=子欄目
FREEMARKER.TAG.cms_catalog.level.CurrentAndChild=當前欄目及子欄目
FREEMARKER.TAG.cms_catalog.level.Self=當前欄目
FREEMARKER.TAG.cms_content.NAME=內容列表標籤
FREEMARKER.TAG.cms_content.DESC=獲取內容數據列表,內嵌`<#list DataList as content>${content.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_content.catalogId=欄目ID
FREEMARKER.TAG.cms_content.catalogAlias=欄目別名
FREEMARKER.TAG.cms_content.level=數據獲取範圍,值為`Root`時忽略屬性`catalogid`、`catalogalias`
FREEMARKER.TAG.cms_content.level.Root=所有欄目
FREEMARKER.TAG.cms_content.level.Current=當前欄目
FREEMARKER.TAG.cms_content.level.Child=子欄目
FREEMARKER.TAG.cms_content.level.CurrentAndChild=當前欄目及子欄目
FREEMARKER.TAG.cms_content.sort=排序方式
FREEMARKER.TAG.cms_content.sort.Recent=發布時間降序
FREEMARKER.TAG.cms_content.sort.Views=瀏覽量降序
FREEMARKER.TAG.cms_content.sort.Default=排序欄位降序(預設)
FREEMARKER.TAG.cms_content.hasattribute=包含內容屬性,多個屬性英文逗號分隔
FREEMARKER.TAG.cms_content.noattribute=不包含內容屬性,多個屬性英文逗號分隔
FREEMARKER.TAG.cms_content.status=狀態,'-1'表示不限制狀態
FREEMARKER.TAG.cms_content.status.defaultValue=30: 已發布
FREEMARKER.TAG.cms_content.topflag=是否允許置頂
FREEMARKER.TAG.cms_site_property.NAME=站點自定義屬性標籤
FREEMARKER.TAG.cms_site_property.DESC=獲取站點自定義屬性數據列表,內嵌`<#list DataList as prop>${prop.propName}</#list>`遍曆數據
FREEMARKER.TAG.cms_site_property.siteid=站點ID預設從模板變數中獲取`${Site.siteId}`
FREEMARKER.TAG.cms_site_property.code=屬性編碼
FREEMARKER.TAG.cms_include.NAME=模板引用標籤
FREEMARKER.TAG.cms_include.DESC=引用其他模板內容支援ssi引用標籤
FREEMARKER.TAG.cms_include.file=引用模板檔案路徑相對模板目錄template/
FREEMARKER.TAG.cms_include.ssi=是否啟用SSI
FREEMARKER.TAG.cms_include.virtual=是否啟用virtual模式此模式下區塊無法繼承當前頁面上限文變數需要通過參數傳入需要的變數
FREEMARKER.TAG.cms_include.cache=是否啟用緩存
FREEMARKER.TAG.cms_pagewidget.NAME=頁面部件引用標籤
FREEMARKER.TAG.cms_pagewidget.DESC=引用頁面部件內容支援ssi引用標籤
FREEMARKER.TAG.cms_pagewidget.code=頁面部件編碼
FREEMARKER.TAG.cms_pagewidget.ssi=是否啟用SSI
FREEMARKER.TAG.cms_pagewidget_data.NAME=頁面部件數據標籤
FREEMARKER.TAG.cms_pagewidget_data.DESC=頁面部件數據標籤,標籤體內可使用`${Data.name}`獲取數據
FREEMARKER.TAG.cms_pagewidget_data.code=頁面部件編碼
FREEMARKER.TAG.cms_content_closest.NAME=內容前後篇標籤
FREEMARKER.TAG.cms_content_closest.DESC=內容前後篇標籤,僅支援指定內容當前欄目列表,標籤體內可使用`${Data.title}`獲取數據
FREEMARKER.TAG.cms_content_closest.contentId=內容ID
FREEMARKER.TAG.cms_content_closest.type=類型
FREEMARKER.TAG.cms_content_closest.type.Prev=上一篇
FREEMARKER.TAG.cms_content_closest.type.Next=下一篇
FREEMARKER.TAG.cms_content_rela.NAME=相關內容標籤
FREEMARKER.TAG.cms_content_rela.DESC=相關內容標籤,內嵌`<#list DataList as content>${content.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_content_rela.contentId=內容ID
# freemarker模板函數
FREEMARKER.FUNC.htmlInternalUrl.DESC=將html文本中的內部連結地址“iurl://”解析為正常http(s)訪問地址,例如:`${htmlInternalUrl(ArticleContent)}`
FREEMARKER.FUNC.htmlInternalUrl.Arg1.Name=Html文本內容
FREEMARKER.FUNC.imageSize.DESC=獲得圖片縮放圖函數,不在的縮略圖會自動建立,例如:`${imageSize(content.logo, 300, 300)}`
FREEMARKER.FUNC.imageSize.Arg1.Name=圖片資源內部路徑iurl://
FREEMARKER.FUNC.imageSize.Arg1.Desc=僅支援處理內部資源圖片iurl://
FREEMARKER.FUNC.imageSize.Arg2.Name=寬度
FREEMARKER.FUNC.imageSize.Arg3.Name=高度
FREEMARKER.FUNC.internalUrl.DESC=將內部連結“iurl://”解析為正常http(s)訪問地址,例如:`${internalUrl(content.redirectUrl)}`
FREEMARKER.FUNC.internalUrl.Arg1.Name=內部連結iurl://
FREEMARKER.FUNC.siteUrl.DESC=獲取站點指定發布通道訪問連結,例如:`${siteUrl(Site.siteId, 'h5')}`
FREEMARKER.FUNC.siteUrl.Arg1.Name=站點ID
FREEMARKER.FUNC.siteUrl.Arg2.Name=發布通道編碼
FREEMARKER.FUNC.catalogUrl.DESC=獲取欄目指定發布通道訪問連結,例如:`${catalogUrl(Catalog.catalogId, 'h5')}`
FREEMARKER.FUNC.catalogUrl.Arg1.Name=欄目ID
FREEMARKER.FUNC.catalogUrl.Arg2.Name=發布通道編碼
FREEMARKER.FUNC.contentUrl.DESC=獲取內容指定發布通道訪問連結,例如:`${contentUrl(Content.contentId, 'h5')}`
FREEMARKER.FUNC.contentUrl.Arg1.Name=內容ID
FREEMARKER.FUNC.contentUrl.Arg2.Name=發布通道編碼
FREEMARKER.FUNC.dynamicPageLink.DESC=動態頁面連結獲取函數,例如:`${dynamicPageLink('Search')}`
FREEMARKER.FUNC.dynamicPageLink.Arg1.Name=動態頁面類型
FREEMARKER.FUNC.dynamicPageLink.Arg2.Name=忽略`sid/pp`參數
FREEMARKER.FUNC.dict.DESC=獲取字典數據列表,例如:`${dict('YesOrNo', 'Y')}`
FREEMARKER.FUNC.dict.Arg1.Name=字典類型
FREEMARKER.FUNC.dict.Arg2.Name=字典數據值
FREEMARKER.FUNC.dict.Arg3.Name=國際化標識
FREEMARKER.FUNC.sysConfig.DESC=獲取系統參數配置值,例如:`${sysConfig('SiteApiUrl')}`
FREEMARKER.FUNC.sysConfig.Arg1.Name=系統參數鍵名
FREEMARKER.FUNC.listHtmlInternalUrl.DESC=獲取html文本中的iurl列表例如`${listHtmlInternalUrl(ArticleContent)}`
FREEMARKER.FUNC.listHtmlInternalUrl.Arg1.Name=Html文本內容
FREEMARKER.FUNC.contentPageLink.DESC=獲取內容分頁連結,例如:`${contentPageLink(content.link, 2)}`
FREEMARKER.FUNC.contentPageLink.Arg1.Name=內容連結
FREEMARKER.FUNC.contentPageLink.Arg2.Name=頁碼
FREEMARKER.FUNC.videoPlayer.DESC=將html文本中的視頻資源連結替換為`<video>`視頻播放器
FREEMARKER.FUNC.videoPlayer.Arg1.Name=Html文本內容
FREEMARKER.FUNC.videoPlayer.Arg2.Name=視頻寬度
FREEMARKER.FUNC.videoPlayer.Arg3.Name=視頻高度
FREEMARKER.FUNC.fileExtractor.DESC=提取html文本中的檔案資源連結
FREEMARKER.FUNC.fileExtractor.Arg1.Name=Html文本內容
FREEMARKER.FUNC.fileExtractor.Arg2.Name=檔案類型/尾碼
FREEMARKER.ERR.CatalogNotFound=欄目數據不存在:[欄目ID: {0}], [欄目別名: {1}]
# 校驗規則
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=發布通道配置不能為空
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=屬性編碼只能使用大小寫字母、數字和下劃線
VALIDATOR.CMS.FILE_NAME.REGEXP_ERR=檔案名不能包含斜杠或反斜杠
# 定時任務
SCHEDULED_TASK.RecycleExpireJobHandler=資源回收筒過期內容刪除任務
SCHEDULED_TASK.SitePublishJobHandler=內容定時發布任務
SCHEDULED_TASK.ContentTopCancelJobHandler=內容置頂取消任務
SCHEDULED_TASK.UpdateDynamicDataJobHandler=保存內容動態數據任務
SCHEDULED_TASK.ContentOfflineJobHandler=內容定時下線任務
SCHEDULED_TASK.ResourceChunkClearJobHandler=資源上傳分片檔案過期刪除任務
# 緩存監控
MONITORED.CACHE.SITE=站點
MONITORED.CACHE.CATALOG=欄目
MONITORED.CACHE.PAGE_WIDGET=頁面部件
MONITORED.CACHE.TEMPLATE=模板
# 動態模板
DYNAMIC_PAGE_TYPE.ARG.sid=站點ID
DYNAMIC_PAGE_TYPE.ARG.pp=發布通道編碼
DYNAMIC_PAGE_TYPE.ARG.preview=是否預覽模式
# 內容靜態化子目錄劃分規則
CONTENT_PATH_RULE.IdHash=按/內容ID分片/劃分
CONTENT_PATH_RULE.Year=按/年/劃分
CONTENT_PATH_RULE.Month=按/年/月/劃分
CONTENT_PATH_RULE.Date=按/年/月/日/劃分
CONTENT_PATH_RULE.DateStr=按/年-月-日/劃分
TIP.CMS.CORE.DELETING_MAPPING_CONTENT=正在刪除關聯映射內容:{0}[ID:{1}]
TIP.CMS.CORE.OFFLINE_CONTENT=正在下線內容:{0}
TIP.CMS.CORE.OFFLINE_SUCCESS=下線成功
TIP.CMS.CORE.OFFLINE_MAPPING_CONTENT=正在下線關聯映射內容:{0}[ID:{1}]
TIP.CMS.CORE.PUBLISHING_CONTENT=正在發布內容:{0}
TIP.CMS.CORE.PUBLISHING_CATALOG=正在發布欄目:{0}
TIP.CMS.CORE.PUBLISHING_SITE=正在發布站點:{0}
TIP.CMS.CORE.PUBLISH_SUCCESS=發布成功
TIP.CMS.CORE.COPYING_CONTENT=正在複製:{0} > {1}
TIP.CMS.CORE.COPY_CONTENT_SUCCESS=複製成功
TIP.CMS.CORE.MOVING_CONTENT=正在移動:{0} > {1}
TIP.CMS.CORE.MOVE_CONTENT_SUCCESS=移動成功
CMS.SITE.ID=站點ID
CMS.SITE.PARENT_ID=父級站點ID
CMS.SITE.NAME=站點名稱
CMS.SITE.DESC=站點描述
CMS.SITE.LOGO=站點LOGO
CMS.SITE.LOGO_SRC=LOGO訪問地址
CMS.SITE.PATH=站點路徑
CMS.SITE.RESOURCE_URL=站點資源功能變數名稱
CMS.SITE.DEPT_CODE=所屬機構編碼
CMS.SITE.SEO_KEYWORDS=SEO關鍵詞
CMS.SITE.SEO_DESC=SEO描述
CMS.SITE.SEO_TITLE=SEO標題
CMS.SITE.CONFIG_PROPS=擴展配置
CMS.SITE.LINK=連結地址
CMS.SITE.PROP.ID=站點屬性ID
CMS.SITE.PROP.SITE_ID=所屬站點ID
CMS.SITE.PROP.NAME=屬性名稱
CMS.SITE.PROP.CODE=屬性編碼
CMS.SITE.PROP.VALUE=屬性值
CMS.CATALOG.ID=欄目ID
CMS.CATALOG.SITE_ID=站點ID
CMS.CATALOG.PARENT_ID=父級欄目ID
CMS.CATALOG.ANCESTORS=祖級欄目IDs
CMS.CATALOG.NAME=欄目名稱
CMS.CATALOG.LOGO=欄目引導圖
CMS.CATALOG.LOGO_SRC=欄目引導圖訪問路徑
CMS.CATALOG.ALIAS=欄目別名
CMS.CATALOG.DESC=欄目簡介
CMS.CATALOG.DEPT_CODE=所屬部門編碼
CMS.CATALOG.TYPE=欄目類型
CMS.CATALOG.PATH=欄目目錄
CMS.CATALOG.REDIRECT_URL=標題欄目跳轉地址
CMS.CATALOG.TREE_LEVEL=欄目層級
CMS.CATALOG.CHILD_COUNT=子欄目數
CMS.CATALOG.CONTENT_COUNT=內容數量
CMS.CATALOG.SEO_KEYWORDS=SEO關鍵詞
CMS.CATALOG.SEO_DESC=SEO描述
CMS.CATALOG.SEO_TITLE=SEO標題
CMS.CATALOG.CONFIG_PROPS=擴展配置
CMS.CATALOG.LINK=欄目首頁連結
CMS.CATALOG.LIST_LINK=欄目列表頁連結(無首頁模板時與首頁連結一致)
CMS.CONTENT.ID=內容ID
CMS.CONTENT.SITE_ID=所屬站點ID
CMS.CONTENT.CATALOG_ID=所屬欄目ID
CMS.CONTENT.CATALOG_ANCESTORS=所屬欄目祖級IDs
CMS.CONTENT.TOP_CATALOG=所屬頂級欄目ID
CMS.CONTENT.DEPT_ID=所屬部門ID
CMS.CONTENT.DEPT_CODE=所屬部門編碼
CMS.CONTENT.TYPE=內容類型
CMS.CONTENT.TITLE=標題
CMS.CONTENT.SUB_TITLE=副標題
CMS.CONTENT.SHORT_TITLE=短標題
CMS.CONTENT.TITLE_STYLE=標題樣式
CMS.CONTENT.LOGO=封面圖
CMS.CONTENT.LOGO_SRC=封面圖訪問路徑
CMS.CONTENT.IMAGES=封面圖列表
CMS.CONTENT.SOURCE=來源
CMS.CONTENT.SOURCE_URL=來源URL
CMS.CONTENT.ORIGINAL=是否原創
CMS.CONTENT.AUTHOR=作者
CMS.CONTENT.EDITOR=編輯
CMS.CONTENT.CONTRIBUTOR_ID=投稿用戶ID
CMS.CONTENT.SUMMARY=摘要
CMS.CONTENT.ATTRS=內容屬性標識列表
CMS.CONTENT.STATUS=內容狀態
CMS.CONTENT.LINK_FLAG=是否連結內容
CMS.CONTENT.REDIRECT_URL=跳轉連結
CMS.CONTENT.TOP_FLAG=置頂標識
CMS.CONTENT.TOP_DATE=置頂結束時間
CMS.CONTENT.KEYWORDS=關鍵詞
CMS.CONTENT.TAGS=TAGs
CMS.CONTENT.PUBLISH_DATE=發布時間
CMS.CONTENT.SEO_TITLE=SEO標題
CMS.CONTENT.SEO_KEYWORDS=SEO關鍵詞
CMS.CONTENT.SEO_DESC=SEO描述
CMS.CONTENT.LIKE=點贊數(非即時)
CMS.CONTENT.COMMENT=評論數(非即時)
CMS.CONTENT.FAVORITE=收藏數(非即時)
CMS.CONTENT.VIEW=文章瀏覽數(非即時)
CMS.CONTENT.PROP1=備用欄位1
CMS.CONTENT.PROP2=備用欄位2
CMS.CONTENT.PROP3=備用欄位3
CMS.CONTENT.PROP4=備用欄位4
CMS.CONTENT.LINK=內容連結
CMS.PAGE_WIDGET.ID=頁面部件ID
CMS.PAGE_WIDGET.SITE_ID=所屬站點ID
CMS.PAGE_WIDGET.CATALOG_ID=所屬欄目ID
CMS.PAGE_WIDGET.CATALOG_ANCESTORS=所屬欄目祖級IDs
CMS.PAGE_WIDGET.TYPE=類型
CMS.PAGE_WIDGET.NAME=名稱
CMS.PAGE_WIDGET.CODE=編碼
CMS.PAGE_WIDGET.PUBLISH_PIPE=發布通道
CMS.PAGE_WIDGET.CONTENT_OBJ=擴展數據

View File

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

View File

@ -177,7 +177,7 @@ public class CustomFormCoreDataHandler implements ICoreDataHandler {
for (CmsCustomFormData data : list) {
String oldKey = data.getModelId() + "-" + data.getDataId();
try {
data.setDataId(IdUtils.getSnowflakeId());
data.setDataId(IdUtils.getSnowflakeId()+"");
data.setModelId(customFormIdMapping.get(data.getModelId()));
// 处理图片和富文本内部链接
MetaModel model = modelService.getMetaModel(data.getModelId());

View File

@ -31,8 +31,8 @@ import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
import com.chestnut.contentcore.service.IPublishPipeService;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.customform.domain.CmsCustomForm;
import com.chestnut.customform.domain.dto.CustomFormAddDTO;
import com.chestnut.customform.domain.dto.CustomFormEditDTO;
import com.chestnut.customform.domain.dto.CreateCustomFormRequest;
import com.chestnut.customform.domain.dto.UpdateCustomFormRequest;
import com.chestnut.customform.domain.vo.CustomFormVO;
import com.chestnut.customform.permission.CustomFormPriv;
import com.chestnut.customform.rule.ICustomFormLimitRule;
@ -109,27 +109,25 @@ public class CustomFormController extends BaseRestController {
@Log(title = "新增自定义表单", businessType = BusinessType.INSERT)
@Priv(type = AdminUserType.TYPE, value = CustomFormPriv.Add)
@PostMapping
public R<?> add(@RequestBody @Validated CustomFormAddDTO dto) {
public R<?> add(@RequestBody @Validated CreateCustomFormRequest req) {
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
dto.setOperator(StpAdminUtil.getLoginUser());
dto.setSiteId(site.getSiteId());
this.customFormService.addCustomForm(dto);
req.setSiteId(site.getSiteId());
this.customFormService.addCustomForm(req);
return R.ok();
}
@Log(title = "编辑自定义表单", businessType = BusinessType.UPDATE)
@Priv(type = AdminUserType.TYPE, value = {CustomFormPriv.Add, CustomFormPriv.Edit})
@PutMapping
public R<?> edit(@RequestBody @Validated CustomFormEditDTO dto) {
dto.setOperator(StpAdminUtil.getLoginUser());
this.customFormService.editCustomForm(dto);
public R<?> edit(@RequestBody @Validated UpdateCustomFormRequest req) {
this.customFormService.editCustomForm(req);
return R.ok();
}
@Log(title = "删除自定义表单", businessType = BusinessType.DELETE)
@Priv(type = AdminUserType.TYPE, value = CustomFormPriv.Delete)
@PostMapping("/delete")
public R<?> remove(@RequestBody @Validated @NotEmpty List<Long> formIds) {
public R<?> remove(@RequestBody @NotEmpty List<Long> formIds) {
this.customFormService.deleteCustomForm(formIds);
return R.ok();
}
@ -137,7 +135,7 @@ public class CustomFormController extends BaseRestController {
@Log(title = "发布自定义表单", businessType = BusinessType.UPDATE)
@Priv(type = AdminUserType.TYPE, value = { CustomFormPriv.Add, CustomFormPriv.Edit })
@PutMapping("/publish")
public R<?> publish(@RequestBody @Validated @NotEmpty List<Long> formIds) {
public R<?> publish(@RequestBody @NotEmpty List<Long> formIds) {
this.customFormService.publishCustomForms(formIds, StpAdminUtil.getLoginUser().getUsername());
return R.ok();
}
@ -145,7 +143,7 @@ public class CustomFormController extends BaseRestController {
@Log(title = " 下线自定义表单", businessType = BusinessType.UPDATE)
@Priv(type = AdminUserType.TYPE, value = { CustomFormPriv.Add, CustomFormPriv.Edit })
@PutMapping("/offline")
public R<?> offline(@RequestBody @Validated @NotEmpty List<Long> formIds) throws IOException {
public R<?> offline(@RequestBody @NotEmpty List<Long> formIds) throws IOException {
this.customFormService.offlineCustomForms(formIds, StpAdminUtil.getLoginUser().getUsername());
return R.ok();
}

View File

@ -44,7 +44,7 @@ public class CmsCustomFormData extends BaseModelData implements Serializable {
public static final String TABLE_NAME = "cms_cfd_default";
@TableId(value = "data_id", type = IdType.INPUT)
private Long dataId;
private String dataId;
/**
* 关联表单ID元数据模型ID
@ -74,7 +74,7 @@ public class CmsCustomFormData extends BaseModelData implements Serializable {
@Override
public void setFieldValue(String fieldName, Object fieldValue) {
switch(fieldName) {
case "data_id" -> this.setDataId(ConvertUtils.toLong(fieldValue));
case "data_id" -> this.setDataId(ConvertUtils.toStr(fieldValue));
case "model_id" -> this.setModelId(ConvertUtils.toLong(fieldValue));
case "site_id" -> this.setSiteId(ConvertUtils.toLong(fieldValue));
case "client_ip" -> this.setClientIp(ConvertUtils.toStr(fieldValue));

View File

@ -19,9 +19,9 @@ import com.chestnut.common.security.domain.BaseDTO;
import com.chestnut.system.fixed.dict.YesOrNo;
import com.chestnut.system.validator.Dict;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
/**
* 自定义表单添加DTO
@ -31,7 +31,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CustomFormAddDTO extends BaseDTO {
public class CreateCustomFormRequest extends BaseDTO {
/**
* 站点ID
@ -42,29 +42,34 @@ public class CustomFormAddDTO extends BaseDTO {
* 名称
*/
@NotBlank
@Length(max = 100)
private String name;
/**
* 编码
*/
@NotBlank
@Length(max = 50)
private String code;
/**
* 模型数据表
*/
@NotBlank
@Length(max = 100)
private String tableName;
/**
* 是否需要验证码
*/
@NotBlank
@Dict(YesOrNo.TYPE)
private String needCaptcha;
/**
* 是否需要会员登录
*/
@NotBlank
@Dict(YesOrNo.TYPE)
private String needLogin;
@ -72,10 +77,12 @@ public class CustomFormAddDTO extends BaseDTO {
* 提交用户唯一性限制无限制IP浏览器指纹
*/
@NotBlank
@Length(max = 20)
private String ruleLimit;
/**
* 备注
*/
@Length(max = 500)
private String remark;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2022-2025 兮玥(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.domain.dto;
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
import com.chestnut.system.validator.LongId;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* 自定义表单编辑DTO
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class UpdateCustomFormRequest extends CreateCustomFormRequest {
@LongId
private Long formId;
/**
* 模板配置
*/
private List<PublishPipeTemplate> templates;
}

View File

@ -17,11 +17,9 @@ package com.chestnut.customform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.chestnut.customform.domain.CmsCustomForm;
import com.chestnut.customform.domain.dto.CustomFormAddDTO;
import com.chestnut.customform.domain.dto.CustomFormEditDTO;
import com.chestnut.customform.domain.dto.CreateCustomFormRequest;
import com.chestnut.customform.domain.dto.UpdateCustomFormRequest;
import com.chestnut.customform.rule.ICustomFormLimitRule;
import com.chestnut.xmodel.domain.XModel;
import com.chestnut.xmodel.dto.XModelDTO;
import java.io.IOException;
import java.util.List;
@ -36,7 +34,7 @@ public interface ICustomFormService extends IService<CmsCustomForm> {
* @param dto
* @return
*/
void addCustomForm(CustomFormAddDTO dto);
void addCustomForm(CreateCustomFormRequest dto);
/**
* 编辑自定义表单
@ -44,7 +42,7 @@ public interface ICustomFormService extends IService<CmsCustomForm> {
* @param dto
* @return
*/
void editCustomForm(CustomFormEditDTO dto);
void editCustomForm(UpdateCustomFormRequest dto);
/**
* 删除自定义表单

View File

@ -36,8 +36,8 @@ import com.chestnut.contentcore.util.TemplateUtils;
import com.chestnut.customform.CmsCustomFormMetaModelType;
import com.chestnut.customform.CustomFormConsts;
import com.chestnut.customform.domain.CmsCustomForm;
import com.chestnut.customform.domain.dto.CustomFormAddDTO;
import com.chestnut.customform.domain.dto.CustomFormEditDTO;
import com.chestnut.customform.domain.dto.CreateCustomFormRequest;
import com.chestnut.customform.domain.dto.UpdateCustomFormRequest;
import com.chestnut.customform.fixed.dict.CustomFormStatus;
import com.chestnut.customform.mapper.CustomFormMapper;
import com.chestnut.customform.publishpipe.PublishPipeProp_CustomFormTemplate;
@ -84,7 +84,7 @@ public class CustomFormServiceImpl extends ServiceImpl<CustomFormMapper, CmsCust
@Override
@Transactional(rollbackFor = Exception.class)
public void addCustomForm(CustomFormAddDTO dto) {
public void addCustomForm(CreateCustomFormRequest dto) {
CmsCustomForm customForm = new CmsCustomForm();
customForm.setFormId(IdUtils.getSnowflakeId());
customForm.setSiteId(dto.getSiteId());
@ -111,7 +111,7 @@ public class CustomFormServiceImpl extends ServiceImpl<CustomFormMapper, CmsCust
@Override
@Transactional(rollbackFor = Exception.class)
public void editCustomForm(CustomFormEditDTO dto) {
public void editCustomForm(UpdateCustomFormRequest dto) {
CmsCustomForm form = this.getById(dto.getFormId());
Assert.notNull(form, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("formId", dto.getFormId()));

View File

@ -114,19 +114,19 @@ public class CmsCustomFormTag extends AbstractTag {
String siteRoot = SiteUtils.getSiteRoot(site, context.getPublishPipeCode());
String staticFileName = form.getCode() + "." + site.getStaticSuffix(context.getPublishPipeCode());
String staticFilePath = CustomFormConsts.STATICIZE_DIRECTORY + staticFileName;
if (ssi) {
// 读取自定义表单静态化内容
String staticContent = templateService.getTemplateStaticContentCache(templateKey);
if (Objects.isNull(staticContent) || !new File(siteRoot + staticFilePath).exists()) {
staticContent = this.processTemplate(env, form, site, context.getPublishPipeCode(), templateKey);
String staticContent = templateService.getTemplateStaticContentCache(templateKey);
if (Objects.isNull(staticContent) || !new File(siteRoot + staticFilePath).exists()) {
staticContent = this.processTemplate(env, form, site, context.getPublishPipeCode(), templateKey);
this.templateService.setTemplateStaticContentCache(templateKey, staticContent);
if (ssi) {
FileUtils.writeStringToFile(new File(siteRoot + staticFilePath), staticContent, StandardCharsets.UTF_8);
this.templateService.setTemplateStaticContentCache(templateKey, staticContent);
}
String prefix = SiteUtils.getPublishPipePrefix(site, context.getPublishPipeCode(), context.isPreview());
}
if (ssi) {
String prefix = CmsIncludeTag.getIncludePathPrefix(context.getPublishPipeCode(), site);
env.getOut().write(StringUtils.messageFormat(CmsIncludeTag.SSI_INCLUDE_TAG, prefix + staticFilePath));
} else {
// 非ssi模式无法使用缓存
String staticContent = this.processTemplate(env, form, site, context.getPublishPipeCode(), templateKey);
env.getOut().write(staticContent);
}
}

View File

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

View File

@ -16,6 +16,7 @@
package com.chestnut.cms.dynamic.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.cms.dynamic.domain.CmsDynamicPage;
import com.chestnut.cms.dynamic.service.IDynamicPageService;
import com.chestnut.common.async.AsyncTaskManager;
@ -26,6 +27,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
@ -40,11 +43,21 @@ public class DynamicListener {
try {
long total = this.dynamicPageService
.count(new LambdaQueryWrapper<CmsDynamicPage>().eq(CmsDynamicPage::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total),
"正在删除自定义动态模板页面数据:" + (i * pageSize) + "/" + total);
this.dynamicPageService.remove(new LambdaQueryWrapper<CmsDynamicPage>().eq(CmsDynamicPage::getSiteId, site.getSiteId())
.last("limit " + pageSize));
Page<CmsDynamicPage> pages = this.dynamicPageService.lambdaQuery()
.select(CmsDynamicPage::getPageId)
.eq(CmsDynamicPage::getSiteId, site.getSiteId())
.gt(CmsDynamicPage::getPageId, lastId)
.orderByAsc(CmsDynamicPage::getPageId)
.page(Page.of(1, pageSize, false));
if (!pages.getRecords().isEmpty()) {
List<Long> pageIds = pages.getRecords().stream().map(CmsDynamicPage::getPageId).toList();
this.dynamicPageService.removeBatchByIds(pageIds);
lastId = pageIds.get(pageIds.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除自定义动态模板页面数据错误:" + e.getMessage());

View File

@ -1,9 +1,9 @@
DYNAMIC_PAGE_INIT_DATA.Member=登錄會員
DYNAMIC_PAGE_INIT_DATA.Pagination=分頁參數
FREEMARKER.FUNC.customDynamicPageLink.DESC=獲取自定義動態模板頁面鏈接,例如:`${customDynamicPageLink('Test','a=1&b=2',true)}`
FREEMARKER.FUNC.customDynamicPageLink.DESC=獲取自定義動態模板頁面連結,例如:`${customDynamicPageLink('Test','a=1&b=2',true)}`
FREEMARKER.FUNC.customDynamicPageLink.Arg1.Name=自定義動態模板編碼
FREEMARKER.FUNC.customDynamicPageLink.Arg2.Name=鏈接參數
FREEMARKER.FUNC.customDynamicPageLink.Arg2.Name=連結參數
FREEMARKER.FUNC.customDynamicPageLink.Arg3.Name=忽略`sid/pp`參數
MONITORED.CACHE.DYNAMIC_PAGE=動態模板頁

View File

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

View File

@ -168,14 +168,14 @@ public class EXModelCoreDataHandler implements ICoreDataHandler {
try {
Long dataId = switch (data.getDataType()) {
case ExtendModelDataType.SITE -> context.getSite().getSiteId();
case ExtendModelDataType.CATALOG -> context.getCatalogIdMap().get(data.getDataId());
case ExtendModelDataType.CONTENT -> context.getContentIdMap().get(data.getDataId());
case ExtendModelDataType.CATALOG -> context.getCatalogIdMap().get(ConvertUtils.toLong(data.getDataId()));
case ExtendModelDataType.CONTENT -> context.getContentIdMap().get(ConvertUtils.toLong(data.getDataId()));
default -> null;
};
if (Objects.isNull(dataId)) {
throw new RuntimeException("Old `data_id` linked data is missing.");
}
data.setDataId(dataId);
data.setDataId(dataId.toString());
data.setModelId(modelIdMapping.get(data.getModelId()));
// 处理图片和富文本内部链接
MetaModel model = modelService.getMetaModel(data.getModelId());

View File

@ -32,10 +32,10 @@ import com.chestnut.exmodel.CmsExtendMetaModelType;
import com.chestnut.exmodel.permission.EXModelPriv;
import com.chestnut.exmodel.service.ExModelService;
import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil;
import com.chestnut.system.validator.LongId;
import com.chestnut.xmodel.domain.XModel;
import com.chestnut.xmodel.dto.XModelDTO;
import com.chestnut.xmodel.dto.CreateXModelRequest;
import com.chestnut.xmodel.dto.UpdateXModelRequest;
import com.chestnut.xmodel.dto.XModelFieldDataDTO;
import com.chestnut.xmodel.service.IModelService;
import jakarta.validation.constraints.NotEmpty;
@ -95,29 +95,26 @@ public class EXModelController extends BaseRestController {
@Log(title = "新增扩展模型", businessType = BusinessType.INSERT)
@Priv(type = AdminUserType.TYPE, value = EXModelPriv.Add)
@PostMapping
public R<?> add(@RequestBody @Validated XModelDTO dto) {
public R<?> add(@RequestBody @Validated CreateXModelRequest req) {
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
dto.setOwnerType(CmsExtendMetaModelType.TYPE);
dto.setOwnerId(site.getSiteId().toString());
dto.setOperator(StpAdminUtil.getLoginUser());
this.modelService.addModel(dto);
req.setOwnerType(CmsExtendMetaModelType.TYPE);
req.setOwnerId(site.getSiteId().toString());
this.modelService.addModel(req);
return R.ok();
}
@Log(title = "编辑扩展模板", businessType = BusinessType.UPDATE)
@Priv(type = AdminUserType.TYPE, value = { EXModelPriv.Add, EXModelPriv.Edit })
@PutMapping
public R<?> edit(@RequestBody @Validated XModelDTO dto) {
dto.setOperator(StpAdminUtil.getLoginUser());
this.modelService.editModel(dto);
public R<?> edit(@RequestBody @Validated UpdateXModelRequest req) {
this.modelService.editModel(req);
return R.ok();
}
@Log(title = "删除扩展模型", businessType = BusinessType.DELETE)
@Priv(type = AdminUserType.TYPE, value = EXModelPriv.Delete)
@PostMapping("/delete")
public R<?> remove(@RequestBody @Validated @NotEmpty List<XModelDTO> dtoList) {
List<Long> modelIds = dtoList.stream().map(XModelDTO::getModelId).toList();
public R<?> remove(@RequestBody @Validated @NotEmpty List<Long> modelIds) {
this.modelService.deleteModel(modelIds);
return R.ok();
}

View File

@ -36,7 +36,7 @@ public class CmsExtendModelData extends BaseModelData {
/**
* 关联数据ID联合主键
*/
private Long dataId;
private String dataId;
/**
* 关联数据类型联合主键
@ -51,7 +51,7 @@ public class CmsExtendModelData extends BaseModelData {
@Override
public void setFieldValue(String fieldName, Object fieldValue) {
switch(fieldName) {
case "data_id" -> this.setDataId(ConvertUtils.toLong(fieldValue));
case "data_id" -> this.setDataId(ConvertUtils.toStr(fieldValue));
case "data_type" -> this.setDataType(ConvertUtils.toStr(fieldValue));
case "model_id" -> this.setModelId(ConvertUtils.toLong(fieldValue));
default -> super.setFieldValue(fieldName, fieldValue);

View File

@ -22,4 +22,4 @@ DICT.ExtendModelDataType.content=內容
META.CONTROL_TYPE.CMSImage=圖片上傳
META.CONTROL_TYPE.UEditor=富文本編輯器
META.CONTROL_TYPE.CMSContentSelect=內容選擇
META.CONTROL_TYPE.CMSResource=資源文件上傳
META.CONTROL_TYPE.CMSResource=資源檔案上傳

View File

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

View File

@ -11,15 +11,14 @@ CMS.IMAGE.CONTENT_ID=所屬內容ID
CMS.IMAGE.SITE_ID=所屬站點ID
CMS.IMAGE.TITLE=標題
CMS.IMAGE.DESC=簡介
CMS.IMAGE.FILE_NAME=圖片文件
CMS.IMAGE.FILE_NAME=圖片原檔案
CMS.IMAGE.PATH=圖片資源路徑
CMS.IMAGE.SRC=圖片訪問路徑
CMS.IMAGE.TYPE=圖片類型
CMS.IMAGE.FILE_SIZE=圖片大小
CMS.IMAGE.WIDTH=圖片寬度
CMS.IMAGE.HEIGHT=圖片高度
CMS.IMAGE.REDIRECT_URL=跳轉鏈接
CMS.IMAGE.REDIRECT_URL=跳轉連結

View File

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

View File

@ -28,6 +28,6 @@ import freemarker.template.TemplateException;
public class LinkGroupNotFoundTemplateException extends TemplateException {
public LinkGroupNotFoundTemplateException(String linkGroupCode, Environment env) {
super(I18nUtils.get("FREEMARKER.ERR.LinkGroupNotFound"), env);
super(I18nUtils.parse("FREEMARKER.ERR.LinkGroupNotFound", env.getLocale(), linkGroupCode), env);
}
}

View File

@ -16,6 +16,7 @@
package com.chestnut.link.listener;
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.contentcore.domain.CmsSite;
import com.chestnut.contentcore.listener.event.BeforeSiteDeleteEvent;
@ -28,6 +29,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
@ -45,20 +48,40 @@ public class LinkListener {
// 删除友链数据
long total = this.linkService
.count(new LambdaQueryWrapper<CmsLink>().eq(CmsLink::getSiteId, site.getSiteId()));
long lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total),
"正在删除友链数据:" + (i * pageSize) + "/" + total);
this.linkService.remove(new LambdaQueryWrapper<CmsLink>().eq(CmsLink::getSiteId, site.getSiteId())
.last("limit " + pageSize));
Page<CmsLink> links = this.linkService.lambdaQuery()
.select(CmsLink::getLinkId)
.eq(CmsLink::getSiteId, site.getSiteId())
.gt(CmsLink::getLinkId, lastId)
.orderByAsc(CmsLink::getLinkId)
.page(Page.of(1, pageSize, false));
if (!links.getRecords().isEmpty()) {
List<Long> linkIds = links.getRecords().stream().map(CmsLink::getLinkId).toList();
this.linkService.removeBatchByIds(linkIds);
lastId = linkIds.get(linkIds.size() - 1);
}
}
// 删除友链分组数据
total = this.linkGroupService
.count(new LambdaQueryWrapper<CmsLinkGroup>().eq(CmsLinkGroup::getSiteId, site.getSiteId()));
lastId = 0;
for (long i = 0; i * pageSize < total; i++) {
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total),
"正在删除友链分组数据:" + (i * pageSize) + "/" + total);
this.linkGroupService.remove(new LambdaQueryWrapper<CmsLinkGroup>()
.eq(CmsLinkGroup::getSiteId, site.getSiteId()).last("limit " + pageSize));
Page<CmsLinkGroup> linkGroups = this.linkGroupService.lambdaQuery()
.select(CmsLinkGroup::getLinkGroupId)
.eq(CmsLinkGroup::getSiteId, site.getSiteId())
.gt(CmsLinkGroup::getLinkGroupId, lastId)
.orderByAsc(CmsLinkGroup::getLinkGroupId)
.page(Page.of(1, pageSize, false));
if (!linkGroups.getRecords().isEmpty()) {
List<Long> linkGroupIds = linkGroups.getRecords().stream().map(CmsLinkGroup::getLinkGroupId).toList();
this.linkGroupService.removeBatchByIds(linkGroupIds);
lastId = linkGroupIds.get(linkGroupIds.size() - 1);
}
}
} catch (Exception e) {
AsyncTaskManager.addErrMessage("删除友链数据错误:" + e.getMessage());

View File

@ -8,7 +8,7 @@ FREEMARKER.TAG.cms_link.NAME=友链列表标签
FREEMARKER.TAG.cms_link.DESC=获取友链数据列表,内嵌`<#list DataList as link>${link.name}</#list>`遍历数据
FREEMARKER.TAG.cms_link.code=友链分组编码
FREEMARKER.ERR.LinkGroupNotFound=友链分组数据不存在。
FREEMARKER.ERR.LinkGroupNotFound=友链分组`{0}`不存在。
CMS.LINK_GROUP.ID=友链分组ID
CMS.LINK_GROUP.SITE_ID=所属站点ID

View File

@ -8,7 +8,7 @@ FREEMARKER.TAG.cms_link.NAME=Friend-link list tag
FREEMARKER.TAG.cms_link.DESC=Fetch friend-link list, use `<#list>` in tag like `<#list DataList as link>${link.name}</#list>` to walk through the list of friend-links.
FREEMARKER.TAG.cms_link.code=Friend-link group code
FREEMARKER.ERR.LinkGroupNotFound=Friend-link group not exists.
FREEMARKER.ERR.LinkGroupNotFound=Friend-link group `{0}` not exists.
CMS.LINK_GROUP.ID=ID
CMS.LINK_GROUP.SITE_ID=Site ID

View File

@ -8,7 +8,7 @@ FREEMARKER.TAG.cms_link.NAME=友鏈列表標籤
FREEMARKER.TAG.cms_link.DESC=獲取友鏈數據列表,內嵌`<#list DataList as link>${link.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_link.code=友鏈分組編碼
FREEMARKER.ERR.LinkGroupNotFound=友鏈分組數據不存在。
FREEMARKER.ERR.LinkGroupNotFound=友鏈分組`{0}`不存在。
CMS.LINK_GROUP.ID=友鏈分組ID
CMS.LINK_GROUP.SITE_ID=所屬站點ID
@ -16,14 +16,12 @@ CMS.LINK_GROUP.NAME=分組名稱
CMS.LINK_GROUP.CODE=分組編碼
CMS.LINK.ID=友鏈ID
CMS.LINK.SITE_ID=所屬站點ID
CMS.LINK.GROUP_ID=所屬分組ID
CMS.LINK.SITE_ID=站點ID
CMS.LINK.GROUP_ID=分組ID
CMS.LINK.NAME=名稱
CMS.LINK.URL=鏈接
CMS.LINK.URL=連結
CMS.LINK.LOGO=Logo
CMS.LINK.SRC=Logo src
CMS.LINK.SRC=Logo訪問地址

View File

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

View File

@ -11,25 +11,25 @@ FREEMARKER.TAG.cms_video.DESC=獲取視頻集視頻數據列表,內嵌`<#list
FREEMARKER.TAG.cms_video.contentId=視頻內容ID
CMS.MEDIA.AUDIO.ID=音頻ID
CMS.MEDIA.AUDIO.CONTENT_ID=關聯內容ID
CMS.MEDIA.AUDIO.SITE_ID=關聯站點ID
CMS.MEDIA.AUDIO.CONTENT_ID=所屬內容ID
CMS.MEDIA.AUDIO.SITE_ID=所屬站點ID
CMS.MEDIA.AUDIO.TITLE=音頻標題
CMS.MEDIA.AUDIO.AUTHOR=作者
CMS.MEDIA.AUDIO.DESC=簡介
CMS.MEDIA.AUDIO.TYPE=音頻類型
CMS.MEDIA.AUDIO.PATH=音頻資源路徑
CMS.MEDIA.AUDIO.SRC=音頻訪問地址
CMS.MEDIA.AUDIO.FILE_SIZE=文件大小,單位:字節
CMS.MEDIA.AUDIO.FILE_SIZE=檔案大小,單位:位元組
CMS.MEDIA.AUDIO.FORMAT=音頻格式
CMS.MEDIA.AUDIO.DURATION=時長,單位:毫秒
CMS.MEDIA.AUDIO.DECODER=編碼方式
CMS.MEDIA.AUDIO.CHANNELS=聲道數
CMS.MEDIA.AUDIO.BIT_RATE=比特率
CMS.MEDIA.AUDIO.SAMPLING_RATE=樣率
CMS.MEDIA.AUDIO.SAMPLING_RATE=樣率
CMS.MEDIA.VIDEO.ID=視頻ID
CMS.MEDIA.VIDEO.CONTENT_ID=關聯內容ID
CMS.MEDIA.VIDEO.SITE_ID=關聯站點ID
CMS.MEDIA.VIDEO.CONTENT_ID=所屬內容ID
CMS.MEDIA.VIDEO.SITE_ID=所屬站點ID
CMS.MEDIA.VIDEO.COVER=視頻封面資源路徑
CMS.MEDIA.VIDEO.COVER_SRC=視頻封面訪問地址
CMS.MEDIA.VIDEO.TITLE=視頻標題
@ -37,7 +37,7 @@ CMS.MEDIA.VIDEO.DESC=簡介
CMS.MEDIA.VIDEO.TYPE=視頻類型
CMS.MEDIA.VIDEO.PATH=視頻資源路徑/第三方引用代碼
CMS.MEDIA.VIDEO.SRC=視頻訪問地址
CMS.MEDIA.VIDEO.FILE_SIZE=文件大小,單位:字節
CMS.MEDIA.VIDEO.FILE_SIZE=檔案大小,單位:位元組
CMS.MEDIA.VIDEO.FORMAT=視頻格式
CMS.MEDIA.VIDEO.DURATION=時長,單位:毫秒
CMS.MEDIA.VIDEO.DECODER=編碼方式

View File

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

View File

@ -19,7 +19,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.article.ArticleContent;
import com.chestnut.article.ArticleContentType;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.article.format.ArticleBodyFormat_RichText;
import com.chestnut.article.service.IArticleService;
import com.chestnut.cms.member.domain.dto.ArticleContributeDTO;
import com.chestnut.cms.member.domain.vo.MemberContentVO;
@ -27,7 +29,7 @@ import com.chestnut.cms.member.properties.EnableContributeProperty;
import com.chestnut.common.domain.R;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.common.security.domain.Operator;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.IdUtils;
@ -71,6 +73,7 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.chestnut.common.utils.SortUtils.getDefaultSortValue;
@ -111,13 +114,14 @@ public class MemberContributeApiController extends BaseRestController implements
if (!ContentStatus.isDraft(xContent.getStatus())) {
return R.fail("只能删除待审核的初稿");
}
if (xContent.getContributorId() != StpMemberUtil.getLoginIdAsLong()) {
Operator operator = Operator.of(StpMemberUtil.getLoginUser());
if (!Objects.equals(xContent.getContributorId(), operator.getUserId())) {
return R.fail("内容ID错误");
}
IContentType contentType = ContentCoreUtils.getContentType(xContent.getContentType());
IContent<?> content = contentType.loadContent(xContent);
content.setOperator(StpMemberUtil.getLoginUser());
content.setOperator(operator);
transactionTemplate.executeWithoutResult(transactionStatus -> content.delete());
applicationContext.publishEvent(new AfterContentDeleteEvent(this, content));
return R.ok();
@ -144,10 +148,10 @@ public class MemberContributeApiController extends BaseRestController implements
if (!EnableContributeProperty.getValue(catalog.getConfigProps())) {
return R.fail("参数`catalogId`异常:" + dto.getCatalogId());
}
LoginUser loginUser = StpMemberUtil.getLoginUser();
Operator operator = Operator.of(StpMemberUtil.getLoginUser());
if (IdUtils.validate(dto.getContentId())) {
CmsContent cmsContent = this.contentService.dao().getById(dto.getContentId());
if (!loginUser.getUserId().equals(cmsContent.getContributorId())) {
if (!operator.getUserId().equals(cmsContent.getContributorId())) {
return R.fail("内容ID错误");
}
if (!ContentStatus.isDraft(cmsContent.getStatus())) {
@ -163,7 +167,7 @@ public class MemberContributeApiController extends BaseRestController implements
cmsContent.setTags(dto.getTags().toArray(String[]::new));
// 重置发布状态
cmsContent.setStatus(ContentStatus.DRAFT);
cmsContent.updateBy(loginUser.getUsername());
cmsContent.updateBy(operator.getUsername());
if (!dto.getCatalogId().equals(cmsContent.getCatalogId())) {
CmsCatalog fromCatalog = this.catalogService.getCatalog(cmsContent.getCatalogId());
CmsCatalog toCatalog = this.catalogService.getCatalog(dto.getCatalogId());
@ -178,11 +182,16 @@ public class MemberContributeApiController extends BaseRestController implements
}
CmsArticleDetail articleDetail = this.articleService.dao().getById(cmsContent.getContentId());
articleDetail.setContentHtml(dto.getContentHtml());
IArticleBodyFormat articleBodyFormat = articleService.getArticleBodyFormat(dto.getFormat());
if (Objects.isNull(articleBodyFormat)) {
dto.setFormat(ArticleBodyFormat_RichText.ID);
}
articleDetail.setFormat(dto.getFormat());
ArticleContent content = new ArticleContent();
content.setContentEntity(cmsContent);
content.setExtendEntity(articleDetail);
content.setOperator(loginUser);
content.setOperator(operator);
applicationContext.publishEvent(new BeforeContentSaveEvent(this, content, false));
content.save();
applicationContext.publishEvent(new AfterContentSaveEvent(this, content, false));
@ -218,7 +227,7 @@ public class MemberContributeApiController extends BaseRestController implements
if (content.hasExtendEntity() && StringUtils.isEmpty(extendEntity.getContentHtml())) {
throw CommonErrorCode.NOT_EMPTY.exception("contentHtml");
}
content.setOperator(StpMemberUtil.getLoginUser());
content.setOperator(operator);
applicationContext.publishEvent(new BeforeContentSaveEvent(this, content, true));
content.add();
applicationContext.publishEvent(new AfterContentSaveEvent(this, content, true));

View File

@ -69,4 +69,9 @@ public class ArticleContributeDTO {
* 引导图
*/
private String logo;
/**
* 文章格式
*/
private String format;
}

View File

@ -4,10 +4,10 @@ ERRCODE.CMS.MEMBER.NO_CONTRIBUTE_PRIV=無投稿許可權
# freemarker模板標籤
FREEMARKER.TAG.cms_favorite_content.NAME=收藏內容列表標籤
FREEMARKER.TAG.cms_favorite_content.DESC=獲取收藏內容數據列表,內嵌`<#list DataList as content>${content.title}</#list>`遍曆數據
FREEMARKER.TAG.cms_favorite_content.uid=用户ID
FREEMARKER.TAG.cms_favorite_content.uid=會員ID
FREEMARKER.TAG.cms_member_follow.NAME=關注/粉絲列表標籤
FREEMARKER.TAG.cms_member_follow.DESC=獲取關注/粉絲數據列表,內嵌`<#list DataList as member>${member.displayName}</#list>`遍曆數據
FREEMARKER.TAG.cms_member_follow.uid=用户ID
FREEMARKER.TAG.cms_member_follow.uid=會員ID
FREEMARKER.TAG.cms_member_follow.type=類型
FREEMARKER.TAG.cms_member_follow.type.follow=關注
FREEMARKER.TAG.cms_member_follow.type.follower=粉絲
@ -18,7 +18,7 @@ FREEMARKER.FUNC.accountUrl.Arg1.Name=會員ID
FREEMARKER.FUNC.accountUrl.Arg2.Name=頁面類型
FREEMARKER.FUNC.accountUrl.Arg3.Name=是否忽略`sid/pp`參數
# 动态模板
# 動態模板
DYNAMIC_PAGE_TYPE.AccountBindEmail.NAME=會員綁定郵箱頁
DYNAMIC_PAGE_TYPE.AccountCentre.NAME=會員個人中心頁
DYNAMIC_PAGE_TYPE.AccountCentre.ARG.memberId=會員ID
@ -38,4 +38,4 @@ CMS.MEMBER.SLOGAN=標語
CMS.MEMBER.STAT=統計數據<分類,數量>
CMS.MEMBER.MENU=會員菜單
CMS.MEMBER.MENU.NAME=會員菜單名稱
CMS.MEMBER.MENU.URL=會員菜單鏈接
CMS.MEMBER.MENU.URL=會員菜單連結

View File

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

View File

@ -26,7 +26,6 @@ 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;
@ -46,7 +45,7 @@ public class CMSSearchLogController extends BaseRestController {
@Priv(type = AdminUserType.TYPE)
@GetMapping
public R<?> getPageList(@RequestParam(value = "query", required = false) String query) {
public R<?> getPageList(@RequestParam(required = false) String query) {
PageRequest pr = this.getPageRequest();
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
Page<SearchLog> page = this.searchLogService.lambdaQuery()

View File

@ -28,10 +28,11 @@ 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.domain.dto.CreateSearchWordRequest;
import com.chestnut.search.service.ISearchWordService;
import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Priv(type = AdminUserType.TYPE)
@ -45,7 +46,7 @@ public class CMSSearchWordStatController extends BaseRestController {
private final ISearchWordService searchWordStatService;
@GetMapping
public R<?> getPageList(@RequestParam(value = "query", required = false) String query) {
public R<?> getPageList(@RequestParam(required = false) String query) {
PageRequest pr = this.getPageRequest();
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
Page<SearchWord> page = this.searchWordStatService.lambdaQuery()
@ -58,11 +59,10 @@ public class CMSSearchWordStatController extends BaseRestController {
@Log(title = "新增搜索词", businessType = BusinessType.INSERT)
@PostMapping
public R<?> addWord(@RequestBody SearchWord wordStat) {
public R<?> addWord(@RequestBody @Validated CreateSearchWordRequest req) {
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
wordStat.setSource(CmsSearchConstants.generateSearchSource(site.getSiteId()));
wordStat.createBy(StpAdminUtil.getLoginUser().getUsername());
this.searchWordStatService.addWord(wordStat);
req.setSource(CmsSearchConstants.generateSearchSource(site.getSiteId()));
this.searchWordStatService.addWord(req);
return R.ok();
}
}

View File

@ -54,6 +54,7 @@ import com.chestnut.xmodel.core.IMetaModelType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.hibernate.validator.constraints.Length;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@ -85,9 +86,9 @@ public class ContentIndexController extends BaseRestController {
}
@GetMapping("/contents")
public R<?> selectDocumentList(@RequestParam(value = "query", required = false) String query,
public R<?> selectDocumentList(@RequestParam(value = "query", required = false) @Length(max = 200) String query,
@RequestParam(value = "onlyTitle", required = false ,defaultValue = "false") Boolean onlyTitle,
@RequestParam(value = "contentType", required = false) String contentType) throws ElasticsearchException, IOException {
@RequestParam(value = "contentType", required = false) @Length(max = 20) String contentType) throws ElasticsearchException, IOException {
this.checkElasticSearchEnabled();
PageRequest pr = this.getPageRequest();
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());

View File

@ -19,7 +19,9 @@ import com.chestnut.cms.search.impl.SearchDynamicPageType;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.ServletUtils;
import com.chestnut.contentcore.service.impl.DynamicPageService;
import com.chestnut.system.validator.LongId;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
@ -44,11 +46,11 @@ public class CmsSearchController extends BaseRestController {
private final DynamicPageService dynamicPageService;
@GetMapping(SearchDynamicPageType.REQUEST_PATH)
public void searchPage(@RequestParam(value ="q", required = false, defaultValue = "") @Length(max = 50) String query,
@RequestParam("sid") Long siteId,
@RequestParam("pp") String publishPipeCode,
public void searchPage(@RequestParam(value ="q", required = false, defaultValue = "") @Length(max = 200) String query,
@RequestParam("sid") @LongId Long siteId,
@RequestParam("pp") @NotBlank @Length(max = 50) String publishPipeCode,
@RequestParam(value = "ot", required = false ,defaultValue = "false") Boolean onlyTitle,
@RequestParam(value = "ct", required = false) String contentType,
@RequestParam(value = "ct", required = false) @Length(max = 20) String contentType,
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "false") Boolean preview,
HttpServletResponse response)

View File

@ -40,8 +40,9 @@ import com.chestnut.search.SearchConsts;
import com.chestnut.search.service.ISearchLogService;
import com.chestnut.system.validator.LongId;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
@ -51,6 +52,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
@ -65,6 +67,9 @@ import java.util.stream.Collectors;
@RequestMapping("/api/cms/search")
public class SearchApiController extends BaseRestController {
private static final String SORT_SCORE = "1"; // 排序方式相关度
private static final String SORT_PUBLISH_DATE = "2"; // 排序方式发布时间
private final ICatalogService catalogService;
private final ElasticsearchClient esClient;
@ -75,50 +80,81 @@ public class SearchApiController extends BaseRestController {
@GetMapping("/query")
public R<?> selectDocumentList(
@RequestParam(value = "sid") Long siteId,
@RequestParam(value = "pp") String publishPipeCode,
@RequestParam(value = "q") @Length(max = 50) String query,
@RequestParam(value = "ot", required = false ,defaultValue = "false") Boolean onlyTitle,
@RequestParam(value = "ct", required = false) String contentType,
@RequestParam(value = "page", required = false, defaultValue = "1") @Min(1) Integer page,
@RequestParam(value = "sid") Long siteId, // 站点ID
@RequestParam(value = "pp") String publishPipeCode, // 发布通道
@RequestParam(value = "q") @Length(max = 50) String query, // 检索词
@RequestParam(value = "ot", required = false ,defaultValue = "false") Boolean onlyTitle, // 是否只查标题
@RequestParam(value = "ct", required = false) String contentType, // 类型类型
@RequestParam(value = "ts", required = false) String[] tags, // 标签列表
@RequestParam(value = "st", required = false, defaultValue = SORT_SCORE) String sortType, // 排序方式1 = 相关度2 = 发布时间
@RequestParam(value = "bt", required = false) LocalDate beginTime, // 发布时间Begin
@RequestParam(value = "et", required = false) LocalDate endTime, // 发布时间End
@RequestParam(value = "page", required = false, defaultValue = "1") @Min(1) Integer page, // 页码
@RequestParam(value = "size", required = false, defaultValue = "10") @Min(1) Integer pageSize, // 每页数量
@RequestParam(value = "preview", required = false, defaultValue = "false") Boolean preview) throws ElasticsearchException, IOException {
int pageSize = 10;
String indexName = CmsSearchConstants.indexName(siteId.toString());
SearchResponse<ObjectNode> sr = esClient.search(s -> {
s.index(indexName) // 索引
.query(q ->
q.bool(b -> {
if (StringUtils.isNotEmpty(contentType)) {
b.must(must -> must.term(tq -> tq.field("contentType.keyword").value(contentType)));
}
if (StringUtils.isNotEmpty(query)) {
if (onlyTitle) {
b.must(must -> must
.match(match -> match
.analyzer(SearchConsts.IKAnalyzeType_Smart)
.field("title")
.query(query)
)
);
} else {
b.must(must -> must
.multiMatch(match -> match
.analyzer(SearchConsts.IKAnalyzeType_Smart)
.fields("title^10", "fullText^1")
.query(query)
)
);
}
}
return b;
})
);
.query(q ->
q.bool(b -> {
if (StringUtils.isNotEmpty(contentType)) {
b.must(must -> must.term(tq -> tq.field("contentType.keyword").value(contentType)));
}
if (StringUtils.isNotEmpty(query)) {
if (onlyTitle) {
b.must(must -> must
.match(match -> match
.analyzer(SearchConsts.IKAnalyzeType_Smart)
.field("title")
.query(query)
)
);
} else {
b.must(must -> must
.multiMatch(match -> match
.analyzer(SearchConsts.IKAnalyzeType_Smart)
.fields("title^10", "fullText^1")
.query(query)
)
);
}
}
if (StringUtils.isNotEmpty(tags)) {
b.must(should -> {
for (String tag : tags) {
should.constantScore(cs ->
cs.boost(1F).filter(f ->
f.match(m ->
m.field("tags").query(tag))));
}
return should;
});
}
if (Objects.nonNull(beginTime) || Objects.nonNull(endTime)) {
b.must(must -> must.range(range ->
range.number(num -> {
num.field("publishDate");
if (Objects.nonNull(beginTime)) {
num.gte((double) beginTime.atStartOfDay().toEpochSecond(ZoneOffset.UTC));
}
if (Objects.nonNull(endTime)) {
num.lte((double) endTime.atStartOfDay().toEpochSecond(ZoneOffset.UTC));
}
return num;
})
));
}
return b;
})
);
if (StringUtils.isNotEmpty(query)) {
s.highlight(h ->
h.fields("title", f -> f.preTags("<font color='red'>").postTags("</font>"))
.fields("fullText", f -> f.preTags("<font color='red'>").postTags("</font>")));
s.highlight(h ->
h.fields("title", f -> f.preTags("<font color='red'>").postTags("</font>"))
.fields("fullText", f -> f.preTags("<font color='red'>").postTags("</font>")));
}
if (SORT_SCORE.equals(sortType)) {
s.sort(sort -> sort.field(f -> f.field("_score").order(SortOrder.Desc)));
}
s.sort(sort -> sort.field(f -> f.field("_score").order(SortOrder.Desc)));
s.sort(sort -> sort.field(f -> f.field("publishDate").order(SortOrder.Desc))); // 排序: _score:desc + publishDate:desc
// s.source(source -> source.filter(f -> f.excludes("fullText"))); // 过滤字段
s.from((page - 1) * pageSize).size(pageSize); // 分页0开始
@ -166,13 +202,13 @@ public class SearchApiController extends BaseRestController {
@GetMapping("/tag")
public R<?> selectDocumentByTag(
@RequestParam(value = "sid") Long siteId,
@RequestParam(value = "sid") @LongId Long siteId,
@RequestParam(value = "cid", required = false, defaultValue = "0") Long catalogId,
@RequestParam(value = "pp") String publishPipeCode,
@RequestParam(value = "q", required = false) @Length(max = 200) String query,
@RequestParam(value = "ct", required = false) String contentType,
@RequestParam(value = "pp") @NotBlank @Length(max = 50) String publishPipeCode,
@RequestParam(value = "q", required = false) @NotBlank @Length(max = 200) String query,
@RequestParam(value = "ct", required = false) @Length(max = 20) String contentType,
@RequestParam(value = "page", required = false, defaultValue = "1") @Min(1) Integer page,
@RequestParam(value = "size", required = false, defaultValue = "10") @Min(1) Integer size,
@RequestParam(value = "size", required = false, defaultValue = "10") @Min(1) @Max(100) Integer size,
@RequestParam(value = "preview", required = false, defaultValue = "false") Boolean preview) throws ElasticsearchException, IOException {
String indexName = CmsSearchConstants.indexName(siteId.toString());
SearchResponse<ObjectNode> sr = esClient.search(s -> {
@ -238,9 +274,9 @@ public class SearchApiController extends BaseRestController {
@GetMapping("/suggest")
public R<List<String>> getSuggestWords(@RequestParam("sid") @LongId Long siteId,
@RequestParam(value = "cid", required = false, defaultValue = "0") Long catalogId,
@RequestParam(value = "q") @NotEmpty @Length(max = 50) String query,
@RequestParam(value = "ct", required = false) String contentType,
@RequestParam(value = "size", required = false) Integer size) throws IOException {
@RequestParam(value = "q") @NotBlank @Length(max = 200) String query,
@RequestParam(value = "ct", required = false) @Length(max = 20) String contentType,
@RequestParam(value = "size", required = false) @Min(1) @Max(100) Integer size) throws IOException {
String suggester = "title-suggest";
String indexName = CmsSearchConstants.indexName(siteId.toString());
SearchResponse<ObjectNode> sr = esClient.search(s ->
@ -279,11 +315,11 @@ public class SearchApiController extends BaseRestController {
@GetMapping("/group/catalog")
public R<?> groupBy(@RequestParam("sid") @LongId Long siteId,
@RequestParam(value = "q") @Length(max = 50) String query,
@RequestParam(value = "q") @NotBlank @Length(max = 200) String query,
@RequestParam(value = "cid", defaultValue = "0") Long catalogId,
@RequestParam(value = "level", defaultValue = "0") Integer level,
@RequestParam(value = "ot", required = false ,defaultValue = "false") Boolean onlyTitle,
@RequestParam(value = "ct", required = false) String contentType) throws ElasticsearchException, IOException {
@RequestParam(value = "ct", required = false) @Length(max = 20) String contentType) throws ElasticsearchException, IOException {
CmsCatalog catalog = IdUtils.validate(catalogId) ? catalogService.getCatalog(catalogId) : null;
String indexName = CmsSearchConstants.indexName(siteId.toString());
SearchResponse<ObjectNode> sr = esClient.search(s ->

View File

@ -12,8 +12,8 @@ FREEMARKER.TAG.cms_search_content.mode.TagAnd=標籤與檢索,多個標籤英
FREEMARKER.TAG.cms_search_content.level=數據獲取範圍V1.5.6+
FREEMARKER.TAG.cms_search_content.level.Current=當前欄目
FREEMARKER.TAG.cms_search_content.level.CurrentAndChild=當前欄目和子欄目
FREEMARKER.TAG.cms_search_word.NAME=檢索詞熱詞標籤
FREEMARKER.TAG.cms_search_word.DESC=檢索詞熱詞標籤
FREEMARKER.TAG.cms_search_word.NAME=搜索熱詞標籤
FREEMARKER.TAG.cms_search_word.DESC=搜索熱詞標籤
CONFIG.CMSSearchAnalyzeType=索引分詞方式
@ -25,9 +25,10 @@ DYNAMIC_PAGE_TYPE.Search.ARG.ct=內容類型
DYNAMIC_PAGE_TYPE.Search.ARG.page=頁碼
PROGRESS.INFO.BUILDING_INDEX=正在重建欄目索引:{0}
PROGRESS.INFO.BUILD_SUCCEED=重建索引完成
CMS.ES_CONTENT.FULL_TEXT=全文內容
CMS.ES_CONTENT.PUBLISH_DATE_TIMESTAMP=時間戳
CMS.ES_CONTENT.CREATE_TIME_TIMESTAMP=建時間戳
CMS.ES_CONTENT.PUBLISH_DATE_TIMESTAMP=時間戳
CMS.ES_CONTENT.CREATE_TIME_TIMESTAMP=時間戳
CMS.ES_CONTENT.HIT_SCORE=匹配度
CMS.ES_CONTENT.EXTEND_DATA=擴展模型數據

View File

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

View File

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

View File

@ -21,6 +21,6 @@ STAT.MENU.ContentStatByUser=用戶發布統計
# TAG
FREEMARKER.TAG.cms_stat.NAME=訪問統計標籤
FREEMARKER.TAG.cms_stat.DESC=用於在模板頁面中插入內置訪問統計腳本代碼
FREEMARKER.TAG.cms_stat.DESC=用於在模板頁面中插入內置訪問統計指令碼或直譯式程式代碼
SCHEDULED_TASK.StatContentCountByStatus=內容發統計任務
SCHEDULED_TASK.StatContentCountByStatus=內容發統計任務

View File

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

View File

@ -3,6 +3,6 @@ FREEMARKER.TAG.cms_vote.NAME=調查問卷列表標籤
FREEMARKER.TAG.cms_vote.DESC=獲取調查問卷數據列表,內嵌`<#list DataList as vote>${vote.title}</#list>`遍曆數據
FREEMARKER.TAG.cms_vote_subject.NAME=調查問卷主題列表標籤
FREEMARKER.TAG.cms_vote_subject.DESC=獲取調查問卷主題數據列表,內嵌`<#list DataList as subject>${subject.title}</#list>`遍曆數據
FREEMARKER.TAG.cms_vote_subject.code=調查問卷編碼
FREEMARKER.TAG.cms_vote_subject.code=問卷調查編碼
FREEMARKER.ERR.VOTE_NOT_FOUND=調查問卷數據不存在code = {0}。

View File

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

View File

@ -26,8 +26,8 @@ import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil;
import com.chestnut.word.domain.HotWordGroup;
import com.chestnut.word.domain.dto.CreateHotWordGroupRequest;
import com.chestnut.word.permission.WordPriv;
import com.chestnut.word.service.IHotWordGroupService;
import lombok.RequiredArgsConstructor;
@ -72,11 +72,7 @@ public class CMSHotWordGroupController extends BaseRestController {
List<HotWordGroup> list = this.hotWordGroupService.lambdaQuery()
.eq(HotWordGroup::getOwner, currentSite.getSiteId().toString())
.list();
List<Map<String, Object>> options = new ArrayList<>();
list.forEach(g -> {
options.add(Map.of("code", g.getCode(), "name", g.getName()));
});
return this.bindDataTable(options);
return this.bindSelectOptions(list, HotWordGroup::getCode, HotWordGroup::getName);
}
@Priv(type = AdminUserType.TYPE, value = WordPriv.View)
@ -99,10 +95,9 @@ public class CMSHotWordGroupController extends BaseRestController {
@Priv(type = AdminUserType.TYPE, value = WordPriv.View)
@PostMapping
public R<?> add(@RequestBody @Validated HotWordGroup group) {
public R<?> add(@RequestBody @Validated CreateHotWordGroupRequest req) {
CmsSite site = siteService.getCurrentSite(ServletUtils.getRequest());
group.setOwner(site.getSiteId().toString());
group.createBy(StpAdminUtil.getLoginUser().getUsername());
return R.ok(this.hotWordGroupService.addHotWordGroup(group));
req.setOwner(site.getSiteId().toString());
return R.ok(this.hotWordGroupService.addHotWordGroup(req));
}
}

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