版本更新:v1.5.1

This commit is contained in:
liweiyi 2025-01-19 00:20:44 +08:00
parent 29537b2388
commit bd42862278
630 changed files with 15840 additions and 5328 deletions

18
NOTICE
View File

@ -13,15 +13,7 @@ Copyright 2002-2024 The Apache Software Foundation
This product includes software developed at This product includes software developed at
The Apache Software Foundation (https://www.apache.org/). The Apache Software Foundation (https://www.apache.org/).
----------------------------------------------------------------------- -----------------------------------------------------------------------
This product contains code from the redisson Project: This product contains code from the Apache Commons Text Project:
Apache Commons Text
Copyright 2014-2024 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (https://www.apache.org/).
-----------------------------------------------------------------------
This product contains code from the redisson Project:
Apache Commons Text Apache Commons Text
Copyright 2014-2024 The Apache Software Foundation Copyright 2014-2024 The Apache Software Foundation
@ -41,3 +33,11 @@ The files
- velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java - velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java
- velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java - velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java
are Copyright 2006 Sun Microsystems, Inc., and licenced under a BSD-like licence. are Copyright 2006 Sun Microsystems, Inc., and licenced under a BSD-like licence.
-----------------------------------------------------------------------
This product contains code from the flexmark-java Project:
Copyright (c) 2015-2016, Atlassian Pty Ltd
All rights reserved.
Copyright (c) 2016-2018, Vladimir Schneider,
All rights reserved.

View File

@ -1,4 +1,4 @@
# ChestnutCMS v1.5.0 # ChestnutCMS v1.5.1
### 系统简介 ### 系统简介
@ -10,9 +10,9 @@ ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi
账号demo / a123456 账号demo / a123456
企业站演示地址:<http://swikoon.1000mz.com> 企业站演示地址:<https://swikoon.1000mz.com>
资讯站演示地址:<http://news.1000mz.com>会员演示账号xxx333@126.com / a123456 资讯站演示地址:<https://news.1000mz.com>会员演示账号xxx333@126.com / a123456
图片站演示地址PC端<http://tpz.1000mz.com> 移动端:<http://mtpz.1000mz.com> 图片站演示地址PC端<http://tpz.1000mz.com> 移动端:<http://mtpz.1000mz.com>

View File

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

View File

@ -5,7 +5,7 @@ chestnut:
# 代号 # 代号
alias: ChestnutCMS alias: ChestnutCMS
# 版本 # 版本
version: 1.5.0 version: 1.5.1
# 版权年份 # 版权年份
copyrightYear: 2022-2024 copyrightYear: 2022-2024
system: system:
@ -191,10 +191,8 @@ spring:
# Actuator 监控端点的配置项 # Actuator 监控端点的配置项
management: management:
trace:
http:
enabled: true
endpoints: endpoints:
enabled-by-default: false
web: web:
exposure: exposure:
include: '*' include: '*'
@ -203,6 +201,9 @@ management:
show-details: ALWAYS show-details: ALWAYS
logfile: logfile:
external-file: ./logs/client.log external-file: ./logs/client.log
httpexchanges:
recording:
enabled: true
sa-token: sa-token:
# token名称 (同时也是cookie名称) # token名称 (同时也是cookie名称)
@ -237,6 +238,9 @@ mybatis-plus:
xss: xss:
# 过滤开关 # 过滤开关
enabled: true enabled: true
# 过滤链接
urlPatterns:
- /*
xxl: xxl:
job: job:

View File

@ -5,7 +5,7 @@ chestnut:
# 代号 # 代号
alias: ChestnutCMS alias: ChestnutCMS
# 版本 # 版本
version: 1.5.0 version: 1.5.1
# 版权年份 # 版权年份
copyrightYear: 2022-2024 copyrightYear: 2022-2024
system: system:
@ -195,7 +195,7 @@ sa-token:
# token有效期单位s 默认30天, -1代表永不过期 # token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000 timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
active-timeout: -1 active-timeout: 1800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) # 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)

View File

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

View File

@ -4,9 +4,6 @@ ALTER TABLE cms_image DROP COLUMN deleted;
ALTER TABLE cms_audio DROP COLUMN deleted; ALTER TABLE cms_audio DROP COLUMN deleted;
ALTER TABLE cms_video DROP COLUMN deleted; ALTER TABLE cms_video DROP COLUMN deleted;
-- ----------------------------
-- Table structure for b_cms_content
-- ----------------------------
CREATE TABLE `b_cms_content` ( CREATE TABLE `b_cms_content` (
`content_id` bigint NOT NULL COMMENT '主键ID', `content_id` bigint NOT NULL COMMENT '主键ID',
`site_id` bigint NOT NULL COMMENT '所属站点ID', `site_id` bigint NOT NULL COMMENT '所属站点ID',
@ -69,9 +66,7 @@ CREATE TABLE `b_cms_content` (
`backup_remark` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL, `backup_remark` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`backup_id`) USING BTREE PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_article_detail
-- ----------------------------
CREATE TABLE `b_cms_article_detail` ( CREATE TABLE `b_cms_article_detail` (
`content_id` bigint NOT NULL COMMENT 'ID', `content_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL, `site_id` bigint NOT NULL,
@ -85,9 +80,7 @@ CREATE TABLE `b_cms_article_detail` (
`backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`backup_id`) USING BTREE PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_image
-- ----------------------------
CREATE TABLE `b_cms_image` ( CREATE TABLE `b_cms_image` (
`image_id` bigint NOT NULL COMMENT '主键ID', `image_id` bigint NOT NULL COMMENT '主键ID',
`site_id` bigint DEFAULT NULL, `site_id` bigint DEFAULT NULL,
@ -114,9 +107,7 @@ CREATE TABLE `b_cms_image` (
`backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`backup_id`) USING BTREE PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_audio
-- ----------------------------
CREATE TABLE `b_cms_audio` ( CREATE TABLE `b_cms_audio` (
`audio_id` bigint NOT NULL COMMENT 'ID', `audio_id` bigint NOT NULL COMMENT 'ID',
`content_id` bigint NOT NULL COMMENT '所属内容ID', `content_id` bigint NOT NULL COMMENT '所属内容ID',
@ -145,9 +136,7 @@ CREATE TABLE `b_cms_audio` (
`backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`backup_id`) USING BTREE PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_video
-- ----------------------------
CREATE TABLE `b_cms_video` ( CREATE TABLE `b_cms_video` (
`video_id` bigint NOT NULL, `video_id` bigint NOT NULL,
`content_id` bigint NOT NULL COMMENT '所属内容ID', `content_id` bigint NOT NULL COMMENT '所属内容ID',
@ -179,9 +168,6 @@ CREATE TABLE `b_cms_video` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- __CC_IGNORE__ -- __CC_IGNORE__
-- ----------------------------
-- Table structure for cms_book
-- ----------------------------
CREATE TABLE `cms_book` ( CREATE TABLE `cms_book` (
`content_id` bigint NOT NULL COMMENT 'ID', `content_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL COMMENT '所属站点ID', `site_id` bigint NOT NULL COMMENT '所属站点ID',
@ -199,9 +185,7 @@ CREATE TABLE `cms_book` (
`completed` varchar(1) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '是否完结', `completed` varchar(1) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '是否完结',
PRIMARY KEY (`content_id`) PRIMARY KEY (`content_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for b_cms_book
-- ----------------------------
CREATE TABLE `b_cms_book` ( CREATE TABLE `b_cms_book` (
`content_id` bigint NOT NULL COMMENT 'ID', `content_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL COMMENT '所属站点ID', `site_id` bigint NOT NULL COMMENT '所属站点ID',
@ -223,9 +207,7 @@ CREATE TABLE `b_cms_book` (
`backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `backup_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`backup_id`) USING BTREE PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for cms_book_chapter
-- ----------------------------
CREATE TABLE `cms_book_chapter` ( CREATE TABLE `cms_book_chapter` (
`chapter_id` bigint NOT NULL COMMENT 'ID', `chapter_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL COMMENT '所属站点ID', `site_id` bigint NOT NULL COMMENT '所属站点ID',
@ -243,9 +225,7 @@ CREATE TABLE `cms_book_chapter` (
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注', `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`chapter_id`) PRIMARY KEY (`chapter_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for b_cms_book_chapter
-- ----------------------------
CREATE TABLE `b_cms_book_chapter` ( CREATE TABLE `b_cms_book_chapter` (
`chapter_id` bigint NOT NULL COMMENT 'ID', `chapter_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL COMMENT '所属站点ID', `site_id` bigint NOT NULL COMMENT '所属站点ID',

View File

@ -0,0 +1,83 @@
UPDATE cms_content SET `link_flag` = 'N' WHERE link_flag is null;
ALTER TABLE cms_content MODIFY COLUMN `link_flag` varchar(1) DEFAULT 'N';
UPDATE cms_content SET `is_lock` = 'N' WHERE is_lock is null;
ALTER TABLE cms_content MODIFY COLUMN `is_lock` varchar(1) DEFAULT 'N';
ALTER TABLE cms_video MODIFY COLUMN `path` varchar(1000);
ALTER TABLE b_cms_video MODIFY COLUMN `path` varchar(1000);
ALTER TABLE cms_content ADD COLUMN `images` varchar(500);
UPDATE cms_content SET images = CASE WHEN logo IS NOT NULL AND logo != '' THEN CONCAT('["', logo, '"]') ELSE '[]' END;
ALTER TABLE cms_content DROP COLUMN logo;
ALTER TABLE cms_article_detail DROP COLUMN content_json;
ALTER TABLE cms_article_detail ADD COLUMN `format` varchar(10);
UPDATE cms_article_detail SET format = 'RichText';
CREATE TABLE `cms_content_op_log` (
`log_id` bigint NOT NULL COMMENT 'ID',
`site_id` bigint NOT NULL COMMENT '站点ID',
`content_id` bigint NOT NULL COMMENT '内容ID',
`type` varchar(20) COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作方式',
`details` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作明细',
`operator_type` varchar(20) COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作人类型',
`operator` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作人',
`log_time` datetime NOT NULL COMMENT '日志时间',
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/* __CC_IGNORE__ */
CREATE TABLE `cc_check_latest_version_log` (
`log_id` bigint NOT NULL COMMENT 'ID',
`from` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '来源',
`referer` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Referer',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`log_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
CREATE TABLE `cc_certificate` (
`cert_id` bigint NOT NULL COMMENT 'ID',
`issuer` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '签发者类型',
`domain` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '域名',
`acme_account_id` bigint DEFAULT NULL COMMENT '关联ACME账号ID',
`issue_time` bigint DEFAULT NULL COMMENT '证书签发时间',
`expire_time` bigint DEFAULT NULL COMMENT '证书过期时间',
`key_path` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '证书Key路径',
`crt_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '证书路径',
`crt_country` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '证书信息:国家',
`crt_state` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '证书信息:省',
`crt_locality` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '证书信息:地区',
`crt_organization` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '证书信息:组织机构',
`status` varchar(20) COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态',
`config_props` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '配置参数',
`create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后修改人',
`update_time` datetime DEFAULT NULL COMMENT '最后修改时间',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`cert_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='域名证书表';
CREATE TABLE `cc_acme_account` (
`account_id` bigint NOT NULL,
`issuer` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`key_pair_path` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`location_url` varchar(500) COLLATE utf8mb4_general_ci DEFAULT NULL,
`domain_num` int NOT NULL,
`contact` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后修改人',
`update_time` datetime DEFAULT NULL COMMENT '最后修改时间',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='ACME账号表';
INSERT INTO `sys_menu` VALUES (2082, '域名证书', 3, 5, 'deploy/certificate', 'deploy/certificate/index', '', 'N', 'Y', 'C', 'Y', '0', 'deploy:cert:view', 'skill', 'admin', '2025-01-14 00:00:00', '', NULL, '');
INSERT INTO `sys_i18n_dict` VALUES (632978182307909, 'zh-CN', 'MENU.NAME.2082', '证书管理');
INSERT INTO `sys_i18n_dict` VALUES (633640198516805, 'en', 'MENU.NAME.2082', 'Domain Cert');
INSERT INTO `sys_i18n_dict` VALUES (633640198524997, 'zh-TW', 'MENU.NAME.2082', '證書管理');
/* __CC_IGNORE_END__ */

View File

@ -1,28 +0,0 @@
package com.chestnut.member;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.chestnut.member.domain.Member;
import com.chestnut.member.service.IMemberExpConfigService;
import com.chestnut.member.service.IMemberService;
@SpringBootTest
public class MemberTest {
@Autowired
private IMemberService memberService;
@Autowired
private IMemberExpConfigService expConfigService;
@Test
void testMemberSignIn() {
Member member = this.memberService.getById(398339741712453L);
expConfigService.list().forEach(expConfig -> {
expConfigService.triggerExpOperation(expConfig.getOpType(), member.getMemberId());
});
}
}

View File

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

View File

@ -29,7 +29,6 @@ import org.springframework.stereotype.Component;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.logging.Logger;
/** /**
* 广告页面部件内容核心数据处理器 * 广告页面部件内容核心数据处理器
@ -61,6 +60,7 @@ public class AdCoreDataHandler implements ICoreDataHandler {
files.forEach(f -> { files.forEach(f -> {
List<CmsAdvertisement> list = JacksonUtils.fromList(f, CmsAdvertisement.class); List<CmsAdvertisement> list = JacksonUtils.fromList(f, CmsAdvertisement.class);
for (CmsAdvertisement data : list) { for (CmsAdvertisement data : list) {
Long oldAdvertisementId = data.getAdvertisementId();
try { try {
data.setAdvertisementId(IdUtils.getSnowflakeId()); data.setAdvertisementId(IdUtils.getSnowflakeId());
data.setSiteId(context.getSite().getSiteId()); data.setSiteId(context.getSite().getSiteId());
@ -70,8 +70,7 @@ public class AdCoreDataHandler implements ICoreDataHandler {
data.setRedirectUrl(context.dealInternalUrl(data.getRedirectUrl())); data.setRedirectUrl(context.dealInternalUrl(data.getRedirectUrl()));
advertisementService.save(data); advertisementService.save(data);
} catch (Exception e) { } catch (Exception e) {
AsyncTaskManager.addErrMessage("导入广告数据失败:" + data.getName() AsyncTaskManager.addErrMessage("导入广告数据`" + oldAdvertisementId + "`失败:" + e.getMessage());
+ "[" + data.getAdvertisementId() + "]");
log.error("Import advertisement failed: {}", data.getAdvertisementId(), e); log.error("Import advertisement failed: {}", data.getAdvertisementId(), e);
} }
} }

View File

@ -0,0 +1,70 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.advertisement.cache;
import com.chestnut.common.redis.IMonitoredCache;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.contentcore.config.CMSConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.function.Supplier;
/**
* AdMonitoredCache
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IMonitoredCache.BEAN_PREFIX + AdMonitoredCache.ID)
@RequiredArgsConstructor
public class AdMonitoredCache implements IMonitoredCache<Map<String, String>> {
public static final String ID = "AD";
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "adv-ids";
private final RedisCache redisCache;
@Override
public String getId() {
return ID;
}
@Override
public String getCacheName() {
return "{MONITORED.CACHE.AD}";
}
@Override
public String getCacheKey() {
return CACHE_PREFIX;
}
@Override
public Map<String, String> getCache(String cacheKey) {
return redisCache.getCacheMap(cacheKey, String.class);
}
public Map<String, String> getCache(Supplier<Map<String, String>> supplier) {
return redisCache.getCacheMap(CACHE_PREFIX, String.class, supplier);
}
public void clear() {
this.redisCache.deleteObject(CACHE_PREFIX);
}
}

View File

@ -15,16 +15,16 @@
*/ */
package com.chestnut.advertisement.pojo.vo; package com.chestnut.advertisement.pojo.vo;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.chestnut.advertisement.domain.CmsAdvertisement; import com.chestnut.advertisement.domain.CmsAdvertisement;
import com.chestnut.common.annotation.XComment;
import com.chestnut.contentcore.util.InternalUrlUtils; import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDateTime;
/** /**
* 广告数据VO * 广告数据VO
* *
@ -36,79 +36,49 @@ import lombok.Setter;
@NoArgsConstructor @NoArgsConstructor
public class AdvertisementVO { public class AdvertisementVO {
/** @XComment("广告ID")
* 广告ID
*/
private Long advertisementId; private Long advertisementId;
/** @XComment("所属广告版本页面部件ID")
* 所属广告位ID
*/
private Long adSpaceId; private Long adSpaceId;
/** @XComment("类型")
* 类型
*/
private String type; private String type;
/** @XComment("名称")
* 名称
*/
private String name; private String name;
/** @XComment("权重")
* 权重
*/
private Integer weight; private Integer weight;
/** @XComment("关键词")
* 关键词
*/
private String keywords; private String keywords;
/** @XComment("状态")
* 状态
*/
private String state; private String state;
/** @XComment("上线时间")
* 上线时间
*/
private LocalDateTime onlineDate; private LocalDateTime onlineDate;
/** @XComment("下线时间")
* 下线时间
*/
private LocalDateTime offlineDate; private LocalDateTime offlineDate;
/** @XComment("原始跳转链接")
* 跳转链接
*/
private String redirectUrl; private String redirectUrl;
/** @XComment("实际跳转链接(可设置为中转地址)")
* 跳转链接可设置为中转地址
*/
private String link; private String link;
/** @XComment("素材路径")
* 素材链接
*/
private String resourcePath; private String resourcePath;
/** @XComment("素材访问链接")
* 素材真实地址
*/
private String resourceSrc; private String resourceSrc;
/** @XComment("创建人")
* 创建人
*/
private String createBy; private String createBy;
/** @XComment("创建时间")
* 创建时间
*/
private LocalDateTime createTime; private LocalDateTime createTime;
public AdvertisementVO(CmsAdvertisement ad) { public AdvertisementVO(CmsAdvertisement ad) {

View File

@ -15,35 +15,41 @@
*/ */
package com.chestnut.advertisement.service.impl; package com.chestnut.advertisement.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chestnut.advertisement.AdSpacePageWidgetType;
import com.chestnut.advertisement.IAdvertisementType;
import com.chestnut.advertisement.cache.AdMonitoredCache;
import com.chestnut.advertisement.domain.CmsAdvertisement;
import com.chestnut.advertisement.mapper.CmsAdvertisementMapper;
import com.chestnut.advertisement.pojo.dto.AdvertisementDTO;
import com.chestnut.advertisement.service.IAdvertisementService;
import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.contentcore.core.IPageWidget;
import com.chestnut.contentcore.core.IPageWidgetType;
import com.chestnut.contentcore.domain.CmsPageWidget;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
import com.chestnut.contentcore.publish.IStaticizeType;
import com.chestnut.contentcore.service.IPageWidgetService;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.system.fixed.dict.EnableOrDisable;
import com.chestnut.system.security.StpAdminUtil;
import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
import com.chestnut.contentcore.service.ISiteService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chestnut.advertisement.IAdvertisementType;
import com.chestnut.advertisement.domain.CmsAdvertisement;
import com.chestnut.advertisement.mapper.CmsAdvertisementMapper;
import com.chestnut.advertisement.pojo.dto.AdvertisementDTO;
import com.chestnut.advertisement.service.IAdvertisementService;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.IdUtils;
import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.domain.CmsPageWidget;
import com.chestnut.contentcore.service.IPageWidgetService;
import com.chestnut.system.fixed.dict.EnableOrDisable;
import lombok.RequiredArgsConstructor;
/** /**
* <p> * <p>
* 广告数据服务实现类 * 广告数据服务实现类
@ -57,9 +63,7 @@ import lombok.RequiredArgsConstructor;
public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper, CmsAdvertisement> public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper, CmsAdvertisement>
implements IAdvertisementService { implements IAdvertisementService {
private static final String CACHE_KEY_ADV_IDS = CMSConfig.CachePrefix + "adv-ids"; private final AdMonitoredCache adCache;
private final RedisCache redisCache;
private final Map<String, IAdvertisementType> advertisementTypes; private final Map<String, IAdvertisementType> advertisementTypes;
@ -67,6 +71,8 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
private final ISiteService siteService; private final ISiteService siteService;
private final AsyncTaskManager asyncTaskManager;
@Override @Override
public IAdvertisementType getAdvertisementType(String typeId) { public IAdvertisementType getAdvertisementType(String typeId) {
return this.advertisementTypes.get(IAdvertisementType.BEAN_NAME_PREFIX + typeId); return this.advertisementTypes.get(IAdvertisementType.BEAN_NAME_PREFIX + typeId);
@ -79,10 +85,12 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
@Override @Override
public Map<String, String> getAdvertisementMap() { public Map<String, String> getAdvertisementMap() {
return this.redisCache.getCacheMap(CACHE_KEY_ADV_IDS, return adCache.getCache(() -> {
() -> this.lambdaQuery().select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getName)).list() return this.lambdaQuery()
.stream().collect( .select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getName))
Collectors.toMap(ad -> ad.getAdvertisementId().toString(), CmsAdvertisement::getName))); .list().stream()
.collect(Collectors.toMap(ad -> ad.getAdvertisementId().toString(), CmsAdvertisement::getName));
});
} }
@Override @Override
@ -99,7 +107,7 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
advertisement.createBy(dto.getOperator().getUsername()); advertisement.createBy(dto.getOperator().getUsername());
this.save(advertisement); this.save(advertisement);
this.redisCache.deleteObject(CACHE_KEY_ADV_IDS); this.adCache.clear();
return advertisement; return advertisement;
} }
@ -116,12 +124,14 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void deleteAdvertisement(List<Long> advertisementIds) { public void deleteAdvertisement(List<Long> advertisementIds) {
this.removeByIds(advertisementIds); this.removeByIds(advertisementIds);
this.redisCache.deleteObject(CACHE_KEY_ADV_IDS); this.adCache.clear();
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void enableAdvertisement(List<Long> advertisementIds, String operator) { public void enableAdvertisement(List<Long> advertisementIds, String operator) {
List<CmsAdvertisement> list = this.listByIds(advertisementIds); List<CmsAdvertisement> list = this.listByIds(advertisementIds);
for (CmsAdvertisement ad : list) { for (CmsAdvertisement ad : list) {
@ -134,6 +144,7 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void disableAdvertisement(List<Long> advertisementIds, String operator) { public void disableAdvertisement(List<Long> advertisementIds, String operator) {
List<CmsAdvertisement> list = this.listByIds(advertisementIds); List<CmsAdvertisement> list = this.listByIds(advertisementIds);
for (CmsAdvertisement ad : list) { for (CmsAdvertisement ad : list) {
@ -143,7 +154,14 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
} }
} }
this.updateBatchById(list); this.updateBatchById(list);
// todo 重新发布 // 重新发布
asyncTaskManager.execute(() -> {
CmsPageWidget pageWidget = this.pageWidgetService.getById(list.get(0).getAdSpaceId());
IPageWidgetType pwt = pageWidgetService.getPageWidgetType(AdSpacePageWidgetType.ID);
IPageWidget pw = pwt.loadPageWidget(pageWidget);
pw.setOperator(StpAdminUtil.getLoginUser());
pw.publish();
});
} }
@Override @Override

View File

@ -45,11 +45,15 @@ import java.util.Map;
public class CmsAdvertisementTag extends AbstractListTag { public class CmsAdvertisementTag extends AbstractListTag {
public final static String TAG_NAME = "cms_advertisement"; public final static String TAG_NAME = "cms_advertisement";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}"; public final static String NAME = "{FREEMARKER.TAG." + TAG_NAME + ".NAME}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}"; public final static String DESC = "{FREEMARKER.TAG." + TAG_NAME + ".DESC}";
public final static String ATTR_USAGE_CODE = "{FREEMARKER.TAG." + TAG_NAME + ".code}";
public final static String ATTR_USAGE_TYPE = "{FREEMARKER.TAG." + TAG_NAME + ".type}";
public final static String ATTR_OPTION_TYPE_NONE = "{FREEMARKER.TAG." + TAG_NAME + ".type.None}";
public final static String ATTR_OPTION_TYPE_STAT = "{FREEMARKER.TAG." + TAG_NAME + ".type.Stat}";
final static String TagAttr_Code = "code"; final static String ATTR_CODE = "code";
final static String TagAttr_RedirectType = "type"; final static String ATTR_TYPE = "type";
private final IAdvertisementService advertisementService; private final IAdvertisementService advertisementService;
@ -58,23 +62,23 @@ public class CmsAdvertisementTag extends AbstractListTag {
@Override @Override
public List<TagAttr> getTagAttrs() { public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = super.getTagAttrs(); List<TagAttr> tagAttrs = super.getTagAttrs();
tagAttrs.add(new TagAttr(TagAttr_Code, true, TagAttrDataType.STRING, "广告位编码")); tagAttrs.add(new TagAttr(ATTR_CODE, true, TagAttrDataType.STRING, ATTR_USAGE_CODE));
tagAttrs.add(new TagAttr(TagAttr_RedirectType, false, TagAttrDataType.STRING, "广告跳转方式", tagAttrs.add(new TagAttr(ATTR_TYPE, false, TagAttrDataType.STRING, ATTR_USAGE_TYPE,
RedirectType.toTagAttrOptions(), RedirectType.None.name())); RedirectType.toTagAttrOptions(), RedirectType.None.name()));
return tagAttrs; return tagAttrs;
} }
@Override @Override
public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException { public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException {
String code = MapUtils.getString(attrs, TagAttr_Code); String code = MapUtils.getString(attrs, ATTR_CODE);
String redirectType = MapUtils.getString(attrs, TagAttr_RedirectType, RedirectType.None.name()); String redirectType = MapUtils.getString(attrs, ATTR_TYPE, RedirectType.None.name());
Long siteId = TemplateUtils.evalSiteId(env); Long siteId = TemplateUtils.evalSiteId(env);
CmsPageWidget adSpace = this.pageWidgetService.getOne(new LambdaQueryWrapper<CmsPageWidget>() CmsPageWidget adSpace = this.pageWidgetService.getOne(new LambdaQueryWrapper<CmsPageWidget>()
.eq(CmsPageWidget::getSiteId, siteId) .eq(CmsPageWidget::getSiteId, siteId)
.eq(CmsPageWidget::getCode, code)); .eq(CmsPageWidget::getCode, code));
if (adSpace == null) { if (adSpace == null) {
throw new TemplateException(StringUtils.messageFormat("<@{0}>AD place `{1}` not exists.", this.getTagName(), code), env) ; throw new TemplateException(StringUtils.messageFormat("Advertising space `{0}` not exists.", code), env) ;
} }
String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition); String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition);
@ -83,9 +87,6 @@ public class CmsAdvertisementTag extends AbstractListTag {
.eq(CmsAdvertisement::getState, EnableOrDisable.ENABLE); .eq(CmsAdvertisement::getState, EnableOrDisable.ENABLE);
q.apply(StringUtils.isNotEmpty(condition), condition); q.apply(StringUtils.isNotEmpty(condition), condition);
Page<CmsAdvertisement> pageResult = this.advertisementService.page(new Page<>(pageIndex, size, page), q); Page<CmsAdvertisement> pageResult = this.advertisementService.page(new Page<>(pageIndex, size, page), q);
if (pageIndex > 1 & pageResult.getRecords().isEmpty()) {
throw new TemplateException(StringUtils.messageFormat("Page data empty: pageIndex = {0}", pageIndex), env) ;
}
TemplateContext context = FreeMarkerUtils.getTemplateContext(env); TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
List<AdvertisementVO> list = pageResult.getRecords().stream().map(ad ->{ List<AdvertisementVO> list = pageResult.getRecords().stream().map(ad ->{
AdvertisementVO vo = new AdvertisementVO(ad); AdvertisementVO vo = new AdvertisementVO(ad);
@ -99,6 +100,11 @@ public class CmsAdvertisementTag extends AbstractListTag {
return TagPageData.of(list, pageResult.getTotal()); return TagPageData.of(list, pageResult.getTotal());
} }
@Override
public Class<AdvertisementVO> getDataClass() {
return AdvertisementVO.class;
}
@Override @Override
public String getTagName() { public String getTagName() {
return TAG_NAME; return TAG_NAME;
@ -115,8 +121,8 @@ public class CmsAdvertisementTag extends AbstractListTag {
} }
private enum RedirectType { private enum RedirectType {
None("原始链接"), None(ATTR_OPTION_TYPE_NONE),
Stat("统计链接"); Stat(ATTR_OPTION_TYPE_STAT);
private final String desc; private final String desc;

View File

@ -5,8 +5,12 @@ CMS.CONTENCORE.PAGEWIDGET.ads=广告位
ADVERTISEMENT.TYPE.image=图片 ADVERTISEMENT.TYPE.image=图片
# 模板freemarker # 模板freemarker
FREEMARKER.TAG.NAME.cms_advertisement=广告列表标签 FREEMARKER.TAG.cms_advertisement.NAME=广告列表标签
FREEMARKER.TAG.DESC.cms_advertisement=获取广告数据列表,内嵌<#list DataList as ad>${ad.name}</#list>遍历数据 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=统计链接
# 统计菜单 # 统计菜单
STAT.MENU.CmsAdv=广告数据统计 STAT.MENU.CmsAdv=广告数据统计
@ -17,3 +21,5 @@ STAT.MENU.CmsAdViewLog=广告展现日志
# 定时任务 # 定时任务
SCHEDULED_TASK.AdvertisementStatJob=广告统计任务 SCHEDULED_TASK.AdvertisementStatJob=广告统计任务
SCHEDULED_TASK.AdvertisementPublishJob=广告定时发布下线任务 SCHEDULED_TASK.AdvertisementPublishJob=广告定时发布下线任务
MONITORED.CACHE.AD=广告

View File

@ -5,8 +5,12 @@ CMS.CONTENCORE.PAGEWIDGET.ads=AD
ADVERTISEMENT.TYPE.image=Image ADVERTISEMENT.TYPE.image=Image
# 模板freemarker # 模板freemarker
FREEMARKER.TAG.NAME.cms_advertisement=Advertisement list tag FREEMARKER.TAG.cms_advertisement.NAME=Advertisement list tag
FREEMARKER.TAG.DESC.cms_advertisement=Fetch advertising data list, use <#list> in tag like "<#list DataList as ad>${ad.name}</#list>" to walk through the list of ad. FREEMARKER.TAG.cms_advertisement.DESC=Fetch advertising data list, use `<#list>` in tag like `<#list DataList as ad>${ad.name}</#list>` to walk through the list of ad.
FREEMARKER.TAG.cms_advertisement.code=Advertisement code
FREEMARKER.TAG.cms_advertisement.type=Redirect type
FREEMARKER.TAG.cms_advertisement.type.None=Source link
FREEMARKER.TAG.cms_advertisement.type.Stat=Statistics link
# 统计菜单 # 统计菜单
STAT.MENU.CmsAdv=Advertising Statistics STAT.MENU.CmsAdv=Advertising Statistics
@ -17,3 +21,5 @@ STAT.MENU.CmsAdViewLog=View Logs
# 定时任务 # 定时任务
SCHEDULED_TASK.AdvertisementStatJob=AD Statistics Task SCHEDULED_TASK.AdvertisementStatJob=AD Statistics Task
SCHEDULED_TASK.AdvertisementPublishJob=AD Publish/Offline Task SCHEDULED_TASK.AdvertisementPublishJob=AD Publish/Offline Task
MONITORED.CACHE.AD=Advertisement

View File

@ -5,8 +5,12 @@ CMS.CONTENCORE.PAGEWIDGET.ads=廣告位
ADVERTISEMENT.TYPE.image=圖片 ADVERTISEMENT.TYPE.image=圖片
# 模板freemarker # 模板freemarker
FREEMARKER.TAG.NAME.cms_advertisement=廣告列表標籤 FREEMARKER.TAG.cms_advertisement.NAME=廣告列表標籤
FREEMARKER.TAG.DESC.cms_advertisement=獲取廣告數據列表,內嵌<#list DataList as ad>${ad.name}</#list>遍曆數據 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=統計鏈接
# 統計菜單 # 統計菜單
STAT.MENU.CmsAdv=廣告數據統計 STAT.MENU.CmsAdv=廣告數據統計
@ -17,3 +21,5 @@ STAT.MENU.CmsAdViewLog=廣告展現日誌
# 定時任務 # 定時任務
SCHEDULED_TASK.AdvertisementStatJob=廣告統計任務 SCHEDULED_TASK.AdvertisementStatJob=廣告統計任務
SCHEDULED_TASK.AdvertisementPublishJob=廣告定時發布下線任務 SCHEDULED_TASK.AdvertisementPublishJob=廣告定時發布下線任務
MONITORED.CACHE.AD=廣告

View File

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

View File

@ -23,7 +23,6 @@ import com.chestnut.common.utils.HtmlUtils;
import com.chestnut.common.utils.SpringUtils; import com.chestnut.common.utils.SpringUtils;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.AbstractContent; import com.chestnut.contentcore.core.AbstractContent;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.enums.ContentCopyType; import com.chestnut.contentcore.enums.ContentCopyType;
import com.chestnut.contentcore.service.IResourceService; import com.chestnut.contentcore.service.IResourceService;
@ -31,6 +30,7 @@ import com.chestnut.contentcore.util.ResourceUtils;
import com.chestnut.system.fixed.dict.YesOrNo; import com.chestnut.system.fixed.dict.YesOrNo;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -41,11 +41,9 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
private IResourceService resourceService; private IResourceService resourceService;
@Override @Override
public Long add() { protected void add0() {
super.add();
if (!this.hasExtendEntity()) { if (!this.hasExtendEntity()) {
this.getContentService().dao().save(this.getContentEntity()); return;
return this.getContentEntity().getContentId();
} }
CmsArticleDetail articleDetail = this.getExtendEntity(); CmsArticleDetail articleDetail = this.getExtendEntity();
articleDetail.setContentId(this.getContentEntity().getContentId()); articleDetail.setContentId(this.getContentEntity().getContentId());
@ -60,23 +58,22 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
} }
articleDetail.setContentHtml(contentHtml); articleDetail.setContentHtml(contentHtml);
// 正文首图作为logo // 正文首图作为logo
if (StringUtils.isEmpty(this.getContentEntity().getLogo()) if (StringUtils.isEmpty(this.getContentEntity().getImages())
&& AutoArticleLogo.getValue(this.getSite().getConfigProps())) { && AutoArticleLogo.getValue(this.getSite().getConfigProps())) {
this.getContentEntity().setLogo(this.getFirstImage(articleDetail.getContentHtml())); String firstImage = this.getFirstImage(articleDetail.getContentHtml());
if (Objects.nonNull(firstImage)) {
this.getContentEntity().setImages(List.of(firstImage));
}
} }
this.getContentService().dao().save(this.getContentEntity());
this.getArticleService().dao().save(articleDetail); this.getArticleService().dao().save(articleDetail);
return this.getContentEntity().getContentId();
} }
@Override @Override
public Long save() { protected void save0() {
super.save();
// 非映射内容或标题内容修改文章详情 // 非映射内容或标题内容修改文章详情
if (!this.hasExtendEntity()) { if (!this.hasExtendEntity()) {
this.getContentService().dao().updateById(this.getContentEntity());
this.getArticleService().dao().removeById(this.getContentEntity().getContentId()); this.getArticleService().dao().removeById(this.getContentEntity().getContentId());
return this.getContentEntity().getContentId(); return;
} }
CmsArticleDetail articleDetail = this.getExtendEntity(); CmsArticleDetail articleDetail = this.getExtendEntity();
// 处理内部链接 // 处理内部链接
@ -89,13 +86,14 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
} }
articleDetail.setContentHtml(contentHtml); articleDetail.setContentHtml(contentHtml);
// 正文首图作为logo // 正文首图作为logo
if (StringUtils.isEmpty(this.getContentEntity().getLogo()) if (StringUtils.isEmpty(this.getContentEntity().getImages())
&& AutoArticleLogo.getValue(this.getSite().getConfigProps())) { && AutoArticleLogo.getValue(this.getSite().getConfigProps())) {
this.getContentEntity().setLogo(this.getFirstImage(articleDetail.getContentHtml())); String firstImage = this.getFirstImage(articleDetail.getContentHtml());
if (Objects.nonNull(firstImage)) {
this.getContentEntity().setImages(List.of(firstImage));
}
} }
this.getContentService().dao().updateById(this.getContentEntity());
this.getArticleService().dao().saveOrUpdate(articleDetail); this.getArticleService().dao().saveOrUpdate(articleDetail);
return this.getContentEntity().getContentId();
} }
/** /**
@ -116,8 +114,7 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
} }
@Override @Override
public void delete() { protected void delete0() {
super.delete();
if (this.hasExtendEntity()) { if (this.hasExtendEntity()) {
this.getArticleService().dao() this.getArticleService().dao()
.deleteByIdAndBackup(this.getExtendEntity(), this.getOperatorUName()); .deleteByIdAndBackup(this.getExtendEntity(), this.getOperatorUName());
@ -125,16 +122,13 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
} }
@Override @Override
public CmsContent copyTo(CmsCatalog toCatalog, Integer copyType) { public void copyTo0(CmsContent newContent, Integer copyType) {
CmsContent copyContent = super.copyTo(toCatalog, copyType);
if (this.hasExtendEntity() && ContentCopyType.isIndependency(copyType)) { if (this.hasExtendEntity() && ContentCopyType.isIndependency(copyType)) {
Long newContentId = (Long) this.getParams().get("NewContentId");
CmsArticleDetail newArticleDetail = new CmsArticleDetail(); CmsArticleDetail newArticleDetail = new CmsArticleDetail();
BeanUtils.copyProperties(this.getExtendEntity(), newArticleDetail, "contentId"); BeanUtils.copyProperties(this.getExtendEntity(), newArticleDetail, "contentId");
newArticleDetail.setContentId(newContentId); newArticleDetail.setContentId(newContent.getContentId());
this.getArticleService().dao().save(newArticleDetail); this.getArticleService().dao().save(newArticleDetail);
} }
return copyContent;
} }
@Override @Override

View File

@ -35,23 +35,19 @@ import com.chestnut.contentcore.domain.*;
import com.chestnut.contentcore.domain.dto.PublishPipeProp; import com.chestnut.contentcore.domain.dto.PublishPipeProp;
import com.chestnut.contentcore.domain.vo.ContentVO; import com.chestnut.contentcore.domain.vo.ContentVO;
import com.chestnut.contentcore.enums.ContentCopyType; import com.chestnut.contentcore.enums.ContentCopyType;
import com.chestnut.contentcore.enums.ContentOpType; import com.chestnut.contentcore.fixed.dict.ContentOpType;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.service.ICatalogService; import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService; import com.chestnut.contentcore.service.IContentService;
import com.chestnut.contentcore.service.IPublishPipeService; import com.chestnut.contentcore.service.IPublishPipeService;
import com.chestnut.contentcore.service.ISiteService; import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.fixed.dict.YesOrNo; import com.chestnut.system.fixed.dict.YesOrNo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.InputStream;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Optional;
@Component(IContentType.BEAN_NAME_PREFIX + ArticleContentType.ID) @Component(IContentType.BEAN_NAME_PREFIX + ArticleContentType.ID)
@RequiredArgsConstructor @RequiredArgsConstructor
@ -101,29 +97,22 @@ public class ArticleContentType implements IContentType {
} }
@Override @Override
public IContent<?> readRequest(HttpServletRequest request) throws IOException { public IContent<?> readFrom(InputStream is) {
ArticleDTO dto = JacksonUtils.from(request.getInputStream(), ArticleDTO.class); ArticleDTO dto = JacksonUtils.from(is, ArticleDTO.class);
return readFrom0(dto);
CmsContent contentEntity;
if (dto.getOpType() == ContentOpType.UPDATE) {
contentEntity = this.contentService.dao().getById(dto.getContentId());
Assert.notNull(contentEntity,
() -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", dto.getContentId()));
} else {
contentEntity = new CmsContent();
} }
BeanUtils.copyProperties(dto, contentEntity);
CmsCatalog catalog = this.catalogService.getCatalog(dto.getCatalogId());
contentEntity.setSiteId(catalog.getSiteId());
contentEntity.setAttributes(ContentAttribute.convertInt(dto.getAttributes()));
// 发布通道配置
Map<String, Map<String, Object>> publishPipProps = new HashMap<>();
dto.getPublishPipeProps().forEach(prop -> {
publishPipProps.put(prop.getPipeCode(), prop.getProps());
});
contentEntity.setPublishPipeProps(publishPipProps);
private ArticleContent readFrom0(ArticleDTO dto) {
// 内容基础信息
CmsContent contentEntity = dto.convertToContentEntity(this.catalogService, this.contentService);
// 文章扩展信息
CmsArticleDetail extendEntity = new CmsArticleDetail(); CmsArticleDetail extendEntity = new CmsArticleDetail();
if (ContentOpType.UPDATE.equals(dto.getOpType())) {
Optional<CmsArticleDetail> opt = this.articleService.dao().getOptById(contentEntity.getContentId());
if (opt.isPresent()) {
extendEntity = opt.get();
}
}
BeanUtils.copyProperties(dto, extendEntity); BeanUtils.copyProperties(dto, extendEntity);
ArticleContent content = new ArticleContent(); ArticleContent content = new ArticleContent();
@ -148,9 +137,6 @@ public class ArticleContentType implements IContentType {
CmsArticleDetail extendEntity = this.articleService.dao().getById(contentId); CmsArticleDetail extendEntity = this.articleService.dao().getById(contentId);
vo = ArticleVO.newInstance(contentEntity, extendEntity); vo = ArticleVO.newInstance(contentEntity, extendEntity);
if (StringUtils.isNotEmpty(vo.getLogo())) {
vo.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(vo.getLogo()));
}
// 发布通道模板数据 // 发布通道模板数据
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(), List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
PublishPipePropUseType.Content, contentEntity.getPublishPipeProps()); PublishPipePropUseType.Content, contentEntity.getPublishPipeProps());

View File

@ -103,7 +103,7 @@ public class ArticleCoreDataHandler implements ICoreDataHandler {
data.setContentHtml(html.toString()); data.setContentHtml(html.toString());
articleService.dao().save(data); articleService.dao().save(data);
} catch (Exception e) { } catch (Exception e) {
AsyncTaskManager.addErrMessage("导入文章数据失败:" + oldContentId); AsyncTaskManager.addErrMessage("导入文章数据`" + oldContentId + "`失败:" + e.getMessage());
log.error("Import article detail failed: {}", oldContentId, e); log.error("Import article detail failed: {}", oldContentId, e);
} }
} }

View File

@ -13,32 +13,37 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.chestnut.common.storage; package com.chestnut.article;
import java.util.List; /**
* 文正正文文档格式
*
* @author 兮玥
* @email 190785909@qq.com
*/
public interface IArticleBodyFormat {
public class StorageListResult<T> { String BEAN_PREFIX = "ArticleBodyFormat_";
private List<T> objects;
/** /**
* 列举文件使用的continuationToken * ID
*/ */
private String nextContinuationToken ; String getId();
public String getNextContinuationToken() { /**
return nextContinuationToken; * 名称
*/
String getName();
/**
* 编辑器内容初始化处理
*/
default String initEditor(String contentHtml) {
return contentHtml;
} }
public void setNextContinuationToken(String nextContinuationToken) { /**
this.nextContinuationToken = nextContinuationToken; * 文章正文内容发布预览处理
} */
String deal(String contentHtml, String publishPipeCode, boolean isPreview);
public List<T> getObjects() {
return objects;
}
public void setObjects(List<T> objects) {
this.objects = objects;
}
} }

View File

@ -16,8 +16,10 @@
package com.chestnut.article.controller; package com.chestnut.article.controller;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.PublishPipeProp_UEditorCss; import com.chestnut.article.PublishPipeProp_UEditorCss;
import com.chestnut.common.domain.R; import com.chestnut.common.domain.R;
import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.security.anno.Priv; import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController; import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
@ -28,13 +30,16 @@ import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IPublishPipeService; import com.chestnut.contentcore.service.IPublishPipeService;
import com.chestnut.contentcore.service.ISiteService; import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.system.security.AdminUserType; import com.chestnut.system.security.AdminUserType;
import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -57,6 +62,8 @@ public class ArticleController extends BaseRestController {
private final IPublishPipeService publishPipeService; private final IPublishPipeService publishPipeService;
private final List<IArticleBodyFormat> articleBodyFormatList;
@GetMapping("/ueditor_css") @GetMapping("/ueditor_css")
public R<?> getUEditorCss(@RequestParam Long catalogId) { public R<?> getUEditorCss(@RequestParam Long catalogId) {
CmsCatalog catalog = catalogService.getCatalog(catalogId); CmsCatalog catalog = catalogService.getCatalog(catalogId);
@ -73,5 +80,24 @@ public class ArticleController extends BaseRestController {
}); });
return R.ok(data); return R.ok(data);
} }
@GetMapping("/formats")
public R<?> getArticleBodyFormats() {
List<ArticleBodyFormat> list = this.articleBodyFormatList.stream().map(item -> {
ArticleBodyFormat format = new ArticleBodyFormat();
format.setId(item.getId());
format.setName(I18nUtils.get(item.getName()));
return format;
}).toList();
I18nUtils.replaceI18nFields(list);
return R.ok(list);
}
@Getter
@Setter
static class ArticleBodyFormat {
private String id;
private String name;
}
} }

