版本更新: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
The Apache Software Foundation (https://www.apache.org/).
-----------------------------------------------------------------------
This product contains code from the redisson 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:
This product contains code from the Apache Commons Text Project:
Apache Commons Text
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/VelocityScriptEngineFactory.java
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
企业站演示地址:<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>

View File

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

View File

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

View File

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

View File

@ -1,222 +1,222 @@
# 项目相关配置
chestnut:
# 名称
name: 栗子内容管理系统
# 代号
alias: ChestnutCMS
# 版本
version: 1.5.0
# 版权年份
copyrightYear: 2022-2024
system:
# 演示模式开关
demoMode: true
# 文件路径 示例( Windows配置D:/chestnut/uploadPathLinux配置 /home/app/uploadPath
uploadPath: /home/app/uploadPath
# 验证码类型 math 数组计算 char 字符验证
captchaType: math
freemarker:
templateLoaderPath: /home/app/statics
cms:
resourceRoot: /home/app/wwwroot_release
# 开发环境配置
server:
# 服务器的HTTP端口默认为8090
port: 8090
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.chestnut: debug
org.springframework: warn
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 20MB
# 设置总上传的文件大小
max-request-size: 100MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: false
freemarker:
check-template-location: false
elasticsearch:
uris: http://cc-elasticsearch:9200
username: elastic
password: hello1234
# redis 配置
data:
redis:
# 地址
host: cc-redis
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: b18a03
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
flyway:
enabled: false
# 迁移sql脚本文件存放路径默认classpath:db/migration
locations: classpath:db/migration/mysql
# 迁移sql脚本文件名称的前缀默认V
sql-migration-prefix: V
# 迁移sql脚本文件名称分隔符默认2个下划线__
sql-migration-separator: __
# 迁移sql脚本文件名称后缀
sql-migration-suffixes: .sql
# 迁移时是否进行校验
validate-on-migrate: true
# 当迁移发现数据库非空且存在没有元数据的表时自动执行基准迁移新建schema_version表
baseline-on-migrate: true
# 数据库配置
datasource:
# type: com.alibaba.druid.pool.DruidDataSource
type: com.zaxxer.hikari.HikariDataSource
dynamic:
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
# 主库
datasource:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3308/chestnut_cms?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: hello1234
# 从库
#slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
# username:
# password:
hikari:
# 连接池名
pool-name: HikariCP
# 连接超时时间:毫秒, 默认30秒
connection-timeout: 2000
# 最小空闲连接默认值10小于0或大于maximum-pool-size都会重置为maximum-pool-size
minimum-idle: 5
# 最大连接数小于等于0会被重置为默认值10大于零小于1会被重置为minimum-idle的值
maximum-pool-size: 20
# 空闲连接最大存活时间默认值60000010分钟大于等于max-lifetime且max-lifetime>0会被重置为0不等于0且小于10秒会被重置为10秒。
idle-timeout: 200000
# 连接池返回的连接默认自动提交,默认只 true
auto-commit: true
# 连接最大存活时间不等于0且小于30秒会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
max-lifetime: 1800000
# 用于测试连接是否可用的查询语句
connection-test-query: SELECT 1
# 监控配置
application:
name: "ChestnutCMS"
boot:
admin:
client:
# 增加客户端开关
enabled: false
# Admin Server URL
url: http://127.0.0.1:8090/admin
instance:
service-host-type: IP
username: chestnut
password: 123456
# SaToken配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token前缀
token-prefix: Bearer
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
active-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: true
# MyBatis配置
mybatis-plus:
global-config:
enable-sql-runner: true
# 搜索指定包别名
typeAliasesPackage: com.chestnut.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
mode: clean
# 过滤链接
urlPatterns:
- /system/*
- /monitor/*
- /tool/*
xxl:
job:
enable: false
accessToken: default_token
adminAddresses: http://127.0.0.1:18080/xxl-job-admin
executor:
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
appname: chestnut-admin
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
#address:
### 执行器IP [选填]默认为空表示自动获取IP多网卡时可手动设置指定IP该IP不会绑定Host仅作为通讯实用地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"
ip:
### 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
port: 9968
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logpath: E:/dev/workspace_chestnut/ChestnutCMS/chestnut-modules/chestnut-xxljob/jobhandler
### 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能默认30
# 项目相关配置
chestnut:
# 名称
name: 栗子内容管理系统
# 代号
alias: ChestnutCMS
# 版本
version: 1.5.1
# 版权年份
copyrightYear: 2022-2024
system:
# 演示模式开关
demoMode: true
# 文件路径 示例( Windows配置D:/chestnut/uploadPathLinux配置 /home/app/uploadPath
uploadPath: /home/app/uploadPath
# 验证码类型 math 数组计算 char 字符验证
captchaType: math
freemarker:
templateLoaderPath: /home/app/statics
cms:
resourceRoot: /home/app/wwwroot_release
# 开发环境配置
server:
# 服务器的HTTP端口默认为8090
port: 8090
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.chestnut: debug
org.springframework: warn
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 20MB
# 设置总上传的文件大小
max-request-size: 100MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: false
freemarker:
check-template-location: false
elasticsearch:
uris: http://cc-elasticsearch:9200
username: elastic
password: hello1234
# redis 配置
data:
redis:
# 地址
host: cc-redis
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: b18a03
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
flyway:
enabled: false
# 迁移sql脚本文件存放路径默认classpath:db/migration
locations: classpath:db/migration/mysql
# 迁移sql脚本文件名称的前缀默认V
sql-migration-prefix: V
# 迁移sql脚本文件名称分隔符默认2个下划线__
sql-migration-separator: __
# 迁移sql脚本文件名称后缀
sql-migration-suffixes: .sql
# 迁移时是否进行校验
validate-on-migrate: true
# 当迁移发现数据库非空且存在没有元数据的表时自动执行基准迁移新建schema_version表
baseline-on-migrate: true
# 数据库配置
datasource:
# type: com.alibaba.druid.pool.DruidDataSource
type: com.zaxxer.hikari.HikariDataSource
dynamic:
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
# 主库
datasource:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3308/chestnut_cms?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: hello1234
# 从库
#slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
# username:
# password:
hikari:
# 连接池名
pool-name: HikariCP
# 连接超时时间:毫秒, 默认30秒
connection-timeout: 2000
# 最小空闲连接默认值10小于0或大于maximum-pool-size都会重置为maximum-pool-size
minimum-idle: 5
# 最大连接数小于等于0会被重置为默认值10大于零小于1会被重置为minimum-idle的值
maximum-pool-size: 20
# 空闲连接最大存活时间默认值60000010分钟大于等于max-lifetime且max-lifetime>0会被重置为0不等于0且小于10秒会被重置为10秒。
idle-timeout: 200000
# 连接池返回的连接默认自动提交,默认只 true
auto-commit: true
# 连接最大存活时间不等于0且小于30秒会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
max-lifetime: 1800000
# 用于测试连接是否可用的查询语句
connection-test-query: SELECT 1
# 监控配置
application:
name: "ChestnutCMS"
boot:
admin:
client:
# 增加客户端开关
enabled: false
# Admin Server URL
url: http://127.0.0.1:8090/admin
instance:
service-host-type: IP
username: chestnut
password: 123456
# SaToken配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token前缀
token-prefix: Bearer
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
active-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: true
# MyBatis配置
mybatis-plus:
global-config:
enable-sql-runner: true
# 搜索指定包别名
typeAliasesPackage: com.chestnut.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
mode: clean
# 过滤链接
urlPatterns:
- /system/*
- /monitor/*
- /tool/*
xxl:
job:
enable: false
accessToken: default_token
adminAddresses: http://127.0.0.1:18080/xxl-job-admin
executor:
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
appname: chestnut-admin
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
#address:
### 执行器IP [选填]默认为空表示自动获取IP多网卡时可手动设置指定IP该IP不会绑定Host仅作为通讯实用地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"
ip:
### 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
port: 9968
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logpath: E:/dev/workspace_chestnut/ChestnutCMS/chestnut-modules/chestnut-xxljob/jobhandler
### 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能默认30
logretentiondays: 30

View File

@ -4,9 +4,6 @@ ALTER TABLE cms_image DROP COLUMN deleted;
ALTER TABLE cms_audio DROP COLUMN deleted;
ALTER TABLE cms_video DROP COLUMN deleted;
-- ----------------------------
-- Table structure for b_cms_content
-- ----------------------------
CREATE TABLE `b_cms_content` (
`content_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,
PRIMARY KEY (`backup_id`) USING BTREE
) 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` (
`content_id` bigint NOT NULL COMMENT 'ID',
`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,
PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_image
-- ----------------------------
CREATE TABLE `b_cms_image` (
`image_id` bigint NOT NULL COMMENT '主键ID',
`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,
PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_audio
-- ----------------------------
CREATE TABLE `b_cms_audio` (
`audio_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,
PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for b_cms_video
-- ----------------------------
CREATE TABLE `b_cms_video` (
`video_id` bigint NOT NULL,
`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;
-- __CC_IGNORE__
-- ----------------------------
-- Table structure for cms_book
-- ----------------------------
CREATE TABLE `cms_book` (
`content_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 '是否完结',
PRIMARY KEY (`content_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for b_cms_book
-- ----------------------------
CREATE TABLE `b_cms_book` (
`content_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,
PRIMARY KEY (`backup_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for cms_book_chapter
-- ----------------------------
CREATE TABLE `cms_book_chapter` (
`chapter_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 '备注',
PRIMARY KEY (`chapter_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for b_cms_book_chapter
-- ----------------------------
CREATE TABLE `b_cms_book_chapter` (
`chapter_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>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.5.0</version>
<version>1.5.1</version>
</parent>
<artifactId>chestnut-cms-advertisement</artifactId>

View File

@ -29,7 +29,6 @@ import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
import java.util.logging.Logger;
/**
* 广告页面部件内容核心数据处理器
@ -61,6 +60,7 @@ public class AdCoreDataHandler implements ICoreDataHandler {
files.forEach(f -> {
List<CmsAdvertisement> list = JacksonUtils.fromList(f, CmsAdvertisement.class);
for (CmsAdvertisement data : list) {
Long oldAdvertisementId = data.getAdvertisementId();
try {
data.setAdvertisementId(IdUtils.getSnowflakeId());
data.setSiteId(context.getSite().getSiteId());
@ -70,8 +70,7 @@ public class AdCoreDataHandler implements ICoreDataHandler {
data.setRedirectUrl(context.dealInternalUrl(data.getRedirectUrl()));
advertisementService.save(data);
} catch (Exception e) {
AsyncTaskManager.addErrMessage("导入广告数据失败:" + data.getName()
+ "[" + data.getAdvertisementId() + "]");
AsyncTaskManager.addErrMessage("导入广告数据`" + oldAdvertisementId + "`失败:" + e.getMessage());
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;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.chestnut.advertisement.domain.CmsAdvertisement;
import com.chestnut.common.annotation.XComment;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* 广告数据VO
*
@ -36,79 +36,49 @@ import lombok.Setter;
@NoArgsConstructor
public class AdvertisementVO {
/**
* 广告ID
*/
@XComment("广告ID")
private Long advertisementId;
/**
* 所属广告位ID
*/
@XComment("所属广告版本页面部件ID")
private Long adSpaceId;
/**
* 类型
*/
@XComment("类型")
private String type;
/**
* 名称
*/
@XComment("名称")
private String name;
/**
* 权重
*/
@XComment("权重")
private Integer weight;
/**
* 关键词
*/
@XComment("关键词")
private String keywords;
/**
* 状态
*/
@XComment("状态")
private String state;
/**
* 上线时间
*/
@XComment("上线时间")
private LocalDateTime onlineDate;
/**
* 下线时间
*/
@XComment("下线时间")
private LocalDateTime offlineDate;
/**
* 跳转链接
*/
@XComment("原始跳转链接")
private String redirectUrl;
/**
* 跳转链接可设置为中转地址
*/
@XComment("实际跳转链接(可设置为中转地址)")
private String link;
/**
* 素材链接
*/
@XComment("素材路径")
private String resourcePath;
/**
* 素材真实地址
*/
@XComment("素材访问链接")
private String resourceSrc;
/**
* 创建人
*/
@XComment("创建人")
private String createBy;
/**
* 创建时间
*/
@XComment("创建时间")
private LocalDateTime createTime;
public AdvertisementVO(CmsAdvertisement ad) {

View File

@ -15,35 +15,41 @@
*/
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.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
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>
* 广告数据服务实现类
@ -57,9 +63,7 @@ import lombok.RequiredArgsConstructor;
public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper, CmsAdvertisement>
implements IAdvertisementService {
private static final String CACHE_KEY_ADV_IDS = CMSConfig.CachePrefix + "adv-ids";
private final RedisCache redisCache;
private final AdMonitoredCache adCache;
private final Map<String, IAdvertisementType> advertisementTypes;
@ -67,6 +71,8 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
private final ISiteService siteService;
private final AsyncTaskManager asyncTaskManager;
@Override
public IAdvertisementType getAdvertisementType(String typeId) {
return this.advertisementTypes.get(IAdvertisementType.BEAN_NAME_PREFIX + typeId);
@ -79,10 +85,12 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
@Override
public Map<String, String> getAdvertisementMap() {
return this.redisCache.getCacheMap(CACHE_KEY_ADV_IDS,
() -> this.lambdaQuery().select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getName)).list()
.stream().collect(
Collectors.toMap(ad -> ad.getAdvertisementId().toString(), CmsAdvertisement::getName)));
return adCache.getCache(() -> {
return this.lambdaQuery()
.select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getName))
.list().stream()
.collect(Collectors.toMap(ad -> ad.getAdvertisementId().toString(), CmsAdvertisement::getName));
});
}
@Override
@ -99,7 +107,7 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
advertisement.createBy(dto.getOperator().getUsername());
this.save(advertisement);
this.redisCache.deleteObject(CACHE_KEY_ADV_IDS);
this.adCache.clear();
return advertisement;
}
@ -116,12 +124,14 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAdvertisement(List<Long> advertisementIds) {
this.removeByIds(advertisementIds);
this.redisCache.deleteObject(CACHE_KEY_ADV_IDS);
this.adCache.clear();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void enableAdvertisement(List<Long> advertisementIds, String operator) {
List<CmsAdvertisement> list = this.listByIds(advertisementIds);
for (CmsAdvertisement ad : list) {
@ -134,6 +144,7 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
}
@Override
@Transactional(rollbackFor = Exception.class)
public void disableAdvertisement(List<Long> advertisementIds, String operator) {
List<CmsAdvertisement> list = this.listByIds(advertisementIds);
for (CmsAdvertisement ad : list) {
@ -143,7 +154,14 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
}
}
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

View File

@ -45,11 +45,15 @@ import java.util.Map;
public class CmsAdvertisementTag extends AbstractListTag {
public final static String TAG_NAME = "cms_advertisement";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}";
public final static String NAME = "{FREEMARKER.TAG." + TAG_NAME + ".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 TagAttr_RedirectType = "type";
final static String ATTR_CODE = "code";
final static String ATTR_TYPE = "type";
private final IAdvertisementService advertisementService;
@ -58,23 +62,23 @@ public class CmsAdvertisementTag extends AbstractListTag {
@Override
public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = super.getTagAttrs();
tagAttrs.add(new TagAttr(TagAttr_Code, true, TagAttrDataType.STRING, "广告位编码"));
tagAttrs.add(new TagAttr(TagAttr_RedirectType, false, TagAttrDataType.STRING, "广告跳转方式",
tagAttrs.add(new TagAttr(ATTR_CODE, true, TagAttrDataType.STRING, ATTR_USAGE_CODE));
tagAttrs.add(new TagAttr(ATTR_TYPE, false, TagAttrDataType.STRING, ATTR_USAGE_TYPE,
RedirectType.toTagAttrOptions(), RedirectType.None.name()));
return tagAttrs;
}
@Override
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 redirectType = MapUtils.getString(attrs, TagAttr_RedirectType, RedirectType.None.name());
String code = MapUtils.getString(attrs, ATTR_CODE);
String redirectType = MapUtils.getString(attrs, ATTR_TYPE, RedirectType.None.name());
Long siteId = TemplateUtils.evalSiteId(env);
CmsPageWidget adSpace = this.pageWidgetService.getOne(new LambdaQueryWrapper<CmsPageWidget>()
.eq(CmsPageWidget::getSiteId, siteId)
.eq(CmsPageWidget::getCode, code));
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);
@ -83,9 +87,6 @@ public class CmsAdvertisementTag extends AbstractListTag {
.eq(CmsAdvertisement::getState, EnableOrDisable.ENABLE);
q.apply(StringUtils.isNotEmpty(condition), condition);
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);
List<AdvertisementVO> list = pageResult.getRecords().stream().map(ad ->{
AdvertisementVO vo = new AdvertisementVO(ad);
@ -99,6 +100,11 @@ public class CmsAdvertisementTag extends AbstractListTag {
return TagPageData.of(list, pageResult.getTotal());
}
@Override
public Class<AdvertisementVO> getDataClass() {
return AdvertisementVO.class;
}
@Override
public String getTagName() {
return TAG_NAME;
@ -115,8 +121,8 @@ public class CmsAdvertisementTag extends AbstractListTag {
}
private enum RedirectType {
None("原始链接"),
Stat("统计链接");
None(ATTR_OPTION_TYPE_NONE),
Stat(ATTR_OPTION_TYPE_STAT);
private final String desc;

View File

@ -5,8 +5,12 @@ CMS.CONTENCORE.PAGEWIDGET.ads=广告位
ADVERTISEMENT.TYPE.image=图片
# 模板freemarker
FREEMARKER.TAG.NAME.cms_advertisement=广告列表标签
FREEMARKER.TAG.DESC.cms_advertisement=获取广告数据列表,内嵌<#list DataList as ad>${ad.name}</#list>遍历数据
FREEMARKER.TAG.cms_advertisement.NAME=广告列表标签
FREEMARKER.TAG.cms_advertisement.DESC=获取广告数据列表,内嵌`<#list DataList as ad>${ad.name}</#list>`遍历数据
FREEMARKER.TAG.cms_advertisement.code=广告位编码
FREEMARKER.TAG.cms_advertisement.type=广告链接类型
FREEMARKER.TAG.cms_advertisement.type.None=原始链接
FREEMARKER.TAG.cms_advertisement.type.Stat=统计链接
# 统计菜单
STAT.MENU.CmsAdv=广告数据统计
@ -16,4 +20,6 @@ STAT.MENU.CmsAdViewLog=广告展现日志
# 定时任务
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
# 模板freemarker
FREEMARKER.TAG.NAME.cms_advertisement=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.NAME=Advertisement list tag
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
@ -16,4 +20,6 @@ STAT.MENU.CmsAdViewLog=View Logs
# 定时任务
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=圖片
# 模板freemarker
FREEMARKER.TAG.NAME.cms_advertisement=廣告列表標籤
FREEMARKER.TAG.DESC.cms_advertisement=獲取廣告數據列表,內嵌<#list DataList as ad>${ad.name}</#list>遍曆數據
FREEMARKER.TAG.cms_advertisement.NAME=廣告列表標籤
FREEMARKER.TAG.cms_advertisement.DESC=獲取廣告數據列表,內嵌`<#list DataList as ad>${ad.name}</#list>`遍曆數據
FREEMARKER.TAG.cms_advertisement.code=廣告位編碼
FREEMARKER.TAG.cms_advertisement.type=廣告鏈接類型
FREEMARKER.TAG.cms_advertisement.type.None=原始鏈接
FREEMARKER.TAG.cms_advertisement.type.Stat=統計鏈接
# 統計菜單
STAT.MENU.CmsAdv=廣告數據統計
@ -17,3 +21,5 @@ STAT.MENU.CmsAdViewLog=廣告展現日誌
# 定時任務
SCHEDULED_TASK.AdvertisementStatJob=廣告統計任務
SCHEDULED_TASK.AdvertisementPublishJob=廣告定時發布下線任務
MONITORED.CACHE.AD=廣告

View File

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

View File

@ -35,23 +35,19 @@ import com.chestnut.contentcore.domain.*;
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
import com.chestnut.contentcore.domain.vo.ContentVO;
import com.chestnut.contentcore.enums.ContentCopyType;
import com.chestnut.contentcore.enums.ContentOpType;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.fixed.dict.ContentOpType;
import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IContentService;
import com.chestnut.contentcore.service.IPublishPipeService;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.fixed.dict.YesOrNo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Component(IContentType.BEAN_NAME_PREFIX + ArticleContentType.ID)
@RequiredArgsConstructor
@ -101,29 +97,22 @@ public class ArticleContentType implements IContentType {
}
@Override
public IContent<?> readRequest(HttpServletRequest request) throws IOException {
ArticleDTO dto = JacksonUtils.from(request.getInputStream(), ArticleDTO.class);
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);
public IContent<?> readFrom(InputStream is) {
ArticleDTO dto = JacksonUtils.from(is, ArticleDTO.class);
return readFrom0(dto);
}
private ArticleContent readFrom0(ArticleDTO dto) {
// 内容基础信息
CmsContent contentEntity = dto.convertToContentEntity(this.catalogService, this.contentService);
// 文章扩展信息
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);
ArticleContent content = new ArticleContent();
@ -148,9 +137,6 @@ public class ArticleContentType implements IContentType {
CmsArticleDetail extendEntity = this.articleService.dao().getById(contentId);
vo = ArticleVO.newInstance(contentEntity, extendEntity);
if (StringUtils.isNotEmpty(vo.getLogo())) {
vo.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(vo.getLogo()));
}
// 发布通道模板数据
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
PublishPipePropUseType.Content, contentEntity.getPublishPipeProps());

View File

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

View File

@ -13,32 +13,37 @@
* See the License for the specific language governing permissions and
* 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
*/
private String nextContinuationToken ;
public String getNextContinuationToken() {
return nextContinuationToken;
}
/**
* ID
*/
String getId();
public void setNextContinuationToken(String nextContinuationToken) {
this.nextContinuationToken = nextContinuationToken;
}
/**
* 名称
*/
String getName();
public List<T> getObjects() {
return objects;
}
/**
* 编辑器内容初始化处理
*/
default String initEditor(String contentHtml) {
return contentHtml;
}
public void setObjects(List<T> objects) {
this.objects = objects;
}
/**
* 文章正文内容发布预览处理
*/
String deal(String contentHtml, String publishPipeCode, boolean isPreview);
}

View File

@ -16,8 +16,10 @@
package com.chestnut.article.controller;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.PublishPipeProp_UEditorCss;
import com.chestnut.common.domain.R;
import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.utils.StringUtils;
@ -28,13 +30,16 @@ import com.chestnut.contentcore.service.ICatalogService;
import com.chestnut.contentcore.service.IPublishPipeService;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.system.security.AdminUserType;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -57,6 +62,8 @@ public class ArticleController extends BaseRestController {
private final IPublishPipeService publishPipeService;
private final List<IArticleBodyFormat> articleBodyFormatList;
@GetMapping("/ueditor_css")
public R<?> getUEditorCss(@RequestParam Long catalogId) {
CmsCatalog catalog = catalogService.getCatalog(catalogId);
@ -73,5 +80,24 @@ public class ArticleController extends BaseRestController {
});
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());
}
List<Long> contentIds = pageResult.getRecords().stream().map(CmsContent::getContentId).toList();
Map<Long, CmsArticleDetail> articleDetails = this.articleService.dao().listByIds(contentIds)
.stream().collect(Collectors.toMap(CmsArticleDetail::getContentId, c -> c));
Map<Long, CmsArticleDetail> articleDetails = new HashMap<>();
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<>();
pageResult.getRecords().forEach(c -> {
ArticleApiVO dto = ArticleApiVO.newInstance(c);
CmsCatalog catalog = loadedCatalogs.get(c.getCatalogId());
if (Objects.isNull(catalog)) {
catalog = this.catalogService.getCatalog(c.getCatalogId());
loadedCatalogs.put(catalog.getCatalogId(), catalog);
ArticleApiVO vo = ArticleApiVO.newInstance(c, null);
CmsCatalog catalog = this.catalogService.getCatalog(c.getCatalogId());
vo.setCatalogName(catalog.getName());
vo.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
vo.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview));
vo.setLogoSrc(InternalUrlUtils.getActualUrl(c.getLogo(), publishPipeCode, preview));
if (text && articleDetails.containsKey(c.getContentId())) {
vo.setContentHtml(articleDetails.get(c.getContentId()).getContentHtml());
}
dto.setCatalogName(catalog.getName());
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);
list.add(vo);
});
return R.ok(list);
}

View File

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

View File

@ -15,17 +15,12 @@
*/
package com.chestnut.article.domain.dto;
import org.springframework.beans.BeanUtils;
import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent;
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.Setter;
import org.springframework.beans.BeanUtils;
@Getter
@Setter
@ -46,13 +41,14 @@ public class ArticleDTO extends ContentDTO {
*/
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();
BeanUtils.copyProperties(content, dto);
dto.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
dto.initByContent(content, preview);
BeanUtils.copyProperties(articleDetail, dto);
return dto;
}

View File

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

View File

@ -15,20 +15,14 @@
*/
package com.chestnut.article.domain.vo;
import java.util.Objects;
import org.springframework.beans.BeanUtils;
import com.chestnut.article.domain.CmsArticleDetail;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.domain.CmsContent;
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.Setter;
import java.util.Objects;
@Getter
@Setter
public class ArticleVO extends ContentVO {
@ -48,16 +42,20 @@ public class ArticleVO extends ContentVO {
*/
private String pageTitles;
/**
* 文档格式
*/
private String format;
public static ArticleVO newInstance(CmsContent content, CmsArticleDetail articleDetail) {
ArticleVO dto = new ArticleVO();
BeanUtils.copyProperties(content, dto);
dto.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
ArticleVO vo = new ArticleVO();
vo.initByContent(content, true);
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;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.domain.BCmsArticleDetail;
import com.chestnut.article.domain.CmsArticleDetail;
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.listener.event.AfterContentEditorInitEvent;
import com.chestnut.contentcore.listener.event.BeforeSiteDeleteEvent;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
@Slf4j
@Component
@ -81,7 +82,12 @@ public class ArticleListener {
@EventListener
public void afterContentEditorInit(AfterContentEditorInitEvent event) {
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;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.dao.CmsArticleDetailDAO;
import com.chestnut.common.db.mybatisplus.HasDAO;
@ -25,4 +26,5 @@ import com.chestnut.common.db.mybatisplus.HasDAO;
* @email 190785909@qq.com
*/
public interface IArticleService extends HasDAO<CmsArticleDetailDAO> {
IArticleBodyFormat getArticleBodyFormat(String format);
}

View File

@ -15,12 +15,15 @@
*/
package com.chestnut.article.service.impl;
import com.chestnut.article.IArticleBodyFormat;
import com.chestnut.article.dao.CmsArticleDetailDAO;
import com.chestnut.article.service.IArticleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
@ -28,8 +31,17 @@ public class ArticleServiceImpl implements IArticleService {
private final CmsArticleDetailDAO dao;
private final Map<String, IArticleBodyFormat> articleBodyFormatMap;
@Override
public CmsArticleDetailDAO 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;
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.core.TemplateContext;
import com.chestnut.common.staticize.func.AbstractFunc;
import com.chestnut.common.utils.Assert;
import com.chestnut.common.utils.StringUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateModelException;
@ -28,6 +30,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
* Freemarker模板自定义函数处理Html文本内容中的内部链接
@ -38,7 +41,13 @@ public class dealArticleBodyFunction extends AbstractFunc {
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
public String getFuncName() {
@ -55,18 +64,33 @@ public class dealArticleBodyFunction extends AbstractFunc {
if (args.length < 1) {
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());
SimpleScalar simpleScalar = (SimpleScalar) args[0];
String contentHtml = simpleScalar.getAsString();
// 处理内容扩展模板占位符
contentHtml = ArticleUtils.dealContentEx(contentHtml, context.getPublishPipeCode(), context.isPreview());
// 处理正文内部链接
contentHtml = InternalUrlUtils.dealInternalUrl(contentHtml, context.getPublishPipeCode(), context.isPreview());
IArticleBodyFormat articleBodyFormat = articleService.getArticleBodyFormat(format);
Assert.notNull(articleBodyFormat, () -> new TemplateModelException("Unsupported article body format: " + format));
if (Objects.isNull(articleBodyFormat)) {
articleBodyFormat = articleService.getArticleBodyFormat(ArticleBodyFormat_RichText.ID);
}
contentHtml = articleBodyFormat.deal(contentHtml, context.getPublishPipeCode(), context.isPreview());
return contentHtml;
}
@Override
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.mapper.CmsArticleDetailMapper;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.staticize.FreeMarkerUtils;
import com.chestnut.common.staticize.StaticizeConstants;
import com.chestnut.common.staticize.core.TemplateContext;
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.TagAttr;
import com.chestnut.common.utils.StringUtils;
@ -30,35 +33,36 @@ import com.chestnut.contentcore.mapper.CmsContentMapper;
import freemarker.core.Environment;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@RequiredArgsConstructor
@Component
@RequiredArgsConstructor
public class CmsArticleTag extends AbstractTag {
public static final String TAG_NAME = "cms_article";
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}";
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}";
public final static String NAME = "{FREEMARKER.TAG." + TAG_NAME + ".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 TagAttr_Page = "page";
public static final String ATTR_CONTENT_ID = "contentId";
public static final String ATTR_PAGE = "page";
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 = "__XY_UEDITOR_PAGE_BREAK__";
private final CmsContentMapper contentMapper;
private final CmsArticleDetailMapper articleMapper;
@ -66,16 +70,16 @@ public class CmsArticleTag extends AbstractTag {
@Override
public List<TagAttr> getTagAttrs() {
List<TagAttr> tagAttrs = new ArrayList<>();
tagAttrs.add(new TagAttr(TagAttr_ContentId, true, TagAttrDataType.INTEGER, "文章内容ID"));
tagAttrs.add(new TagAttr(TagAttr_Page, false, TagAttrDataType.BOOLEAN, "是否分页默认false"));
tagAttrs.add(new TagAttr(ATTR_CONTENT_ID, true, TagAttrDataType.INTEGER, ATTR_USAGE_CONTENT_ID));
tagAttrs.add(new TagAttr(ATTR_PAGE, false, TagAttrDataType.BOOLEAN, ATTR_USAGE_PAGE, Boolean.FALSE.toString()));
return tagAttrs;
}
@Override
public Map<String, TemplateModel> execute0(Environment env, Map<String, String> attrs)
throws TemplateException, IOException {
String contentHtml = null;
long contentId = MapUtils.getLongValue(attrs, TagAttr_ContentId, 0);
throws TemplateException {
String contentHtml;
long contentId = MapUtils.getLongValue(attrs, ATTR_CONTENT_ID, 0);
if (contentId <= 0) {
throw new TemplateException("Invalid contentId: " + contentId, env);
}
@ -92,24 +96,32 @@ public class CmsArticleTag extends AbstractTag {
}
contentHtml = articleDetail.getContentHtml();
TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
boolean page = MapUtils.getBooleanValue(attrs, TagAttr_Page, false);
boolean page = MapUtils.getBooleanValue(attrs, ATTR_PAGE, false);
if (page) {
if (context.isPaged()) {
throw new TemplateException("分页标识已被其他标签激活", env);
throw new DuplicatePageFlagException(env);
}
context.setPaged(true);
String[] pageContents = contentHtml.split(PAGE_BREAK_SPLITER);
if (context.getPageIndex() > pageContents.length) {
throw new TemplateException(StringUtils.messageFormat("文章内容分页越界:{0}, 最大页码:{1}。", context.getPageIndex(),
pageContents.length), env);
throw new PageIndexOutOfBoundsException(context.getPageIndex(), pageContents.length, env);
}
context.setPageTotal(pageContents.length);
env.setGlobalVariable(StaticizeConstants.TemplateVariable_PageTotal,
this.wrap(env, context.getPageTotal()));
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
@ -126,4 +138,21 @@ public class CmsArticleTag extends AbstractTag {
public String getDescription() {
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=文章
# 模板freemarker
FREEMARKER.TAG.NAME.cms_article=文章正文数据标签
FREEMARKER.TAG.DESC.cms_article=获取文章正文数据,标签内使用${ArticleContent}获取正文详情
FREEMARKER.FUNC.DESC.dealArticleBody=文章正文处理函数,主要用来处理文章内容中的内部链接和扩展模板占位符
FREEMARKER.TAG.cms_article.NAME=文章正文数据标签
FREEMARKER.TAG.cms_article.DESC=获取文章正文数据,标签内使用`${ArticleContent}`获取正文详情
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
# 模板freemarker
FREEMARKER.TAG.NAME.cms_article=Article body tag
FREEMARKER.TAG.DESC.cms_article=Fetch article body details, use "${ArticleContent}" in tag to get body text.
FREEMARKER.FUNC.DESC.dealArticleBody=Article body deal function
FREEMARKER.TAG.cms_article.NAME=Article body tag
FREEMARKER.TAG.cms_article.DESC=Fetch article body details, use `${ArticleContent}` in tag to get body text.
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=文章
# 模板freemarker
FREEMARKER.TAG.NAME.cms_article=文章正文數據標籤
FREEMARKER.TAG.DESC.cms_article=獲取文章正文數據,標籤內使用${ArticleContent}獲取正文詳情
FREEMARKER.FUNC.DESC.dealArticleBody=文章正文處理函數,主要用來處理文章內容中的內部連結和擴展模板佔位符
FREEMARKER.TAG.cms_article.NAME=文章正文數據標籤
FREEMARKER.TAG.cms_article.DESC=獲取文章正文數據,標籤內使用`${ArticleContent}`獲取正文詳情
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>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.5.0</version>
<version>1.5.1</version>
</parent>
<artifactId>chestnut-cms-block</artifactId>

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# freemarker模板标签
FREEMARKER.TAG.NAME.cms_comment=评论列表标签
FREEMARKER.TAG.DESC.cms_comment=获取评论数据列表,内嵌<#list DataList as comment>${comment.name}</#list>遍历数据
FREEMARKER.TAG.cms_comment.NAME=评论列表标签
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.TAG.NAME.cms_comment=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.NAME=Comment list tag
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.TAG.NAME.cms_comment=評論列表標籤
FREEMARKER.TAG.DESC.cms_comment=獲取評論數據列表,內嵌<#list DataList as comment>${comment.name}</#list>遍曆數據
FREEMARKER.TAG.cms_comment.NAME=評論列表標籤
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>
<groupId>com.chestnut</groupId>
<artifactId>chestnut-cms</artifactId>
<version>1.5.0</version>
<version>1.5.1</version>
</parent>
<artifactId>chestnut-cms-contentcore</artifactId>
<description>内容核心</description>
<dependencies>
<!-- 图片处理工具库 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
</dependency>
<dependency>
<groupId>com.chestnut</groupId>
<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)) {
RESOURCE_ROOT = SpringUtils.getAppParentDirectory() + "/wwwroot_release/";
}
RESOURCE_ROOT = FileExUtils.normalizePath(RESOURCE_ROOT);
if (!RESOURCE_ROOT.endsWith("/")) {
RESOURCE_ROOT += "/";
}
RESOURCE_ROOT = StringUtils.appendIfMissing(FileExUtils.normalizePath(RESOURCE_ROOT), "/");
FileExUtils.mkdirs(RESOURCE_ROOT);
properties.setResourceRoot(RESOURCE_ROOT);
log.info("ResourceRoot: " + RESOURCE_ROOT);
@ -116,7 +113,7 @@ public class CMSConfig implements WebMvcConfigurer {
public void resetCache() {
if (this.properties.getResetCache()) {
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());
}
}

View File

@ -16,11 +16,13 @@
package com.chestnut.contentcore.controller;
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.AsyncTaskManager;
import com.chestnut.common.domain.R;
import com.chestnut.common.domain.TreeNode;
import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.extend.annotation.XssIgnore;
import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.log.annotation.Log;
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.StpAdminUtil;
import com.chestnut.system.validator.LongId;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
@ -177,14 +180,13 @@ public class CatalogController extends BaseRestController {
@DeleteMapping("/{catalogId}")
public R<String> deleteCatalog(@PathVariable("catalogId") @LongId Long catalogId) {
LoginUser operator = StpAdminUtil.getLoginUser();
AsyncTask task = new AsyncTask() {
AsyncTask task = new AsyncTask("Catalog-" + catalogId) {
@Override
public void run0() {
catalogService.deleteCatalog(catalogId, operator);
}
};
task.setTaskId("DeleteCatalog_" + catalogId);
this.asyncTaskManager.execute(task);
return R.ok(task.getTaskId());
}
@ -282,6 +284,7 @@ public class CatalogController extends BaseRestController {
/**
* 保存栏目扩展配置
*/
@XssIgnore
@Priv(type = AdminUserType.TYPE, value = "Catalog:Edit:${#catalogId}")
@Log(title = "栏目扩展", businessType = BusinessType.UPDATE, isSaveRequestData = false)
@PutMapping("/extends/{catalogId}")
@ -376,4 +379,48 @@ public class CatalogController extends BaseRestController {
}
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.contentcore.core.IContent;
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.CmsContent;
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.util.CmsPrivUtils;
import com.chestnut.contentcore.util.ContentCoreUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.permission.PermissionUtils;
import com.chestnut.system.security.AdminUserType;
import com.chestnut.system.security.StpAdminUtil;
@ -59,7 +57,6 @@ import freemarker.template.TemplateException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.validation.annotation.Validated;
@ -67,7 +64,6 @@ import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -137,15 +133,7 @@ public class ContentController extends BaseRestController {
q.orderByDesc(CmsContent::getTopFlag).orderByDesc(CmsContent::getSortFlag);
}
Page<CmsContent> page = q.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true));
List<ListContentVO> list = new ArrayList<>();
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);
});
List<ListContentVO> list = page.getRecords().stream().map(ListContentVO::newInstance).toList();
return this.bindDataTable(list, (int) page.getTotal());
}
@ -171,7 +159,7 @@ public class ContentController extends BaseRestController {
throws IOException {
IContentType ct = ContentCoreUtils.getContentType(contentType);
IContent<?> content = ct.readRequest(request);
IContent<?> content = ct.readFrom(request.getInputStream());
content.setOperator(StpAdminUtil.getLoginUser());
PermissionUtils.checkPermission(CatalogPrivItem.AddContent.getPermissionKey(content.getCatalogId()),
content.getOperator());
@ -187,7 +175,7 @@ public class ContentController extends BaseRestController {
throws IOException {
IContentType ct = ContentCoreUtils.getContentType(contentType);
IContent<?> content = ct.readRequest(request);
IContent<?> content = ct.readFrom(request.getInputStream());
content.setOperator(StpAdminUtil.getLoginUser());
PermissionUtils.checkPermission(CatalogPrivItem.EditContent.getPermissionKey(content.getCatalogId()),
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.contentcore.core.IInternalDataType;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.vo.ContentPathRuleVO;
import com.chestnut.contentcore.domain.vo.DynamicPageTypeVO;
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
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 Map<String, Object> params) {
try {
// TODO 缓存
long s = System.currentTimeMillis();
CmsSite site = this.siteService.getSite(siteId);
// 模板ID = 通道:站点目录:模板文件名
@ -164,9 +164,25 @@ public class CoreController extends BaseRestController {
public R<?> getDynamicPageTypes() {
List<DynamicPageTypeVO> list = ContentCoreUtils.getDynamicPageTypes().stream()
.map(DynamicPageTypeVO::newInstance).toList();
list.forEach( vo ->
vo.setName(I18nUtils.get(vo.getName()))
);
list.forEach( vo -> {
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);
}
}

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.domain.CmsResource;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.domain.dto.ImageCropDTO;
import com.chestnut.contentcore.domain.dto.ResourceUploadDTO;
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.fixed.config.ResourceUploadAcceptSize;
@ -52,7 +51,6 @@ import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FileUtils;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
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));
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.domain.R;
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.enums.BusinessType;
import com.chestnut.common.security.anno.Priv;
@ -264,6 +265,7 @@ public class SiteController extends BaseRestController {
* @param siteId 站点ID
* @param configs 扩展配置数据
*/
@XssIgnore
@Priv(type = AdminUserType.TYPE, value = "Site:Edit:${#siteId}")
@Log(title = "站点扩展", businessType = BusinessType.UPDATE, isSaveRequestData = false)
@PostMapping("/extends/{siteId}")

View File

@ -15,28 +15,27 @@
*/
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.i18n.I18nUtils;
import com.chestnut.common.security.anno.Priv;
import com.chestnut.common.security.web.BaseRestController;
import com.chestnut.common.staticize.func.IFunction;
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.TemplateTagVO;
import com.chestnut.contentcore.perms.ContentCorePriv;
import com.chestnut.system.security.AdminUserType;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 静态化管理
*
*
* @author 兮玥
* @email 190785909@qq.com
*/
@ -56,11 +55,22 @@ public class StaticizeController extends BaseRestController {
@GetMapping("/tags")
public R<?> getTemplateTags() {
List<TemplateTagVO> list = this.tags.stream().map(tag -> {
TemplateTagVO vo = TemplateTagVO.builder().name(I18nUtils.get(tag.getName())).tagName(tag.getTagName())
.description(I18nUtils.get(tag.getDescription())).tagAttrs(tag.getTagAttrs()).build();
TemplateTagVO vo = TemplateTagVO.builder()
.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 -> {
attr.setName(I18nUtils.get(attr.getName()));
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;
}).toList();
@ -73,8 +83,12 @@ public class StaticizeController extends BaseRestController {
@GetMapping("/functions")
public R<?> getTemplateFunctions() {
List<TemplateFuncVO> list = this.functions.stream().map(func -> {
TemplateFuncVO vo = TemplateFuncVO.builder().funcName(func.getFuncName())
.desc(I18nUtils.get(func.getDesc())).funcArgs(func.getFuncArgs()).build();
TemplateFuncVO vo = TemplateFuncVO.builder()
.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 -> {
arg.setName(I18nUtils.get(arg.getName()));
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.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;
/**
@ -128,15 +131,10 @@ public class ContentApiController extends BaseRestController {
if (pageResult.getRecords().isEmpty()) {
return R.ok(List.of());
}
Map<Long, CmsCatalog> loadedCatalogs = new HashMap<>();
List<ContentApiVO> list = new ArrayList<>();
pageResult.getRecords().forEach(c -> {
ContentApiVO dto = ContentApiVO.newInstance(c);
CmsCatalog catalog = loadedCatalogs.get(c.getCatalogId());
if (catalog == null) {
catalog = this.catalogService.getCatalog(c.getCatalogId());
loadedCatalogs.put(catalog.getCatalogId(), catalog);
}
CmsCatalog catalog = this.catalogService.getCatalog(c.getCatalogId());
dto.setCatalogName(catalog.getName());
dto.setCatalogLink(catalogService.getCatalogLink(catalog, 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.enums.ContentCopyType;
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
import com.chestnut.contentcore.fixed.dict.ContentOpType;
import com.chestnut.contentcore.fixed.dict.ContentStatus;
import com.chestnut.contentcore.listener.event.*;
import com.chestnut.contentcore.properties.PublishedContentEditProperty;
import com.chestnut.contentcore.service.ICatalogService;
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.util.CatalogUtils;
import com.chestnut.contentcore.util.ContentCoreUtils;
import com.chestnut.contentcore.util.ContentLogUtils;
import com.chestnut.contentcore.util.InternalUrlUtils;
import com.chestnut.system.fixed.dict.YesOrNo;
import lombok.Setter;
@ -141,6 +144,8 @@ public abstract class AbstractContent<T> implements IContent<T> {
throw ContentCoreErrorCode.TITLE_REPLEAT.exception();
}
checkRedirectUrl();
SpringUtils.publishEvent(new BeforeContentSaveEvent(this, this, true));
content.setSiteId(catalog.getSiteId());
content.setCatalogAncestors(catalog.getAncestors());
content.setTopCatalog(CatalogUtils.getTopCatalog(catalog));
@ -150,13 +155,24 @@ public abstract class AbstractContent<T> implements IContent<T> {
content.setStatus(ContentStatus.DRAFT);
content.setSortFlag(SortUtils.getDefaultSortValue());
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());
this.getContentService().dao().save(this.getContentEntity());
this.add0();
// 栏目内容数+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();
}
protected abstract void add0();
void checkLock() {
boolean lockContent = content.isLock() && StringUtils.isNotEmpty(content.getLockUser())
&& !content.getLockUser().equals(this.getOperatorUName());
@ -190,26 +206,39 @@ public abstract class AbstractContent<T> implements IContent<T> {
}
}
checkRedirectUrl();
SpringUtils.publishEvent(new BeforeContentSaveEvent(this, this, false));
if (ContentStatus.isToPublishOrPublished(content.getStatus())) {
content.setStatus(ContentStatus.EDITING);
}
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();
}
protected abstract void save0();
@Override
public void delete() {
this.checkLock();
// 删除到备份表
this.getContentService().dao().deleteByIdAndBackup(this.getContentEntity(), getOperatorUName());
this.delete0();
// 直接删除站内映射内容
this.getContentService().dao().remove(new LambdaQueryWrapper<CmsContent>()
.eq(CmsContent::getCopyType, ContentCopyType.Mapping)
.eq(CmsContent::getCopyId, this.getContentEntity().getContentId()));
// 栏目内容数-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
public boolean publish() {
checkLock();
@ -227,6 +256,7 @@ public abstract class AbstractContent<T> implements IContent<T> {
}
if (update) {
this.getContentService().dao().updateById(content);
ContentLogUtils.addLog(ContentOpType.PUBLISH, this.getContentEntity(), this.getOperator());
}
// 静态化
this.getPublishService().asyncPublishContent(this);
@ -263,12 +293,16 @@ public abstract class AbstractContent<T> implements IContent<T> {
newContent.setOfflineDate(null);
}
this.getContentService().dao().save(newContent);
this.getParams().put("NewContentId", newContent.getContentId());
copyTo0(newContent, copyType);
// 栏目内容数+1
this.getCatalogService().changeContentCount(toCatalog.getCatalogId(), 1);
SpringUtils.publishEvent(new AfterContentCopyEvent(this, this.getContentEntity(), newContent));
return newContent;
}
protected abstract void copyTo0(CmsContent newContent, Integer copyType);
@Override
public void moveTo(CmsCatalog toCatalog) {
checkLock();
@ -308,24 +342,26 @@ public abstract class AbstractContent<T> implements IContent<T> {
if (ContentStatus.isPublished(this.getContentEntity().getStatus())) {
this.getPublishService().publishContent(List.of(content.getContentId()), getOperator());
}
ContentLogUtils.addLog(ContentOpType.TOP, this.getContentEntity(), this.getOperator());
}
@Override
public void cancelTop() {
content.setTopFlag(0L);
content.setTopDate(null);
content.updateBy(this.getOperatorUName());
if (content.getTopFlag() > 0L) {
return;
}
this.getContentService().dao().updateById(content);
// 重新发布内容
if (ContentStatus.isPublished(this.getContentEntity().getStatus())) {
this.getPublishService().publishContent(List.of(content.getContentId()), getOperator());
}
ContentLogUtils.addLog(ContentOpType.CANCEL_TOP, this.getContentEntity(), this.getOperator());
}
@Override
public void sort(Long targetContentId) {
if (targetContentId.equals(this.getContentEntity().getContentId())) {
return; // 排序目标是自己直接返回
return;
}
checkLock();
CmsContent next = this.getContentService().dao().getById(targetContentId);
@ -346,33 +382,41 @@ public abstract class AbstractContent<T> implements IContent<T> {
}
this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(content);
ContentLogUtils.addLog(ContentOpType.SORT, this.getContentEntity(), this.getOperator());
}
@Override
public void offline() {
String status = this.getContentEntity().getStatus();
this.getContentEntity().setStatus(ContentStatus.OFFLINE);
this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(this.getContentEntity());
if (ContentStatus.isPublished(status)) {
// 已发布内容删除静态页面
this.getContentService().deleteStaticFiles(this.getContentEntity());
// 重新发布内容所在栏目和父级栏目
String[] catalogIds = this.getContentEntity().getCatalogAncestors()
.split(CatalogUtils.ANCESTORS_SPLITER);
for (String catalogId : catalogIds) {
this.getPublishService().publishCatalog(this.getCatalogService().getCatalog(Long.valueOf(catalogId)),
false, false, null, this.getOperator());
}
if (!ContentStatus.isOffline(status)) {
this.getContentEntity().setStatus(ContentStatus.OFFLINE);
this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(this.getContentEntity());
}
if (ContentStatus.isPublished(status)) {
// 已发布内容删除静态页面
this.getContentService().deleteStaticFiles(this.getContentEntity());
// 重新发布内容所在栏目和父级栏目
String[] catalogIds = this.getContentEntity().getCatalogAncestors()
.split(CatalogUtils.ANCESTORS_SPLITER);
for (String catalogId : catalogIds) {
this.getPublishService().publishCatalog(this.getCatalogService().getCatalog(Long.valueOf(catalogId)),
false, false, null, this.getOperator());
}
}
ContentLogUtils.addLog(ContentOpType.OFFLINE, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentOfflineEvent(this, this));
}
@Override
public void toPublish() {
this.getContentEntity().setStatus(ContentStatus.TO_PUBLISHED);
this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(this.getContentEntity());
if (!ContentStatus.isToPublish(this.getContentEntity().getStatus())) {
this.getContentEntity().setStatus(ContentStatus.TO_PUBLISHED);
this.getContentEntity().updateBy(this.getOperatorUName());
this.getContentService().dao().updateById(this.getContentEntity());
}
ContentLogUtils.addLog(ContentOpType.TO_PUBLISH, this.getContentEntity(), this.getOperator());
SpringUtils.publishEvent(new AfterContentToPublishEvent(this, this));
}
@Override

View File

@ -105,7 +105,7 @@ public abstract class AbstractPageWidget implements IPageWidget {
}
@Override
public void publish() throws TemplateException, IOException {
public void publish() {
CmsPageWidget pageWidgetEntity = this.getPageWidgetEntity();
pageWidgetEntity.setState(PageWidgetStatus.PUBLISHED);
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.CmsContent;
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);
/**
* 请求读取内容数据
* 输入流读取内容数据
*
* @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.utils.StringUtils;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@ -31,9 +33,9 @@ public interface IDynamicPageType {
String BEAN_PREFIX = "DynamicPageType_";
RequestArg REQUEST_ARG_SITE_ID = new RequestArg("sid", "站点ID", RequestArgType.Parameter, true, null);
RequestArg REQUEST_ARG_PUBLISHPIPE_CODE = new RequestArg("pp", "发布通道编码", RequestArgType.Parameter, true, null);
RequestArg REQUEST_ARG_PREVIEW = new RequestArg("preview", "是否预览模式", RequestArgType.Parameter, false, "false");
RequestArg REQUEST_ARG_SITE_ID = new RequestArg("sid", "{DYNAMIC_PAGE_TYPE.ARG.sid}", RequestArgType.Parameter, true);
RequestArg REQUEST_ARG_PUBLISHPIPE_CODE = new RequestArg("pp", "{DYNAMIC_PAGE_TYPE.ARG.pp}", RequestArgType.Parameter, true);
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(
String name, // 参数名
@Getter
@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
boolean mandatory, // 是否必填
String defaultValue // 默认值
){}
public RequestArg(String name, String desc, RequestArgType type, boolean mandatory, String defValue) {
this.name = name;
this.desc = desc;
this.type = type;
this.mandatory = mandatory;
this.defValue = defValue;
}
}
enum RequestArgType {
Parameter, Path

View File

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

View File

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

View File

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

View File

@ -112,7 +112,7 @@ public class SiteImportContext implements ISiteThemeContext {
if (IdUtils.validate(id)) {
internalURL.setId(id);
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();
}

View File

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

View File

@ -53,7 +53,7 @@ public class InternalDataType_Catalog implements IInternalDataType {
public String getPageData(RequestData requestData) throws IOException, TemplateException {
CmsCatalog catalog = catalogService.getCatalog(requestData.getDataId());
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

View File

@ -55,7 +55,7 @@ public class InternalDataType_Content implements IInternalDataType {
CmsContent content = contentService.dao().getById(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

View File

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

View File

@ -15,38 +15,31 @@
*/
package com.chestnut.contentcore.core.impl;
import java.awt.image.BufferedImage;
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.storage.IFileStorageType;
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.domain.CmsResource;
import com.chestnut.contentcore.domain.CmsSite;
import com.chestnut.contentcore.enums.WatermarkerPosition;
import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty;
import com.chestnut.contentcore.properties.*;
import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty.ImageWatermarkArgs;
import com.chestnut.contentcore.properties.ImageWatermarkProperty;
import com.chestnut.contentcore.service.ISiteService;
import com.chestnut.contentcore.util.FileStorageHelper;
import com.chestnut.contentcore.util.SiteUtils;
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 Map<String, IFileStorageType> fileStorageTypeMap;
@Override
public String getId() {
return ID;
@ -90,49 +85,71 @@ public class ResourceType_Image implements IResourceType {
@Override
public byte[] process(CmsResource resource, byte[] bytes) throws IOException {
CmsSite site = siteService.getSite(resource.getSiteId());
// 提取图片宽高属性
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) {
// 提取图片宽高属性
BufferedImage bi = ImageIO.read(is);
resource.setWidth(bi.getWidth());
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())
&& !"webp".equalsIgnoreCase(resource.getSuffix())) {
// TODO webp水印支持
if (ImageWatermarkProperty.getValue(site.getConfigProps())) {
ImageWatermarkArgs args = ImageWatermarkArgsProperty.getValue(site.getConfigProps());
if (StringUtils.isNotEmpty(args.getImage())) {
// 水印图片占比大小调整
String siteResourceRoot = SiteUtils.getSiteResourceRoot(site);
File file = new File(siteResourceRoot + args.getImage());
if (file.exists()) {
float waterremakImageWidth = bi.getWidth() * args.getRatio() * 0.01f;
BufferedImage biWatermarkImage = ImageIO.read(file);
biWatermarkImage = Thumbnails.of(biWatermarkImage)
.scale(waterremakImageWidth / biWatermarkImage.getWidth()).asBufferedImage();
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// 添加水印
Thumbnails.of(bi)
.watermark(WatermarkerPosition.valueOf(args.getPosition()).position(),
biWatermarkImage, args.getOpacity())
.scale(1f).outputFormat(resource.getSuffix()).toOutputStream(os);
String ext = FilenameUtils.getExtension(resource.getFileName());
ImageHelper.of(bi).format(ext).watermark(
biWatermarkImage,
args.getRatio() * 0.01f,
args.getOpacity(),
WatermarkPosition.str2Position(args.getPosition())
).to(os);
bytes = os.toByteArray();
}
}
}
}
} catch (Exception e) {
log.error("图片处理失败:", e);
} catch (IOException e) {
log.error("Read image failed: " + resource.getPath(), e);
resource.setWidth(0);
resource.setHeight(0);
}
resource.setFileSize((long) bytes.length);
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

@ -1,260 +1,254 @@
/*
* 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 java.util.HashMap;
import java.util.Map;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.chestnut.common.db.domain.BaseEntity;
import com.chestnut.contentcore.core.impl.PublishPipeProp_IndexTemplate;
import com.chestnut.contentcore.core.impl.PublishPipeProp_ListTemplate;
import com.chestnut.system.fixed.dict.EnableOrDisable;
import com.chestnut.system.fixed.dict.YesOrNo;
import lombok.Getter;
import lombok.Setter;
/**
* 栏目表对象 [cms_catalog]
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
@TableName(value = CmsCatalog.TABLE_NAME, autoResultMap = true)
public class CmsCatalog extends BaseEntity {
private static final long serialVersionUID = 1L;
public static final String TABLE_NAME = "cms_catalog";
/**
* 栏目ID
*/
@TableId(value = "catalog_id", type = IdType.INPUT)
private Long catalogId;
/**
* 站点ID
*/
private Long siteId;
/**
* 父级栏目ID
*/
private Long parentId;
/**
* 祖级栏目ID列表
*/
private String ancestors;
/**
* 栏目名称
*/
private String name;
/**
* 父级栏目名称非表字段
*/
@TableField(exist = false)
private String parentName;
/**
* logo
*/
private String logo;
/**
* logo预览地址非表字段
*/
@TableField(exist = false)
private String logoSrc;
/**
* 栏目别名
*/
private String alias;
/**
* 栏目简介
*/
private String description;
/**
* 所属部门编码
*/
private String deptCode;
/**
* 栏目类型
*/
private String catalogType;
/**
* 栏目目录
*/
private String path;
/**
* 跳转地址标题栏目跳转地址
*/
private String redirectUrl;
/**
* 是否生成静态页面
*/
private String staticFlag;
/**
* 栏目是否可见
*/
private String visibleFlag;
/**
* 栏目是否在标签中忽略
*/
private String tagIgnore;
/**
* 排序字段
*/
private Long sortFlag;
/**
* 栏目首页命名
*/
private String indexFileName;
/**
* 列表页命名规则
*/
private String listNameRule;
/**
* 详情页命名规则
*/
private String detailNameRule;
/**
* 栏目层级
*/
private Integer treeLevel;
/**
* 子栏目数
*/
private Integer childCount;
/**
* 内容数量
*/
private Integer contentCount;
/**
* 状态
*/
private String status;
/**
* 点击量
*/
private Integer hitCount;
/**
* SEO关键词
*/
private String seoKeywords;
/**
* SEO描述
*/
private String seoDescription;
/**
* SEO标题
*/
private String seoTitle;
/**
* 链接非表字段
*/
@TableField(exist = false)
private String link;
/**
* 列表页链接无首页模板时与link一致
*/
@TableField(exist = false)
private String listLink;
/**
* 发布通道配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Map<String, Object>> publishPipeProps;
/**
* 扩展配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> configProps;
public Map<String, String> getConfigProps() {
if (this.configProps == null) {
this.configProps = new HashMap<>();
}
return configProps;
}
public Map<String, Object> getPublishPipeProps(String publishPipeCode) {
if (this.publishPipeProps == null) {
this.publishPipeProps = new HashMap<>();
}
Map<String, Object> map = this.publishPipeProps.get(publishPipeCode);
if (map == null) {
map = new HashMap<>();
this.publishPipeProps.put(publishPipeCode, map);
}
return map;
}
public String getIndexTemplate(String publishPipeCode) {
return PublishPipeProp_IndexTemplate.getValue(publishPipeCode, this.publishPipeProps);
}
public String getListTemplate(String publishPipeCode) {
return PublishPipeProp_ListTemplate.getValue(publishPipeCode, this.publishPipeProps);
}
public boolean isStaticize() {
return YesOrNo.isYes(this.staticFlag);
}
public boolean isVisible() {
return YesOrNo.isYes(this.visibleFlag);
}
public boolean isEnable() {
return EnableOrDisable.isEnable(this.status);
}
}
/*
* 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.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.chestnut.common.db.domain.BaseEntity;
import com.chestnut.contentcore.core.impl.PublishPipeProp_IndexTemplate;
import com.chestnut.contentcore.core.impl.PublishPipeProp_ListTemplate;
import com.chestnut.system.fixed.dict.EnableOrDisable;
import com.chestnut.system.fixed.dict.YesOrNo;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
/**
* 栏目表对象 [cms_catalog]
*
* @author 兮玥
* @email 190785909@qq.com
*/
@Getter
@Setter
@TableName(value = CmsCatalog.TABLE_NAME, autoResultMap = true)
public class CmsCatalog extends BaseEntity {
private static final long serialVersionUID = 1L;
public static final String TABLE_NAME = "cms_catalog";
/**
* 栏目ID
*/
@TableId(value = "catalog_id", type = IdType.INPUT)
private Long catalogId;
/**
* 站点ID
*/
private Long siteId;
/**
* 父级栏目ID
*/
private Long parentId;
/**
* 祖级栏目ID列表
*/
private String ancestors;
/**
* 栏目名称
*/
private String name;
/**
* 父级栏目名称非表字段
*/
@TableField(exist = false)
private String parentName;
/**
* logo
*/
private String logo;
/**
* logo预览地址非表字段
*/
@TableField(exist = false)
private String logoSrc;
/**
* 栏目别名
*/
private String alias;
/**
* 栏目简介
*/
private String description;
/**
* 所属部门编码
*/
private String deptCode;
/**
* 栏目类型
*/
private String catalogType;
/**
* 栏目目录
*/
private String path;
/**
* 跳转地址标题栏目跳转地址
*/
private String redirectUrl;
/**
* 是否生成静态页面
*/
private String staticFlag;
/**
* 栏目是否可见
*/
private String visibleFlag;
/**
* 栏目是否在标签中忽略
*/
private String tagIgnore;
/**
* 排序字段
*/
private Long sortFlag;
/**
* 栏目首页命名
*/
private String indexFileName;
/**
* 列表页命名规则
*/
private String listNameRule;
/**
* 详情页命名规则
*/
private String detailNameRule;
/**
* 栏目层级
*/
private Integer treeLevel;
/**
* 子栏目数
*/
private Integer childCount;
/**
* 内容数量
*/
private Integer contentCount;
/**
* 状态
*/
private String status;
/**
* 点击量
*/
private Integer hitCount;
/**
* SEO关键词
*/
private String seoKeywords;
/**
* SEO描述
*/
private String seoDescription;
/**
* SEO标题
*/
private String seoTitle;
/**
* 链接非表字段
*/
@TableField(exist = false)
private String link;
/**
* 列表页链接无首页模板时与link一致
*/
@TableField(exist = false)
private String listLink;
/**
* 发布通道配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Map<String, Object>> publishPipeProps;
/**
* 扩展配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, String> configProps;
public Map<String, String> getConfigProps() {
if (this.configProps == null) {
this.configProps = new HashMap<>();
}
return configProps;
}
public Map<String, Object> getPublishPipeProps(String publishPipeCode) {
if (this.publishPipeProps == null) {
this.publishPipeProps = new HashMap<>();
}
return this.publishPipeProps.computeIfAbsent(publishPipeCode, k -> new HashMap<>());
}
public String getIndexTemplate(String publishPipeCode) {
return PublishPipeProp_IndexTemplate.getValue(publishPipeCode, this.publishPipeProps);
}
public String getListTemplate(String publishPipeCode) {
return PublishPipeProp_ListTemplate.getValue(publishPipeCode, this.publishPipeProps);
}
public boolean isStaticize() {
return YesOrNo.isYes(this.staticFlag);
}
public boolean isVisible() {
return YesOrNo.isYes(this.visibleFlag);
}
public boolean isEnable() {
return EnableOrDisable.isEnable(this.status);
}
}

View File

@ -30,6 +30,7 @@ import org.springframework.beans.BeanUtils;
import java.io.Serial;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
@ -119,10 +120,17 @@ public class CmsContent extends BaseEntity implements IBackupable<BCmsContent> {
private String titleStyle;
/**
* logo
* 封面图
*/
@TableField(exist = false)
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) {
this.publishPipeProps = new HashMap<>();
}
Map<String, Object> map = this.publishPipeProps.get(publishPipeCode);
if (map == null) {
map = new HashMap<>();
this.publishPipeProps.put(publishPipeCode, map);
}
return map;
return this.publishPipeProps.computeIfAbsent(publishPipeCode, k -> new HashMap<>());
}
public String getIndexTemplate(String publishPipeCode) {
@ -158,9 +153,7 @@ public class CmsSite extends BaseEntity {
public String getUrl(String publishPipeCode) {
String ppUrl = PublishPipeProp_SiteUrl.getValue(publishPipeCode, this.publishPipeProps);
if (ppUrl != null && !ppUrl.endsWith("/")) {
ppUrl += "/";
}
ppUrl = StringUtils.appendIfMissing(ppUrl, "/");
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.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.chestnut.common.annotation.XComment;
import com.chestnut.common.db.domain.BaseEntity;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
@ -40,31 +40,21 @@ public class CmsSiteProperty extends BaseEntity {
public static final String TABLE_NAME = "cms_site_property";
/**
* 属性ID-主键
*/
@TableId(value = "property_id", type = IdType.INPUT)
@XComment("ID")
private Long propertyId;
/**
* 所属站点ID
*/
@XComment("所属站点ID")
private Long siteId;
/**
* 属性名称
*/
@XComment("属性名称")
@NotBlank
private String propName;
/**
* 属性代码
*/
@Pattern(regexp = "[A-Za-z0-9_]+")
@XComment("属性编码")
@Pattern(regexp = "[A-Za-z0-9_]+", message = "{VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR}")
private String propCode;
/**
* 属性值
*/
@XComment("属性值")
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 jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.Setter;
@ -92,6 +91,11 @@ public class CatalogUpdateDTO extends BaseDTO {
*/
private String redirectUrl;
/*
* 内容路径规则
*/
private String detailNameRule;
/*
* SEO关键词
*/

View File

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

View File

@ -15,11 +15,15 @@
*/
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.enums.ContentOpType;
import com.chestnut.contentcore.domain.InitByContent;
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.Setter;
import org.springframework.beans.BeanUtils;
@ -31,12 +35,12 @@ import java.util.Map;
@Getter
@Setter
public class ContentDTO {
public class ContentDTO implements InitByContent {
/**
* 操作类型
*/
private ContentOpType opType;
private String opType;
/**
* 内容ID
@ -74,14 +78,24 @@ public class ContentDTO {
private String titleStyle;
/**
* 引导
* 封面
*/
private String logo;
/**
* 引导图预览路径
* 封面图预览路径
*/
private String logoSrc;
/**
* 其他图片
*/
private List<String> images = List.of();
/**
* 其他图片预览路径
*/
private List<String> imagesSrc = List.of();
/**
* 发布链接
@ -257,14 +271,33 @@ public class ContentDTO {
* 备用字段4
*/
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) {
ContentDTO dto = new ContentDTO();
BeanUtils.copyProperties(cmsContent, dto);
dto.setAttributes(ContentAttribute.convertStr(cmsContent.getAttributes()));
if (StringUtils.isNotEmpty(dto.getLogo())) {
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
}
dto.initByContent(cmsContent, false);
return dto;
}
}

View File

@ -21,5 +21,14 @@ import lombok.Setter;
@Getter
@Setter
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
* limitations under the License.
*/
package com.chestnut.cms.stat.baidu.vo;
import java.util.List;
import java.util.Map;
package com.chestnut.contentcore.domain.dto;
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.Setter;
import java.util.List;
@Getter
@Setter
public class BaiduTimeTrendVO extends LineChartVO {
public class MergeCatalogDTO extends BaseDTO {
private Integer offset;
/**
* 栏目ID
*/
@NotNull
@LongId
public Long catalogId;
/**
* 时间范围
* 被合并栏目IDs
*/
private String timeSpan;
/**
* 指标字段
*/
private List<String> fields;
/**
* 总数
*/
private Integer total;
/**
* 指标合计
*/
private Map<String, Object> sum;
/**
*
*/
private Map<String, Object> pageSum;
@NotEmpty
public List<Long> mergeCatalogIds;
}

View File

@ -16,15 +16,15 @@
package com.chestnut.contentcore.domain.vo;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
import com.chestnut.contentcore.domain.InitByContent;
import lombok.Getter;
import lombok.Setter;
import java.time.ZoneOffset;
import java.util.List;
@Getter
@Setter
public class ContentApiVO {
public class ContentApiVO implements InitByContent {
/**
* 内容ID
@ -72,14 +72,24 @@ public class ContentApiVO {
private String titleStyle;
/**
* 引导
* 封面
*/
private String logo;
/**
* 引导图预览路径
* 封面图预览路径
*/
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;
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) {
ContentApiVO vo = new ContentApiVO();
vo.copyProperties(content);
vo.initByContent(content, false);
return vo;
}
}

View File

@ -13,31 +13,24 @@
* See the License for the specific language governing permissions and
* 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.Setter;
/**
* 被监控的Redis缓存数据
*/
@Getter
@Setter
public class MonitoredCache {
public class ContentPathRuleVO {
private String id;
/**
* 缓存名称
*/
private String cacheName = "";
private String name;
/**
* 缓存键名
*/
private String cacheKey = "";
public MonitoredCache(String cacheName, String cacheKey) {
this.cacheName = cacheName;
this.cacheKey = cacheKey;
public static ContentPathRuleVO newInstance(IContentPathRule contentPathRule) {
ContentPathRuleVO vo = new ContentPathRuleVO();
vo.setId(contentPathRule.getId());
vo.setName(contentPathRule.getName());
return vo;
}
}

View File

@ -15,26 +15,20 @@
*/
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.util.Date;
import java.util.HashMap;
import java.util.List;
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
@Setter
public class ContentVO {
public class ContentVO implements InitByContent {
/**
* 内容ID
@ -82,14 +76,24 @@ public class ContentVO {
private String showSubTitle;
/**
* 引导
* 封面
*/
private String logo;
/**
* 引导图预览路径
* 封面图预览路径
*/
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;
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;
import java.time.LocalDateTime;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.chestnut.contentcore.core.impl.InternalDataType_Content;
import com.chestnut.contentcore.domain.CmsContent;
import com.chestnut.contentcore.domain.InitByContent;
import com.chestnut.contentcore.util.InternalUrlUtils;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Getter
@Setter
public class ListContentVO {
public class ListContentVO implements InitByContent {
/*
* 内容ID
@ -72,6 +75,16 @@ public class ListContentVO {
*/
private String logoSrc;
/*
* 引导图
*/
private List<String> images;
/*
* 引导图预览路径
*/
private List<String> imagesSrc;
/*
* 内部链接
*/
@ -166,4 +179,11 @@ public class ListContentVO {
* 创建时间
*/
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;
import java.util.List;
import com.chestnut.common.staticize.func.IFunction.FuncArg;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
@ -35,4 +34,6 @@ public class TemplateFuncVO {
private String desc;
private List<FuncArg> funcArgs;
private String demoLink;
}

View File

@ -15,14 +15,13 @@
*/
package com.chestnut.contentcore.domain.vo;
import java.util.List;
import com.chestnut.common.staticize.tag.TagAttr;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
@ -35,4 +34,6 @@ public class TemplateTagVO {
private String description;
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 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;
/**
* 水印类型
*
* @author 兮玥
* @email 190785909@qq.com
*/
public enum WatermarkerType {
/**
@ -23,7 +29,7 @@ public enum WatermarkerType {
NONE,
/**
* 图片谁赢
* 图片水印
*/
IMAGE,

View File

@ -69,6 +69,11 @@ public enum ContentCoreErrorCode implements ErrorCode {
*/
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
public String value() {

View File

@ -13,17 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chestnut.contentcore.exception;
/**
* InternalUrlParseException
*
* @author 兮玥
* @email 190785909@qq.com
*/
public class InternalUrlParseException extends RuntimeException {
public InternalUrlParseException(String message, Exception e) {
super("Parse iurl failed: " + message, e);
}
}
package com.chestnut.contentcore.exception;
/**
* InternalUrlParseException
*
* @author 兮玥
* @email 190785909@qq.com
*/
public class InternalUrlParseException extends RuntimeException {
public InternalUrlParseException(String message) {
super("Parse iurl failed: " + message);
}
public InternalUrlParseException(String message, Exception e) {
super("Parse iurl failed: " + message, e);
}
}

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