View File

@ -113,27 +113,24 @@ public class ArticleApiController extends BaseRestController {
return R.ok(List.of()); return R.ok(List.of());
} }
List<Long> contentIds = pageResult.getRecords().stream().map(CmsContent::getContentId).toList(); List<Long> contentIds = pageResult.getRecords().stream().map(CmsContent::getContentId).toList();
Map<Long, CmsArticleDetail> articleDetails = this.articleService.dao().listByIds(contentIds) Map<Long, CmsArticleDetail> articleDetails = new HashMap<>();
.stream().collect(Collectors.toMap(CmsArticleDetail::getContentId, c -> c)); if (text) {
articleDetails.putAll(this.articleService.dao().listByIds(contentIds)
.stream().collect(Collectors.toMap(CmsArticleDetail::getContentId, c -> c)));
}
Map<Long, CmsCatalog> loadedCatalogs = new HashMap<>();
List<ArticleApiVO> list = new ArrayList<>(); List<ArticleApiVO> list = new ArrayList<>();
pageResult.getRecords().forEach(c -> { pageResult.getRecords().forEach(c -> {
ArticleApiVO dto = ArticleApiVO.newInstance(c); ArticleApiVO vo = ArticleApiVO.newInstance(c, null);
CmsCatalog catalog = loadedCatalogs.get(c.getCatalogId()); CmsCatalog catalog = this.catalogService.getCatalog(c.getCatalogId());
if (Objects.isNull(catalog)) { vo.setCatalogName(catalog.getName());
catalog = this.catalogService.getCatalog(c.getCatalogId()); vo.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
loadedCatalogs.put(catalog.getCatalogId(), catalog); 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());
} }
dto.setCatalogName(catalog.getName()); list.add(vo);
dto.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
dto.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview));
dto.setLogoSrc(InternalUrlUtils.getActualUrl(c.getLogo(), publishPipeCode, preview));
CmsArticleDetail articleDetail = articleDetails.get(c.getContentId());
if (Objects.nonNull(articleDetail)) {
dto.setContentHtml(articleDetail.getContentHtml());
}
list.add(dto);
}); });
return R.ok(list); return R.ok(list);
} }

View File

@ -47,11 +47,6 @@ public class CmsArticleDetail implements IBackupable<BCmsArticleDetail> {
*/ */
private Long siteId; private Long siteId;
/**
* 正文详情json
*/
private String contentJson;
/** /**
* 正文详情html * 正文详情html
*/ */
@ -67,6 +62,11 @@ public class CmsArticleDetail implements IBackupable<BCmsArticleDetail> {
*/ */
private String downloadRemoteImage; private String downloadRemoteImage;
/**
* 正文格式图文混排MARKDOWN
*/
private String format;
@Override @Override
public BCmsArticleDetail toBackupEntity() { public BCmsArticleDetail toBackupEntity() {
BCmsArticleDetail backupEntity = new BCmsArticleDetail(); BCmsArticleDetail backupEntity = new BCmsArticleDetail();

View File

@ -15,17 +15,12 @@
*/ */
package com.chestnut.article.domain.dto; package com.chestnut.article.domain.dto;
import org.springframework.beans.BeanUtils;
import com.chestnut.article.domain.CmsArticleDetail; import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.dto.ContentDTO; import com.chestnut.contentcore.domain.dto.ContentDTO;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.beans.BeanUtils;
@Getter @Getter
@Setter @Setter
@ -46,13 +41,14 @@ public class ArticleDTO extends ContentDTO {
*/ */
private String pageTitles; private String pageTitles;
public static ArticleDTO newInstance(CmsContent content, CmsArticleDetail articleDetail) { /**
* 文档格式
*/
private String format;
public static ArticleDTO newInstance(CmsContent content, CmsArticleDetail articleDetail, boolean preview) {
ArticleDTO dto = new ArticleDTO(); ArticleDTO dto = new ArticleDTO();
BeanUtils.copyProperties(content, dto); dto.initByContent(content, preview);
dto.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
BeanUtils.copyProperties(articleDetail, dto); BeanUtils.copyProperties(articleDetail, dto);
return dto; return dto;
} }

View File

@ -15,11 +15,14 @@
*/ */
package com.chestnut.article.domain.vo; package com.chestnut.article.domain.vo;
import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.vo.ContentApiVO; import com.chestnut.contentcore.domain.vo.ContentApiVO;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.Objects;
/** /**
* <TODO description class purpose> * <TODO description class purpose>
* *
@ -32,9 +35,12 @@ public class ArticleApiVO extends ContentApiVO {
private String contentHtml; private String contentHtml;
public static ArticleApiVO newInstance(CmsContent cmsContent) { public static ArticleApiVO newInstance(CmsContent cmsContent, CmsArticleDetail articleDetail) {
ArticleApiVO vo = new ArticleApiVO(); ArticleApiVO vo = new ArticleApiVO();
vo.copyProperties(cmsContent); vo.initByContent(cmsContent, false);
if (Objects.nonNull(articleDetail)) {
vo.setContentHtml(articleDetail.getContentHtml());
}
return vo; return vo;
} }
} }

View File

@ -15,20 +15,14 @@
*/ */
package com.chestnut.article.domain.vo; package com.chestnut.article.domain.vo;
import java.util.Objects;
import org.springframework.beans.BeanUtils;
import com.chestnut.article.domain.CmsArticleDetail; import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.vo.ContentVO; import com.chestnut.contentcore.domain.vo.ContentVO;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.Objects;
@Getter @Getter
@Setter @Setter
public class ArticleVO extends ContentVO { public class ArticleVO extends ContentVO {
@ -48,16 +42,20 @@ public class ArticleVO extends ContentVO {
*/ */
private String pageTitles; private String pageTitles;
/**
* 文档格式
*/
private String format;
public static ArticleVO newInstance(CmsContent content, CmsArticleDetail articleDetail) { public static ArticleVO newInstance(CmsContent content, CmsArticleDetail articleDetail) {
ArticleVO dto = new ArticleVO(); ArticleVO vo = new ArticleVO();
BeanUtils.copyProperties(content, dto); vo.initByContent(content, true);
dto.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
if (Objects.nonNull(articleDetail)) { if (Objects.nonNull(articleDetail)) {
BeanUtils.copyProperties(articleDetail, dto); vo.setContentHtml(articleDetail.getContentHtml());
vo.setDownloadRemoteImage(articleDetail.getDownloadRemoteImage());
vo.setPageTitles(articleDetail.getPageTitles());
vo.setFormat(articleDetail.getFormat());
} }
return dto; return vo;
} }
} }

View File

@ -0,0 +1,57 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.article.format;
import com.chestnut.article.ArticleUtils;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.contentcore.util.InternalUrlUtils;
import org.springframework.stereotype.Component;
/**
* 文章正文文档格式富文本
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IArticleBodyFormat.BEAN_PREFIX + ArticleBodyFormat_RichText.ID)
public class ArticleBodyFormat_RichText implements IArticleBodyFormat {
public static final String ID = "RichText";
@Override
public String getId() {
return ID;
}
@Override
public String getName() {
return "{ArticleBodyFormat.RichText}";
}
@Override
public String initEditor(String contentHtml) {
return InternalUrlUtils.dealResourceInternalUrl(contentHtml);
}
@Override
public String deal(String contentHtml, String publishPipeCode, boolean isPreview) {
// 处理内容扩展模板占位符
contentHtml = ArticleUtils.dealContentEx(contentHtml, publishPipeCode, isPreview);
// 处理正文内部链接
contentHtml = InternalUrlUtils.dealInternalUrl(contentHtml, publishPipeCode, isPreview);
return contentHtml;
}
}

View File

@ -16,6 +16,7 @@
package com.chestnut.article.listener; package com.chestnut.article.listener;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.domain.BCmsArticleDetail; import com.chestnut.article.domain.BCmsArticleDetail;
import com.chestnut.article.domain.CmsArticleDetail; import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.article.domain.vo.ArticleVO; import com.chestnut.article.domain.vo.ArticleVO;
@ -24,13 +25,13 @@ import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.listener.event.AfterContentEditorInitEvent; import com.chestnut.contentcore.listener.event.AfterContentEditorInitEvent;
import com.chestnut.contentcore.listener.event.BeforeSiteDeleteEvent; import com.chestnut.contentcore.listener.event.BeforeSiteDeleteEvent;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
import java.util.Objects;
@Slf4j @Slf4j
@Component @Component
@ -81,7 +82,12 @@ public class ArticleListener {
@EventListener @EventListener
public void afterContentEditorInit(AfterContentEditorInitEvent event) { public void afterContentEditorInit(AfterContentEditorInitEvent event) {
if (event.getContentVO() instanceof ArticleVO vo) { if (event.getContentVO() instanceof ArticleVO vo) {
vo.setContentHtml(InternalUrlUtils.dealResourceInternalUrl(vo.getContentHtml())); IArticleBodyFormat articleBodyFormat = articleService.getArticleBodyFormat(vo.getFormat());
if (Objects.nonNull(articleBodyFormat)) {
vo.setContentHtml(articleBodyFormat.initEditor(vo.getContentHtml()));
} else {
log.warn("Unsupported article body format: " + vo.getFormat());
}
} }
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.chestnut.article.service; package com.chestnut.article.service;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.dao.CmsArticleDetailDAO; import com.chestnut.article.dao.CmsArticleDetailDAO;
import com.chestnut.common.db.mybatisplus.HasDAO; import com.chestnut.common.db.mybatisplus.HasDAO;
@ -25,4 +26,5 @@ import com.chestnut.common.db.mybatisplus.HasDAO;
* @email 190785909@qq.com * @email 190785909@qq.com
*/ */
public interface IArticleService extends HasDAO<CmsArticleDetailDAO> { public interface IArticleService extends HasDAO<CmsArticleDetailDAO> {
IArticleBodyFormat getArticleBodyFormat(String format);
} }

View File

@ -15,12 +15,15 @@
*/ */
package com.chestnut.article.service.impl; package com.chestnut.article.service.impl;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.dao.CmsArticleDetailDAO; import com.chestnut.article.dao.CmsArticleDetailDAO;
import com.chestnut.article.service.IArticleService; import com.chestnut.article.service.IArticleService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Map;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -28,8 +31,17 @@ public class ArticleServiceImpl implements IArticleService {
private final CmsArticleDetailDAO dao; private final CmsArticleDetailDAO dao;
private final Map<String, IArticleBodyFormat> articleBodyFormatMap;
@Override @Override
public CmsArticleDetailDAO dao() { public CmsArticleDetailDAO dao() {
return this.dao; return this.dao;
} }
@Override
public IArticleBodyFormat getArticleBodyFormat(String format) {
return articleBodyFormatMap.get(IArticleBodyFormat.BEAN_PREFIX + format);
}
} }

View File

@ -15,12 +15,14 @@
*/ */
package com.chestnut.article.template.func; package com.chestnut.article.template.func;
import com.chestnut.article.ArticleUtils; import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.format.ArticleBodyFormat_RichText;
import com.chestnut.article.service.IArticleService;
import com.chestnut.common.staticize.FreeMarkerUtils; import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.staticize.func.AbstractFunc; import com.chestnut.common.staticize.func.AbstractFunc;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
import freemarker.core.Environment; import freemarker.core.Environment;
import freemarker.template.SimpleScalar; import freemarker.template.SimpleScalar;
import freemarker.template.TemplateModelException; import freemarker.template.TemplateModelException;
@ -28,6 +30,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Freemarker模板自定义函数处理Html文本内容中的内部链接 * Freemarker模板自定义函数处理Html文本内容中的内部链接
@ -38,7 +41,13 @@ public class dealArticleBodyFunction extends AbstractFunc {
static final String FUNC_NAME = "dealArticleBody"; static final String FUNC_NAME = "dealArticleBody";
private static final String DESC = "{FREEMARKER.FUNC.DESC." + FUNC_NAME + "}"; private static final String DESC = "{FREEMARKER.FUNC." + FUNC_NAME + ".DESC}";
private static final String ARG1_NAME = "{FREEMARKER.FUNC." + FUNC_NAME + ".Arg1.Name}";
private static final String ARG2_NAME = "{FREEMARKER.FUNC." + FUNC_NAME + ".Arg2.Name}";
private final IArticleService articleService;
@Override @Override
public String getFuncName() { public String getFuncName() {
@ -55,18 +64,33 @@ public class dealArticleBodyFunction extends AbstractFunc {
if (args.length < 1) { if (args.length < 1) {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
String format;
if (args.length > 1) {
String _format = ((SimpleScalar) args[1]).getAsString();
if (StringUtils.isEmpty(_format)) {
_format = ArticleBodyFormat_RichText.ID;
}
format = _format;
} else {
format = ArticleBodyFormat_RichText.ID;
}
TemplateContext context = FreeMarkerUtils.getTemplateContext(Environment.getCurrentEnvironment()); TemplateContext context = FreeMarkerUtils.getTemplateContext(Environment.getCurrentEnvironment());
SimpleScalar simpleScalar = (SimpleScalar) args[0]; SimpleScalar simpleScalar = (SimpleScalar) args[0];
String contentHtml = simpleScalar.getAsString(); String contentHtml = simpleScalar.getAsString();
// 处理内容扩展模板占位符 IArticleBodyFormat articleBodyFormat = articleService.getArticleBodyFormat(format);
contentHtml = ArticleUtils.dealContentEx(contentHtml, context.getPublishPipeCode(), context.isPreview()); Assert.notNull(articleBodyFormat, () -> new TemplateModelException("Unsupported article body format: " + format));
// 处理正文内部链接 if (Objects.isNull(articleBodyFormat)) {
contentHtml = InternalUrlUtils.dealInternalUrl(contentHtml, context.getPublishPipeCode(), context.isPreview()); articleBodyFormat = articleService.getArticleBodyFormat(ArticleBodyFormat_RichText.ID);
}
contentHtml = articleBodyFormat.deal(contentHtml, context.getPublishPipeCode(), context.isPreview());
return contentHtml; return contentHtml;
} }
@Override @Override
public List<FuncArg> getFuncArgs() { public List<FuncArg> getFuncArgs() {
return List.of(new FuncArg("HTML文章正文内容", FuncArgType.String, true, null)); return List.of(
new FuncArg(ARG1_NAME, FuncArgType.String, true, null),
new FuncArg(ARG2_NAME, FuncArgType.String, false, null, ArticleBodyFormat_RichText.ID)
);
} }
} }

View File

@ -17,10 +17,13 @@ package com.chestnut.article.template.tag;
import com.chestnut.article.domain.CmsArticleDetail; import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.article.mapper.CmsArticleDetailMapper; import com.chestnut.article.mapper.CmsArticleDetailMapper;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.staticize.FreeMarkerUtils; import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.StaticizeConstants; import com.chestnut.common.staticize.StaticizeConstants;
import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.staticize.enums.TagAttrDataType; import com.chestnut.common.staticize.enums.TagAttrDataType;
import com.chestnut.common.staticize.exception.DuplicatePageFlagException;
import com.chestnut.common.staticize.exception.PageIndexOutOfBoundsException;
import com.chestnut.common.staticize.tag.AbstractTag; import com.chestnut.common.staticize.tag.AbstractTag;
import com.chestnut.common.staticize.tag.TagAttr; import com.chestnut.common.staticize.tag.TagAttr;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
@ -30,35 +33,36 @@ import com.chestnut.contentcore.mapper.CmsContentMapper;
import freemarker.core.Environment; import freemarker.core.Environment;
import freemarker.template.TemplateException; import freemarker.template.TemplateException;
import freemarker.template.TemplateModel; import freemarker.template.TemplateModel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@RequiredArgsConstructor
@Component @Component
@RequiredArgsConstructor
public class CmsArticleTag extends AbstractTag { public class CmsArticleTag extends AbstractTag {
public static final String TAG_NAME = "cms_article"; public static final String TAG_NAME = "cms_article";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}"; public final static String NAME = "{FREEMARKER.TAG." + TAG_NAME + ".NAME}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}"; 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_PAGE = "{FREEMARKER.TAG." + TAG_NAME + ".page}";
public static final String TagAttr_ContentId = "contentId"; public static final String ATTR_CONTENT_ID = "contentId";
public static final String ATTR_PAGE = "page";
public static final String TagAttr_Page = "page";
public static final String TemplateVariable_ArticleContent = "ArticleContent"; public static final String TemplateVariable_ArticleContent = "ArticleContent";
// CKEditor5: <div class="page-break" style="page-break-after:always;"><span style="display:none;">&nbsp;</span></div>
// private static final String PAGE_BREAK_SPLITER = "<div[^>]+class=['\"]page-break['\"].*?</div>"; // private static final String PAGE_BREAK_SPLITER = "<div[^>]+class=['\"]page-break['\"].*?</div>";
private static final String PAGE_BREAK_SPLITER = "__XY_UEDITOR_PAGE_BREAK__"; private static final String PAGE_BREAK_SPLITER = "__XY_UEDITOR_PAGE_BREAK__";
private final CmsContentMapper contentMapper; private final CmsContentMapper contentMapper;
private final CmsArticleDetailMapper articleMapper; private final CmsArticleDetailMapper articleMapper;
@ -66,16 +70,16 @@ public class CmsArticleTag extends AbstractTag {
@Override @Override
public List<TagAttr> getTagAttrs() { public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = new ArrayList<>(); List<TagAttr> tagAttrs = new ArrayList<>();
tagAttrs.add(new TagAttr(TagAttr_ContentId, true, TagAttrDataType.INTEGER, "文章内容ID")); tagAttrs.add(new TagAttr(ATTR_CONTENT_ID, true, TagAttrDataType.INTEGER, ATTR_USAGE_CONTENT_ID));
tagAttrs.add(new TagAttr(TagAttr_Page, false, TagAttrDataType.BOOLEAN, "是否分页默认false")); tagAttrs.add(new TagAttr(ATTR_PAGE, false, TagAttrDataType.BOOLEAN, ATTR_USAGE_PAGE, Boolean.FALSE.toString()));
return tagAttrs; return tagAttrs;
} }
@Override @Override
public Map<String, TemplateModel> execute0(Environment env, Map<String, String> attrs) public Map<String, TemplateModel> execute0(Environment env, Map<String, String> attrs)
throws TemplateException, IOException { throws TemplateException {
String contentHtml = null; String contentHtml;
long contentId = MapUtils.getLongValue(attrs, TagAttr_ContentId, 0); long contentId = MapUtils.getLongValue(attrs, ATTR_CONTENT_ID, 0);
if (contentId <= 0) { if (contentId <= 0) {
throw new TemplateException("Invalid contentId: " + contentId, env); throw new TemplateException("Invalid contentId: " + contentId, env);
} }
@ -92,24 +96,32 @@ public class CmsArticleTag extends AbstractTag {
} }
contentHtml = articleDetail.getContentHtml(); contentHtml = articleDetail.getContentHtml();
TemplateContext context = FreeMarkerUtils.getTemplateContext(env); TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
boolean page = MapUtils.getBooleanValue(attrs, TagAttr_Page, false); boolean page = MapUtils.getBooleanValue(attrs, ATTR_PAGE, false);
if (page) { if (page) {
if (context.isPaged()) { if (context.isPaged()) {
throw new TemplateException("分页标识已被其他标签激活", env); throw new DuplicatePageFlagException(env);
} }
context.setPaged(true); context.setPaged(true);
String[] pageContents = contentHtml.split(PAGE_BREAK_SPLITER); String[] pageContents = contentHtml.split(PAGE_BREAK_SPLITER);
if (context.getPageIndex() > pageContents.length) { if (context.getPageIndex() > pageContents.length) {
throw new TemplateException(StringUtils.messageFormat("文章内容分页越界:{0}, 最大页码:{1}。", context.getPageIndex(), throw new PageIndexOutOfBoundsException(context.getPageIndex(), pageContents.length, env);
pageContents.length), env);
} }
context.setPageTotal(pageContents.length); context.setPageTotal(pageContents.length);
env.setGlobalVariable(StaticizeConstants.TemplateVariable_PageTotal, env.setGlobalVariable(StaticizeConstants.TemplateVariable_PageTotal,
this.wrap(env, context.getPageTotal())); this.wrap(env, context.getPageTotal()));
contentHtml = pageContents[context.getPageIndex() - 1]; contentHtml = pageContents[context.getPageIndex() - 1];
} }
return Map.of(TemplateVariable_ArticleContent, this.wrap(env, contentHtml)); ArticleTagData data = new ArticleTagData(articleDetail.getFormat(), contentHtml);
return Map.of(
TemplateVariable_ArticleContent, this.wrap(env, contentHtml), // 兼容历史版本保留ArticleContent
StaticizeConstants.TemplateVariable_Data, this.wrap(env, data)
);
}
@Override
public Class<ArticleTagData> getDataClass() {
return ArticleTagData.class;
} }
@Override @Override
@ -126,4 +138,21 @@ public class CmsArticleTag extends AbstractTag {
public String getDescription() { public String getDescription() {
return DESC; return DESC;
} }
@Getter
@Setter
@NoArgsConstructor
public static class ArticleTagData {
@XComment("文章正文格式")
private String Format;
@XComment("文章正文")
private String ArticleContent;
public ArticleTagData(String format, String articleContent) {
this.Format = format;
this.ArticleContent = articleContent;
}
}
} }

View File

@ -2,6 +2,13 @@
CMS.CONTENTCORE.CONTENT_TYPE.article=文章 CMS.CONTENTCORE.CONTENT_TYPE.article=文章
# 模板freemarker # 模板freemarker
FREEMARKER.TAG.NAME.cms_article=文章正文数据标签 FREEMARKER.TAG.cms_article.NAME=文章正文数据标签
FREEMARKER.TAG.DESC.cms_article=获取文章正文数据,标签内使用${ArticleContent}获取正文详情 FREEMARKER.TAG.cms_article.DESC=获取文章正文数据,标签内使用`${ArticleContent}`获取正文详情
FREEMARKER.FUNC.DESC.dealArticleBody=文章正文处理函数,主要用来处理文章内容中的内部链接和扩展模板占位符 FREEMARKER.TAG.cms_article.contentId=文章ID
FREEMARKER.TAG.cms_article.page=是否分頁
FREEMARKER.FUNC.dealArticleBody.DESC=文章正文处理函数,主要用来处理文章内容中的内部链接和扩展模板占位符
FREEMARKER.FUNC.dealArticleBody.Arg1.Name=文章正文内容
FREEMARKER.FUNC.dealArticleBody.Arg2.Name=文章正文格式
# 固定字典项
ArticleBodyFormat.RichText=富文本

View File

@ -2,6 +2,12 @@
CMS.CONTENTCORE.CONTENT_TYPE.article=Article CMS.CONTENTCORE.CONTENT_TYPE.article=Article
# 模板freemarker # 模板freemarker
FREEMARKER.TAG.NAME.cms_article=Article body tag FREEMARKER.TAG.cms_article.NAME=Article body tag
FREEMARKER.TAG.DESC.cms_article=Fetch article body details, use "${ArticleContent}" in tag to get body text. FREEMARKER.TAG.cms_article.DESC=Fetch article body details, use `${ArticleContent}` in tag to get body text.
FREEMARKER.FUNC.DESC.dealArticleBody=Article body deal function FREEMARKER.TAG.cms_article.contentId=Article content id.
FREEMARKER.TAG.cms_article.page=Pageable
FREEMARKER.FUNC.dealArticleBody.DESC=Article body deal function
FREEMARKER.FUNC.dealArticleBody.Arg1.Name=Article body text
FREEMARKER.FUNC.dealArticleBody.Arg2.Name=Article body format
ArticleBodyFormat.RichText=Rich Text

View File

@ -2,6 +2,12 @@
CMS.CONTENTCORE.CONTENT_TYPE.article=文章 CMS.CONTENTCORE.CONTENT_TYPE.article=文章
# 模板freemarker # 模板freemarker
FREEMARKER.TAG.NAME.cms_article=文章正文數據標籤 FREEMARKER.TAG.cms_article.NAME=文章正文數據標籤
FREEMARKER.TAG.DESC.cms_article=獲取文章正文數據,標籤內使用${ArticleContent}獲取正文詳情 FREEMARKER.TAG.cms_article.DESC=獲取文章正文數據,標籤內使用`${ArticleContent}`獲取正文詳情
FREEMARKER.FUNC.DESC.dealArticleBody=文章正文處理函數,主要用來處理文章內容中的內部連結和擴展模板佔位符 FREEMARKER.TAG.cms_article.contentId=文章ID
FREEMARKER.TAG.cms_article.page=是否分頁
FREEMARKER.FUNC.dealArticleBody.DESC=文章正文處理函數,主要用來處理文章內容中的內部連結和擴展模板佔位符
FREEMARKER.FUNC.dealArticleBody.Arg1.Name=文章正文內容
FREEMARKER.FUNC.dealArticleBody.Arg2.Name=文章正文格式
ArticleBodyFormat.RichText=富文本

View File

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

View File

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

View File

@ -42,8 +42,14 @@ import java.util.stream.Collectors;
public class CmsCommentTag extends AbstractListTag { public class CmsCommentTag extends AbstractListTag {
public final static String TAG_NAME = "cms_comment"; public final static String TAG_NAME = "cms_comment";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}"; public final static String NAME = "{FREEMARKER.TAG." + TAG_NAME + ".NAME}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}"; public final static String DESC = "{FREEMARKER.TAG." + TAG_NAME + ".DESC}";
public final static String ATTR_USAGE_UID = "{FREEMARKER.TAG." + TAG_NAME + ".uid}";
public final static String ATTR_USAGE_TYPE = "{FREEMARKER.TAG." + TAG_NAME + ".type}";
public final static String ATTR_UID = "uid";
public final static String ATTR_TYPE = "type";
private final ICommentService commentService; private final ICommentService commentService;
@ -52,15 +58,15 @@ public class CmsCommentTag extends AbstractListTag {
@Override @Override
public List<TagAttr> getTagAttrs() { public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = super.getTagAttrs(); List<TagAttr> tagAttrs = super.getTagAttrs();
tagAttrs.add(new TagAttr("uid", true, TagAttrDataType.INTEGER, "用户ID")); tagAttrs.add(new TagAttr(ATTR_UID, true, TagAttrDataType.INTEGER, ATTR_USAGE_UID));
tagAttrs.add(new TagAttr("type", false, TagAttrDataType.STRING, "来源类型", CommentConsts.COMMENT_SOURCE_TYPE)); tagAttrs.add(new TagAttr(ATTR_TYPE, false, TagAttrDataType.STRING, ATTR_USAGE_TYPE, CommentConsts.COMMENT_SOURCE_TYPE));
return tagAttrs; return tagAttrs;
} }
@Override @Override
public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException { public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException {
long uid = MapUtils.getLongValue(attrs, "uid"); long uid = MapUtils.getLongValue(attrs, ATTR_UID);
String sourceType = attrs.get("type"); String sourceType = attrs.get(ATTR_TYPE);
Page<Comment> pageResult = this.commentService.lambdaQuery() Page<Comment> pageResult = this.commentService.lambdaQuery()
.eq(Comment::getSourceType, sourceType) .eq(Comment::getSourceType, sourceType)
@ -87,6 +93,11 @@ public class CmsCommentTag extends AbstractListTag {
return TagPageData.of(pageResult.getRecords(), pageResult.getTotal()); return TagPageData.of(pageResult.getRecords(), pageResult.getTotal());
} }
@Override
public Class<Comment> getDataClass() {
return Comment.class;
}
@Override @Override
public String getTagName() { public String getTagName() {
return TAG_NAME; return TAG_NAME;

View File

@ -1,3 +1,5 @@
# freemarker模板标签 # freemarker模板标签
FREEMARKER.TAG.NAME.cms_comment=评论列表标签 FREEMARKER.TAG.cms_comment.NAME=评论列表标签
FREEMARKER.TAG.DESC.cms_comment=获取评论数据列表,内嵌<#list DataList as comment>${comment.name}</#list>遍历数据 FREEMARKER.TAG.cms_comment.DESC=获取评论数据列表,内嵌`<#list DataList as comment>${comment.name}</#list>`遍历数据
FREEMARKER.TAG.cms_comment.uid=用户ID
FREEMARKER.TAG.cms_comment.type=来源类型

View File

@ -1,3 +1,5 @@
# freemarker模板标签 # freemarker模板标签
FREEMARKER.TAG.NAME.cms_comment=Comment list tag FREEMARKER.TAG.cms_comment.NAME=Comment list tag
FREEMARKER.TAG.DESC.cms_comment=Fetch comment list, use <#list> in tag like "<#list DataList as comment>${comment.title}</#list>" to walk through the list of comments. FREEMARKER.TAG.cms_comment.DESC=Fetch comment list, use `<#list>` in tag like `<#list DataList as comment>${comment.title}</#list>` to walk through the list of comments.
FREEMARKER.TAG.cms_comment.uid=User id
FREEMARKER.TAG.cms_comment.type=Owner type

View File

@ -1,3 +1,5 @@
# freemarker模板標籤 # freemarker模板標籤
FREEMARKER.TAG.NAME.cms_comment=評論列表標籤 FREEMARKER.TAG.cms_comment.NAME=評論列表標籤
FREEMARKER.TAG.DESC.cms_comment=獲取評論數據列表,內嵌<#list DataList as comment>${comment.name}</#list>遍曆數據 FREEMARKER.TAG.cms_comment.DESC=獲取評論數據列表,內嵌`<#list DataList as comment>${comment.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_comment.uid=用戶ID
FREEMARKER.TAG.cms_comment.type=來源類型

View File

@ -7,19 +7,13 @@
<parent> <parent>
<groupId>com.chestnut</groupId> <groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId> <artifactId>chestnut-cms</artifactId>
<version>1.5.0</version> <version>1.5.1</version>
</parent> </parent>
<artifactId>chestnut-cms-contentcore</artifactId> <artifactId>chestnut-cms-contentcore</artifactId>
<description>内容核心</description> <description>内容核心</description>
<dependencies> <dependencies>
<!-- 图片处理工具库 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.chestnut</groupId> <groupId>com.chestnut</groupId>
<artifactId>chestnut-search</artifactId> <artifactId>chestnut-search</artifactId>

View File

@ -0,0 +1,91 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.cache;
import com.chestnut.common.redis.IMonitoredCache;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.domain.CmsCatalog;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
/**
* CatalogMonitoredCache
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IMonitoredCache.BEAN_PREFIX + CatalogMonitoredCache.ID)
@RequiredArgsConstructor
public class CatalogMonitoredCache implements IMonitoredCache<CmsCatalog> {
public static final String ID = "Catalog";
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "catalog:";
private final RedisCache redisCache;
@Override
public String getId() {
return ID;
}
@Override
public String getCacheName() {
return "{MONITORED.CACHE.CATALOG}";
}
@Override
public String getCacheKey() {
return CACHE_PREFIX;
}
@Override
public CmsCatalog getCache(String cacheKey) {
return redisCache.getCacheObject(cacheKey, CmsCatalog.class);
}
private String cacheKeyById(Long catalogId) {
return CACHE_PREFIX + "id:" + catalogId;
}
private String cacheKeyByAlias(Long siteId, String alias) {
return CACHE_PREFIX + "alias:" + siteId + ":" + alias;
}
public CmsCatalog getCacheById(Long catalogId) {
return redisCache.getCacheObject(cacheKeyById(catalogId), CmsCatalog.class);
}
public CmsCatalog getCacheById(Long catalogId, Supplier<CmsCatalog> supplier) {
return redisCache.getCacheObject(cacheKeyById(catalogId), CmsCatalog.class, supplier);
}
public CmsCatalog getCacheByAlias(Long siteId, String alias) {
return redisCache.getCacheObject(cacheKeyByAlias(siteId, alias), CmsCatalog.class);
}
public CmsCatalog getCacheByAlias(Long siteId, String alias, Supplier<CmsCatalog> supplier) {
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()));
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.cache;
import com.chestnut.common.redis.IMonitoredCache;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.domain.CmsPageWidget;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
/**
* PageWidgetMonitoredCache
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IMonitoredCache.BEAN_PREFIX + PageWidgetMonitoredCache.ID)
@RequiredArgsConstructor
public class PageWidgetMonitoredCache implements IMonitoredCache<CmsPageWidget> {
public static final String ID = "PageWidget";
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "pagewidget:";
private final RedisCache redisCache;
@Override
public String getId() {
return ID;
}
@Override
public String getCacheName() {
return "{MONITORED.CACHE.PAGE_WIDGET}";
}
@Override
public CmsPageWidget getCache(String cacheKey) {
return redisCache.getCacheObject(cacheKey, CmsPageWidget.class);
}
@Override
public String getCacheKey() {
return CACHE_PREFIX;
}
public CmsPageWidget getCache(Long siteId, String code, Supplier<CmsPageWidget> supplier) {
return redisCache.getCacheObject(CACHE_PREFIX + siteId + ":" + code, CmsPageWidget.class, supplier);
}
public void clear(CmsPageWidget pageWidget) {
this.redisCache.deleteObject(CACHE_PREFIX + pageWidget.getSiteId() + ":" + pageWidget.getCode());
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.cache;
import com.chestnut.common.redis.IMonitoredCache;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.contentcore.config.CMSConfig;
import com.chestnut.contentcore.domain.CmsSite;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
/**
* SiteMonitoredCache
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IMonitoredCache.BEAN_PREFIX + SiteMonitoredCache.ID)
@RequiredArgsConstructor
public class SiteMonitoredCache implements IMonitoredCache<CmsSite> {
public static final String ID = "Site";
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "site:";
private final RedisCache redisCache;
@Override
public String getId() {
return ID;
}
@Override
public String getCacheName() {
return "{MONITORED.CACHE.SITE}";
}
@Override
public CmsSite getCache(String cacheKey) {
return redisCache.getCacheObject(cacheKey, CmsSite.class);
}
@Override
public String getCacheKey() {
return CACHE_PREFIX;
}
public CmsSite getCache(Long siteId, Supplier<CmsSite> supplier) {
return redisCache.getCacheObject(CACHE_PREFIX + siteId, CmsSite.class, supplier);
}
public void clear(long siteId) {
this.redisCache.deleteObject(CACHE_PREFIX + siteId);
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.cache;
import com.chestnut.common.redis.IMonitoredCache;
import com.chestnut.common.redis.RedisCache;
import com.chestnut.contentcore.config.CMSConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* TemplateMonitoredCache
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Component(IMonitoredCache.BEAN_PREFIX + TemplateMonitoredCache.ID)
@RequiredArgsConstructor
public class TemplateMonitoredCache implements IMonitoredCache<String> {
public static final String ID = "Template";
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "template:";
private final RedisCache redisCache;
@Override
public String getId() {
return ID;
}
@Override
public String getCacheName() {
return "{MONITORED.CACHE.TEMPLATE}";
}
@Override
public String getCache(String cacheKey) {
return redisCache.getCacheObject(cacheKey, String.class);
}
@Override
public String getCacheKey() {
return CACHE_PREFIX;
}
public String getCache(String templateKey, Supplier<String> supplier) {
return redisCache.getCacheObject(CACHE_PREFIX + templateKey, String.class, supplier);
}
public void clear(String templateKey) {
this.redisCache.deleteObject(CACHE_PREFIX + templateKey);
}
public void setCache(String templateKey, String staticContent, int timeout, TimeUnit timeUnit) {
redisCache.setCacheObject(CACHE_PREFIX + templateKey, staticContent, timeout, timeUnit);
}
}

View File

@ -69,10 +69,7 @@ public class CMSConfig implements WebMvcConfigurer {
if (StringUtils.isEmpty(RESOURCE_ROOT)) { if (StringUtils.isEmpty(RESOURCE_ROOT)) {
RESOURCE_ROOT = SpringUtils.getAppParentDirectory() + "/wwwroot_release/"; RESOURCE_ROOT = SpringUtils.getAppParentDirectory() + "/wwwroot_release/";
} }
RESOURCE_ROOT = FileExUtils.normalizePath(RESOURCE_ROOT); RESOURCE_ROOT = StringUtils.appendIfMissing(FileExUtils.normalizePath(RESOURCE_ROOT), "/");
if (!RESOURCE_ROOT.endsWith("/")) {
RESOURCE_ROOT += "/";
}
FileExUtils.mkdirs(RESOURCE_ROOT); FileExUtils.mkdirs(RESOURCE_ROOT);
properties.setResourceRoot(RESOURCE_ROOT); properties.setResourceRoot(RESOURCE_ROOT);
log.info("ResourceRoot: " + RESOURCE_ROOT); log.info("ResourceRoot: " + RESOURCE_ROOT);
@ -116,7 +113,7 @@ public class CMSConfig implements WebMvcConfigurer {
public void resetCache() { public void resetCache() {
if (this.properties.getResetCache()) { if (this.properties.getResetCache()) {
Collection<String> keys = this.redisCache.keys(this.properties.getCacheName() + "*"); Collection<String> keys = this.redisCache.keys(this.properties.getCacheName() + "*");
this.redisCache.deleteObject(keys); this.redisCache.deleteObjects(keys);
log.info("Clear redis caches with prefix `{}`", this.properties.getCacheName()); log.info("Clear redis caches with prefix `{}`", this.properties.getCacheName());
} }
} }

View File

@ -16,11 +16,13 @@
package com.chestnut.contentcore.controller; package com.chestnut.contentcore.controller;
import cn.dev33.satoken.annotation.SaMode; import cn.dev33.satoken.annotation.SaMode;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.chestnut.common.async.AsyncTask; import com.chestnut.common.async.AsyncTask;
import com.chestnut.common.async.AsyncTaskManager; import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.domain.R; import com.chestnut.common.domain.R;
import com.chestnut.common.domain.TreeNode; import com.chestnut.common.domain.TreeNode;
import com.chestnut.common.exception.CommonErrorCode; import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.extend.annotation.XssIgnore;
import com.chestnut.common.i18n.I18nUtils; import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.log.annotation.Log; import com.chestnut.common.log.annotation.Log;
import com.chestnut.common.log.enums.BusinessType; import com.chestnut.common.log.enums.BusinessType;
@ -52,6 +54,7 @@ import com.chestnut.contentcore.util.SiteUtils;
import com.chestnut.system.security.AdminUserType; import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil; import com.chestnut.system.security.StpAdminUtil;
import com.chestnut.system.validator.LongId; import com.chestnut.system.validator.LongId;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -177,14 +180,13 @@ public class CatalogController extends BaseRestController {
@DeleteMapping("/{catalogId}") @DeleteMapping("/{catalogId}")
public R<String> deleteCatalog(@PathVariable("catalogId") @LongId Long catalogId) { public R<String> deleteCatalog(@PathVariable("catalogId") @LongId Long catalogId) {
LoginUser operator = StpAdminUtil.getLoginUser(); LoginUser operator = StpAdminUtil.getLoginUser();
AsyncTask task = new AsyncTask() { AsyncTask task = new AsyncTask("Catalog-" + catalogId) {
@Override @Override
public void run0() { public void run0() {
catalogService.deleteCatalog(catalogId, operator); catalogService.deleteCatalog(catalogId, operator);
} }
}; };
task.setTaskId("DeleteCatalog_" + catalogId);
this.asyncTaskManager.execute(task); this.asyncTaskManager.execute(task);
return R.ok(task.getTaskId()); return R.ok(task.getTaskId());
} }
@ -282,6 +284,7 @@ public class CatalogController extends BaseRestController {
/** /**
* 保存栏目扩展配置 * 保存栏目扩展配置
*/ */
@XssIgnore
@Priv(type = AdminUserType.TYPE, value = "Catalog:Edit:${#catalogId}") @Priv(type = AdminUserType.TYPE, value = "Catalog:Edit:${#catalogId}")
@Log(title = "栏目扩展", businessType = BusinessType.UPDATE, isSaveRequestData = false) @Log(title = "栏目扩展", businessType = BusinessType.UPDATE, isSaveRequestData = false)
@PutMapping("/extends/{catalogId}") @PutMapping("/extends/{catalogId}")
@ -376,4 +379,48 @@ public class CatalogController extends BaseRestController {
} }
return R.ok(Map.of("alias", alias, "path", path)); return R.ok(Map.of("alias", alias, "path", path));
} }
@Priv(type = AdminUserType.TYPE)
@GetMapping("/tree")
public R<String> getCatalogTree(@RequestParam Long catalogId, HttpServletRequest request) {
CmsSite site = siteService.getCurrentSite(request);
CmsCatalog parent = null;
if (IdUtils.validate(catalogId)) {
parent = catalogService.getCatalog(catalogId);
}
LambdaQueryChainWrapper<CmsCatalog> q = catalogService.lambdaQuery()
.select(CmsCatalog::getName, CmsCatalog::getAncestors, CmsCatalog::getTreeLevel)
.eq(CmsCatalog::getSiteId, site.getSiteId());
if (Objects.nonNull(parent)) {
q.likeRight(CmsCatalog::getAncestors, parent.getAncestors());
}
List<CmsCatalog> list = q.list();
list.sort(Comparator.comparing(CmsCatalog::getAncestors));
StringBuilder sb = new StringBuilder();
list.forEach(catalog -> {
String prefix = StringUtils.leftPad("", (catalog.getTreeLevel() -1) * 2);
sb.append(prefix).append(catalog.getName()).append(StringUtils.LF);
});
return R.ok(sb.toString());
}
@Priv(type = AdminUserType.TYPE, value = "Catalog:Edit:${#catalogId}")
@Log(title = "清空", businessType = BusinessType.UPDATE)
@PostMapping("/clear")
public R<String> clearCatalog(@RequestBody ClearCatalogDTO dto) {
LoginUser operator = StpAdminUtil.getLoginUser();
dto.setOperator(operator);
AsyncTask task = catalogService.clearCatalog(dto);
return R.ok(task.getTaskId());
}
@Priv(type = AdminUserType.TYPE, value = "Catalog:Edit:${#catalogId}")
@Log(title = "合并", businessType = BusinessType.UPDATE)
@PostMapping("/merge")
public R<String> mergeCatalog(@RequestBody MergeCatalogDTO dto) {
LoginUser operator = StpAdminUtil.getLoginUser();
dto.setOperator(operator);
AsyncTask task = catalogService.mergeCatalogs(dto);
return R.ok(task.getTaskId());
}
} }

View File

@ -32,7 +32,6 @@ import com.chestnut.common.utils.ServletUtils;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.core.IContent; import com.chestnut.contentcore.core.IContent;
import com.chestnut.contentcore.core.IContentType; import com.chestnut.contentcore.core.IContentType;
import com.chestnut.contentcore.core.impl.InternalDataType_Content;
import com.chestnut.contentcore.domain.CmsCatalog; import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
@ -50,7 +49,6 @@ import com.chestnut.contentcore.user.preference.IncludeChildContentPreference;
import com.chestnut.contentcore.user.preference.ShowContentSubTitlePreference; import com.chestnut.contentcore.user.preference.ShowContentSubTitlePreference;
import com.chestnut.contentcore.util.CmsPrivUtils; import com.chestnut.contentcore.util.CmsPrivUtils;
import com.chestnut.contentcore.util.ContentCoreUtils; import com.chestnut.contentcore.util.ContentCoreUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.permission.PermissionUtils; import com.chestnut.system.permission.PermissionUtils;
import com.chestnut.system.security.AdminUserType; import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil; import com.chestnut.system.security.StpAdminUtil;
@ -59,7 +57,6 @@ import freemarker.template.TemplateException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -67,7 +64,6 @@ import org.springframework.web.bind.annotation.*;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -137,15 +133,7 @@ public class ContentController extends BaseRestController {
q.orderByDesc(CmsContent::getTopFlag).orderByDesc(CmsContent::getSortFlag); q.orderByDesc(CmsContent::getTopFlag).orderByDesc(CmsContent::getSortFlag);
} }
Page<CmsContent> page = q.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true)); Page<CmsContent> page = q.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true));
List<ListContentVO> list = new ArrayList<>(); List<ListContentVO> list = page.getRecords().stream().map(ListContentVO::newInstance).toList();
page.getRecords().forEach(content -> {
ListContentVO vo = new ListContentVO();
BeanUtils.copyProperties(content, vo);
vo.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(content.getLogo()));
vo.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
vo.setInternalUrl(InternalUrlUtils.getInternalUrl(InternalDataType_Content.ID, content.getContentId()));
list.add(vo);
});
return this.bindDataTable(list, (int) page.getTotal()); return this.bindDataTable(list, (int) page.getTotal());
} }
@ -171,7 +159,7 @@ public class ContentController extends BaseRestController {
throws IOException { throws IOException {
IContentType ct = ContentCoreUtils.getContentType(contentType); IContentType ct = ContentCoreUtils.getContentType(contentType);
IContent<?> content = ct.readRequest(request); IContent<?> content = ct.readFrom(request.getInputStream());
content.setOperator(StpAdminUtil.getLoginUser()); content.setOperator(StpAdminUtil.getLoginUser());
PermissionUtils.checkPermission(CatalogPrivItem.AddContent.getPermissionKey(content.getCatalogId()), PermissionUtils.checkPermission(CatalogPrivItem.AddContent.getPermissionKey(content.getCatalogId()),
content.getOperator()); content.getOperator());
@ -187,7 +175,7 @@ public class ContentController extends BaseRestController {
throws IOException { throws IOException {
IContentType ct = ContentCoreUtils.getContentType(contentType); IContentType ct = ContentCoreUtils.getContentType(contentType);
IContent<?> content = ct.readRequest(request); IContent<?> content = ct.readFrom(request.getInputStream());
content.setOperator(StpAdminUtil.getLoginUser()); content.setOperator(StpAdminUtil.getLoginUser());
PermissionUtils.checkPermission(CatalogPrivItem.EditContent.getPermissionKey(content.getCatalogId()), PermissionUtils.checkPermission(CatalogPrivItem.EditContent.getPermissionKey(content.getCatalogId()),
content.getOperator()); content.getOperator());

View File

@ -0,0 +1,63 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.common.domain.R;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.security.web.PageRequest;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContentOpLog;
import com.chestnut.contentcore.service.IContentOpLogService;
import com.chestnut.system.security.AdminUserType;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 内容操作日志管理
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Priv(type = AdminUserType.TYPE)
@RestController
@RequiredArgsConstructor
@RequestMapping("/cms/content/log")
public class ContentOpLogController extends BaseRestController {
private final IContentOpLogService contentOpLogService;
@GetMapping
public R<?> getContentOpLogPageList(@RequestParam Long contentId,
@RequestParam(required = false) String type,
@RequestParam(required = false) String operator) {
PageRequest pr = this.getPageRequest();
LambdaQueryWrapper<CmsContentOpLog> q = new LambdaQueryWrapper<CmsContentOpLog>()
.eq(CmsContentOpLog::getContentId, contentId)
.eq(StringUtils.isNotEmpty(type), CmsContentOpLog::getType, type)
.eq(StringUtils.isNotEmpty(operator), CmsContentOpLog::getOperator, operator)
.orderByDesc(CmsContentOpLog::getLogId);
Page<CmsContentOpLog> pageResult = contentOpLogService.page(
new Page<>(pr.getPageNumber(), pr.getPageSize(), true), q
);
return this.bindDataTable(pageResult);
}
}

View File

@ -25,6 +25,7 @@ import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.ServletUtils; import com.chestnut.common.utils.ServletUtils;
import com.chestnut.contentcore.core.IInternalDataType; import com.chestnut.contentcore.core.IInternalDataType;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.vo.ContentPathRuleVO;
import com.chestnut.contentcore.domain.vo.DynamicPageTypeVO; import com.chestnut.contentcore.domain.vo.DynamicPageTypeVO;
import com.chestnut.contentcore.exception.ContentCoreErrorCode; import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.service.ISiteService; import com.chestnut.contentcore.service.ISiteService;
@ -126,7 +127,6 @@ public class CoreController extends BaseRestController {
@RequestParam(value = "pi", required = false, defaultValue = "1") Integer pageIndex, @RequestParam(value = "pi", required = false, defaultValue = "1") Integer pageIndex,
@RequestParam Map<String, Object> params) { @RequestParam Map<String, Object> params) {
try { try {
// TODO 缓存
long s = System.currentTimeMillis(); long s = System.currentTimeMillis();
CmsSite site = this.siteService.getSite(siteId); CmsSite site = this.siteService.getSite(siteId);
// 模板ID = 通道:站点目录:模板文件名 // 模板ID = 通道:站点目录:模板文件名
@ -164,9 +164,25 @@ public class CoreController extends BaseRestController {
public R<?> getDynamicPageTypes() { public R<?> getDynamicPageTypes() {
List<DynamicPageTypeVO> list = ContentCoreUtils.getDynamicPageTypes().stream() List<DynamicPageTypeVO> list = ContentCoreUtils.getDynamicPageTypes().stream()
.map(DynamicPageTypeVO::newInstance).toList(); .map(DynamicPageTypeVO::newInstance).toList();
list.forEach( vo -> list.forEach( vo -> {
vo.setName(I18nUtils.get(vo.getName())) vo.setName(I18nUtils.get(vo.getName()));
); vo.setDesc(I18nUtils.get(vo.getDesc()));
vo.getRequestArgs().forEach(requestArg -> {
requestArg.setName(I18nUtils.get(requestArg.getName()));
requestArg.setDesc(I18nUtils.get(requestArg.getDesc()));
});
});
return R.ok(list);
}
@Priv(type = AdminUserType.TYPE)
@GetMapping("/cms/contentPathRules")
public R<?> getContentPathRules() {
List<ContentPathRuleVO> list = ContentCoreUtils.getContentPathRules().stream()
.map(ContentPathRuleVO::newInstance).toList();
list.forEach( vo -> {
vo.setName(I18nUtils.get(vo.getName()));
});
return R.ok(list); return R.ok(list);
} }
} }

View File

@ -0,0 +1,84 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.controller;
import cn.dev33.satoken.annotation.SaMode;
import com.chestnut.common.domain.R;
import com.chestnut.common.log.annotation.Log;
import com.chestnut.common.log.enums.BusinessType;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.contentcore.domain.dto.ImageCropDTO;
import com.chestnut.contentcore.domain.dto.ImageRotateDTO;
import com.chestnut.contentcore.perms.ContentCorePriv;
import com.chestnut.contentcore.service.IImageProcessService;
import com.chestnut.contentcore.util.CmsPrivUtils;
import com.chestnut.system.security.AdminUserType;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* 图片资源处理控制器
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Priv(
type = AdminUserType.TYPE,
value = { ContentCorePriv.ResourceView, CmsPrivUtils.PRIV_SITE_VIEW_PLACEHOLDER},
mode = SaMode.AND
)
@RestController
@RequestMapping("/cms/process/image")
@RequiredArgsConstructor
public class ImageProcessController extends BaseRestController {
private final IImageProcessService imageProcessService;
@Log(title = "图片裁剪", businessType = BusinessType.UPDATE)
@PostMapping("/crop")
public R<?> cropImage(@RequestBody @Validated ImageCropDTO dto) throws IOException {
this.imageProcessService.cropImage(dto);
return R.ok();
}
@Log(title = "旋转缩放", businessType = BusinessType.UPDATE)
@PostMapping("/rotate")
public R<?> rotateImage(@RequestBody @Validated ImageRotateDTO dto) throws IOException {
this.imageProcessService.rotateImage(dto);
return R.ok();
}
@Log(title = "文字水印", businessType = BusinessType.UPDATE)
@PostMapping("/image/textWatermark")
public R<?> textWatermark(@RequestBody @Validated ImageCropDTO dto) throws IOException {
return R.ok();
}
@Log(title = "图片水印", businessType = BusinessType.UPDATE)
@PostMapping("/image/imageWatermark")
public R<?> imageWatermark(@RequestBody @Validated ImageCropDTO dto) throws IOException {
return R.ok();
}
}

View File

@ -34,7 +34,6 @@ import com.chestnut.contentcore.core.IResourceType;
import com.chestnut.contentcore.core.impl.InternalDataType_Resource; import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
import com.chestnut.contentcore.domain.CmsResource; import com.chestnut.contentcore.domain.CmsResource;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.dto.ImageCropDTO;
import com.chestnut.contentcore.domain.dto.ResourceUploadDTO; import com.chestnut.contentcore.domain.dto.ResourceUploadDTO;
import com.chestnut.contentcore.exception.ContentCoreErrorCode; import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.fixed.config.ResourceUploadAcceptSize; import com.chestnut.contentcore.fixed.config.ResourceUploadAcceptSize;
@ -52,7 +51,6 @@ import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -198,11 +196,4 @@ public class ResourceController extends BaseRestController {
Assert.notNull(resource, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("resourceId", resourceId)); Assert.notNull(resource, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("resourceId", resourceId));
this.resourceService.downloadResource(resource, response); this.resourceService.downloadResource(resource, response);
} }
@Log(title = "图片裁剪", businessType = BusinessType.UPDATE)
@PostMapping("/image/cut")
public R<?> cutImage(@RequestBody @Validated ImageCropDTO imageCutDTO) {
// TODO
return R.fail("TODO");
}
} }

View File

@ -21,6 +21,7 @@ import com.chestnut.common.async.AsyncTask;
import com.chestnut.common.async.AsyncTaskManager; import com.chestnut.common.async.AsyncTaskManager;
import com.chestnut.common.domain.R; import com.chestnut.common.domain.R;
import com.chestnut.common.exception.CommonErrorCode; import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.extend.annotation.XssIgnore;
import com.chestnut.common.log.annotation.Log; import com.chestnut.common.log.annotation.Log;
import com.chestnut.common.log.enums.BusinessType; import com.chestnut.common.log.enums.BusinessType;
import com.chestnut.common.security.anno.Priv; import com.chestnut.common.security.anno.Priv;
@ -264,6 +265,7 @@ public class SiteController extends BaseRestController {
* @param siteId 站点ID * @param siteId 站点ID
* @param configs 扩展配置数据 * @param configs 扩展配置数据
*/ */
@XssIgnore
@Priv(type = AdminUserType.TYPE, value = "Site:Edit:${#siteId}") @Priv(type = AdminUserType.TYPE, value = "Site:Edit:${#siteId}")
@Log(title = "站点扩展", businessType = BusinessType.UPDATE, isSaveRequestData = false) @Log(title = "站点扩展", businessType = BusinessType.UPDATE, isSaveRequestData = false)
@PostMapping("/extends/{siteId}") @PostMapping("/extends/{siteId}")

View File

@ -15,24 +15,23 @@
*/ */
package com.chestnut.contentcore.controller; package com.chestnut.contentcore.controller;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.chestnut.common.domain.R; import com.chestnut.common.domain.R;
import com.chestnut.common.i18n.I18nUtils; import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.security.anno.Priv; import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController; import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.staticize.func.IFunction; import com.chestnut.common.staticize.func.IFunction;
import com.chestnut.common.staticize.tag.ITag; import com.chestnut.common.staticize.tag.ITag;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.vo.TemplateFuncVO; import com.chestnut.contentcore.domain.vo.TemplateFuncVO;
import com.chestnut.contentcore.domain.vo.TemplateTagVO; import com.chestnut.contentcore.domain.vo.TemplateTagVO;
import com.chestnut.contentcore.perms.ContentCorePriv; import com.chestnut.contentcore.perms.ContentCorePriv;
import com.chestnut.system.security.AdminUserType; import com.chestnut.system.security.AdminUserType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/** /**
* 静态化管理 * 静态化管理
@ -56,11 +55,22 @@ public class StaticizeController extends BaseRestController {
@GetMapping("/tags") @GetMapping("/tags")
public R<?> getTemplateTags() { public R<?> getTemplateTags() {
List<TemplateTagVO> list = this.tags.stream().map(tag -> { List<TemplateTagVO> list = this.tags.stream().map(tag -> {
TemplateTagVO vo = TemplateTagVO.builder().name(I18nUtils.get(tag.getName())).tagName(tag.getTagName()) TemplateTagVO vo = TemplateTagVO.builder()
.description(I18nUtils.get(tag.getDescription())).tagAttrs(tag.getTagAttrs()).build(); .name(I18nUtils.get(tag.getName()))
.tagName(tag.getTagName())
.description(I18nUtils.get(tag.getDescription()))
.tagAttrs(tag.getTagAttrs())
.demoLink("https://www.1000mz.com/docs/template/tags/" + tag.getTagName())
.build();
vo.getTagAttrs().forEach(attr -> { vo.getTagAttrs().forEach(attr -> {
attr.setName(I18nUtils.get(attr.getName())); attr.setName(I18nUtils.get(attr.getName()));
attr.setUsage(I18nUtils.get(attr.getUsage())); attr.setUsage(I18nUtils.get(attr.getUsage()));
attr.setDefaultValue(I18nUtils.get(attr.getDefaultValue()));
if (StringUtils.isNotEmpty(attr.getOptions())) {
attr.getOptions().forEach(option -> {
option.setDesc(I18nUtils.get(option.getDesc()));
});
}
}); });
return vo; return vo;
}).toList(); }).toList();
@ -73,8 +83,12 @@ public class StaticizeController extends BaseRestController {
@GetMapping("/functions") @GetMapping("/functions")
public R<?> getTemplateFunctions() { public R<?> getTemplateFunctions() {
List<TemplateFuncVO> list = this.functions.stream().map(func -> { List<TemplateFuncVO> list = this.functions.stream().map(func -> {
TemplateFuncVO vo = TemplateFuncVO.builder().funcName(func.getFuncName()) TemplateFuncVO vo = TemplateFuncVO.builder()
.desc(I18nUtils.get(func.getDesc())).funcArgs(func.getFuncArgs()).build(); .funcName(func.getFuncName())
.desc(I18nUtils.get(func.getDesc()))
.funcArgs(func.getFuncArgs())
.demoLink("https://www.1000mz.com/docs/template/functions/" + func.getFuncName())
.build();
vo.getFuncArgs().forEach(arg -> { vo.getFuncArgs().forEach(arg -> {
arg.setName(I18nUtils.get(arg.getName())); arg.setName(I18nUtils.get(arg.getName()));
arg.setDesc(I18nUtils.get(arg.getDesc())); arg.setDesc(I18nUtils.get(arg.getDesc()));

View File

@ -39,7 +39,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@ -128,15 +131,10 @@ public class ContentApiController extends BaseRestController {
if (pageResult.getRecords().isEmpty()) { if (pageResult.getRecords().isEmpty()) {
return R.ok(List.of()); return R.ok(List.of());
} }
Map<Long, CmsCatalog> loadedCatalogs = new HashMap<>();
List<ContentApiVO> list = new ArrayList<>(); List<ContentApiVO> list = new ArrayList<>();
pageResult.getRecords().forEach(c -> { pageResult.getRecords().forEach(c -> {
ContentApiVO dto = ContentApiVO.newInstance(c); ContentApiVO dto = ContentApiVO.newInstance(c);
CmsCatalog catalog = loadedCatalogs.get(c.getCatalogId()); CmsCatalog catalog = this.catalogService.getCatalog(c.getCatalogId());
if (catalog == null) {
catalog = this.catalogService.getCatalog(c.getCatalogId());
loadedCatalogs.put(catalog.getCatalogId(), catalog);
}
dto.setCatalogName(catalog.getName()); dto.setCatalogName(catalog.getName());
dto.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview)); dto.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
dto.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview)); dto.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview));

View File

@ -24,7 +24,9 @@ import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.enums.ContentCopyType; import com.chestnut.contentcore.enums.ContentCopyType;
import com.chestnut.contentcore.exception.ContentCoreErrorCode; import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.fixed.dict.ContentOpType;
import com.chestnut.contentcore.fixed.dict.ContentStatus; import com.chestnut.contentcore.fixed.dict.ContentStatus;
import com.chestnut.contentcore.listener.event.*;
import com.chestnut.contentcore.properties.PublishedContentEditProperty; import com.chestnut.contentcore.properties.PublishedContentEditProperty;
import com.chestnut.contentcore.service.ICatalogService; import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService; import com.chestnut.contentcore.service.IContentService;
@ -32,6 +34,7 @@ import com.chestnut.contentcore.service.IPublishService;
import com.chestnut.contentcore.service.ISiteService; import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.util.CatalogUtils; import com.chestnut.contentcore.util.CatalogUtils;
import com.chestnut.contentcore.util.ContentCoreUtils; import com.chestnut.contentcore.util.ContentCoreUtils;
import com.chestnut.contentcore.util.ContentLogUtils;
import com.chestnut.contentcore.util.InternalUrlUtils; import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.fixed.dict.YesOrNo; import com.chestnut.system.fixed.dict.YesOrNo;
import lombok.Setter; import lombok.Setter;
@ -141,6 +144,8 @@ public abstract class AbstractContent<T> implements IContent<T> {
throw ContentCoreErrorCode.TITLE_REPLEAT.exception(); throw ContentCoreErrorCode.TITLE_REPLEAT.exception();
} }
checkRedirectUrl(); checkRedirectUrl();
SpringUtils.publishEvent(new BeforeContentSaveEvent(this, this, true));
content.setSiteId(catalog.getSiteId()); content.setSiteId(catalog.getSiteId());
content.setCatalogAncestors(catalog.getAncestors()); content.setCatalogAncestors(catalog.getAncestors());
content.setTopCatalog(CatalogUtils.getTopCatalog(catalog)); content.setTopCatalog(CatalogUtils.getTopCatalog(catalog));
@ -150,13 +155,24 @@ public abstract class AbstractContent<T> implements IContent<T> {
content.setStatus(ContentStatus.DRAFT); content.setStatus(ContentStatus.DRAFT);
content.setSortFlag(SortUtils.getDefaultSortValue()); content.setSortFlag(SortUtils.getDefaultSortValue());
content.setIsLock(YesOrNo.NO); content.setIsLock(YesOrNo.NO);
if (StringUtils.isEmpty(content.getLinkFlag())) {
content.setLinkFlag(YesOrNo.NO);
}
if (Objects.isNull(content.getCopyType())) {
content.setCopyType(ContentCopyType.NONE);
}
content.createBy(this.getOperatorUName()); content.createBy(this.getOperatorUName());
this.getContentService().dao().save(this.getContentEntity());
this.add0();
// 栏目内容数+1 // 栏目内容数+1
this.getCatalogService().changeContentCount(catalog.getCatalogId(), 1); this.getCatalogService().changeContentCount(catalog.getCatalogId(), 1);
ContentLogUtils.addLog(ContentOpType.ADD, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentSaveEvent(this, this, true));
return this.getContentEntity().getContentId(); return this.getContentEntity().getContentId();
} }
protected abstract void add0();
void checkLock() { void checkLock() {
boolean lockContent = content.isLock() && StringUtils.isNotEmpty(content.getLockUser()) boolean lockContent = content.isLock() && StringUtils.isNotEmpty(content.getLockUser())
&& !content.getLockUser().equals(this.getOperatorUName()); && !content.getLockUser().equals(this.getOperatorUName());
@ -190,26 +206,39 @@ public abstract class AbstractContent<T> implements IContent<T> {
} }
} }
checkRedirectUrl(); checkRedirectUrl();
SpringUtils.publishEvent(new BeforeContentSaveEvent(this, this, false));
if (ContentStatus.isToPublishOrPublished(content.getStatus())) { if (ContentStatus.isToPublishOrPublished(content.getStatus())) {
content.setStatus(ContentStatus.EDITING); content.setStatus(ContentStatus.EDITING);
} }
content.updateBy(this.getOperatorUName()); content.updateBy(this.getOperatorUName());
contentService.dao().updateById(this.getContentEntity());
this.save0();
ContentLogUtils.addLog(ContentOpType.UPDATE, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentSaveEvent(this, this, false));
return this.getContentEntity().getContentId(); return this.getContentEntity().getContentId();
} }
protected abstract void save0();
@Override @Override
public void delete() { public void delete() {
this.checkLock(); this.checkLock();
// 删除到备份表 // 删除到备份表
this.getContentService().dao().deleteByIdAndBackup(this.getContentEntity(), getOperatorUName()); this.getContentService().dao().deleteByIdAndBackup(this.getContentEntity(), getOperatorUName());
this.delete0();
// 直接删除站内映射内容 // 直接删除站内映射内容
this.getContentService().dao().remove(new LambdaQueryWrapper<CmsContent>() this.getContentService().dao().remove(new LambdaQueryWrapper<CmsContent>()
.eq(CmsContent::getCopyType, ContentCopyType.Mapping) .eq(CmsContent::getCopyType, ContentCopyType.Mapping)
.eq(CmsContent::getCopyId, this.getContentEntity().getContentId())); .eq(CmsContent::getCopyId, this.getContentEntity().getContentId()));
// 栏目内容数-1 // 栏目内容数-1
this.getCatalogService().changeContentCount(getCatalogId(), -1); this.getCatalogService().changeContentCount(getCatalogId(), -1);
ContentLogUtils.addLog(ContentOpType.DELETE, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentDeleteEvent(this, this));
} }
protected abstract void delete0();
@Override @Override
public boolean publish() { public boolean publish() {
checkLock(); checkLock();
@ -227,6 +256,7 @@ public abstract class AbstractContent<T> implements IContent<T> {
} }
if (update) { if (update) {
this.getContentService().dao().updateById(content); this.getContentService().dao().updateById(content);
ContentLogUtils.addLog(ContentOpType.PUBLISH, this.getContentEntity(), this.getOperator());
} }
// 静态化 // 静态化
this.getPublishService().asyncPublishContent(this); this.getPublishService().asyncPublishContent(this);
@ -263,12 +293,16 @@ public abstract class AbstractContent<T> implements IContent<T> {
newContent.setOfflineDate(null); newContent.setOfflineDate(null);
} }
this.getContentService().dao().save(newContent); this.getContentService().dao().save(newContent);
this.getParams().put("NewContentId", newContent.getContentId()); copyTo0(newContent, copyType);
// 栏目内容数+1 // 栏目内容数+1
this.getCatalogService().changeContentCount(toCatalog.getCatalogId(), 1); this.getCatalogService().changeContentCount(toCatalog.getCatalogId(), 1);
SpringUtils.publishEvent(new AfterContentCopyEvent(this, this.getContentEntity(), newContent));
return newContent; return newContent;
} }
protected abstract void copyTo0(CmsContent newContent, Integer copyType);
@Override @Override
public void moveTo(CmsCatalog toCatalog) { public void moveTo(CmsCatalog toCatalog) {
checkLock(); checkLock();
@ -308,24 +342,26 @@ public abstract class AbstractContent<T> implements IContent<T> {
if (ContentStatus.isPublished(this.getContentEntity().getStatus())) { if (ContentStatus.isPublished(this.getContentEntity().getStatus())) {
this.getPublishService().publishContent(List.of(content.getContentId()), getOperator()); this.getPublishService().publishContent(List.of(content.getContentId()), getOperator());
} }
ContentLogUtils.addLog(ContentOpType.TOP, this.getContentEntity(), this.getOperator());
} }
@Override @Override
public void cancelTop() { public void cancelTop() {
content.setTopFlag(0L); if (content.getTopFlag() > 0L) {
content.setTopDate(null); return;
content.updateBy(this.getOperatorUName()); }
this.getContentService().dao().updateById(content); this.getContentService().dao().updateById(content);
// 重新发布内容 // 重新发布内容
if (ContentStatus.isPublished(this.getContentEntity().getStatus())) { if (ContentStatus.isPublished(this.getContentEntity().getStatus())) {
this.getPublishService().publishContent(List.of(content.getContentId()), getOperator()); this.getPublishService().publishContent(List.of(content.getContentId()), getOperator());
} }
ContentLogUtils.addLog(ContentOpType.CANCEL_TOP, this.getContentEntity(), this.getOperator());
} }
@Override @Override
public void sort(Long targetContentId) { public void sort(Long targetContentId) {
if (targetContentId.equals(this.getContentEntity().getContentId())) { if (targetContentId.equals(this.getContentEntity().getContentId())) {
return; // 排序目标是自己直接返回 return;
} }
checkLock(); checkLock();
CmsContent next = this.getContentService().dao().getById(targetContentId); CmsContent next = this.getContentService().dao().getById(targetContentId);
@ -346,15 +382,17 @@ public abstract class AbstractContent<T> implements IContent<T> {
} }
this.getContentEntity().updateBy(this.getOperatorUName()); this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(content); this.getContentService().dao().updateById(content);
ContentLogUtils.addLog(ContentOpType.SORT, this.getContentEntity(), this.getOperator());
} }
@Override @Override
public void offline() { public void offline() {
String status = this.getContentEntity().getStatus(); String status = this.getContentEntity().getStatus();
if (!ContentStatus.isOffline(status)) {
this.getContentEntity().setStatus(ContentStatus.OFFLINE); this.getContentEntity().setStatus(ContentStatus.OFFLINE);
this.getContentEntity().updateBy(this.getOperatorUName()); this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(this.getContentEntity()); this.getContentService().dao().updateById(this.getContentEntity());
}
if (ContentStatus.isPublished(status)) { if (ContentStatus.isPublished(status)) {
// 已发布内容删除静态页面 // 已发布内容删除静态页面
this.getContentService().deleteStaticFiles(this.getContentEntity()); this.getContentService().deleteStaticFiles(this.getContentEntity());
@ -366,14 +404,20 @@ public abstract class AbstractContent<T> implements IContent<T> {
false, false, null, this.getOperator()); false, false, null, this.getOperator());
} }
} }
ContentLogUtils.addLog(ContentOpType.OFFLINE, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentOfflineEvent(this, this));
} }
@Override @Override
public void toPublish() { public void toPublish() {
if (!ContentStatus.isToPublish(this.getContentEntity().getStatus())) {
this.getContentEntity().setStatus(ContentStatus.TO_PUBLISHED); this.getContentEntity().setStatus(ContentStatus.TO_PUBLISHED);
this.getContentEntity().updateBy(this.getOperatorUName()); this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(this.getContentEntity()); this.getContentService().dao().updateById(this.getContentEntity());
} }
ContentLogUtils.addLog(ContentOpType.TO_PUBLISH, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentToPublishEvent(this, this));
}
@Override @Override
public void archive() { public void archive() {

View File

@ -105,7 +105,7 @@ public abstract class AbstractPageWidget implements IPageWidget {
} }
@Override @Override
public void publish() throws TemplateException, IOException { public void publish() {
CmsPageWidget pageWidgetEntity = this.getPageWidgetEntity(); CmsPageWidget pageWidgetEntity = this.getPageWidgetEntity();
pageWidgetEntity.setState(PageWidgetStatus.PUBLISHED); pageWidgetEntity.setState(PageWidgetStatus.PUBLISHED);
pageWidgetEntity.updateBy(this.getOperator().getUsername()); pageWidgetEntity.updateBy(this.getOperator().getUsername());

View File

@ -18,9 +18,8 @@ package com.chestnut.contentcore.core;
import com.chestnut.contentcore.domain.BCmsContent; import com.chestnut.contentcore.domain.BCmsContent;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.vo.ContentVO; import com.chestnut.contentcore.domain.vo.ContentVO;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.InputStream;
/** /**
* 内容类型 * 内容类型
@ -79,11 +78,11 @@ public interface IContentType extends Comparable<IContentType> {
IContent<?> loadContent(CmsContent xContent); IContent<?> loadContent(CmsContent xContent);
/** /**
* 请求读取内容数据 * 输入流读取内容数据
* *
* @param request * @param is 输入流
*/ */
IContent<?> readRequest(HttpServletRequest request) throws IOException; IContent<?> readFrom(InputStream is);
/** /**
* 初始化内容编辑页面数据 * 初始化内容编辑页面数据

View File

@ -17,6 +17,8 @@ package com.chestnut.contentcore.core;
import com.chestnut.common.staticize.core.TemplateContext; import com.chestnut.common.staticize.core.TemplateContext;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
import lombok.Getter;
import lombok.Setter;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -31,9 +33,9 @@ public interface IDynamicPageType {
String BEAN_PREFIX = "DynamicPageType_"; String BEAN_PREFIX = "DynamicPageType_";
RequestArg REQUEST_ARG_SITE_ID = new RequestArg("sid", "站点ID", RequestArgType.Parameter, true, null); RequestArg REQUEST_ARG_SITE_ID = new RequestArg("sid", "{DYNAMIC_PAGE_TYPE.ARG.sid}", RequestArgType.Parameter, true);
RequestArg REQUEST_ARG_PUBLISHPIPE_CODE = new RequestArg("pp", "发布通道编码", RequestArgType.Parameter, true, null); RequestArg REQUEST_ARG_PUBLISHPIPE_CODE = new RequestArg("pp", "{DYNAMIC_PAGE_TYPE.ARG.pp}", RequestArgType.Parameter, true);
RequestArg REQUEST_ARG_PREVIEW = new RequestArg("preview", "是否预览模式", RequestArgType.Parameter, false, "false"); RequestArg REQUEST_ARG_PREVIEW = new RequestArg("preview", "{DYNAMIC_PAGE_TYPE.ARG.preview}", RequestArgType.Parameter, false, "false");
/** /**
* 类型 * 类型
@ -89,17 +91,27 @@ public interface IDynamicPageType {
} }
record RequestArg( @Getter
String name, // 参数名 @Setter
class RequestArg {
private String name;
private String desc;
private RequestArgType type;
private boolean mandatory;
private String defValue;
String desc, // 参数说明 public RequestArg(String name, String desc, RequestArgType type, boolean mandatory) {
this(name, desc, type, mandatory, null);
}
RequestArgType type, // 类型parameter, path public RequestArg(String name, String desc, RequestArgType type, boolean mandatory, String defValue) {
this.name = name;
boolean mandatory, // 是否必填 this.desc = desc;
this.type = type;
String defaultValue // 默认值 this.mandatory = mandatory;
){} this.defValue = defValue;
}
}
enum RequestArgType { enum RequestArgType {
Parameter, Path Parameter, Path

View File

@ -15,13 +15,9 @@
*/ */
package com.chestnut.contentcore.core; package com.chestnut.contentcore.core;
import java.io.IOException;
import com.chestnut.common.security.domain.LoginUser; import com.chestnut.common.security.domain.LoginUser;
import com.chestnut.contentcore.domain.CmsPageWidget; import com.chestnut.contentcore.domain.CmsPageWidget;
import freemarker.template.TemplateException;
/** /**
* 页面部件 * 页面部件
* *
@ -59,5 +55,5 @@ public interface IPageWidget {
void delete(); void delete();
void publish() throws TemplateException, IOException; void publish();
} }

View File

@ -79,4 +79,8 @@ public interface IResourceType {
resource.setFileSize((long) bytes.length); resource.setFileSize((long) bytes.length);
return bytes; return bytes;
} }
default void afterProcess(CmsResource resource) {
}
} }

View File

@ -15,17 +15,16 @@
*/ */
package com.chestnut.contentcore.core; package com.chestnut.contentcore.core;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.exception.InternalUrlParseException;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.util.HtmlUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.springframework.web.util.HtmlUtils;
import com.chestnut.common.utils.StringUtils;
import lombok.Getter;
import lombok.Setter;
/** /**
* 内部数据自定义URL<br/> * 内部数据自定义URL<br/>
* 内部数据包括内容栏目站点资源等<br/> * 内部数据包括内容栏目站点资源等<br/>
@ -107,6 +106,9 @@ public class InternalURL {
url = HtmlUtils.htmlUnescape(url); url = HtmlUtils.htmlUnescape(url);
String content = url.substring(IURLProtocol.length()); String content = url.substring(IURLProtocol.length());
int i = content.lastIndexOf("?"); int i = content.lastIndexOf("?");
if (i < 0) {
throw new InternalUrlParseException("Invalid iurl: missing parameters.");
}
// 默认iurl的路径部分就是内部数据类型如果参数中含有type则使用参数type路径部分作为path // 默认iurl的路径部分就是内部数据类型如果参数中含有type则使用参数type路径部分作为path
String type = content.substring(0, i); String type = content.substring(0, i);
iurl.setType(type); iurl.setType(type);

View File

@ -112,7 +112,7 @@ public class SiteImportContext implements ISiteThemeContext {
if (IdUtils.validate(id)) { if (IdUtils.validate(id)) {
internalURL.setId(id); internalURL.setId(id);
if (InternalDataType_Resource.ID.equals(internalURL.getType())) { if (InternalDataType_Resource.ID.equals(internalURL.getType())) {
internalURL.setParams(Map.of("sid", site.getSiteId().toString())); internalURL.getParams().put("sid", site.getSiteId().toString());
} }
return internalURL.toIUrl(); return internalURL.toIUrl();
} }

View File

@ -64,11 +64,13 @@ public class ContentCoreResourceStat implements IResourceStat {
*/ */
private Set<Long> contentLogo() { private Set<Long> contentLogo() {
Set<Long> resourceIds = new HashSet<>(); Set<Long> resourceIds = new HashSet<>();
this.contentService.dao().lambdaQuery().select(List.of(CmsContent::getLogo)).list().forEach(content -> { this.contentService.dao().lambdaQuery().select(List.of(CmsContent::getImages)).list().forEach(content -> {
if (Objects.nonNull(content.getImages())) {
InternalURL internalURL = InternalUrlUtils.parseInternalUrl(content.getLogo()); InternalURL internalURL = InternalUrlUtils.parseInternalUrl(content.getLogo());
if (Objects.nonNull(internalURL)) { if (Objects.nonNull(internalURL)) {
resourceIds.add(internalURL.getId()); resourceIds.add(internalURL.getId());
} }
}
}); });
return resourceIds; return resourceIds;
} }

View File

@ -53,7 +53,7 @@ public class InternalDataType_Catalog implements IInternalDataType {
public String getPageData(RequestData requestData) throws IOException, TemplateException { public String getPageData(RequestData requestData) throws IOException, TemplateException {
CmsCatalog catalog = catalogService.getCatalog(requestData.getDataId()); CmsCatalog catalog = catalogService.getCatalog(requestData.getDataId());
boolean listFlag = YesOrNo.isYes(requestData.getParams().get("list")); boolean listFlag = YesOrNo.isYes(requestData.getParams().get("list"));
return this.publishService.getCatalogPageData(catalog, requestData.getPageIndex(), listFlag, requestData.getPublishPipeCode(), requestData.isPreview()); return this.publishService.getCatalogPageData(catalog, requestData, listFlag);
} }
@Override @Override

View File

@ -55,7 +55,7 @@ public class InternalDataType_Content implements IInternalDataType {
CmsContent content = contentService.dao().getById(requestData.getDataId()); CmsContent content = contentService.dao().getById(requestData.getDataId());
Assert.notNull(content, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", requestData.getDataId())); Assert.notNull(content, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", requestData.getDataId()));
return this.publishService.getContentPageData(content, requestData.getPageIndex(), requestData.getPublishPipeCode(), requestData.isPreview()); return this.publishService.getContentPageData(content, requestData);
} }
@Override @Override

View File

@ -15,19 +15,17 @@
*/ */
package com.chestnut.contentcore.core.impl; package com.chestnut.contentcore.core.impl;
import java.io.IOException;
import org.springframework.stereotype.Component;
import com.chestnut.contentcore.core.IInternalDataType; import com.chestnut.contentcore.core.IInternalDataType;
import com.chestnut.contentcore.core.InternalURL; import com.chestnut.contentcore.core.InternalURL;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.service.IPublishService; import com.chestnut.contentcore.service.IPublishService;
import com.chestnut.contentcore.service.ISiteService; import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.util.SiteUtils; import com.chestnut.contentcore.util.SiteUtils;
import freemarker.template.TemplateException; import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.IOException;
/** /**
* 内部数据类型站点 * 内部数据类型站点
@ -53,7 +51,7 @@ public class InternalDataType_Site implements IInternalDataType {
@Override @Override
public String getPageData(RequestData requestData) throws IOException, TemplateException { public String getPageData(RequestData requestData) throws IOException, TemplateException {
CmsSite site = siteService.getSite(requestData.getDataId()); CmsSite site = siteService.getSite(requestData.getDataId());
return this.publishService.getSitePageData(site, requestData.getPublishPipeCode(), requestData.isPreview()); return this.publishService.getSitePageData(site, requestData);
} }
@Override @Override

View File

@ -15,38 +15,31 @@
*/ */
package com.chestnut.contentcore.core.impl; package com.chestnut.contentcore.core.impl;
import java.awt.image.BufferedImage; import com.chestnut.common.storage.IFileStorageType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import javax.imageio.ImageIO;
import com.chestnut.common.utils.file.ImageUtils;
import com.chestnut.contentcore.properties.ThumbnailHeightProperty;
import com.chestnut.contentcore.properties.ThumbnailWidthProperty;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnailator;
import net.coobird.thumbnailator.util.ThumbnailatorUtils;
import org.apache.commons.compress.utils.FileNameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.utils.StringUtils;
import com.chestnut.common.utils.image.ImageHelper;
import com.chestnut.common.utils.image.ImageUtils;
import com.chestnut.common.utils.image.WatermarkPosition;
import com.chestnut.contentcore.core.IResourceType; import com.chestnut.contentcore.core.IResourceType;
import com.chestnut.contentcore.domain.CmsResource; import com.chestnut.contentcore.domain.CmsResource;
import com.chestnut.contentcore.domain.CmsSite; import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.enums.WatermarkerPosition; import com.chestnut.contentcore.properties.*;
import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty;
import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty.ImageWatermarkArgs; import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty.ImageWatermarkArgs;
import com.chestnut.contentcore.properties.ImageWatermarkProperty;
import com.chestnut.contentcore.service.ISiteService; import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.util.FileStorageHelper;
import com.chestnut.contentcore.util.SiteUtils; import com.chestnut.contentcore.util.SiteUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.coobird.thumbnailator.Thumbnails; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.FileNameUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Map;
import java.util.Objects;
/** /**
* 资源类型图片 * 资源类型图片
@ -67,6 +60,8 @@ public class ResourceType_Image implements IResourceType {
private final ISiteService siteService; private final ISiteService siteService;
private final Map<String, IFileStorageType> fileStorageTypeMap;
@Override @Override
public String getId() { public String getId() {
return ID; return ID;
@ -90,49 +85,71 @@ public class ResourceType_Image implements IResourceType {
@Override @Override
public byte[] process(CmsResource resource, byte[] bytes) throws IOException { public byte[] process(CmsResource resource, byte[] bytes) throws IOException {
CmsSite site = siteService.getSite(resource.getSiteId()); CmsSite site = siteService.getSite(resource.getSiteId());
// 提取图片宽高属性
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) { try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) {
// 提取图片宽高属性
BufferedImage bi = ImageIO.read(is); BufferedImage bi = ImageIO.read(is);
resource.setWidth(bi.getWidth()); resource.setWidth(bi.getWidth());
resource.setHeight(bi.getHeight()); resource.setHeight(bi.getHeight());
// 默认缩略图处理
int w = ThumbnailWidthProperty.getValue(site.getConfigProps());
int h = ThumbnailHeightProperty.getValue(site.getConfigProps());
if (w > 0 && h > 0) {
String siteResourceRoot = SiteUtils.getSiteResourceRoot(site);
Thumbnails.of(bi).size(w, h).toFile(siteResourceRoot + ImageUtils.getThumbnailFileName(resource.getPath(), w, h));
}
// 添加水印 // 添加水印
if (ImageWatermarkProperty.getValue(site.getConfigProps()) if (ImageWatermarkProperty.getValue(site.getConfigProps())) {
&& !"webp".equalsIgnoreCase(resource.getSuffix())) {
// TODO webp水印支持
ImageWatermarkArgs args = ImageWatermarkArgsProperty.getValue(site.getConfigProps()); ImageWatermarkArgs args = ImageWatermarkArgsProperty.getValue(site.getConfigProps());
if (StringUtils.isNotEmpty(args.getImage())) { if (StringUtils.isNotEmpty(args.getImage())) {
// 水印图片占比大小调整
String siteResourceRoot = SiteUtils.getSiteResourceRoot(site); String siteResourceRoot = SiteUtils.getSiteResourceRoot(site);
File file = new File(siteResourceRoot + args.getImage()); File file = new File(siteResourceRoot + args.getImage());
if (file.exists()) { if (file.exists()) {
float waterremakImageWidth = bi.getWidth() * args.getRatio() * 0.01f;
BufferedImage biWatermarkImage = ImageIO.read(file); BufferedImage biWatermarkImage = ImageIO.read(file);
biWatermarkImage = Thumbnails.of(biWatermarkImage)
.scale(waterremakImageWidth / biWatermarkImage.getWidth()).asBufferedImage();
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// 添加水印 String ext = FilenameUtils.getExtension(resource.getFileName());
Thumbnails.of(bi) ImageHelper.of(bi).format(ext).watermark(
.watermark(WatermarkerPosition.valueOf(args.getPosition()).position(), biWatermarkImage,
biWatermarkImage, args.getOpacity()) args.getRatio() * 0.01f,
.scale(1f).outputFormat(resource.getSuffix()).toOutputStream(os); args.getOpacity(),
WatermarkPosition.str2Position(args.getPosition())
).to(os);
bytes = os.toByteArray(); bytes = os.toByteArray();
} }
} }
} }
} }
} catch (Exception e) { } catch (IOException e) {
log.error("图片处理失败:", e); log.error("Read image failed: " + resource.getPath(), e);
resource.setWidth(0); resource.setWidth(0);
resource.setHeight(0); resource.setHeight(0);
} }
resource.setFileSize((long) bytes.length); resource.setFileSize((long) bytes.length);
return bytes; return bytes;
} }
@Override
public void afterProcess(CmsResource resource) {
CmsSite site = siteService.getSite(resource.getSiteId());
int w = ThumbnailWidthProperty.getValue(site.getConfigProps());
int h = ThumbnailHeightProperty.getValue(site.getConfigProps());
if (w > 0 && h > 0) {
// 读取存储配置
String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
IFileStorageType fst = fileStorageTypeMap.get(IFileStorageType.BEAN_NAME_PREIFX + fileStorageType);
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
// 生成默认缩略图
String ext = FilenameUtils.getExtension(resource.getFileName());
String thumbnailPath = ImageUtils.getThumbnailFileName(resource.getPath(), w, h);
InputStream read = fileStorageHelper.read(resource.getPath());
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
ImageHelper.of(read).format(ext).resize(w, h).to(bos);
fileStorageHelper.write(thumbnailPath, bos.toByteArray());
} catch (IOException e) {
log.warn("Generate default thumbnail image failed: " + resource.getPath(), e);
// 生成缩略图失败直接使用源图作为缩略图
fileStorageHelper.write(thumbnailPath, read);
} finally {
try {
if (Objects.nonNull(read)) {
read.close();
}
} catch (IOException e) {
log.warn("Input stream close err!", e);
}
}
}
}
} }

View File

@ -15,9 +15,6 @@
*/ */
package com.chestnut.contentcore.domain; package com.chestnut.contentcore.domain;
import java.util.HashMap;
import java.util.Map;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
@ -28,10 +25,12 @@ import com.chestnut.contentcore.core.impl.PublishPipeProp_IndexTemplate;
import com.chestnut.contentcore.core.impl.PublishPipeProp_ListTemplate; import com.chestnut.contentcore.core.impl.PublishPipeProp_ListTemplate;
import com.chestnut.system.fixed.dict.EnableOrDisable; import com.chestnut.system.fixed.dict.EnableOrDisable;
import com.chestnut.system.fixed.dict.YesOrNo; import com.chestnut.system.fixed.dict.YesOrNo;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
/** /**
* 栏目表对象 [cms_catalog] * 栏目表对象 [cms_catalog]
* *
@ -230,12 +229,7 @@ public class CmsCatalog extends BaseEntity {
if (this.publishPipeProps == null) { if (this.publishPipeProps == null) {
this.publishPipeProps = new HashMap<>(); this.publishPipeProps = new HashMap<>();
} }
Map<String, Object> map = this.publishPipeProps.get(publishPipeCode); return this.publishPipeProps.computeIfAbsent(publishPipeCode, k -> new HashMap<>());
if (map == null) {
map = new HashMap<>();
this.publishPipeProps.put(publishPipeCode, map);
}
return map;
} }
public String getIndexTemplate(String publishPipeCode) { public String getIndexTemplate(String publishPipeCode) {

View File

@ -30,6 +30,7 @@ import org.springframework.beans.BeanUtils;
import java.io.Serial; import java.io.Serial;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -119,10 +120,17 @@ public class CmsContent extends BaseEntity implements IBackupable<BCmsContent> {
private String titleStyle; private String titleStyle;
/** /**
* logo * 封面图
*/ */
@TableField(exist = false)
private String logo; private String logo;
/**
* 新封面图字段
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> images;
/** /**
* 来源 * 来源
*/ */

View File

@ -0,0 +1,69 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.chestnut.common.annotation.XComment;
import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 内容操作记录 [cms_content_op_log]
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
@TableName(CmsContentOpLog.TABLE_NAME)
public class CmsContentOpLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
public static final String TABLE_NAME = "cms_content_op_log";
@TableId(value = "log_id", type = IdType.INPUT)
@XComment("ID")
private Long logId;
@XComment("所属站点ID")
private Long siteId;
@XComment("所属内容ID")
private Long contentId;
@XComment("操作类型")
private String type;
@XComment("操作明细")
private String details;
@XComment("操作人类型")
private String operatorType;
@XComment("操作人用户名")
private String operator;
@XComment("日志时间")
private LocalDateTime logTime;
}

View File

@ -140,12 +140,7 @@ public class CmsSite extends BaseEntity {
if (this.publishPipeProps == null) { if (this.publishPipeProps == null) {
this.publishPipeProps = new HashMap<>(); this.publishPipeProps = new HashMap<>();
} }
Map<String, Object> map = this.publishPipeProps.get(publishPipeCode); return this.publishPipeProps.computeIfAbsent(publishPipeCode, k -> new HashMap<>());
if (map == null) {
map = new HashMap<>();
this.publishPipeProps.put(publishPipeCode, map);
}
return map;
} }
public String getIndexTemplate(String publishPipeCode) { public String getIndexTemplate(String publishPipeCode) {
@ -158,9 +153,7 @@ public class CmsSite extends BaseEntity {
public String getUrl(String publishPipeCode) { public String getUrl(String publishPipeCode) {
String ppUrl = PublishPipeProp_SiteUrl.getValue(publishPipeCode, this.publishPipeProps); String ppUrl = PublishPipeProp_SiteUrl.getValue(publishPipeCode, this.publishPipeProps);
if (ppUrl != null && !ppUrl.endsWith("/")) { ppUrl = StringUtils.appendIfMissing(ppUrl, "/");
ppUrl += "/";
}
return Objects.requireNonNullElse(ppUrl, StringUtils.EMPTY); return Objects.requireNonNullElse(ppUrl, StringUtils.EMPTY);
} }
} }

View File

@ -18,8 +18,8 @@ package com.chestnut.contentcore.domain;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.db.domain.BaseEntity; import com.chestnut.common.db.domain.BaseEntity;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import lombok.Getter; import lombok.Getter;
@ -40,31 +40,21 @@ public class CmsSiteProperty extends BaseEntity {
public static final String TABLE_NAME = "cms_site_property"; public static final String TABLE_NAME = "cms_site_property";
/**
* 属性ID-主键
*/
@TableId(value = "property_id", type = IdType.INPUT) @TableId(value = "property_id", type = IdType.INPUT)
@XComment("ID")
private Long propertyId; private Long propertyId;
/** @XComment("所属站点ID")
* 所属站点ID
*/
private Long siteId; private Long siteId;
/** @XComment("属性名称")
* 属性名称
*/
@NotBlank @NotBlank
private String propName; private String propName;
/** @XComment("属性编码")
* 属性代码 @Pattern(regexp = "[A-Za-z0-9_]+", message = "{VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR}")
*/
@Pattern(regexp = "[A-Za-z0-9_]+")
private String propCode; private String propCode;
/** @XComment("属性值")
* 属性值
*/
private String propValue; private String propValue;
} }

View File

@ -0,0 +1,51 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.util.InternalUrlUtils;
import org.springframework.beans.BeanUtils;
import java.util.List;
public interface InitByContent {
void setAttributes(String[] attributes);
void setLogo(String logo);
void setLogoSrc(String logoSrc);
void setImages(List<String> images);
void setImagesSrc(List<String> imagesSrc);
default void initByContent(CmsContent content, boolean preview) {
BeanUtils.copyProperties(content, this);
this.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
if (StringUtils.isNotEmpty(content.getImages())) {
this.setLogo(content.getImages().get(0));
if (preview) {
this.setImagesSrc(content.getImages().stream().map(InternalUrlUtils::getActualPreviewUrl).toList());
this.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(content.getLogo()));
}
} else {
this.setImages(List.of());
this.setImagesSrc(List.of());
}
}
}

View File

@ -21,7 +21,6 @@ import com.chestnut.system.validator.Dict;
import com.chestnut.system.validator.LongId; import com.chestnut.system.validator.LongId;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -92,6 +91,11 @@ public class CatalogUpdateDTO extends BaseDTO {
*/ */
private String redirectUrl; private String redirectUrl;
/*
* 内容路径规则
*/
private String detailNameRule;
/* /*
* SEO关键词 * SEO关键词
*/ */

View File

@ -13,25 +13,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.chestnut.cms.stat.baidu.vo; package com.chestnut.contentcore.domain.dto;
import java.util.List;
import java.util.Map;
import com.chestnut.common.security.domain.BaseDTO;
import com.chestnut.system.validator.LongId;
import jakarta.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class LineChartVO { public class ClearCatalogDTO extends BaseDTO {
/** /**
* x轴 * 栏目ID
*/ */
private List<String> xAxisDatas; @NotNull
@LongId
/** public Long catalogId;
* y轴数据
*/
private Map<String, List<Object>> datas;
} }

View File

@ -15,11 +15,15 @@
*/ */
package com.chestnut.contentcore.domain.dto; package com.chestnut.contentcore.domain.dto;
import com.chestnut.common.utils.StringUtils; import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.utils.Assert;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.enums.ContentOpType; import com.chestnut.contentcore.domain.InitByContent;
import com.chestnut.contentcore.fixed.dict.ContentAttribute; import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.util.InternalUrlUtils; import com.chestnut.contentcore.fixed.dict.ContentOpType;
import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@ -31,12 +35,12 @@ import java.util.Map;
@Getter @Getter
@Setter @Setter
public class ContentDTO { public class ContentDTO implements InitByContent {
/** /**
* 操作类型 * 操作类型
*/ */
private ContentOpType opType; private String opType;
/** /**
* 内容ID * 内容ID
@ -74,15 +78,25 @@ public class ContentDTO {
private String titleStyle; private String titleStyle;
/** /**
* 引导 * 封面
*/ */
private String logo; private String logo;
/** /**
* 引导图预览路径 * 封面图预览路径
*/ */
private String logoSrc; private String logoSrc;
/**
* 其他图片
*/
private List<String> images = List.of();
/**
* 其他图片预览路径
*/
private List<String> imagesSrc = List.of();
/** /**
* 发布链接 * 发布链接
*/ */
@ -258,13 +272,32 @@ public class ContentDTO {
*/ */
private String prop4; private String prop4;
public CmsContent convertToContentEntity(ICatalogService catalogService, IContentService contentService) {
CmsContent contentEntity;
if (ContentOpType.UPDATE.equals(this.getOpType())) {
contentEntity = contentService.dao().getById(this.getContentId());
Assert.notNull(contentEntity,
() -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", this.getContentId()));
} else {
contentEntity = new CmsContent();
}
BeanUtils.copyProperties(this, contentEntity);
// 所属站点
CmsCatalog catalog = catalogService.getCatalog(this.getCatalogId());
contentEntity.setSiteId(catalog.getSiteId());
contentEntity.setAttributes(ContentAttribute.convertInt(this.getAttributes()));
// 发布通道配置
Map<String, Map<String, Object>> publishPipProps = new HashMap<>();
this.getPublishPipeProps().forEach(prop -> {
publishPipProps.put(prop.getPipeCode(), prop.getProps());
});
contentEntity.setPublishPipeProps(publishPipProps);
return contentEntity;
}
public static ContentDTO newInstance(CmsContent cmsContent) { public static ContentDTO newInstance(CmsContent cmsContent) {
ContentDTO dto = new ContentDTO(); ContentDTO dto = new ContentDTO();
BeanUtils.copyProperties(cmsContent, dto); dto.initByContent(cmsContent, false);
dto.setAttributes(ContentAttribute.convertStr(cmsContent.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
return dto; return dto;
} }
} }

View File

@ -22,4 +22,13 @@ import lombok.Setter;
@Setter @Setter
public class ImageCropDTO { public class ImageCropDTO {
private Long resourceId;
private Integer x;
private Integer y;
private Integer width;
private Integer height;
} }

View File

@ -0,0 +1,51 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ImageRotateDTO {
private Long resourceId;
/**
* 缩略图宽度
*/
private Integer width;
/**
* 缩略图高度
*/
private Integer height;
/**
* 旋转角度
*/
private Integer rotate;
/**
* 水平翻转
*/
private Boolean flipX;
/**
* 垂直翻转
*/
private Boolean flipY;
}

View File

@ -13,42 +13,31 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.chestnut.cms.stat.baidu.vo; package com.chestnut.contentcore.domain.dto;
import java.util.List;
import java.util.Map;
import com.chestnut.common.security.domain.BaseDTO;
import com.chestnut.system.validator.LongId;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Getter @Getter
@Setter @Setter
public class BaiduTimeTrendVO extends LineChartVO { public class MergeCatalogDTO extends BaseDTO {
private Integer offset;
/** /**
* 时间范围 * 栏目ID
*/ */
private String timeSpan; @NotNull
@LongId
public Long catalogId;
/** /**
* 指标字段 * 被合并栏目IDs
*/ */
private List<String> fields; @NotEmpty
public List<Long> mergeCatalogIds;
/**
* 总数
*/
private Integer total;
/**
* 指标合计
*/
private Map<String, Object> sum;
/**
*
*/
private Map<String, Object> pageSum;
} }

View File

@ -16,15 +16,15 @@
package com.chestnut.contentcore.domain.vo; package com.chestnut.contentcore.domain.vo;
import com.chestnut.contentcore.domain.CmsContent; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.fixed.dict.ContentAttribute; import com.chestnut.contentcore.domain.InitByContent;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.time.ZoneOffset; import java.util.List;
@Getter @Getter
@Setter @Setter
public class ContentApiVO { public class ContentApiVO implements InitByContent {
/** /**
* 内容ID * 内容ID
@ -72,15 +72,25 @@ public class ContentApiVO {
private String titleStyle; private String titleStyle;
/** /**
* 引导 * 封面
*/ */
private String logo; private String logo;
/** /**
* 引导图预览路径 * 封面图预览路径
*/ */
private String logoSrc; private String logoSrc;
/**
* 其他图片
*/
private List<String> images = List.of();
/**
* 其他图片预览路径
*/
private List<String> imagesSrc = List.of();
/** /**
* 发布链接 * 发布链接
*/ */
@ -161,35 +171,9 @@ public class ContentApiVO {
*/ */
private Long viewCount; private Long viewCount;
protected void copyProperties(CmsContent content) {
this.setAuthor(content.getAuthor());
this.setCatalogId(content.getCatalogId());
this.setContentId(content.getContentId());
this.setContentType(content.getContentType());
this.setEditor(content.getEditor());
this.setKeywords(content.getKeywords());
this.setLogo(content.getLogo());
this.setOriginal(content.getOriginal());
this.setPublishDate(content.getPublishDate().toInstant(ZoneOffset.UTC).toEpochMilli());
this.setShortTitle(content.getShortTitle());
this.setSubTitle(content.getSubTitle());
this.setTitle(content.getTitle());
this.setSource(content.getSource());
this.setSourceUrl(content.getSourceUrl());
this.setSummary(content.getSummary());
this.setTags(content.getTags());
this.setTitleStyle(content.getTitleStyle());
this.setTopFlag(content.getTopFlag());
this.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
this.setViewCount(content.getViewCount());
this.setLikeCount(content.getLikeCount());
this.setCommentCount(content.getCommentCount());
this.setFavoriteCount(content.getFavoriteCount());
}
public static ContentApiVO newInstance(CmsContent content) { public static ContentApiVO newInstance(CmsContent content) {
ContentApiVO vo = new ContentApiVO(); ContentApiVO vo = new ContentApiVO();
vo.copyProperties(content); vo.initByContent(content, false);
return vo; return vo;
} }
} }

View File

@ -13,31 +13,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.chestnut.common.redis; package com.chestnut.contentcore.domain.vo;
import com.chestnut.contentcore.publish.IContentPathRule;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/**
* 被监控的Redis缓存数据
*/
@Getter @Getter
@Setter @Setter
public class MonitoredCache { public class ContentPathRuleVO {
private String id;
/** private String name;
* 缓存名称
*/
private String cacheName = "";
/** public static ContentPathRuleVO newInstance(IContentPathRule contentPathRule) {
* 缓存键名 ContentPathRuleVO vo = new ContentPathRuleVO();
*/ vo.setId(contentPathRule.getId());
private String cacheKey = ""; vo.setName(contentPathRule.getName());
return vo;
public MonitoredCache(String cacheName, String cacheKey) {
this.cacheName = cacheName;
this.cacheKey = cacheKey;
} }
} }

View File

@ -15,26 +15,20 @@
*/ */
package com.chestnut.contentcore.domain.vo; package com.chestnut.contentcore.domain.vo;
import com.chestnut.contentcore.domain.InitByContent;
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeanUtils;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter;
import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class ContentVO { public class ContentVO implements InitByContent {
/** /**
* 内容ID * 内容ID
@ -82,15 +76,25 @@ public class ContentVO {
private String showSubTitle; private String showSubTitle;
/** /**
* 引导 * 封面
*/ */
private String logo; private String logo;
/** /**
* 引导图预览路径 * 封面图预览路径
*/ */
private String logoSrc; private String logoSrc;
/**
* 新多图封面字段
*/
private List<String> images = List.of();
/**
* 新多图封面预览路径
*/
private List<String> imagesSrc = List.of();
/** /**
* 发布链接 * 发布链接
*/ */
@ -265,14 +269,4 @@ public class ContentVO {
* 自定义参数 * 自定义参数
*/ */
private Map<String, Object> params; private Map<String, Object> params;
public static ContentVO newInstance(CmsContent cmsContent) {
ContentVO dto = new ContentVO();
BeanUtils.copyProperties(cmsContent, dto);
dto.setAttributes(ContentAttribute.convertStr(cmsContent.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
return dto;
}
} }

View File

@ -15,17 +15,20 @@
*/ */
package com.chestnut.contentcore.domain.vo; package com.chestnut.contentcore.domain.vo;
import java.time.LocalDateTime; import com.chestnut.contentcore.core.impl.InternalDataType_Content;
import java.util.Date; import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.InitByContent;
import com.baomidou.mybatisplus.annotation.TableField; import com.chestnut.contentcore.util.InternalUrlUtils;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Getter @Getter
@Setter @Setter
public class ListContentVO { public class ListContentVO implements InitByContent {
/* /*
* 内容ID * 内容ID
@ -72,6 +75,16 @@ public class ListContentVO {
*/ */
private String logoSrc; private String logoSrc;
/*
* 引导图
*/
private List<String> images;
/*
* 引导图预览路径
*/
private List<String> imagesSrc;
/* /*
* 内部链接 * 内部链接
*/ */
@ -166,4 +179,11 @@ public class ListContentVO {
* 创建时间 * 创建时间
*/ */
private LocalDateTime createTime; private LocalDateTime createTime;
public static ListContentVO newInstance(CmsContent content) {
ListContentVO vo = new ListContentVO();
vo.initByContent(content, true);
vo.setInternalUrl(InternalUrlUtils.getInternalUrl(InternalDataType_Content.ID, content.getContentId()));
return vo;
}
} }

View File

@ -0,0 +1,48 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain.vo;
import com.chestnut.common.annotation.XComment;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* TagBaseVO
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class TagBaseVO {
@XComment("创建者")
private String createBy;
@XComment("创建时间")
private LocalDateTime createTime;
@XComment("更新者")
private String updateBy;
@XComment("更新时间")
private LocalDateTime updateTime;
@XComment("备注")
private String remark;
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain.vo;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsCatalog;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeanUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 栏目标签数据对象
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class TagCatalogVO extends TagBaseVO {
@XComment("栏目ID")
private Long catalogId;
@XComment("站点ID")
private Long siteId;
@XComment("父级栏目ID")
private Long parentId;
@XComment("祖级栏目IDs")
private String ancestors;
@XComment("栏目名称")
private String name;
@XComment("栏目引导图")
private String logo;
@XComment(value = "栏目引导图访问路径", deprecated = true, forRemoval = "1.6.0")
private String logoSrc;
@XComment("栏目别名")
private String alias;
@XComment("栏目简介")
private String description;
@XComment("所属部门编码")
private String deptCode;
@XComment("栏目类型")
private String catalogType;
@XComment("栏目目录")
private String path;
@XComment("标题栏目跳转地址")
private String redirectUrl;
@XComment("排序值")
private Long sortFlag;
@XComment("栏目层级")
private Integer treeLevel;
@XComment("子栏目数")
private Integer childCount;
@XComment("内容数量")
private Integer contentCount;
@XComment("SEO关键词")
private String seoKeywords;
@XComment("SEO描述")
private String seoDescription;
@XComment("SEO标题")
private String seoTitle;
@XComment("扩展配置")
private Map<String, String> configProps;
@XComment("栏目链接")
private String link;
@XComment("列表页链接无首页模板时与link一致")
private String listLink;
public static TagCatalogVO newInstance(CmsCatalog catalog, String publishPipeCode, boolean preview) {
TagCatalogVO vo = new TagCatalogVO();
BeanUtils.copyProperties(catalog, vo);
if (StringUtils.isNotEmpty(catalog.getLogo())) {
// 兼容历史版本
vo.setLogoSrc(InternalUrlUtils.getActualUrl(catalog.getLogo(), publishPipeCode, preview));
}
return vo;
}
public Map<String, String> getConfigProps() {
if (this.configProps == null) {
this.configProps = new HashMap<>();
}
return configProps;
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain.vo;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeanUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 内容标签数据对象
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class TagContentVO extends TagBaseVO {
@XComment("内容ID")
private Long contentId;
@XComment("所属站点ID")
private Long siteId;
@XComment("所属栏目ID")
private Long catalogId;
@XComment("所属栏目祖级IDs")
private String catalogAncestors;
@XComment("所属顶级栏目")
private Long topCatalog;
@XComment("所属部门ID")
private Long deptId;
@XComment("所属部门编码")
private String deptCode;
@XComment("内容类型")
private String contentType;
@XComment("标题")
private String title;
@XComment("副标题")
private String subTitle;
@XComment("短标题")
private String shortTitle;
@XComment("标题样式")
private String titleStyle;
@XComment(value = "封面图", deprecated = true, forRemoval = "1.6.0")
private String logo;
@XComment(value = "封面图访问路径", deprecated = true, forRemoval = "1.6.0")
private String logoSrc;
@XComment("封面图列表")
private List<String> images;
@XComment("来源")
private String source;
@XComment("来源URL")
private String sourceUrl;
@XComment("是否原创")
private String original;
@XComment("作者")
private String author;
@XComment("编辑")
private String editor;
@XComment("投稿用户ID")
private Long contributorId;
@XComment("摘要")
private String summary;
@XComment("内容属性标识列表")
private String[] attributes;
@XComment("是否链接内容")
private String linkFlag;
@XComment("跳转链接linkFlag==Y")
private String redirectUrl;
@XComment("置顶标识")
private Long topFlag;
@XComment("置顶结束时间")
private LocalDateTime topDate;
@XComment("排序值")
private Long sortFlag;
@XComment("关键词")
private String[] keywords;
@XComment("TAGs")
private String[] tags;
@XComment("发布时间")
private LocalDateTime publishDate;
@XComment("SEO标题")
private String seoTitle;
@XComment("SEO关键词")
private String seoKeywords;
@XComment("SEO描述")
private String seoDescription;
@XComment("点赞数(非实时)")
private Long likeCount;
@XComment("评论数(非实时)")
private Long commentCount;
@XComment("收藏数(非实时)")
private Long favoriteCount;
@XComment("文章浏览数(非实时)")
private Long viewCount;
@XComment("备用字段1")
private String prop1;
@XComment("备用字段2")
private String prop2;
@XComment("备用字段3")
private String prop3;
@XComment("备用字段4")
private String prop4;
@XComment("内容链接")
private String link;
public static TagContentVO newInstance(CmsContent content, String publishPipeCode, boolean preview) {
TagContentVO vo = new TagContentVO();
BeanUtils.copyProperties(content, vo);
vo.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
if (StringUtils.isNotEmpty(content.getImages())) {
// 兼容历史版本
vo.setLogo(content.getImages().get(0));
vo.setLogoSrc(InternalUrlUtils.getActualUrl(content.getLogo(), publishPipeCode, preview));
}
return vo;
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain.vo;
import com.chestnut.common.annotation.XComment;
import com.chestnut.contentcore.domain.CmsPageWidget;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeanUtils;
/**
* 栏目标签数据对象
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class TagPageWidgetVO {
@XComment("页面部件ID")
private Long pageWidgetId;
@XComment("所属站点ID")
private Long siteId;
@XComment("所属栏目ID")
private Long catalogId;
@XComment("所属栏目祖级IDs")
private String catalogAncestors;
@XComment("类型")
private String type;
@XComment("名称")
private String name;
@XComment("编码")
private String code;
@XComment("发布通道")
private String publishPipeCode;
@XComment("页面部件扩展数据")
private Object contentObj;
public static TagPageWidgetVO newInstance(CmsPageWidget pageWidget) {
TagPageWidgetVO vo = new TagPageWidgetVO();
BeanUtils.copyProperties(pageWidget, vo);
return vo;
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.domain.vo;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeanUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 站点标签数据对象
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
public class TagSiteVO extends TagBaseVO {
@XComment("站点ID")
private Long siteId;
@XComment("父级站点ID")
private Long parentId;
@XComment("站点名称")
private String name;
@XComment("站点描述")
private String description;
@XComment("站点LOGO")
private String logo;
@XComment(value = "站点LOGO访问地址", deprecated = true, forRemoval = "1.6.0")
private String logoSrc;
@XComment("站点目录")
private String path;
@XComment("站点资源访问域名")
private String resourceUrl;
@XComment("所属部门编码")
private String deptCode;
@XComment("排序值")
private Long sortFlag;
@XComment("SEO关键词")
private String seoKeywords;
@XComment("SEO描述")
private String seoDescription;
@XComment("SEO标题")
private String seoTitle;
@XComment("扩展属性配置")
private Map<String, String> configProps;
@XComment("站点访问地址")
private String link;
public static TagSiteVO newInstance(CmsSite site, String publishPipeCode, boolean preview) {
TagSiteVO vo = new TagSiteVO();
BeanUtils.copyProperties(site, vo);
if (StringUtils.isNotEmpty(site.getLogo())) {
// 兼容历史版本
vo.setLogoSrc(InternalUrlUtils.getActualUrl(site.getLogo(), publishPipeCode, preview));
}
return vo;
}
public Map<String, String> getConfigProps() {
if (this.configProps == null) {
this.configProps = new HashMap<>();
}
return configProps;
}
}

View File

@ -15,14 +15,13 @@
*/ */
package com.chestnut.contentcore.domain.vo; package com.chestnut.contentcore.domain.vo;
import java.util.List;
import com.chestnut.common.staticize.func.IFunction.FuncArg; import com.chestnut.common.staticize.func.IFunction.FuncArg;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Getter @Getter
@Setter @Setter
@Builder @Builder
@ -35,4 +34,6 @@ public class TemplateFuncVO {
private String desc; private String desc;
private List<FuncArg> funcArgs; private List<FuncArg> funcArgs;
private String demoLink;
} }

View File

@ -15,14 +15,13 @@
*/ */
package com.chestnut.contentcore.domain.vo; package com.chestnut.contentcore.domain.vo;
import java.util.List;
import com.chestnut.common.staticize.tag.TagAttr; import com.chestnut.common.staticize.tag.TagAttr;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Getter @Getter
@Setter @Setter
@Builder @Builder
@ -35,4 +34,6 @@ public class TemplateTagVO {
private String description; private String description;
private List<TagAttr> tagAttrs; private List<TagAttr> tagAttrs;
private String demoLink;
} }

View File

@ -20,9 +20,13 @@ import java.util.Objects;
/** /**
* 内容复制方式 * 内容复制方式
* *
* @author 兮玥
* @email 190785909@qq.com
*/ */
public class ContentCopyType { public class ContentCopyType {
public static final int NONE = 0;
/** /**
* 独立复制完整拷贝内容所有信息拷贝的内容变更与源内容无关仅仅记录来源 * 独立复制完整拷贝内容所有信息拷贝的内容变更与源内容无关仅仅记录来源
*/ */

View File

@ -1,50 +0,0 @@
/*
* Copyright 2022-2024 兮玥(190785909@qq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.enums;
import net.coobird.thumbnailator.geometry.Position;
import net.coobird.thumbnailator.geometry.Positions;
public enum WatermarkerPosition {
TOP_LEFT(Positions.TOP_LEFT), // 左上
TOP_CENTER(Positions.TOP_CENTER), //
TOP_RIGHT(Positions.TOP_RIGHT), // 右上
CENTER_LEFT(Positions.CENTER_LEFT), //
CENTER(Positions.CENTER), //
CENTER_RIGHT(Positions.CENTER_RIGHT), //
BOTTOM_LEFT(Positions.BOTTOM_LEFT), // 左下
BOTTOM_CENTER(Positions.BOTTOM_CENTER), //
BOTTOM_RIGHT(Positions.BOTTOM_RIGHT); // 右下
private Position position;
WatermarkerPosition(Position position) {
this.position = position;
}
public Position position() {
return position;
}
}

View File

@ -15,6 +15,12 @@
*/ */
package com.chestnut.contentcore.enums; package com.chestnut.contentcore.enums;
/**
* 水印类型
*
* @author 兮玥
* @email 190785909@qq.com
*/
public enum WatermarkerType { public enum WatermarkerType {
/** /**
@ -23,7 +29,7 @@ public enum WatermarkerType {
NONE, NONE,
/** /**
* 图片谁赢 * 图片水印
*/ */
IMAGE, IMAGE,

View File

@ -69,6 +69,11 @@ public enum ContentCoreErrorCode implements ErrorCode {
*/ */
UNSUPPORTED_DYNAMIC_PAGE_TYPE, UNSUPPORTED_DYNAMIC_PAGE_TYPE,
/**
* 不支持的内容详情页路径规则{0}
*/
UNSUPPORTED_CONTENT_PATH_RULE,
/** /**
* 请先删除子栏目 * 请先删除子栏目
*/ */
@ -177,7 +182,27 @@ public enum ContentCoreErrorCode implements ErrorCode {
/** /**
* 上传文件超过限制 * 上传文件超过限制
*/ */
RESOURCE_ACCEPT_SIZE_LIMIT; RESOURCE_ACCEPT_SIZE_LIMIT,
/**
* 不能处理非图片资源
*/
ONLY_SUPPORT_IMAGE,
/**
* 资源存储方式与站点配置不一致
*/
UNSUPPORTED_RESOURCE_STORAGE,
/**
* 被合并栏目不能为空
*/
MERGE_CATALOG_IS_EMPTY,
/**
* 不能合并包含子栏目的栏目
*/
MERGE_CATALOG_NOT_LEAF;
@Override @Override
public String value() { public String value() {

View File

@ -23,6 +23,10 @@ package com.chestnut.contentcore.exception;
*/ */
public class InternalUrlParseException extends RuntimeException { public class InternalUrlParseException extends RuntimeException {
public InternalUrlParseException(String message) {
super("Parse iurl failed: " + message);
}
public InternalUrlParseException(String message, Exception e) { public InternalUrlParseException(String message, Exception e) {
super("Parse iurl failed: " + message, e); super("Parse iurl failed: " + message, e);
} }

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