mirror of
https://gitee.com/liweiyi/ChestnutCMS.git
synced 2025-12-06 16:38:24 +08:00
版本更新:V1.5.6
This commit is contained in:
parent
e55f263d8f
commit
3b838ec2b5
125
README.md
125
README.md
@ -1,4 +1,4 @@
|
||||
# ChestnutCMS v1.5.5
|
||||
# ChestnutCMS v1.5.6
|
||||
|
||||
### 系统简介
|
||||
|
||||
@ -14,33 +14,34 @@ ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi
|
||||
|
||||
资讯站演示地址:<https://news.1000mz.com>(会员演示账号:xxx333@126.com / a123456)
|
||||
|
||||
图片站演示地址:PC端:<http://tpz.1000mz.com> 移动端:<http://mtpz.1000mz.com>
|
||||
图片站演示地址:PC端:<https://tpz.1000mz.com> 移动端:<https://mtpz.1000mz.com>
|
||||
|
||||
游戏站演示地址:PC端:<http://game.1000mz.com> 移动端:<http://mgame.1000mz.com>
|
||||
游戏站演示地址:PC端:<https://game.1000mz.com> 移动端:<https://mgame.1000mz.com>
|
||||
|
||||
影视站演示地址:PC端:<http://movie.1000mz.com> 移动端:<http://movie.1000mz.com>
|
||||
影视站演示地址:PC端:<https://movie.1000mz.com> 移动端:<https://movie.1000mz.com>
|
||||
|
||||
更多演示站访问:<https://www.1000mz.com/themes/>
|
||||
|
||||
### 开发环境
|
||||
- OpenJDK 17
|
||||
- Maven 3.8+
|
||||
- MySQL 8.0+
|
||||
- Redis 5.x+
|
||||
- Maven 3.8
|
||||
- MySQL 8.0
|
||||
- Redis 7.0
|
||||
- NodeJS 16.20.2
|
||||
|
||||
### 主要技术框架
|
||||
|
||||
| 技术框架 | 版本 | 应用说明 |
|
||||
|-------------------|---------|---------|
|
||||
| Spring Boot | 3.1.7 | 基础开发框架 |
|
||||
| Spring Boot Admin | 3.1.7 | 监控框架 |
|
||||
| Mybatis Plus | 3.5.5 | ORM框架 |
|
||||
| Flyway | 9.22.3 | 数据库版本管理 |
|
||||
| Yitter | 1.0.6 | 雪花ID |
|
||||
| Redisson | 3.25.2 | 分布式锁 |
|
||||
| Spring Boot | 3.4.5 | 基础开发框架 |
|
||||
| Spring Boot Admin | 3.4.5 | 监控框架 |
|
||||
| Mybatis Plus | 3.5.12 | ORM框架 |
|
||||
| Flyway | 10.20.1 | 数据库版本管理 |
|
||||
| Redisson | 3.46.0 | 分布式锁 |
|
||||
| FreeMarker | 2.3.32 | 模板引擎 |
|
||||
| Sa-Token | 1.37.0 | 权限认证 |
|
||||
| Xxl-Job | 2.4.0 | 任务调度 |
|
||||
| Lombok | 1.18.26 | 你懂的 |
|
||||
| Sa-Token | 1.43.0 | 权限认证 |
|
||||
| Xxl-Job | 3.1.0 | 任务调度 |
|
||||
| Lombok | 1.18.38 | 你懂的 |
|
||||
|
||||
### 相关文档
|
||||
|
||||
@ -59,49 +60,55 @@ ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi
|
||||
|
||||
### 功能模块
|
||||
|
||||
| 模块 | 简介 |
|
||||
|--------------|----------------------------------------|
|
||||
| 站点管理 | 多站点,支持图片水印、标题查重、扩展模型等扩展配置 |
|
||||
| 栏目管理 | 普通栏目+链接栏目,扩展配置优先级高于站点扩展配置 |
|
||||
| 内容管理 | 内容类型:文章+图片集+音视频集,页面部件:动态自定义区块+广告,内容回收站 |
|
||||
| 资源管理 | 图片、音视频等各类静态资源管理,支持OSS/COS/MinIO对象存储 |
|
||||
| 发布通道 | 支持多通道不同类型静态文件发布,可同时发布到PC、H5,html、json等 |
|
||||
| 模板管理 | 静态化模板,支持在线编辑 |
|
||||
| 模板指令 | FreeMarker自定义标签、模板函数及动态模板的参数及用法说明 |
|
||||
| 文件管理 | 当前站点资源目录及发布通道静态化目录管理,支持文本在线编辑 |
|
||||
| 扩展模型 | 站点、栏目及内容的动态模型扩展,系统默认数据表保存,支持自定义 |
|
||||
| 词汇管理 | 热词、TAG词、敏感词、易错词 |
|
||||
| 内容索引 | 默认支持ElasticSearch+IK创建内容索引,支持标题内容全文检索 |
|
||||
| 检索词库 | 自定义检索词库,支持扩展词和停用词动态扩展 |
|
||||
| 检索日志 | 用户搜索的日志记录 |
|
||||
| 友链管理 | 友情链接 |
|
||||
| 广告管理 | 广告基于页面部件扩展的简单广告功能,支持权重及定时上下线,广告点击/展现统计 |
|
||||
| 评论管理 | 基础功能模块 |
|
||||
| 调查问卷 | 基础功能模块,默认支持文字类型单选、多选、输入、图片、富文本 |
|
||||
| 自定义表单 | 基于元数据模块扩展,支持模板标签 |
|
||||
| 会员管理 | 支持自定义会员等级,等级经验值来源动态配置 |
|
||||
| 访问统计 | 对接百度统计API |
|
||||
| 用户管理 | 后台用户管理,支持用户独立权限配置 |
|
||||
| 机构管理 | 多级系统组织机构(公司、部门、小组) |
|
||||
| 角色管理 | 支持按角色分配菜单权限、站点和栏目相关操作权限配置 |
|
||||
| 岗位管理 | 配置系统用户所属担任职务 |
|
||||
| 菜单管理 | 配置系统菜单,操作权限,按钮权限标识等 |
|
||||
| 字典管理 | 对系统中经常使用的一些固定的数据进行维护,代码层面定义 |
|
||||
| 参数管理 | 对系统动态配置常用参数,代码层面定义 |
|
||||
| 通知公告 | 系统通知公告信息发布维护 |
|
||||
| 安全配置 | 密码强度、密码过期、首次登陆强制修改、登陆异常策略配置 |
|
||||
| 国际化 | 为菜单等动态数据国际化配置提供基础支持,可覆盖后台代码配置 |
|
||||
| 安全配置 | 密码强度、密码过期、首次登陆强制修改、登陆异常策略配置 |
|
||||
| 系统日志 | 统一日志管理,支持扩展 |
|
||||
| 操作日志 | 系统操作日志扩展,记录操作参数、异常信息及请求耗时 |
|
||||
| 登录日志 | 系统登录日志扩展,记录用户登录日志,包含登录异常 |
|
||||
| 在线用户 | 当前系统中活跃用户状态监控,支持踢下线 |
|
||||
| 任务调度 | 基于XXL-JOB的分布式任务调度 |
|
||||
| 定时任务 | 基于Spring的TaskScheduler实现的单机定时任务 |
|
||||
| 异步任务 | 异步任务状态查看,支持手动结束 |
|
||||
| 服务监控 | 监视当前系统CPU、内存、磁盘、堆栈等相关信息 |
|
||||
| 缓存监控 | 对系统的缓存信息查询,命令统计等 |
|
||||
| GroovyScript | 支持Groovy脚本在线执行 |
|
||||
| 模块 | 简介 |
|
||||
|--------------|----------------------------------------------|
|
||||
| 站点管理 | 多站点,支持图片水印、标题查重、扩展模型等扩展配置 |
|
||||
| 栏目管理 | 普通栏目+链接栏目,扩展配置优先级高于站点扩展配置 |
|
||||
| 内容管理 | 内容类型:文章+图片集+音视频集,页面部件:动态自定义区块+广告,内容回收站 |
|
||||
| 资源管理 | 图片、音视频等各类静态资源管理,支持对象存储OSS/COS/MinIO/AmazonS3 |
|
||||
| 发布通道 | 支持多通道不同类型静态文件发布,可同时发布到PC、H5,html、json等 |
|
||||
| 模板管理 | 静态化模板,支持在线编辑 |
|
||||
| 模板指令 | FreeMarker自定义标签、模板函数及动态模板的参数及用法说明,自定义动态模板 |
|
||||
| 文件管理 | 当前站点资源目录及发布通道静态化目录管理,支持文本在线编辑 |
|
||||
| 扩展模型 | 站点、栏目及内容的动态模型扩展,系统默认数据表保存,支持自定义 |
|
||||
| 词汇管理 | 热词、TAG词、敏感词、易错词,支持文章编辑器敏感词/易错词检测,一键替换 |
|
||||
| 内容索引 | 默认支持ElasticSearch+IK创建内容索引,支持标题内容全文检索 |
|
||||
| 检索词库 | 自定义检索词库,支持扩展词和停用词动态扩展 |
|
||||
| 检索日志 | 用户搜索的日志记录 |
|
||||
| 友链管理 | 友情链接 |
|
||||
| 广告管理 | 广告基于页面部件扩展的简单广告功能,支持权重及定时上下线,广告点击/展现统计 |
|
||||
| 评论管理 | 基础功能模块 |
|
||||
| 调查问卷 | 基础功能模块,默认支持文字类型单选、多选、输入、图片、富文本 |
|
||||
| 自定义表单 | 基于元数据模块扩展,支持模板标签 |
|
||||
| 会员管理 | 支持自定义会员等级,等级经验值来源动态配置 |
|
||||
| 访问统计 | 对接百度统计API |
|
||||
| 用户管理 | 后台用户管理,支持用户独立权限配置、角色权限继承 |
|
||||
| 机构管理 | 多级系统组织机构(公司、部门、小组) |
|
||||
| 角色管理 | 支持按角色分配菜单权限、站点和栏目相关操作权限配置 |
|
||||
| 岗位管理 | 配置系统用户所属担任职务 |
|
||||
| 菜单管理 | 配置系统菜单,操作权限,按钮权限标识等 |
|
||||
| 字典管理 | 对系统中经常使用的一些固定的数据进行维护,代码层面定义 |
|
||||
| 参数管理 | 对系统动态配置常用参数,代码层面定义 |
|
||||
| 通知公告 | 系统通知公告信息发布维护 |
|
||||
| 安全配置 | 密码强度、密码过期、首次登陆强制修改、登陆异常策略配置 |
|
||||
| 国际化 | 菜单等动态数据国际化配置 |
|
||||
| 安全配置 | 密码强度、密码过期、首次登陆强制修改、登陆异常策略配置 |
|
||||
| 系统日志 | 统一日志管理,支持扩展 |
|
||||
| 操作日志 | 系统操作日志扩展,记录操作参数、异常信息及请求耗时 |
|
||||
| 登录日志 | 系统登录日志扩展,记录用户登录日志,包含登录异常 |
|
||||
| 在线用户 | 当前系统中活跃用户状态监控,支持踢下线 |
|
||||
| 任务调度 | 基于XXL-JOB的分布式任务调度 |
|
||||
| 定时任务 | 基于Spring的TaskScheduler实现的单机定时任务 |
|
||||
| 异步任务 | 异步任务状态监控 |
|
||||
| 服务监控 | 监视当前系统CPU、内存、磁盘、堆栈等相关信息 |
|
||||
| 缓存监控 | 对系统的缓存信息查询,命令统计等 |
|
||||
| GroovyScript | 支持Groovy脚本在线执行 |
|
||||
|
||||
### 版权说明
|
||||
|
||||
「ChestnutCMS 栗子内容管理系统」软件著作权登记号:2023SR1343023,产品受到相关法律法规的保护,开源不代表放弃版权!!!
|
||||
|
||||
请务必仔细阅读[版权声明](https://www.1000mz.com/docs/others/668421333913669.shtml),避免不必要的版权纠纷,。
|
||||
|
||||
### QQ交流群
|
||||
|
||||
|
||||
@ -34,4 +34,4 @@ VOLUME ["/home/app/logs","/home/app/uploadPath","/home/app/wwwroot_release","/ho
|
||||
|
||||
EXPOSE $SERVER_PORT $NETTY_SOCKET_PORT
|
||||
|
||||
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
|
||||
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]
|
||||
@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>chestnut</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
@ -165,6 +165,9 @@
|
||||
<configuration>
|
||||
<!-- 如果没有该配置,devtools不会生效 -->
|
||||
<!-- <fork>true</fork> -->
|
||||
<layers>
|
||||
<enabled>true</enabled>
|
||||
</layers>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
||||
@ -5,7 +5,7 @@ chestnut:
|
||||
# 代号
|
||||
alias: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.5.5
|
||||
version: 1.5.6
|
||||
# 版权年份
|
||||
copyrightYear: 2022-2024
|
||||
system:
|
||||
@ -13,8 +13,6 @@ chestnut:
|
||||
demoMode: false
|
||||
# 文件路径 示例( Windows配置D:/chestnut/uploadPath,Linux配置 /home/chestnut/uploadPath)
|
||||
uploadPath: 'E:/dev/workspace_chestnut/uploadPath'
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
captchaType: math
|
||||
member:
|
||||
uploadPath: 'E:/dev/workspace_chestnut/_xy_member/'
|
||||
cms:
|
||||
@ -66,9 +64,9 @@ spring:
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 100MB
|
||||
max-file-size: 200MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 100MB
|
||||
max-request-size: 200MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
|
||||
@ -5,7 +5,7 @@ chestnut:
|
||||
# 代号
|
||||
alias: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.5.5
|
||||
version: 1.5.6
|
||||
# 版权年份
|
||||
copyrightYear: 2022-2024
|
||||
system:
|
||||
@ -13,8 +13,6 @@ chestnut:
|
||||
demoMode: true
|
||||
# 文件路径 示例( Windows配置D:/chestnut/uploadPath,Linux配置 /home/app/uploadPath)
|
||||
uploadPath: /home/app/uploadPath
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
captchaType: math
|
||||
freemarker:
|
||||
templateLoaderPath: /home/app/statics
|
||||
cms:
|
||||
@ -63,9 +61,9 @@ spring:
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 100MB
|
||||
max-file-size: 200MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 100MB
|
||||
max-request-size: 200MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
|
||||
@ -5,7 +5,7 @@ chestnut:
|
||||
# 代号
|
||||
alias: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.5.5
|
||||
version: 1.5.6
|
||||
# 版权年份
|
||||
copyrightYear: 2022-2024
|
||||
system:
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
ALTER TABLE cms_custom_form MODIFY COLUMN `templates` varchar(1000);
|
||||
ALTER TABLE cms_page_widget MODIFY COLUMN `publish_pipe_code` varchar(50);
|
||||
ALTER TABLE cms_page_widget ADD COLUMN `templates` varchar(1000) COMMENT '模板配置';
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-advertisement</artifactId>
|
||||
|
||||
@ -25,7 +25,6 @@ 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.Assert;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IPageWidget;
|
||||
@ -41,9 +40,9 @@ import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import freemarker.template.TemplateException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -111,28 +110,30 @@ public class AdSpaceController extends BaseRestController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public R<?> addAdSpace(HttpServletRequest request) throws IOException {
|
||||
PageWidgetAddDTO dto = JacksonUtils.from(request.getInputStream(), PageWidgetAddDTO.class);
|
||||
public R<?> addAdSpace(@RequestBody @Validated PageWidgetAddDTO dto) {
|
||||
dto.setType(pageWidgetType.getId());
|
||||
|
||||
CmsPageWidget cmsPageWdiget = new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, cmsPageWdiget);
|
||||
|
||||
CmsSite site = siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
CmsPageWidget pageWidget = new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, pageWidget);
|
||||
pageWidget.setSiteId(site.getSiteId());
|
||||
pageWidget.setTemplates(dto.getPublishPipeTemplateMap());
|
||||
|
||||
IPageWidget pw = pageWidgetType.newInstance();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
pw.setPageWidgetEntity(pageWidget);
|
||||
pw.setOperator(StpAdminUtil.getLoginUser());
|
||||
CmsSite site = this.siteService.getCurrentSite(request);
|
||||
pw.getPageWidgetEntity().setSiteId(site.getSiteId());
|
||||
this.pageWidgetService.addPageWidget(pw);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public R<?> editAdSpace(HttpServletRequest request) throws IOException {
|
||||
PageWidgetEditDTO dto = JacksonUtils.from(request.getInputStream(), PageWidgetEditDTO.class);
|
||||
CmsPageWidget cmsPageWdiget = new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, cmsPageWdiget);
|
||||
public R<?> editAdSpace(@RequestBody @Validated PageWidgetEditDTO dto) {
|
||||
CmsPageWidget pageWidget = new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, pageWidget);
|
||||
pageWidget.setTemplates(dto.getPublishPipeTemplateMap());
|
||||
|
||||
IPageWidget pw = pageWidgetType.newInstance();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
pw.setPageWidgetEntity(pageWidget);
|
||||
pw.setOperator(StpAdminUtil.getLoginUser());
|
||||
this.pageWidgetService.savePageWidget(pw);
|
||||
return R.ok();
|
||||
|
||||
@ -32,6 +32,9 @@ import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.security.web.PageRequest;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import jakarta.validation.constraints.Min;
|
||||
@ -59,6 +62,10 @@ public class AdvertisementController extends BaseRestController {
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
@GetMapping("/types")
|
||||
public R<?> listAdvertisements() {
|
||||
List<Map<String, String>> list = advertisementService.getAdvertisementTypeList().stream()
|
||||
@ -89,7 +96,11 @@ public class AdvertisementController extends BaseRestController {
|
||||
public R<AdvertisementVO> getAdvertisementInfo(@PathVariable("advertisementId") @Min(1) Long advertisementId) {
|
||||
CmsAdvertisement ad = this.advertisementService.getById(advertisementId);
|
||||
Assert.notNull(ad, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("advertisementId", advertisementId));
|
||||
return R.ok(new AdvertisementVO(ad).dealPreviewResourcePath());
|
||||
|
||||
CmsSite site = siteService.getSite(ad.getSiteId());
|
||||
AdvertisementVO vo = new AdvertisementVO(ad).dealPreviewResourcePath();
|
||||
resourceService.dealDefaultThumbnail(site, vo.getResourcePath(), vo::setResourceSrc);
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
@Log(title = "新增广告", businessType = BusinessType.INSERT)
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-article</artifactId>
|
||||
|
||||
@ -28,12 +28,14 @@ import com.chestnut.contentcore.enums.ContentCopyType;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.util.ResourceUtils;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@Slf4j
|
||||
public class ArticleContent extends AbstractContent<CmsArticleDetail> {
|
||||
|
||||
private IArticleService articleService;
|
||||
@ -48,15 +50,13 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
|
||||
CmsArticleDetail articleDetail = this.getExtendEntity();
|
||||
articleDetail.setContentId(this.getContentEntity().getContentId());
|
||||
articleDetail.setSiteId(this.getContentEntity().getSiteId());
|
||||
// 处理内部链接
|
||||
String contentHtml = ResourceUtils.dealHtmlInternalUrl(articleDetail.getContentHtml());
|
||||
// 处理文章正文远程图片
|
||||
if (YesOrNo.isYes(articleDetail.getDownloadRemoteImage())) {
|
||||
AsyncTaskManager.setTaskPercent(90);
|
||||
contentHtml = this.getResourceService().downloadRemoteImages(contentHtml, this.getSite(),
|
||||
String contentHtml = this.getResourceService().downloadRemoteImages(articleDetail.getContentHtml(), this.getSite(),
|
||||
this.getOperatorUName());
|
||||
articleDetail.setContentHtml(contentHtml);
|
||||
}
|
||||
articleDetail.setContentHtml(contentHtml);
|
||||
// 正文首图作为logo
|
||||
if (StringUtils.isEmpty(this.getContentEntity().getImages())
|
||||
&& AutoArticleLogo.getValue(this.getSite().getConfigProps())) {
|
||||
@ -76,15 +76,13 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
|
||||
return;
|
||||
}
|
||||
CmsArticleDetail articleDetail = this.getExtendEntity();
|
||||
// 处理内部链接
|
||||
String contentHtml = ResourceUtils.dealHtmlInternalUrl(articleDetail.getContentHtml());
|
||||
// 处理文章正文远程图片
|
||||
if (YesOrNo.isYes(articleDetail.getDownloadRemoteImage())) {
|
||||
AsyncTaskManager.setTaskPercent(90);
|
||||
contentHtml = this.getResourceService().downloadRemoteImages(contentHtml, this.getSite(),
|
||||
String contentHtml = this.getResourceService().downloadRemoteImages(articleDetail.getContentHtml(), this.getSite(),
|
||||
this.getOperatorUName());
|
||||
articleDetail.setContentHtml(contentHtml);
|
||||
}
|
||||
articleDetail.setContentHtml(contentHtml);
|
||||
// 正文首图作为logo
|
||||
if (StringUtils.isEmpty(this.getContentEntity().getImages())
|
||||
&& AutoArticleLogo.getValue(this.getSite().getConfigProps())) {
|
||||
@ -116,6 +114,10 @@ public class ArticleContent extends AbstractContent<CmsArticleDetail> {
|
||||
@Override
|
||||
protected void delete0() {
|
||||
if (this.hasExtendEntity()) {
|
||||
if (Objects.isNull(this.getExtendEntity())) {
|
||||
log.warn("The content extend entity is null: {}", this.getContentEntity().getContentId());
|
||||
return;
|
||||
}
|
||||
this.getArticleService().dao()
|
||||
.deleteByIdAndBackup(this.getExtendEntity(), this.getOperatorUName());
|
||||
}
|
||||
|
||||
@ -32,23 +32,23 @@ import com.chestnut.contentcore.core.IContent;
|
||||
import com.chestnut.contentcore.core.IContentType;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp.PublishPipePropUseType;
|
||||
import com.chestnut.contentcore.domain.*;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.domain.vo.ContentVO;
|
||||
import com.chestnut.contentcore.enums.ContentCopyType;
|
||||
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.service.*;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Component(IContentType.BEAN_NAME_PREFIX + ArticleContentType.ID)
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleContentType implements IContentType {
|
||||
@ -67,6 +67,8 @@ public class ArticleContentType implements IContentType {
|
||||
|
||||
private final IContentService contentService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
@ -119,8 +121,15 @@ public class ArticleContentType implements IContentType {
|
||||
content.setContentEntity(contentEntity);
|
||||
content.setExtendEntity(extendEntity);
|
||||
content.setParams(dto.getParams());
|
||||
if (content.hasExtendEntity() && StringUtils.isEmpty(extendEntity.getContentHtml())) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("contentHtml");
|
||||
if (content.hasExtendEntity()) {
|
||||
if (StringUtils.isEmpty(extendEntity.getContentHtml())) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("contentHtml");
|
||||
}
|
||||
IArticleBodyFormat format = articleService.getArticleBodyFormat(extendEntity.getFormat());
|
||||
if (Objects.nonNull(format)) {
|
||||
String contentHtml = format.onSave(extendEntity.getContentHtml());
|
||||
extendEntity.setContentHtml(contentHtml);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
@ -129,6 +138,7 @@ public class ArticleContentType implements IContentType {
|
||||
public ContentVO initEditor(Long catalogId, Long contentId) {
|
||||
CmsCatalog catalog = this.catalogService.getCatalog(catalogId);
|
||||
Assert.notNull(catalog, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("catalogId", catalogId));
|
||||
CmsSite site = siteService.getSite(catalog.getSiteId());
|
||||
List<CmsPublishPipe> publishPipes = this.publishPipeService.getPublishPipes(catalog.getSiteId());
|
||||
ArticleVO vo;
|
||||
if (IdUtils.validate(contentId)) {
|
||||
@ -138,7 +148,7 @@ public class ArticleContentType implements IContentType {
|
||||
CmsArticleDetail extendEntity = this.articleService.dao().getById(contentId);
|
||||
vo = ArticleVO.newInstance(contentEntity, extendEntity);
|
||||
// 发布通道模板数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
PublishPipePropUseType.Content, contentEntity.getPublishPipeProps());
|
||||
vo.setPublishPipeProps(publishPipeProps);
|
||||
} else {
|
||||
@ -146,16 +156,27 @@ public class ArticleContentType implements IContentType {
|
||||
vo.setContentId(IdUtils.getSnowflakeId());
|
||||
vo.setCatalogId(catalog.getCatalogId());
|
||||
vo.setContentType(ID);
|
||||
CmsSite site = siteService.getSite(catalog.getSiteId());
|
||||
vo.setDownloadRemoteImage(DownloadRemoteImage.getValue(site.getConfigProps()));
|
||||
// 发布通道初始数据
|
||||
vo.setPublishPipe(publishPipes.stream().map(CmsPublishPipe::getCode).toArray(String[]::new));
|
||||
// 发布通道模板数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
PublishPipePropUseType.Content, null);
|
||||
vo.setPublishPipeProps(publishPipeProps);
|
||||
}
|
||||
vo.setCatalogName(catalog.getName());
|
||||
// 内容引导图缩略图处理
|
||||
resourceService.dealDefaultThumbnail(site, vo.getImages(), thumbnails -> {
|
||||
vo.setImagesSrc(thumbnails);
|
||||
vo.setLogoSrc(thumbnails.get(0));
|
||||
});
|
||||
// 文章正文内容处理
|
||||
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());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
@ -26,15 +26,13 @@ import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.ICoreDataHandler;
|
||||
import com.chestnut.contentcore.core.SiteExportContext;
|
||||
import com.chestnut.contentcore.core.SiteImportContext;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 文章内容核心数据处理器
|
||||
@ -49,8 +47,6 @@ public class ArticleCoreDataHandler implements ICoreDataHandler {
|
||||
|
||||
private final IArticleService articleService;
|
||||
|
||||
private final Map<String, IArticleBodyFormat> articleBodyFormatMap;
|
||||
|
||||
@Override
|
||||
public void onSiteExport(SiteExportContext context) {
|
||||
// cms_article_detail
|
||||
@ -90,23 +86,14 @@ public class ArticleCoreDataHandler implements ICoreDataHandler {
|
||||
Long contentId = context.getContentIdMap().get(oldContentId);
|
||||
data.setContentId(contentId);
|
||||
data.setSiteId(context.getSite().getSiteId());
|
||||
String contentHtml = data.getContentHtml();
|
||||
// 替换正文内部资源地址
|
||||
StringBuilder html = new StringBuilder();
|
||||
int index = 0;
|
||||
Matcher matcher = InternalUrlUtils.InternalUrlTagPattern.matcher(data.getContentHtml());
|
||||
while (matcher.find()) {
|
||||
String tagStr = matcher.group();
|
||||
String iurl = matcher.group(1);
|
||||
String newIurl = context.dealInternalUrl(iurl);
|
||||
tagStr = StringUtils.replaceEx(tagStr, iurl, newIurl);
|
||||
html.append(contentHtml, index, matcher.start()).append(tagStr);
|
||||
index = matcher.end();
|
||||
if (StringUtils.isEmpty(data.getFormat())) {
|
||||
data.setFormat(ArticleBodyFormat_RichText.ID);
|
||||
}
|
||||
html.append(contentHtml.substring(index));
|
||||
data.setContentHtml(html.toString());
|
||||
if (!articleBodyFormatMap.containsKey(IArticleBodyFormat.BEAN_PREFIX + data.getFormat())) {
|
||||
data.setFormat(ArticleBodyFormat_RichText.ID); // 不支持的文章格式一律设置为富文本格式
|
||||
String contentHtml = data.getContentHtml();
|
||||
IArticleBodyFormat format = articleService.getArticleBodyFormat(data.getFormat());
|
||||
if (Objects.nonNull(format)) {
|
||||
contentHtml = format.onSiteThemImport(context, contentHtml);
|
||||
data.setContentHtml(contentHtml);
|
||||
}
|
||||
articleService.dao().save(data);
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -117,7 +117,7 @@ public class ArticleUtils {
|
||||
* 替换为ssi引用标签
|
||||
* </p>
|
||||
*/
|
||||
public static String dealPageWidget(CmsContent content, String articleBody, boolean isPreview) {
|
||||
public static String dealPageWidget(CmsContent content, String articleBody, String publishPipeCode, boolean isPreview) {
|
||||
if (StringUtils.isBlank(articleBody)) {
|
||||
return articleBody;
|
||||
}
|
||||
@ -138,17 +138,17 @@ public class ArticleUtils {
|
||||
if (isPreview) {
|
||||
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(InternalDataType_PageWidget.ID);
|
||||
placeholderImgTag = internalDataType.getPageData(new IInternalDataType.RequestData(pw.getPageWidgetId(),
|
||||
1, pw.getPublishPipeCode(), true, null));
|
||||
1, publishPipeCode, true, null));
|
||||
} else {
|
||||
boolean ssiEnabled = EnableSSIProperty.getValue(site.getConfigProps());
|
||||
if (catalog.isStaticize() && ssiEnabled) {
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(pw.getPublishPipeCode()));
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(publishPipeCode));
|
||||
String staticFilePath = pw.getPath() + staticFileName;
|
||||
placeholderImgTag = StringUtils.messageFormat(CmsIncludeTag.SSI_INCLUDE_TAG, "/" + staticFilePath);
|
||||
} else {
|
||||
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(InternalDataType_PageWidget.ID);
|
||||
placeholderImgTag = internalDataType.getPageData(new IInternalDataType.RequestData(pw.getPageWidgetId(),
|
||||
1, pw.getPublishPipeCode(), false, null));
|
||||
1, publishPipeCode, false, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.chestnut.article;
|
||||
|
||||
import com.chestnut.contentcore.core.SiteImportContext;
|
||||
|
||||
/**
|
||||
* 文正正文文档格式
|
||||
*
|
||||
@ -42,6 +44,18 @@ public interface IArticleBodyFormat {
|
||||
return contentHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器内容保存处理
|
||||
*/
|
||||
default String onSave(String contentHtml) {
|
||||
return contentHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点主题导入处理
|
||||
*/
|
||||
String onSiteThemImport(SiteImportContext context, String contentHtml);
|
||||
|
||||
/**
|
||||
* 文章正文内容发布预览处理
|
||||
*/
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.chestnut.article;
|
||||
|
||||
import com.alibaba.excel.util.StringUtils;
|
||||
import cn.idev.excel.util.StringUtils;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -17,9 +17,14 @@ package com.chestnut.article.format;
|
||||
|
||||
import com.chestnut.article.ArticleUtils;
|
||||
import com.chestnut.article.IArticleBodyFormat;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.SiteImportContext;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import com.chestnut.contentcore.util.ResourceUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* 文章正文文档格式:富文本
|
||||
*
|
||||
@ -46,6 +51,30 @@ public class ArticleBodyFormat_RichText implements IArticleBodyFormat {
|
||||
return InternalUrlUtils.dealResourceInternalUrl(contentHtml);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onSave(String contentHtml) {
|
||||
return ResourceUtils.dealHtmlInternalUrl(contentHtml);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onSiteThemImport(SiteImportContext context, String contentHtml) {
|
||||
// 替换正文内部资源地址
|
||||
StringBuilder html = new StringBuilder();
|
||||
int index = 0;
|
||||
Matcher matcher = InternalUrlUtils.InternalUrlTagPattern.matcher(contentHtml);
|
||||
while (matcher.find()) {
|
||||
String tagStr = matcher.group();
|
||||
String iurl = matcher.group(1);
|
||||
|
||||
String newIurl = context.dealInternalUrl(iurl);
|
||||
tagStr = StringUtils.replaceEx(tagStr, iurl, newIurl);
|
||||
html.append(contentHtml, index, matcher.start()).append(tagStr);
|
||||
index = matcher.end();
|
||||
}
|
||||
html.append(contentHtml.substring(index));
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deal(String contentHtml, String publishPipeCode, boolean isPreview) {
|
||||
// 处理内容扩展模板占位符
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-block</artifactId>
|
||||
|
||||
@ -68,9 +68,9 @@ public class ManualPageWidgetType implements IPageWidgetType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPageWidget loadPageWidget(CmsPageWidget cmsPageWdiget) {
|
||||
public IPageWidget loadPageWidget(CmsPageWidget cmsPageWidget) {
|
||||
ManualPageWidget pw = new ManualPageWidget();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
pw.setPageWidgetEntity(cmsPageWidget);
|
||||
return pw;
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-comment</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-contentcore</artifactId>
|
||||
|
||||
@ -38,14 +38,12 @@ import com.chestnut.contentcore.core.impl.CatalogType_Link;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.*;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.domain.vo.CatalogVO;
|
||||
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
|
||||
import com.chestnut.contentcore.perms.CatalogPermissionType.CatalogPrivItem;
|
||||
import com.chestnut.contentcore.perms.ContentCorePriv;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.IPublishService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import com.chestnut.contentcore.user.preference.CatalogTreeExpandModePreference;
|
||||
import com.chestnut.contentcore.util.CmsPrivUtils;
|
||||
import com.chestnut.contentcore.util.ConfigPropertyUtils;
|
||||
@ -92,6 +90,8 @@ public class CatalogController extends BaseRestController {
|
||||
|
||||
private final AsyncTaskManager asyncTaskManager;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
/**
|
||||
* 查询栏目数据列表
|
||||
*/
|
||||
@ -118,8 +118,9 @@ public class CatalogController extends BaseRestController {
|
||||
if (StringUtils.isNotEmpty(dto.getLogo())) {
|
||||
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
|
||||
}
|
||||
resourceService.dealDefaultThumbnail(site, dto.getLogo(), dto::setLogoSrc);
|
||||
// 发布通道数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(),
|
||||
PublishPipePropUseType.Catalog, catalog.getPublishPipeProps());
|
||||
dto.setPublishPipeDatas(publishPipeProps);
|
||||
return R.ok(dto);
|
||||
@ -179,12 +180,20 @@ public class CatalogController extends BaseRestController {
|
||||
@Log(title = "删除", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{catalogId}")
|
||||
public R<String> deleteCatalog(@PathVariable("catalogId") @LongId Long catalogId) {
|
||||
CmsCatalog catalog = catalogService.getById(catalogId);
|
||||
Assert.notNull(catalog, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("catalogId", catalog));
|
||||
LoginUser operator = StpAdminUtil.getLoginUser();
|
||||
AsyncTask task = new AsyncTask("Catalog-" + catalogId) {
|
||||
|
||||
@Override
|
||||
public void run0() {
|
||||
catalogService.deleteCatalog(catalogId, operator);
|
||||
List<CmsCatalog> list = catalogService.lambdaQuery()
|
||||
.likeRight(CmsCatalog::getAncestors, catalog.getAncestors())
|
||||
.list();
|
||||
list.sort((c1, c2) -> c2.getTreeLevel() - c1.getTreeLevel());
|
||||
list.forEach(c -> {
|
||||
catalogService.deleteCatalog(c, operator);
|
||||
});
|
||||
}
|
||||
};
|
||||
this.asyncTaskManager.execute(task);
|
||||
|
||||
@ -43,10 +43,7 @@ import com.chestnut.contentcore.listener.event.AfterContentEditorInitEvent;
|
||||
import com.chestnut.contentcore.perms.CatalogPermissionType.CatalogPrivItem;
|
||||
import com.chestnut.contentcore.properties.ShortTitleLabelProperty;
|
||||
import com.chestnut.contentcore.properties.SubTitleLabelProperty;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IContentService;
|
||||
import com.chestnut.contentcore.service.IPublishService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import com.chestnut.contentcore.user.preference.IncludeChildContentPreference;
|
||||
import com.chestnut.contentcore.user.preference.ShowContentSubTitlePreference;
|
||||
import com.chestnut.contentcore.util.CmsPrivUtils;
|
||||
@ -90,6 +87,8 @@ public class ContentController extends BaseRestController {
|
||||
|
||||
private final IPublishService publishService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
@ -136,6 +135,11 @@ public class ContentController extends BaseRestController {
|
||||
}
|
||||
Page<CmsContent> page = q.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true));
|
||||
List<ListContentVO> list = page.getRecords().stream().map(ListContentVO::newInstance).toList();
|
||||
// 内容引导图缩略图处理
|
||||
list.forEach(vo -> resourceService.dealDefaultThumbnail(site, vo.getImages(), thumbnails -> {
|
||||
vo.setImagesSrc(thumbnails);
|
||||
vo.setLogoSrc(thumbnails.get(0));
|
||||
}));
|
||||
return this.bindDataTable(list, (int) page.getTotal());
|
||||
}
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ public class CoreController extends BaseRestController {
|
||||
String pageData = internalDataType.getPageData(data);
|
||||
response.getWriter().write(pageData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(response.getWriter());
|
||||
response.getWriter().write(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,15 +34,18 @@ import com.chestnut.contentcore.core.IPageWidget;
|
||||
import com.chestnut.contentcore.core.IPageWidgetType;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.PageWidgetAddDTO;
|
||||
import com.chestnut.contentcore.domain.dto.PageWidgetEditDTO;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.contentcore.domain.vo.PageWidgetVO;
|
||||
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
|
||||
import com.chestnut.contentcore.perms.ContentCorePriv;
|
||||
import com.chestnut.contentcore.perms.SitePermissionType;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.util.CmsPrivUtils;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
@ -76,6 +79,8 @@ public class PageWidgetController extends BaseRestController {
|
||||
|
||||
private final IPageWidgetService pageWidgetService;
|
||||
|
||||
private final IPublishPipeService publishPipeService;
|
||||
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@GetMapping("/types")
|
||||
public R<?> getPageWidgetTypes() {
|
||||
@ -131,7 +136,14 @@ public class PageWidgetController extends BaseRestController {
|
||||
|
||||
IPageWidgetType pwt = this.pageWidgetService.getPageWidgetType(pageWidget.getType());
|
||||
PageWidgetVO vo = pwt.getPageWidgetVO(pageWidget);
|
||||
|
||||
// 发布通道模板
|
||||
List<CmsPublishPipe> publishPipes = this.publishPipeService.getPublishPipes(pageWidget.getSiteId());
|
||||
List<PublishPipeTemplate> templates = publishPipes.stream().map(pp -> new PublishPipeTemplate(
|
||||
pp.getName(),
|
||||
pp.getCode(),
|
||||
pageWidget.getTemplate(pp.getCode())
|
||||
)).toList();
|
||||
vo.setTemplates(templates);
|
||||
if (pageWidget.getCatalogId() > 0) {
|
||||
CmsCatalog catalog = this.catalogService.getCatalog(pageWidget.getCatalogId());
|
||||
vo.setCatalogName(catalog.getName());
|
||||
@ -154,6 +166,7 @@ public class PageWidgetController extends BaseRestController {
|
||||
CmsPageWidget pageWidget= new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, pageWidget);
|
||||
pageWidget.setSiteId(site.getSiteId());
|
||||
pageWidget.setTemplates(dto.getPublishPipeTemplateMap());
|
||||
|
||||
IPageWidget pw = pwt.newInstance();
|
||||
pw.setPageWidgetEntity(pageWidget);
|
||||
@ -175,7 +188,8 @@ public class PageWidgetController extends BaseRestController {
|
||||
|
||||
BeanUtils.copyProperties(dto, pageWidget);
|
||||
pageWidget.setContent(dto.getContentStr());
|
||||
|
||||
pageWidget.setTemplates(dto.getPublishPipeTemplateMap());
|
||||
|
||||
IPageWidgetType pwt = this.pageWidgetService.getPageWidgetType(pageWidget.getType());
|
||||
IPageWidget pw = pwt.newInstance();
|
||||
pw.setPageWidgetEntity(pageWidget);
|
||||
|
||||
@ -29,7 +29,7 @@ import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.perms.ContentCorePriv;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
@ -71,8 +71,8 @@ public class PublishPipeController extends BaseRestController {
|
||||
@GetMapping("/selectData")
|
||||
public R<?> bindSelectData() {
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
List<PublishPipeProp> datalist = this.publishPipeService.getPublishPipes(site.getSiteId())
|
||||
.stream().map(p -> PublishPipeProp.newInstance(p.getCode(), p.getName(), null))
|
||||
List<PublishPipeProps> datalist = this.publishPipeService.getPublishPipes(site.getSiteId())
|
||||
.stream().map(p -> PublishPipeProps.newInstance(p.getCode(), p.getName(), null))
|
||||
.collect(Collectors.toList());
|
||||
return this.bindDataTable(datalist);
|
||||
}
|
||||
|
||||
@ -35,14 +35,17 @@ import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.common.utils.file.FileExUtils;
|
||||
import com.chestnut.contentcore.core.IProperty.UseType;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp.PublishPipePropUseType;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.*;
|
||||
import com.chestnut.contentcore.domain.dto.PublishSiteDTO;
|
||||
import com.chestnut.contentcore.domain.dto.SiteDTO;
|
||||
import com.chestnut.contentcore.domain.dto.SiteDefaultTemplateDTO;
|
||||
import com.chestnut.contentcore.domain.dto.SiteExportDTO;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.domain.vo.SiteDashboardVO;
|
||||
import com.chestnut.contentcore.perms.ContentCorePriv;
|
||||
import com.chestnut.contentcore.perms.SitePermissionType.SitePrivItem;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.IPublishService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import com.chestnut.contentcore.service.impl.SiteThemeService;
|
||||
import com.chestnut.contentcore.util.ConfigPropertyUtils;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
@ -97,6 +100,8 @@ public class SiteController extends BaseRestController {
|
||||
|
||||
private final SiteThemeService siteExportService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
/**
|
||||
* 获取当前站点数据
|
||||
*
|
||||
@ -109,6 +114,14 @@ public class SiteController extends BaseRestController {
|
||||
return R.ok(Map.of("siteId", site.getSiteId(), "siteName", site.getName()));
|
||||
}
|
||||
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@GetMapping("/getDashboardSiteInfo")
|
||||
public R<SiteDashboardVO> getDashboardSiteInfo() {
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
List<CmsPublishPipe> publishPipes = this.publishPipeService.getPublishPipes(site.getSiteId());
|
||||
return R.ok(SiteDashboardVO.create(site, publishPipes));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前站点
|
||||
*
|
||||
@ -161,8 +174,9 @@ public class SiteController extends BaseRestController {
|
||||
site.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(site.getLogo()));
|
||||
}
|
||||
SiteDTO dto = SiteDTO.newInstance(site);
|
||||
resourceService.dealDefaultThumbnail(site, dto.getLogo(), dto::setLogoSrc);
|
||||
// 发布通道数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(),
|
||||
PublishPipePropUseType.Site, site.getPublishPipeProps());
|
||||
dto.setPublishPipeDatas(publishPipeProps);
|
||||
return R.ok(dto);
|
||||
@ -298,7 +312,7 @@ public class SiteController extends BaseRestController {
|
||||
SiteDefaultTemplateDTO dto = new SiteDefaultTemplateDTO();
|
||||
dto.setSiteId(siteId);
|
||||
// 发布通道数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(site.getSiteId(),
|
||||
PublishPipePropUseType.Site, site.getPublishPipeProps());
|
||||
dto.setPublishPipeProps(publishPipeProps);
|
||||
return R.ok(dto);
|
||||
|
||||
@ -40,6 +40,7 @@ import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import com.chestnut.system.permission.PermissionUtils;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
@ -164,8 +165,8 @@ public abstract class AbstractContent<T> implements IContent<T> {
|
||||
content.setCopyType(ContentCopyType.NONE);
|
||||
}
|
||||
content.createBy(this.getOperatorUName());
|
||||
this.getContentService().dao().save(this.getContentEntity());
|
||||
this.add0();
|
||||
this.getContentService().dao().save(this.getContentEntity());
|
||||
// 栏目内容数+1
|
||||
this.getCatalogService().changeContentCount(catalog.getCatalogId(), 1);
|
||||
ContentLogUtils.addLog(ContentOpType.ADD, this.getContentEntity(), this.getOperator());
|
||||
@ -213,8 +214,8 @@ public abstract class AbstractContent<T> implements IContent<T> {
|
||||
content.setStatus(ContentStatus.EDITING);
|
||||
}
|
||||
content.updateBy(this.getOperatorUName());
|
||||
contentService.dao().updateById(this.getContentEntity());
|
||||
this.save0();
|
||||
contentService.dao().updateById(this.getContentEntity());
|
||||
ContentLogUtils.addLog(ContentOpType.UPDATE, this.getContentEntity(), this.getOperator());
|
||||
SpringUtils.publishEvent(new AfterContentSaveEvent(this, this, false));
|
||||
return this.getContentEntity().getContentId();
|
||||
@ -222,6 +223,10 @@ public abstract class AbstractContent<T> implements IContent<T> {
|
||||
|
||||
protected abstract void save0();
|
||||
|
||||
public boolean isDeleteByCatalog() {
|
||||
return MapUtils.getBoolean(this.getParams(), PARAM_IS_DELETE_BY_CATALOG, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
this.checkLock();
|
||||
@ -232,8 +237,10 @@ public abstract class AbstractContent<T> implements IContent<T> {
|
||||
this.getContentService().dao().remove(new LambdaQueryWrapper<CmsContent>()
|
||||
.eq(CmsContent::getCopyType, ContentCopyType.Mapping)
|
||||
.eq(CmsContent::getCopyId, this.getContentEntity().getContentId()));
|
||||
// 栏目内容数-1
|
||||
this.getCatalogService().changeContentCount(getCatalogId(), -1);
|
||||
if (!isDeleteByCatalog()) {
|
||||
// 栏目内容数-1, 删除栏目时,不需要更新栏目的内容数量
|
||||
this.getCatalogService().changeContentCount(getCatalogId(), -1);
|
||||
}
|
||||
ContentLogUtils.addLog(ContentOpType.DELETE, this.getContentEntity(), this.getOperator());
|
||||
|
||||
SpringUtils.publishEvent(new AfterContentDeleteEvent(this, this));
|
||||
|
||||
@ -20,14 +20,11 @@ import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.SpringUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.fixed.dict.PageWidgetStatus;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.contentcore.service.IPublishService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import freemarker.template.TemplateException;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -36,6 +33,7 @@ import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@ -52,6 +50,8 @@ public abstract class AbstractPageWidget implements IPageWidget {
|
||||
|
||||
private IPublishService publishService;
|
||||
|
||||
private IPublishPipeService publishPipeService;
|
||||
|
||||
private LoginUser operator;
|
||||
|
||||
@Override
|
||||
@ -98,9 +98,12 @@ public abstract class AbstractPageWidget implements IPageWidget {
|
||||
this.getPageWidgetService().removeById(this.getPageWidgetEntity());
|
||||
try {
|
||||
// 删除静态文件
|
||||
FileUtils.delete(new File(this.getStaticFilePath()));
|
||||
List<CmsPublishPipe> publishPipes = this.getPublishPipeService().getAllPublishPipes(this.getPageWidgetEntity().getSiteId());
|
||||
for (CmsPublishPipe publishPipe : publishPipes) {
|
||||
FileUtils.delete(new File(this.getStaticFilePath(publishPipe.getCode())));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Delete file failed: {}", this.getStaticFilePath());
|
||||
log.error("Delete page-widget static file failed: {}", this.getPageWidgetEntity().getPageWidgetId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,11 +117,11 @@ public abstract class AbstractPageWidget implements IPageWidget {
|
||||
this.getPublishService().pageWidgetStaticize(this);
|
||||
}
|
||||
|
||||
public String getStaticFilePath() {
|
||||
public String getStaticFilePath(String publishPipeCode) {
|
||||
CmsSite site = this.getSiteService().getSite(this.getPageWidgetEntity().getSiteId());
|
||||
String siteRoot = SiteUtils.getSiteRoot(site, this.getPageWidgetEntity().getPublishPipeCode());
|
||||
String siteRoot = SiteUtils.getSiteRoot(site, publishPipeCode);
|
||||
return siteRoot + this.getPageWidgetEntity().getPath() + this.getPageWidgetEntity().getPageWidgetId()
|
||||
+ StringUtils.DOT + site.getStaticSuffix(this.getPageWidgetEntity().getPublishPipeCode());
|
||||
+ StringUtils.DOT + site.getStaticSuffix(publishPipeCode);
|
||||
}
|
||||
|
||||
public ISiteService getSiteService() {
|
||||
@ -148,4 +151,11 @@ public abstract class AbstractPageWidget implements IPageWidget {
|
||||
}
|
||||
return publishService;
|
||||
}
|
||||
|
||||
public IPublishPipeService getPublishPipeService() {
|
||||
if(this.publishPipeService == null) {
|
||||
this.publishPipeService = SpringUtils.getBean(IPublishPipeService.class);
|
||||
}
|
||||
return publishPipeService;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,8 @@ import java.util.Objects;
|
||||
*/
|
||||
public interface IContent<T> {
|
||||
|
||||
String PARAM_IS_DELETE_BY_CATALOG = "isDeleteByCatalog";
|
||||
|
||||
/**
|
||||
* 获取站点ID
|
||||
*/
|
||||
|
||||
@ -15,12 +15,13 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsResource;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 资源类型
|
||||
@ -71,16 +72,16 @@ public interface IResourceType {
|
||||
|
||||
/**
|
||||
* 处理资源:提取资源属性、添加水印等
|
||||
*
|
||||
* @param resource
|
||||
* @throws IOException
|
||||
*
|
||||
* @param resource 资源记录
|
||||
* @param tempFile 资源文件
|
||||
* @return
|
||||
* @throws IOException ex
|
||||
*/
|
||||
default byte[] process(CmsResource resource, byte[] bytes) throws IOException {
|
||||
resource.setFileSize((long) bytes.length);
|
||||
return bytes;
|
||||
default List<File> process(CmsResource resource, File tempFile) throws IOException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default void asyncProcess(CmsResource resource) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ public class InternalURL {
|
||||
this.params = new HashMap<>();
|
||||
}
|
||||
if (key.equals("id")) {
|
||||
throw new RuntimeException("内部链接自定义参数不可使用固定参数:id");
|
||||
throw new RuntimeException("Conflict with fixed parameter: id.");
|
||||
}
|
||||
this.params.put(key, value);
|
||||
return this.params;
|
||||
@ -107,24 +107,28 @@ public class InternalURL {
|
||||
String content = url.substring(IURLProtocol.length());
|
||||
int i = content.lastIndexOf("?");
|
||||
if (i < 0) {
|
||||
throw new InternalUrlParseException("Invalid iurl: missing parameters.");
|
||||
throw new InternalUrlParseException("missing parameters.");
|
||||
}
|
||||
// 默认iurl的路径部分就是内部数据类型,如果参数中含有type则使用参数type,路径部分作为path
|
||||
String type = content.substring(0, i);
|
||||
iurl.setType(type);
|
||||
try {
|
||||
// 默认iurl的路径部分就是内部数据类型,如果参数中含有type则使用参数type,路径部分作为path
|
||||
String type = content.substring(0, i);
|
||||
iurl.setType(type);
|
||||
|
||||
String params = content.substring(i + 1);
|
||||
Map<String, String> args = StringUtils.splitToMap(params, "&", "=");
|
||||
if (args.containsKey("type")) {
|
||||
iurl.setPath(iurl.getType());
|
||||
iurl.setType(args.get("type"));
|
||||
args.remove("type");
|
||||
String params = content.substring(i + 1);
|
||||
Map<String, String> args = StringUtils.splitToMap(params, "&", "=");
|
||||
if (args.containsKey("type")) {
|
||||
iurl.setPath(iurl.getType());
|
||||
iurl.setType(args.get("type"));
|
||||
args.remove("type");
|
||||
}
|
||||
iurl.setId(Long.valueOf(args.get("id")));
|
||||
args.remove("id");
|
||||
// 自定义参数
|
||||
iurl.setParams(args);
|
||||
return iurl;
|
||||
} catch (Exception e) {
|
||||
throw new InternalUrlParseException(url, e);
|
||||
}
|
||||
iurl.setId(Long.valueOf(args.get("id")));
|
||||
args.remove("id");
|
||||
// 自定义参数
|
||||
iurl.setParams(args);
|
||||
return iurl;
|
||||
}
|
||||
|
||||
public String toIUrl() {
|
||||
|
||||
@ -20,7 +20,9 @@ import com.chestnut.contentcore.config.CMSConfig;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Catalog;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Content;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Site;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.exception.InternalUrlParseException;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import lombok.Getter;
|
||||
@ -101,12 +103,19 @@ public class SiteImportContext implements ISiteThemeContext {
|
||||
}
|
||||
|
||||
public String dealInternalUrl(String iurl) {
|
||||
InternalURL internalURL = InternalUrlUtils.parseInternalUrl(iurl);
|
||||
InternalURL internalURL;
|
||||
try {
|
||||
internalURL = InternalUrlUtils.parseInternalUrl(iurl);
|
||||
} catch (InternalUrlParseException e) {
|
||||
// Ignore parse err
|
||||
return iurl;
|
||||
}
|
||||
if (Objects.nonNull(internalURL)) {
|
||||
Long id = switch (internalURL.getType()) {
|
||||
case InternalDataType_Catalog.ID -> catalogIdMap.get(internalURL.getId());
|
||||
case InternalDataType_Content.ID -> contentIdMap.get(internalURL.getId());
|
||||
case InternalDataType_Resource.ID -> resourceIdMap.get(internalURL.getId());
|
||||
case InternalDataType_Site.ID -> site.getSiteId();
|
||||
default -> null;
|
||||
};
|
||||
if (IdUtils.validate(id)) {
|
||||
|
||||
@ -15,19 +15,17 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.core.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.contentcore.core.IInternalDataType;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.contentcore.service.IPublishService;
|
||||
|
||||
import freemarker.template.TemplateException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 内部数据类型:页面组件
|
||||
@ -55,6 +53,6 @@ public class InternalDataType_PageWidget implements IInternalDataType {
|
||||
CmsPageWidget pageWidget = pageWidgetService.getById(data.getDataId());
|
||||
Assert.notNull(pageWidget, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("pageWidgetId", data.getDataId()));
|
||||
|
||||
return this.publishService.getPageWidgetPageData(pageWidget, data.isPreview());
|
||||
return this.publishService.getPageWidgetPageData(pageWidget, data.getPublishPipeCode(), data.isPreview());
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,18 +18,19 @@ package com.chestnut.contentcore.core.impl;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.storage.IFileStorageType;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.common.utils.file.FileExUtils;
|
||||
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.properties.*;
|
||||
import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty;
|
||||
import com.chestnut.contentcore.properties.ImageWatermarkArgsProperty.ImageWatermarkArgs;
|
||||
import com.chestnut.contentcore.properties.ImageWatermarkProperty;
|
||||
import com.chestnut.contentcore.properties.ThumbnailHeightProperty;
|
||||
import com.chestnut.contentcore.properties.ThumbnailWidthProperty;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.util.FileStorageHelper;
|
||||
import com.chestnut.contentcore.util.ResourceUtils;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -43,11 +44,11 @@ import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -97,50 +98,90 @@ public class ResourceType_Image implements IResourceType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void asyncProcess(CmsResource resource) {
|
||||
asyncTaskManager.execute(() -> {
|
||||
CmsSite site = siteService.getSite(resource.getSiteId());
|
||||
String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
IFileStorageType fst = fileStorageTypeMap.get(IFileStorageType.BEAN_NAME_PREIFX + fileStorageType);
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
|
||||
String tempDirectory = ResourceUtils.getResourceTempDirectory(site);
|
||||
File tempFile = new File(tempDirectory + resource.getPath());
|
||||
FileExUtils.mkdirs(tempFile.getParent());
|
||||
Map<String, File> paths = new HashMap<>();
|
||||
try (InputStream read = fileStorageHelper.read(resource.getPath())) {
|
||||
FileExUtils.transfer(read, tempFile);
|
||||
// 获取图片属性
|
||||
BufferedImage bi = ImageIO.read(tempFile);
|
||||
resource.setWidth(bi.getWidth());
|
||||
resource.setHeight(bi.getHeight());
|
||||
// 先处理图片水印
|
||||
if (watermark(site, bi, tempFile)) {
|
||||
paths.put(resource.getPath(), tempFile);
|
||||
resource.setFileSize(tempFile.length());
|
||||
}
|
||||
// 默认缩略图处理
|
||||
thumbnail(site, resource, tempFile, paths);
|
||||
} catch (IOException e) {
|
||||
log.error("Image resource process fail.", e);
|
||||
} finally {
|
||||
for (Map.Entry<String, File> entry : paths.entrySet()) {
|
||||
try {
|
||||
Path path = entry.getValue().toPath();
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
fileStorageHelper.write(entry.getKey(), is);
|
||||
}
|
||||
// 删除临时图片文件
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
log.error("Delete temp file failed.", e);
|
||||
}
|
||||
}
|
||||
resourceService.updateById(resource);
|
||||
public List<File> process(CmsResource resource, File file) throws IOException {
|
||||
CmsSite site = siteService.getSite(resource.getSiteId());
|
||||
boolean needWatermark = needWatermark(site);
|
||||
boolean needThumbnail = needThumbnail(site);
|
||||
if (!needWatermark && !needThumbnail) {
|
||||
return List.of(); // 不需要处理
|
||||
}
|
||||
List<File> files = new ArrayList<>();
|
||||
try {
|
||||
// 获取图片属性
|
||||
BufferedImage bi = ImageIO.read(file);
|
||||
resource.setWidth(bi.getWidth());
|
||||
resource.setHeight(bi.getHeight());
|
||||
// 先处理图片水印
|
||||
if (needWatermark) {
|
||||
watermark(site, bi, file);
|
||||
resource.setFileSize(file.length());
|
||||
}
|
||||
});
|
||||
// 默认缩略图处理
|
||||
thumbnail(site, resource, file, files);
|
||||
} catch (IOException e) {
|
||||
log.error("Image resource process fail.", e);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private boolean watermark(CmsSite site, BufferedImage bi, File output) {
|
||||
// @Override
|
||||
// public void asyncProcess(CmsResource resource) {
|
||||
// CmsSite site = siteService.getSite(resource.getSiteId());
|
||||
// boolean needWatermark = needWatermark(site);
|
||||
// boolean needThumbnail = needThumbnail(site);
|
||||
// if (!needWatermark && !needThumbnail) {
|
||||
// return; // 不需要处理
|
||||
// }
|
||||
// asyncTaskManager.execute(() -> {
|
||||
// String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
// IFileStorageType fst = fileStorageTypeMap.get(IFileStorageType.BEAN_NAME_PREIFX + fileStorageType);
|
||||
// FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
|
||||
// String tempDirectory = ResourceUtils.getResourceTempDirectory(site);
|
||||
// File tempFile = new File(tempDirectory + resource.getPath());
|
||||
// FileExUtils.mkdirs(tempFile.getParent());
|
||||
// Map<String, File> paths = new HashMap<>();
|
||||
// try (InputStream read = fileStorageHelper.read(resource.getPath())) {
|
||||
// FileExUtils.transfer(read, tempFile);
|
||||
// // 获取图片属性
|
||||
// BufferedImage bi = ImageIO.read(tempFile);
|
||||
// resource.setWidth(bi.getWidth());
|
||||
// resource.setHeight(bi.getHeight());
|
||||
// // 先处理图片水印
|
||||
// if (needWatermark) {
|
||||
// watermark(site, bi, tempFile);
|
||||
// paths.put(resource.getPath(), tempFile);
|
||||
// resource.setFileSize(tempFile.length());
|
||||
// }
|
||||
// // 默认缩略图处理
|
||||
// thumbnail(site, resource, tempFile);
|
||||
// // 更新文件
|
||||
// for (Map.Entry<String, File> entry : paths.entrySet()) {
|
||||
// try (FileInputStream is = new FileInputStream(entry.getValue())) {
|
||||
// fileStorageHelper.write(entry.getKey(), is, entry.getValue().length());
|
||||
// } catch (IOException e) {
|
||||
// log.error("Write temp file to storage {} failed.", fst.getType(), e);
|
||||
// }
|
||||
// }
|
||||
// if (needWatermark) {
|
||||
// resourceService.updateById(resource);
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// log.error("Image resource process fail.", e);
|
||||
// } finally {
|
||||
// try {
|
||||
// // 删除临时文件
|
||||
// Files.deleteIfExists(tempFile.toPath());
|
||||
// for (File file : paths.values()) {
|
||||
// Files.deleteIfExists(file.toPath());
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// log.error("Delete temp file failed.", e);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
private boolean needWatermark(CmsSite site) {
|
||||
if (!ImageWatermarkProperty.getValue(site.getConfigProps())) {
|
||||
return false;
|
||||
}
|
||||
@ -150,9 +191,13 @@ public class ResourceType_Image implements IResourceType {
|
||||
}
|
||||
String siteResourceRoot = SiteUtils.getSiteResourceRoot(site);
|
||||
File file = new File(siteResourceRoot + args.getImage());
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
private void watermark(CmsSite site, BufferedImage bi, File output) {
|
||||
ImageWatermarkArgs args = ImageWatermarkArgsProperty.getValue(site.getConfigProps());
|
||||
String siteResourceRoot = SiteUtils.getSiteResourceRoot(site);
|
||||
File file = new File(siteResourceRoot + args.getImage());
|
||||
try {
|
||||
BufferedImage biWatermarkImage = ImageIO.read(file);
|
||||
String ext = FilenameUtils.getExtension(output.getName());
|
||||
@ -163,14 +208,18 @@ public class ResourceType_Image implements IResourceType {
|
||||
WatermarkPosition.str2Position(args.getPosition())
|
||||
).toFile(output);
|
||||
log.debug("Watermark success: {}", output.getAbsolutePath());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
log.error("Watermark image fail.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void thumbnail(CmsSite site, CmsResource resource, File tempFile, Map<String, File> paths) {
|
||||
private boolean needThumbnail(CmsSite site) {
|
||||
int w = ThumbnailWidthProperty.getValue(site.getConfigProps());
|
||||
int h = ThumbnailHeightProperty.getValue(site.getConfigProps());
|
||||
return w > 0 && h > 0;
|
||||
}
|
||||
|
||||
private void thumbnail(CmsSite site, CmsResource resource, File tempFile, List<File> files) {
|
||||
// 读取存储配置
|
||||
int w = ThumbnailWidthProperty.getValue(site.getConfigProps());
|
||||
int h = ThumbnailHeightProperty.getValue(site.getConfigProps());
|
||||
@ -182,13 +231,13 @@ public class ResourceType_Image implements IResourceType {
|
||||
File output = new File(tempDirectory + thumbnailPath);
|
||||
try {
|
||||
ImageHelper.of(tempFile).format(ext).resize(w, h).toFile(output);
|
||||
paths.put(thumbnailPath, output);
|
||||
files.add(output);
|
||||
log.debug("Thumbnail success: {}", output.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// 生成缩略图失败直接使用源图作为缩略图
|
||||
Files.copy(tempFile.toPath(), Path.of(thumbnailPath), StandardCopyOption.REPLACE_EXISTING);
|
||||
paths.put(thumbnailPath, output);
|
||||
files.add(output);
|
||||
} catch (IOException ex) {
|
||||
log.error("Copy thumbnail file err.", ex);
|
||||
}
|
||||
|
||||
@ -19,10 +19,18 @@ 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.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 页面部件表对象 [cms_page_widget]
|
||||
@ -84,12 +92,20 @@ public class CmsPageWidget extends BaseEntity {
|
||||
/**
|
||||
* 发布通道
|
||||
*/
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String publishPipeCode;
|
||||
|
||||
|
||||
/**
|
||||
* 关联模板
|
||||
* 模板路径
|
||||
*/
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String template;
|
||||
|
||||
/**
|
||||
* 发布通道模板配置
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Map<String, String> templates;
|
||||
|
||||
/**
|
||||
* 静态化目录
|
||||
@ -103,4 +119,13 @@ public class CmsPageWidget extends BaseEntity {
|
||||
|
||||
@TableField(exist = false)
|
||||
private Object contentObj;
|
||||
|
||||
public String getTemplate(String publishPipeCode) {
|
||||
String templatePath = MapUtils.getString(templates, publishPipeCode);
|
||||
// 兼容历史版本数据
|
||||
if (StringUtils.isEmpty(templatePath) && StringUtils.equals(publishPipeCode, this.publishPipeCode)) {
|
||||
templatePath = this.template;
|
||||
}
|
||||
return templatePath;
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,9 @@ public class CmsResource extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 存储类型,默认:local
|
||||
* @deprecated 1.5.6版本开始不再根据此字段生成资源路径,资源路径统一根据当前站点资源存储配置生成。
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.5.6")
|
||||
private String storageType;
|
||||
|
||||
/**
|
||||
|
||||
@ -40,8 +40,9 @@ public interface InitByContent {
|
||||
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()));
|
||||
List<String> srcList = content.getImages().stream().map(InternalUrlUtils::getActualPreviewUrl).toList();
|
||||
this.setImagesSrc(srcList);
|
||||
this.setLogoSrc(srcList.get(0));
|
||||
}
|
||||
} else {
|
||||
this.setImages(List.of());
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
package com.chestnut.contentcore.domain.dto;
|
||||
|
||||
import com.chestnut.common.security.domain.BaseDTO;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import com.chestnut.system.validator.Dict;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
@ -114,7 +115,7 @@ public class CatalogUpdateDTO extends BaseDTO {
|
||||
/*
|
||||
* 栏目发布通道数据
|
||||
*/
|
||||
private List<PublishPipeProp> publishPipeDatas;
|
||||
private List<PublishPipeProps> publishPipeDatas;
|
||||
|
||||
/*
|
||||
* 自定义参数
|
||||
|
||||
@ -20,6 +20,7 @@ import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.InitByContent;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentOpType;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
@ -220,7 +221,7 @@ public class ContentDTO implements InitByContent {
|
||||
/**
|
||||
* 发布通道配置
|
||||
*/
|
||||
private List<PublishPipeProp> publishPipeProps;
|
||||
private List<PublishPipeProps> publishPipeProps;
|
||||
|
||||
/**
|
||||
* 栏目扩展配置
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
package com.chestnut.contentcore.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@ -32,6 +33,7 @@ public class FileOperateDTO {
|
||||
/*
|
||||
* 重名名名称
|
||||
*/
|
||||
@Pattern(regexp = "^[^/\\\\]*$", message = "{VALIDATOR.CMS.FILE_NAME.REGEXP_ERR}")
|
||||
private String rename;
|
||||
|
||||
/*
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.domain.dto;
|
||||
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
@ -23,6 +25,10 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class PageWidgetAddDTO {
|
||||
@ -55,14 +61,20 @@ public class PageWidgetAddDTO {
|
||||
/**
|
||||
* 发布通道编码
|
||||
*/
|
||||
@NotEmpty
|
||||
private String publishPipeCode;
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String publishPipeCode;
|
||||
|
||||
/**
|
||||
* 模板
|
||||
* 模板路径
|
||||
*/
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String template;
|
||||
|
||||
/**
|
||||
* 发布通道模板配置
|
||||
*/
|
||||
private List<PublishPipeTemplate> templates;
|
||||
|
||||
/**
|
||||
* 静态化目录
|
||||
*/
|
||||
@ -72,4 +84,11 @@ public class PageWidgetAddDTO {
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
public Map<String, String> getPublishPipeTemplateMap() {
|
||||
if (StringUtils.isEmpty(this.templates)) {
|
||||
return Map.of();
|
||||
}
|
||||
return this.templates.stream().collect(Collectors.toMap(PublishPipeTemplate::code, PublishPipeTemplate::template));
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,13 +15,18 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.domain.dto;
|
||||
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class PageWidgetEditDTO {
|
||||
@ -47,13 +52,19 @@ public class PageWidgetEditDTO {
|
||||
/**
|
||||
* 发布通道编码
|
||||
*/
|
||||
@NotEmpty
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String publishPipeCode;
|
||||
|
||||
/**
|
||||
* 模板路径
|
||||
*/
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String template;
|
||||
|
||||
/**
|
||||
* 发布通道模板配置
|
||||
*/
|
||||
private List<PublishPipeTemplate> templates;
|
||||
|
||||
/**
|
||||
* 静态化目录
|
||||
@ -70,4 +81,11 @@ public class PageWidgetEditDTO {
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
public Map<String, String> getPublishPipeTemplateMap() {
|
||||
if (StringUtils.isEmpty(this.templates)) {
|
||||
return Map.of();
|
||||
}
|
||||
return this.templates.stream().collect(Collectors.toMap(PublishPipeTemplate::code, PublishPipeTemplate::template));
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package com.chestnut.contentcore.domain.dto;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@ -61,7 +62,7 @@ public class SiteDTO extends BaseDTO {
|
||||
|
||||
private Map<String, String> configProps;
|
||||
|
||||
private List<PublishPipeProp> publishPipeDatas;
|
||||
private List<PublishPipeProps> publishPipeDatas;
|
||||
|
||||
private Map<String, Object> params;
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ package com.chestnut.contentcore.domain.dto;
|
||||
import java.util.List;
|
||||
|
||||
import com.chestnut.common.security.domain.BaseDTO;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
@ -43,5 +44,5 @@ public class SiteDefaultTemplateDTO extends BaseDTO {
|
||||
* 默认模板属性
|
||||
*/
|
||||
@NotEmpty(message = "{VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY}")
|
||||
public List<PublishPipeProp> publishPipeProps;
|
||||
public List<PublishPipeProps> publishPipeProps;
|
||||
}
|
||||
|
||||
@ -13,13 +13,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chestnut.contentcore.domain.dto;
|
||||
package com.chestnut.contentcore.domain.pojo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PublishPipeProp {
|
||||
/**
|
||||
* PublishPipeProps
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
public class PublishPipeProps {
|
||||
|
||||
private String pipeCode;
|
||||
|
||||
@ -27,35 +38,11 @@ public class PublishPipeProp {
|
||||
|
||||
private Map<String, Object> props;
|
||||
|
||||
public static PublishPipeProp newInstance(String pipeCode, String pipeName, Map<String, Object> props) {
|
||||
PublishPipeProp ppp = new PublishPipeProp();
|
||||
public static PublishPipeProps newInstance(String pipeCode, String pipeName, Map<String, Object> props) {
|
||||
PublishPipeProps ppp = new PublishPipeProps();
|
||||
ppp.setPipeCode(pipeCode);
|
||||
ppp.setPipeName(pipeName);
|
||||
ppp.setProps(Objects.requireNonNullElse(props, new HashMap<>()));
|
||||
return ppp;
|
||||
}
|
||||
|
||||
public String getPipeCode() {
|
||||
return pipeCode;
|
||||
}
|
||||
|
||||
public void setPipeCode(String pipeCode) {
|
||||
this.pipeCode = pipeCode;
|
||||
}
|
||||
|
||||
public String getPipeName() {
|
||||
return pipeName;
|
||||
}
|
||||
|
||||
public void setPipeName(String pipeName) {
|
||||
this.pipeName = pipeName;
|
||||
}
|
||||
|
||||
public Map<String, Object> getProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
public void setProps(Map<String, Object> prop) {
|
||||
this.props = prop;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(190785909@qq.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chestnut.contentcore.domain.pojo;
|
||||
|
||||
/**
|
||||
* PublishPipeTemplate
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public record PublishPipeTemplate(String name, String code, String template) {
|
||||
}
|
||||
@ -21,7 +21,7 @@ import java.util.Map;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@ -186,7 +186,7 @@ public class CatalogVO {
|
||||
/*
|
||||
* 栏目发布通道数据
|
||||
*/
|
||||
private List<PublishPipeProp> publishPipeDatas;
|
||||
private List<PublishPipeProps> publishPipeDatas;
|
||||
|
||||
public static CatalogVO newInstance(CmsCatalog catalog) {
|
||||
CatalogVO dto = new CatalogVO();
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
package com.chestnut.contentcore.domain.vo;
|
||||
|
||||
import com.chestnut.contentcore.domain.InitByContent;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@ -217,7 +217,7 @@ public class ContentVO implements InitByContent {
|
||||
/**
|
||||
* 发布通道配置
|
||||
*/
|
||||
private List<PublishPipeProp> publishPipeProps;
|
||||
private List<PublishPipeProps> publishPipeProps;
|
||||
|
||||
/**
|
||||
* SEO标题
|
||||
|
||||
@ -15,12 +15,13 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.domain.vo;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ -64,13 +65,20 @@ public class PageWidgetVO {
|
||||
/*
|
||||
* 发布通道编码
|
||||
*/
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String publishPipeCode;
|
||||
|
||||
/*
|
||||
* 模板路径
|
||||
*/
|
||||
@Deprecated(since = "1.5.6", forRemoval = true)
|
||||
private String template;
|
||||
|
||||
/*
|
||||
* 发布通道模板配置
|
||||
*/
|
||||
private List<PublishPipeTemplate> templates;
|
||||
|
||||
/*
|
||||
* 静态化目录
|
||||
*/
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(190785909@qq.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chestnut.contentcore.domain.vo;
|
||||
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class SiteDashboardVO {
|
||||
|
||||
private Long siteId;
|
||||
|
||||
private String name;
|
||||
|
||||
private List<PublishPipeProps> urls;
|
||||
|
||||
public static SiteDashboardVO create(CmsSite site, List<CmsPublishPipe> publishPipes) {
|
||||
SiteDashboardVO vo = new SiteDashboardVO();
|
||||
vo.setSiteId(site.getSiteId());
|
||||
vo.setName(site.getName());
|
||||
List<PublishPipeProps> urls = publishPipes.stream().map(pp -> PublishPipeProps.newInstance(
|
||||
pp.getCode(), pp.getName(), Map.of("url", site.getUrl(pp.getCode()))
|
||||
)).toList();
|
||||
vo.setUrls(urls);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -17,25 +17,34 @@ package com.chestnut.contentcore.job;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chestnut.contentcore.core.IContent;
|
||||
import com.chestnut.contentcore.core.IContentType;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentStatus;
|
||||
import com.chestnut.contentcore.publish.staticize.CatalogStaticizeType;
|
||||
import com.chestnut.contentcore.publish.staticize.ContentStaticizeType;
|
||||
import com.chestnut.contentcore.publish.staticize.SiteStaticizeType;
|
||||
import com.chestnut.contentcore.listener.event.AfterContentPublishEvent;
|
||||
import com.chestnut.contentcore.publish.IPublishStrategy;
|
||||
import com.chestnut.contentcore.publish.staticize.CatalogStaticizeType;
|
||||
import com.chestnut.contentcore.publish.staticize.SiteStaticizeType;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IContentService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.util.ContentCoreUtils;
|
||||
import com.chestnut.system.schedule.IScheduledHandler;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 定时发布任务<br/>
|
||||
@ -47,7 +56,7 @@ import java.util.List;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component(IScheduledHandler.BEAN_PREFIX + SitePublishJobHandler.JOB_NAME)
|
||||
public class SitePublishJobHandler extends IJobHandler implements IScheduledHandler {
|
||||
public class SitePublishJobHandler extends IJobHandler implements IScheduledHandler, ApplicationContextAware {
|
||||
|
||||
static final String JOB_NAME = "SitePublishJobHandler";
|
||||
|
||||
@ -59,6 +68,8 @@ public class SitePublishJobHandler extends IJobHandler implements IScheduledHand
|
||||
|
||||
private final IPublishStrategy publishStrategy;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return JOB_NAME;
|
||||
@ -74,6 +85,7 @@ public class SitePublishJobHandler extends IJobHandler implements IScheduledHand
|
||||
logger.info("Job start: {}", JOB_NAME);
|
||||
long s = System.currentTimeMillis();
|
||||
List<CmsSite> sites = this.siteService.list();
|
||||
Set<Long> catalogIds = new HashSet<>();
|
||||
for (CmsSite site : sites) {
|
||||
List<CmsCatalog> catalogList = catalogService
|
||||
.list(new LambdaQueryWrapper<CmsCatalog>().eq(CmsCatalog::getSiteId, site.getSiteId()));
|
||||
@ -89,15 +101,32 @@ public class SitePublishJobHandler extends IJobHandler implements IScheduledHand
|
||||
for (int i = 0; i * pageSize < total; i++) {
|
||||
Page<CmsContent> page = contentService.dao().page(new Page<>(i, pageSize, false), q);
|
||||
for (CmsContent xContent : page.getRecords()) {
|
||||
publishStrategy.publish(ContentStaticizeType.TYPE, xContent.getContentId().toString());
|
||||
IContentType contentType = ContentCoreUtils.getContentType(xContent.getContentType());
|
||||
IContent<?> content = contentType.loadContent(xContent);
|
||||
if (content.publish()) {
|
||||
applicationContext.publishEvent(new AfterContentPublishEvent(contentType, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (total > 0) {
|
||||
catalogIds.add(catalog.getCatalogId());
|
||||
// 发布关联栏目:内容所属栏目及其所有父级栏目
|
||||
long parentId = catalog.getParentId();
|
||||
while (parentId > 0) {
|
||||
CmsCatalog parent = catalogService.getCatalog(parentId);
|
||||
if (parent == null) {
|
||||
break;
|
||||
}
|
||||
catalogIds.add(parent.getCatalogId());
|
||||
parentId = parent.getParentId();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 发布栏目
|
||||
for (CmsCatalog catalog : catalogList) {
|
||||
publishStrategy.publish(CatalogStaticizeType.TYPE, catalog.getCatalogId().toString());
|
||||
}
|
||||
// 发布站点
|
||||
// 发布相关栏目
|
||||
catalogIds.forEach(catalogId -> {
|
||||
publishStrategy.publish(CatalogStaticizeType.TYPE, catalogId.toString());
|
||||
});
|
||||
// 发布站点首页
|
||||
publishStrategy.publish(SiteStaticizeType.TYPE, site.getSiteId().toString());
|
||||
}
|
||||
logger.info("Job '{}' completed, cost: {}ms", JOB_NAME, System.currentTimeMillis() - s);
|
||||
@ -108,4 +137,9 @@ public class SitePublishJobHandler extends IJobHandler implements IScheduledHand
|
||||
public void execute() throws Exception {
|
||||
this.exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ public class ContentCoreListener {
|
||||
public void beforeCatalogDelete(BeforeCatalogDeleteEvent event) {
|
||||
CmsCatalog catalog = event.getCatalog();
|
||||
// 删除栏目内容
|
||||
this.contentService.deleteContentsByCatalog(catalog, true, event.getOperator());
|
||||
this.contentService.deleteContentsByCatalog(catalog, false, event.getOperator());
|
||||
// 删除页面部件
|
||||
this.pageWidgetService.deletePageWidgetsByCatalog(catalog);
|
||||
}
|
||||
|
||||
@ -15,17 +15,17 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.properties;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.common.serializer.DesensitizationJsonSerializer;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IProperty;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 资源存储策略参数
|
||||
@ -77,12 +77,14 @@ public class FileStorageArgsProperty implements IProperty {
|
||||
@Getter
|
||||
@Setter
|
||||
public static class FileStorageArgs {
|
||||
|
||||
|
||||
private String accessKey;
|
||||
|
||||
|
||||
private String accessSecret;
|
||||
|
||||
private String endpoint;
|
||||
|
||||
private String region;
|
||||
|
||||
private String bucket;
|
||||
|
||||
|
||||
@ -54,12 +54,12 @@ public class ThumbnailHeightProperty implements IProperty {
|
||||
|
||||
@Override
|
||||
public boolean validate(String value) {
|
||||
return StringUtils.isEmpty(value) || NumberUtils.isCreatable(value);
|
||||
return NumberUtils.isCreatable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer defaultValue() {
|
||||
return 128;
|
||||
return 218;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -54,7 +54,7 @@ public class ThumbnailWidthProperty implements IProperty {
|
||||
|
||||
@Override
|
||||
public boolean validate(String value) {
|
||||
return StringUtils.isEmpty(value) || NumberUtils.isCreatable(value);
|
||||
return NumberUtils.isCreatable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
package com.chestnut.contentcore.publish.staticize;
|
||||
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.StaticizeService;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
@ -30,9 +31,12 @@ import com.chestnut.contentcore.template.impl.SiteTemplateType;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import com.chestnut.contentcore.util.TemplateUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@ -100,7 +104,11 @@ public class SiteStaticizeType implements IStaticizeType {
|
||||
long s = System.currentTimeMillis();
|
||||
context.setDirectory(SiteUtils.getSiteRoot(site, publishPipeCode));
|
||||
context.setFirstFileName("index" + StringUtils.DOT + site.getStaticSuffix(publishPipeCode));
|
||||
this.staticizeService.process(context);
|
||||
StringWriter writer = new StringWriter();
|
||||
this.staticizeService.process(context, writer);
|
||||
String text = FreeMarkerUtils.createBy(writer.toString());
|
||||
String filePath = context.getStaticizeFilePath(context.getPageIndex());
|
||||
FileUtils.writeStringToFile(new File(filePath), text, StandardCharsets.UTF_8);
|
||||
logger.debug("[{}]首页模板解析:{},耗时:{}ms", publishPipeCode, site.getName(), (System.currentTimeMillis() - s));
|
||||
} catch (Exception e) {
|
||||
logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}][{1}]站点首页解析失败:{2}",
|
||||
|
||||
@ -18,8 +18,10 @@ package com.chestnut.contentcore.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.common.async.AsyncTask;
|
||||
import com.chestnut.common.domain.TreeNode;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.security.domain.LoginUser;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.dto.*;
|
||||
|
||||
@ -96,7 +98,13 @@ public interface ICatalogService extends IService<CmsCatalog> {
|
||||
* @param catalogId
|
||||
* @return
|
||||
*/
|
||||
CmsCatalog deleteCatalog(long catalogId, LoginUser operator);
|
||||
default void deleteCatalog(long catalogId, LoginUser operator) {
|
||||
CmsCatalog catalog = getById(catalogId);
|
||||
Assert.notNull(catalog, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("catalogId", catalog));
|
||||
deleteCatalog(catalog, operator);
|
||||
}
|
||||
|
||||
void deleteCatalog(CmsCatalog catalog, LoginUser operator);
|
||||
|
||||
/**
|
||||
* 获取栏目链接
|
||||
|
||||
@ -19,7 +19,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp.PublishPipePropUseType;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@ -79,8 +79,8 @@ public interface IPublishPipeService extends IService<CmsPublishPipe> {
|
||||
* @param props 数据集合
|
||||
* @return 结果列表
|
||||
*/
|
||||
List<PublishPipeProp> getPublishPipeProps(Long siteId, PublishPipePropUseType useType,
|
||||
Map<String, Map<String, Object>> props);
|
||||
List<PublishPipeProps> getPublishPipeProps(Long siteId, PublishPipePropUseType useType,
|
||||
Map<String, Map<String, Object>> props);
|
||||
|
||||
/**
|
||||
* 获取发布通道属性值
|
||||
|
||||
@ -32,14 +32,12 @@ import java.util.List;
|
||||
public interface IPublishService {
|
||||
|
||||
/**
|
||||
* 发布站点首页<br/>
|
||||
* <p>
|
||||
* 发布站点首页<br>
|
||||
* 此方法供API发布站点首页调用,做基础校验
|
||||
*
|
||||
* @param site
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param site 站点
|
||||
* @throws IOException e1
|
||||
* @throws TemplateException e2
|
||||
*/
|
||||
void publishSiteIndex(CmsSite site) throws IOException, TemplateException;
|
||||
|
||||
@ -48,20 +46,20 @@ public interface IPublishService {
|
||||
* <p>
|
||||
* 发布站点下所有栏目及指定状态内容
|
||||
*
|
||||
* @param site
|
||||
* @param contentStatus
|
||||
* @return
|
||||
* @param site 站点
|
||||
* @param contentStatus 内容状态
|
||||
* @return 结果
|
||||
*/
|
||||
AsyncTask publishAll(CmsSite site, final String contentStatus, final LoginUser operator);
|
||||
|
||||
/**
|
||||
* 站点首页页面内容
|
||||
*
|
||||
* @param site
|
||||
* @param requestData
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param site 站点
|
||||
* @param requestData 请求数据
|
||||
* @return 结果
|
||||
* @throws IOException e1
|
||||
* @throws TemplateException e2
|
||||
*/
|
||||
String getSitePageData(CmsSite site, IInternalDataType.RequestData requestData)
|
||||
throws IOException, TemplateException;
|
||||
@ -69,12 +67,12 @@ public interface IPublishService {
|
||||
/**
|
||||
* 获取栏目模板页面内容
|
||||
*
|
||||
* @param catalog
|
||||
* @param requestData
|
||||
* @param listFlag
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param catalog 栏目
|
||||
* @param requestData 请求数据
|
||||
* @param listFlag 是否分页
|
||||
* @return 结果
|
||||
* @throws IOException e1
|
||||
* @throws TemplateException e2
|
||||
*/
|
||||
String getCatalogPageData(CmsCatalog catalog, IInternalDataType.RequestData requestData, boolean listFlag)
|
||||
throws IOException, TemplateException;
|
||||
@ -82,11 +80,11 @@ public interface IPublishService {
|
||||
/**
|
||||
* 发布栏目,异步任务
|
||||
*
|
||||
* @param catalog
|
||||
* @param catalog 栏目
|
||||
* @param publishChild 是否发布子栏目
|
||||
* @param publishDetail 是否发布详情页
|
||||
* @param publishStatus 指定发布内容状态
|
||||
* @return
|
||||
* @return 结果
|
||||
*/
|
||||
AsyncTask publishCatalog(CmsCatalog catalog, boolean publishChild, boolean publishDetail,
|
||||
String publishStatus, final LoginUser operator);
|
||||
@ -94,11 +92,11 @@ public interface IPublishService {
|
||||
/**
|
||||
* 获取内容模板页面结果
|
||||
*
|
||||
* @param content
|
||||
* @param requestData
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param content 内容
|
||||
* @param requestData 请求数据
|
||||
* @return 结果
|
||||
* @throws IOException e1
|
||||
* @throws TemplateException e2
|
||||
*/
|
||||
String getContentPageData(CmsContent content, IInternalDataType.RequestData requestData)
|
||||
throws IOException, TemplateException;
|
||||
@ -106,29 +104,27 @@ public interface IPublishService {
|
||||
/**
|
||||
* 发布内容,创建异步任务发布
|
||||
*
|
||||
* @param content
|
||||
* @param content 内容
|
||||
*/
|
||||
void asyncPublishContent(IContent<?> content);
|
||||
|
||||
/**
|
||||
* 发布内容
|
||||
*
|
||||
* @param contentIds
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param contentIds 内容ID列表
|
||||
* @param operator 操作人
|
||||
*/
|
||||
void publishContent(List<Long> contentIds, LoginUser operator) ;
|
||||
|
||||
/**
|
||||
* 获取内容扩展模板解析内容
|
||||
*
|
||||
* @param content
|
||||
* @param publishPipeCode
|
||||
* @param isPreview
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param content 内容
|
||||
* @param publishPipeCode 发布通道编码
|
||||
* @param isPreview 是否预览
|
||||
* @return 结果
|
||||
* @throws IOException e1
|
||||
* @throws TemplateException e2
|
||||
*/
|
||||
String getContentExPageData(CmsContent content, String publishPipeCode, boolean isPreview)
|
||||
throws IOException, TemplateException;
|
||||
@ -136,18 +132,19 @@ public interface IPublishService {
|
||||
/**
|
||||
* 获取页面部件模板解析内容
|
||||
*
|
||||
* @param pageWidget
|
||||
* @param isPreview
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws TemplateException
|
||||
* @param pageWidget 页面部件
|
||||
* @param publishPipeCode 发布通道编码
|
||||
* @param isPreview 是否预览
|
||||
* @return 结果
|
||||
* @throws IOException e1
|
||||
* @throws TemplateException e2
|
||||
*/
|
||||
String getPageWidgetPageData(CmsPageWidget pageWidget, boolean isPreview) throws IOException, TemplateException;
|
||||
String getPageWidgetPageData(CmsPageWidget pageWidget, String publishPipeCode, boolean isPreview) throws IOException, TemplateException;
|
||||
|
||||
/**
|
||||
* 页面部件静态化
|
||||
*
|
||||
* @param pageWidget
|
||||
* @param pageWidget 页面部件
|
||||
*/
|
||||
void pageWidgetStaticize(IPageWidget pageWidget);
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
package com.chestnut.contentcore.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.common.storage.IFileStorageType;
|
||||
import com.chestnut.contentcore.core.IResourceType;
|
||||
import com.chestnut.contentcore.core.InternalURL;
|
||||
import com.chestnut.contentcore.domain.CmsResource;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.ResourceUploadDTO;
|
||||
@ -26,14 +26,10 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface IResourceService extends IService<CmsResource> {
|
||||
|
||||
/**
|
||||
* 获取存储方式
|
||||
*/
|
||||
IFileStorageType getFileStorageType(String type);
|
||||
|
||||
/**
|
||||
* 上传资源
|
||||
*/
|
||||
@ -102,4 +98,31 @@ public interface IResourceService extends IService<CmsResource> {
|
||||
* @param operator 操作人
|
||||
*/
|
||||
String downloadRemoteImages(String html, CmsSite site, String operator);
|
||||
|
||||
/**
|
||||
* 检查图片资源缩略图,如果不存在则生成
|
||||
*
|
||||
* @param internalURL 内部链接
|
||||
* @param width 缩略图宽度
|
||||
* @param height 缩略图高度
|
||||
*/
|
||||
void createThumbnailIfNotExists(InternalURL internalURL, int width, int height) throws IOException;
|
||||
|
||||
/**
|
||||
* 处理默认缩略图
|
||||
*
|
||||
* @param site 站点
|
||||
* @param images 源图列表
|
||||
* @param thumbnailsConsumer 缩略图消费者
|
||||
*/
|
||||
void dealDefaultThumbnail(CmsSite site, List<String> images, Consumer<List<String>> thumbnailsConsumer);
|
||||
|
||||
/**
|
||||
* 处理内容引导图缩略图
|
||||
*
|
||||
* @param site 站点
|
||||
* @param imageSrc 源图列表
|
||||
* @param thumbnailConsumer 缩略图消费者
|
||||
*/
|
||||
void dealDefaultThumbnail(CmsSite site, String imageSrc, Consumer<String> thumbnailConsumer);
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.*;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
|
||||
import com.chestnut.contentcore.listener.event.*;
|
||||
import com.chestnut.contentcore.mapper.CmsCatalogMapper;
|
||||
@ -279,7 +280,7 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
|
||||
BeanUtils.copyProperties(dto, catalog);
|
||||
// 发布通道数据处理
|
||||
Map<String, Map<String, Object>> publishPipeProps = dto.getPublishPipeDatas().stream()
|
||||
.collect(Collectors.toMap(PublishPipeProp::getPipeCode, PublishPipeProp::getProps));
|
||||
.collect(Collectors.toMap(PublishPipeProps::getPipeCode, PublishPipeProps::getProps));
|
||||
catalog.setPublishPipeProps(publishPipeProps);
|
||||
catalog.updateBy(dto.getOperator().getUsername());
|
||||
this.updateById(catalog);
|
||||
@ -303,8 +304,7 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public CmsCatalog deleteCatalog(long catalogId, LoginUser operator) {
|
||||
CmsCatalog catalog = this.getById(catalogId);
|
||||
public void deleteCatalog(CmsCatalog catalog, LoginUser operator) {
|
||||
long childCount = lambdaQuery().eq(CmsCatalog::getParentId, catalog.getCatalogId()).count();
|
||||
Assert.isTrue(childCount == 0, ContentCoreErrorCode.DEL_CHILD_FIRST::exception);
|
||||
// 删除前事件发布
|
||||
@ -316,11 +316,10 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
|
||||
parentCatalog.setChildCount(parentCatalog.getChildCount() - 1);
|
||||
updateById(parentCatalog);
|
||||
}
|
||||
removeById(catalogId);
|
||||
removeById(catalog);
|
||||
clearCache(catalog);
|
||||
|
||||
applicationContext.publishEvent(new AfterCatalogDeleteEvent(this, catalog));
|
||||
return catalog;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -387,8 +386,8 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
|
||||
List<CmsCatalog> toCatalogs = this.list(q);
|
||||
if (!toCatalogs.isEmpty()) {
|
||||
for (CmsCatalog toCatalog : toCatalogs) {
|
||||
List<PublishPipeProp> publishPipeProps = dto.getPublishPipeProps();
|
||||
for (PublishPipeProp publishPipeProp : publishPipeProps) {
|
||||
List<PublishPipeProps> publishPipeProps = dto.getPublishPipeProps();
|
||||
for (PublishPipeProps publishPipeProp : publishPipeProps) {
|
||||
Map<String, Object> sitePublishPipeProp = site.getPublishPipeProps()
|
||||
.get(publishPipeProp.getPipeCode());
|
||||
Map<String, Object> catalogPublishPipeProp = toCatalog
|
||||
@ -604,6 +603,9 @@ public class CatalogServiceImpl extends ServiceImpl<CmsCatalogMapper, CmsCatalog
|
||||
lock.lock();
|
||||
try {
|
||||
CmsCatalog catalog = getById(catalogId);
|
||||
if (Objects.isNull(catalog)) {
|
||||
return; // 删除栏目时会先删除栏目在删除内容,导致删除内容时栏目已经不存在了
|
||||
}
|
||||
lambdaUpdate().set(CmsCatalog::getContentCount, catalog.getContentCount() + delta)
|
||||
.eq(CmsCatalog::getCatalogId, catalog.getCatalogId()).update();
|
||||
} finally {
|
||||
|
||||
@ -156,6 +156,7 @@ public class ContentServiceImpl implements IContentService {
|
||||
IContentType contentType = ContentCoreUtils.getContentType(content.getContentType());
|
||||
IContent<?> icontent = contentType.loadContent(content);
|
||||
icontent.setOperator(operator);
|
||||
icontent.getParams().put(IContent.PARAM_IS_DELETE_BY_CATALOG, true);
|
||||
icontent.delete();
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,12 +33,15 @@ import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -149,27 +152,28 @@ public class FileServiceImpl implements IFileService {
|
||||
|
||||
@Override
|
||||
public void renameFile(CmsSite site, String filePath, String rename) throws IOException {
|
||||
this.checkFileType(rename);
|
||||
|
||||
String ext = FilenameUtils.getExtension(rename);
|
||||
if (StringUtils.isNotEmpty(ext)) {
|
||||
this.checkFileType(rename);
|
||||
}
|
||||
String path = FileExUtils.normalizePath(filePath);
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
this.checkSiteDirectory(site, filePath);
|
||||
|
||||
this.checkSiteDirectory(site, path);
|
||||
|
||||
String root = CMSConfig.getResourceRoot();
|
||||
File file = new File(root + path);
|
||||
Assert.isTrue(file.exists(), ContentCoreErrorCode.FILE_NOT_EXIST::exception);
|
||||
|
||||
String dest = root + StringUtils.substringBeforeLast(path, "/") + "/" + rename;
|
||||
File destFile = new File(dest);
|
||||
Assert.isFalse(destFile.exists(), ContentCoreErrorCode.FILE_ALREADY_EXISTS::exception);
|
||||
|
||||
Path dest = Path.of(file.getParent(), rename);
|
||||
Assert.isFalse(Files.exists(dest), ContentCoreErrorCode.FILE_ALREADY_EXISTS::exception);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
FileUtils.moveDirectory(file, destFile);
|
||||
FileUtils.moveDirectory(file, dest.toFile());
|
||||
} else {
|
||||
this.checkFileType(rename);
|
||||
FileUtils.moveFile(file, destFile);
|
||||
FileUtils.moveFile(file, dest.toFile());
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +209,8 @@ public class FileServiceImpl implements IFileService {
|
||||
path = path.substring(1);
|
||||
}
|
||||
this.checkSiteDirectory(site, path);
|
||||
if (!EDITABLE_FILE_TYPE.contains(FileExUtils.getExtension(path))) {
|
||||
String ext = FilenameUtils.getExtension(path);
|
||||
if (!EDITABLE_FILE_TYPE.contains(ext)) {
|
||||
throw ContentCoreErrorCode.NOT_EDITABLE_FILE.exception();
|
||||
}
|
||||
String root = CMSConfig.getResourceRoot();
|
||||
|
||||
@ -29,7 +29,7 @@ import com.chestnut.contentcore.core.IPublishPipeProp.PublishPipePropUseType;
|
||||
import com.chestnut.contentcore.core.impl.PublishPipeProp_IndexTemplate;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.fixed.config.TemplateSuffix;
|
||||
import com.chestnut.contentcore.mapper.CmsPublishPipeMapper;
|
||||
import com.chestnut.contentcore.properties.EnableSiteDeleteBackupProperty;
|
||||
@ -65,13 +65,13 @@ public class PublishPipeServiceImpl extends ServiceImpl<CmsPublishPipeMapper, Cm
|
||||
private final List<IPublishPipeProp> publishPipeProps;
|
||||
|
||||
@Override
|
||||
public List<PublishPipeProp> getPublishPipeProps(Long siteId, PublishPipePropUseType useType,
|
||||
Map<String, Map<String, Object>> props) {
|
||||
public List<PublishPipeProps> getPublishPipeProps(Long siteId, PublishPipePropUseType useType,
|
||||
Map<String, Map<String, Object>> props) {
|
||||
List<IPublishPipeProp> list = this.publishPipeProps.stream()
|
||||
.filter(p -> p.getUseTypes().contains(useType))
|
||||
.toList();
|
||||
return this.getPublishPipes(siteId).stream().map(pp -> {
|
||||
PublishPipeProp prop = PublishPipeProp.newInstance(pp.getCode(), pp.getName(),
|
||||
PublishPipeProps prop = PublishPipeProps.newInstance(pp.getCode(), pp.getName(),
|
||||
Objects.isNull(props) ? null : props.get(pp.getCode()));
|
||||
list.forEach(p -> {
|
||||
if (!prop.getProps().containsKey(p.getKey())) {
|
||||
|
||||
@ -46,6 +46,7 @@ import com.chestnut.contentcore.util.*;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import freemarker.template.TemplateException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -106,7 +107,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
this.staticizeService.process(context, writer);
|
||||
return writer.toString();
|
||||
} finally {
|
||||
logger.debug("[{}]首页模板解析:{}\t耗时:{}ms", requestData.getPublishPipeCode(), site.getName(), System.currentTimeMillis() - s);
|
||||
logger.debug("[{}]Parse index template: {}\t, cost: {}ms", requestData.getPublishPipeCode(), site.getName(), System.currentTimeMillis() - s);
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,7 +230,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
if (listFlag) {
|
||||
String catalogLink = this.catalogService.getCatalogListLink(catalog, 1, requestData.getPublishPipeCode(), requestData.isPreview());
|
||||
templateContext.setFirstFileName(catalogLink);
|
||||
templateContext.setOtherFileName(catalogLink + "&pi=" + TemplateContext.PlaceHolder_PageNo);
|
||||
templateContext.setOtherFileName(TemplateUtils.appendPageIndexParam(catalogLink, TemplateContext.PlaceHolder_PageNo));
|
||||
}
|
||||
try (StringWriter writer = new StringWriter()) {
|
||||
this.staticizeService.process(templateContext, writer);
|
||||
@ -372,7 +373,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
// 分页链接
|
||||
String contentLink = this.contentService.getContentLink(content, 1, requestData.getPublishPipeCode(), requestData.isPreview());
|
||||
templateContext.setFirstFileName(contentLink);
|
||||
templateContext.setOtherFileName(contentLink + "&pi=" + TemplateContext.PlaceHolder_PageNo);
|
||||
templateContext.setOtherFileName(TemplateUtils.appendPageIndexParam(contentLink, TemplateContext.PlaceHolder_PageNo));
|
||||
// staticize
|
||||
this.staticizeService.process(templateContext, writer);
|
||||
logger.debug("[{}][{}]内容模板解析:{},耗时:{}", requestData.getPublishPipeCode(), contentType.getId(), content.getTitle(),
|
||||
@ -484,22 +485,20 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageWidgetPageData(CmsPageWidget pageWidget, boolean isPreview)
|
||||
public String getPageWidgetPageData(CmsPageWidget pageWidget, String publishPipeCode, boolean isPreview)
|
||||
throws IOException, TemplateException {
|
||||
CmsSite site = this.siteService.getById(pageWidget.getSiteId());
|
||||
File templateFile = this.templateService.findTemplateFile(site, pageWidget.getTemplate(),
|
||||
pageWidget.getPublishPipeCode());
|
||||
Assert.notNull(templateFile,
|
||||
() -> ContentCoreErrorCode.TEMPLATE_EMPTY.exception(pageWidget.getName(), pageWidget.getCode()));
|
||||
String template = MapUtils.getString(pageWidget.getTemplates(), publishPipeCode, "");
|
||||
File templateFile = this.templateService.findTemplateFile(site, template,
|
||||
publishPipeCode);
|
||||
Assert.notNull(templateFile, () -> ContentCoreErrorCode.TEMPLATE_EMPTY.exception(template));
|
||||
|
||||
// 生成静态页面
|
||||
try (StringWriter writer = new StringWriter()) {
|
||||
long s = System.currentTimeMillis();
|
||||
// 模板ID = 通道:站点目录:模板文件名
|
||||
String templateKey = SiteUtils.getTemplateKey(site, pageWidget.getPublishPipeCode(),
|
||||
pageWidget.getTemplate());
|
||||
TemplateContext templateContext = new TemplateContext(templateKey, isPreview,
|
||||
pageWidget.getPublishPipeCode());
|
||||
String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, template);
|
||||
TemplateContext templateContext = new TemplateContext(templateKey, isPreview, publishPipeCode);
|
||||
// init template global variables
|
||||
TemplateUtils.initGlobalVariables(site, templateContext);
|
||||
templateContext.getVariables().put(TemplateUtils.TemplateVariable_PageWidget, pageWidget);
|
||||
@ -508,7 +507,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
templateType.initTemplateData(site.getSiteId(), templateContext);
|
||||
// staticize
|
||||
this.staticizeService.process(templateContext, writer);
|
||||
logger.debug("[{}]页面部件【{}#{}】模板解析耗时:{}ms", pageWidget.getPublishPipeCode(), pageWidget.getName(),
|
||||
logger.debug("[{}]页面部件【{}#{}】模板解析耗时:{}ms", publishPipeCode, pageWidget.getName(),
|
||||
pageWidget.getCode(), System.currentTimeMillis() - s);
|
||||
return writer.toString();
|
||||
}
|
||||
@ -516,23 +515,35 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
|
||||
@Override
|
||||
public void pageWidgetStaticize(IPageWidget pageWidget) {
|
||||
long s = System.currentTimeMillis();
|
||||
CmsPageWidget pw = pageWidget.getPageWidgetEntity();
|
||||
CmsSite site = this.siteService.getSite(pw.getSiteId());
|
||||
File templateFile = this.templateService.findTemplateFile(site, pw.getTemplate(), pw.getPublishPipeCode());
|
||||
if (Objects.isNull(templateFile)) {
|
||||
logger.warn(StringUtils.messageFormat("页面部件【{0}%s#{1}%s】模板未配置或文件不存在", pw.getName(), pw.getCode()));
|
||||
if (StringUtils.isEmpty(pw.getTemplates())) {
|
||||
logger.warn("页面部件【{}#{}】未配置模板", pw.getName(), pw.getCode());
|
||||
return;
|
||||
}
|
||||
CmsSite site = this.siteService.getSite(pw.getSiteId());
|
||||
List<CmsPublishPipe> allPublishPipes = this.publishPipeService.getAllPublishPipes(pw.getSiteId());
|
||||
for (CmsPublishPipe publishPipe : allPublishPipes) {
|
||||
String template = pw.getTemplate(publishPipe.getCode());
|
||||
File templateFile = this.templateService.findTemplateFile(site, template, publishPipe.getCode());
|
||||
if (Objects.nonNull(templateFile)) {
|
||||
pageWidgetStaticize0(site, pw, publishPipe.getCode(), template);
|
||||
} else {
|
||||
logger.warn("页面部件【{}#{}】模板未配置或文件不存在", pw.getName(), pw.getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pageWidgetStaticize0(CmsSite site, CmsPageWidget pw, String publishPipeCode, String template) {
|
||||
long s = System.currentTimeMillis();
|
||||
try {
|
||||
// 静态化目录
|
||||
String dirPath = SiteUtils.getSiteRoot(site, pw.getPublishPipeCode()) + pw.getPath();
|
||||
String dirPath = SiteUtils.getSiteRoot(site, publishPipeCode) + pw.getPath();
|
||||
FileExUtils.mkdirs(dirPath);
|
||||
// 自定义模板上下文
|
||||
String templateKey = SiteUtils.getTemplateKey(site, pw.getPublishPipeCode(), pw.getTemplate());
|
||||
TemplateContext templateContext = new TemplateContext(templateKey, false, pw.getPublishPipeCode());
|
||||
String templateKey = SiteUtils.getTemplateKey(site, publishPipeCode, template);
|
||||
TemplateContext templateContext = new TemplateContext(templateKey, false, publishPipeCode);
|
||||
templateContext.setDirectory(dirPath);
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(pw.getPublishPipeCode()));
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(publishPipeCode));
|
||||
templateContext.setFirstFileName(staticFileName);
|
||||
// init template datamode
|
||||
TemplateUtils.initGlobalVariables(site, templateContext);
|
||||
@ -542,10 +553,10 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
templateType.initTemplateData(site.getSiteId(), templateContext);
|
||||
// staticize
|
||||
this.staticizeService.process(templateContext);
|
||||
logger.debug("[{}]页面部件模板解析:{},耗时:{}ms", pw.getPublishPipeCode(), pw.getCode(), System.currentTimeMillis() - s);
|
||||
logger.debug("[{}]页面部件模板解析:{},耗时:{}ms", publishPipeCode, pw.getCode(), System.currentTimeMillis() - s);
|
||||
} catch (TemplateException | IOException e) {
|
||||
logger.error(AsyncTaskManager.addErrMessage(StringUtils.messageFormat("[{0}]页面部件模板解析失败:{1}#{2}",
|
||||
pw.getPublishPipeCode(), pw.getName(), pw.getCode())), e);
|
||||
publishPipeCode, pw.getName(), pw.getCode())), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,15 +18,15 @@ package com.chestnut.contentcore.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.storage.FileStorageService;
|
||||
import com.chestnut.common.storage.IFileStorageType;
|
||||
import com.chestnut.common.storage.StorageReadArgs;
|
||||
import com.chestnut.common.storage.StorageReadArgs.StorageReadArgsBuilder;
|
||||
import com.chestnut.common.storage.exception.StorageErrorCode;
|
||||
import com.chestnut.common.utils.*;
|
||||
import com.chestnut.common.utils.file.FileExUtils;
|
||||
import com.chestnut.common.utils.image.ImageHelper;
|
||||
import com.chestnut.common.utils.image.ImageUtils;
|
||||
import com.chestnut.contentcore.core.IResourceStat;
|
||||
import com.chestnut.contentcore.core.IResourceType;
|
||||
import com.chestnut.contentcore.core.InternalURL;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
|
||||
import com.chestnut.contentcore.core.impl.ResourceType_Image;
|
||||
import com.chestnut.contentcore.domain.CmsResource;
|
||||
@ -34,32 +34,35 @@ import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.ResourceUploadDTO;
|
||||
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
|
||||
import com.chestnut.contentcore.mapper.CmsResourceMapper;
|
||||
import com.chestnut.contentcore.properties.FileStorageArgsProperty;
|
||||
import com.chestnut.contentcore.properties.FileStorageArgsProperty.FileStorageArgs;
|
||||
import com.chestnut.contentcore.properties.FileStorageTypeProperty;
|
||||
import com.chestnut.contentcore.properties.ThumbnailHeightProperty;
|
||||
import com.chestnut.contentcore.properties.ThumbnailWidthProperty;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.util.*;
|
||||
import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResource> implements IResourceService {
|
||||
|
||||
private final Map<String, IFileStorageType> fileStorageTypes;
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
private final List<IResourceStat> resourceStats;
|
||||
|
||||
@ -144,23 +147,42 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
CmsResource resource = this.getById(dto.getResourceId());
|
||||
Assert.notNull(resource, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("resourceId", dto.getResourceId()));
|
||||
|
||||
String oldResourceType = resource.getResourceType();
|
||||
String oldPath = resource.getPath();
|
||||
|
||||
resource.setResourceType(resourceType.getId());
|
||||
resource.setFileName(dto.getFile().getOriginalFilename());
|
||||
resource.setName(StringUtils.isEmpty(dto.getName()) ? dto.getFile().getOriginalFilename() : dto.getName());
|
||||
resource.setSuffix(suffix);
|
||||
|
||||
String fileName = resource.getResourceId() + StringUtils.DOT + suffix;
|
||||
String path = StringUtils.substringBeforeLast(resource.getPath(), "/") + fileName;
|
||||
|
||||
String path = StringUtils.substringBeforeLast(resource.getPath(), "/") + "/" + fileName;
|
||||
resource.setPath(path);
|
||||
resource.updateBy(dto.getOperator().getUsername());
|
||||
resource.setRemark(dto.getRemark());
|
||||
// 删除原文件
|
||||
if (!resource.getPath().equals(oldPath)) {
|
||||
String fileStorageType = FileStorageTypeProperty.getValue(dto.getSite().getConfigProps());
|
||||
IFileStorageType fst = fileStorageService.getFileStorageType(fileStorageType);
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, dto.getSite());
|
||||
deleteResource(oldPath, oldResourceType, fileStorageHelper);
|
||||
}
|
||||
// 处理资源
|
||||
this.processResource(resource, resourceType, dto.getSite(), dto.getFile().getBytes());
|
||||
this.updateById(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void deleteResource(String path, String resourceType, FileStorageHelper fileStorageHelper) {
|
||||
fileStorageHelper.remove(path);
|
||||
// 删除图片缩略图
|
||||
if (ResourceType_Image.ID.equals(resourceType)) {
|
||||
String fileName = StringUtils.substringAfterLast(path, "/");
|
||||
final String fileNamePrefix = StringUtils.substringBeforeLast(fileName, ".") + "_";
|
||||
path = StringUtils.substringBeforeLast(path, "/") + "/" + fileNamePrefix;
|
||||
fileStorageHelper.removeByPrefix(path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsResource addBase64Image(CmsSite site, String operator, String base64Data) throws IOException {
|
||||
if (!ImageUtils.isBase64Image(base64Data)) {
|
||||
@ -234,17 +256,36 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
resource.setWidth(0);
|
||||
resource.setHeight(0);
|
||||
resource.setFileSize((long) bytes.length);
|
||||
// 读取存储配置
|
||||
String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
IFileStorageType fst = getFileStorageType(fileStorageType);
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
|
||||
// 写入磁盘/OSS
|
||||
fileStorageHelper.write(resource.getPath(), bytes);
|
||||
// 设置资源参数
|
||||
resource.setStorageType(fileStorageType);
|
||||
resource.setInternalUrl(InternalDataType_Resource.getInternalUrl(resource));
|
||||
// 默认缩略图处理
|
||||
resourceType.asyncProcess(resource);
|
||||
// 保存临时文件处理
|
||||
String tempDirectory = ResourceUtils.getResourceTempDirectory(site);
|
||||
File tempFile = new File(tempDirectory + resource.getPath());
|
||||
FileUtils.writeByteArrayToFile(tempFile, bytes);
|
||||
List<File> processed = resourceType.process(resource, tempFile);
|
||||
try {
|
||||
// 读取存储配置
|
||||
String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
IFileStorageType fst = fileStorageService.getFileStorageType(fileStorageType);
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
|
||||
// 写入磁盘/OSS
|
||||
fileStorageHelper.write(resource.getPath(), tempFile);
|
||||
if (!processed.isEmpty()) {
|
||||
String prefix = StringUtils.substringBeforeLast(resource.getPath(), "/") + "/";
|
||||
for (File f : processed) {
|
||||
fileStorageHelper.write(prefix + f.getName(), f);
|
||||
}
|
||||
}
|
||||
// 设置资源参数
|
||||
resource.setStorageType(fileStorageType);
|
||||
resource.setInternalUrl(InternalDataType_Resource.getInternalUrl(resource));
|
||||
// 异步后续处理
|
||||
resourceType.asyncProcess(resource);
|
||||
} finally {
|
||||
// 删除临时文件
|
||||
FileUtils.delete(tempFile);
|
||||
for (File file : processed) {
|
||||
FileUtils.delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -252,26 +293,13 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
List<CmsResource> resources = this.listByIds(resourceIds);
|
||||
if (!resources.isEmpty()) {
|
||||
CmsSite site = siteService.getSite(resources.get(0).getSiteId());
|
||||
String siteRoot = SiteUtils.getSiteResourceRoot(site);
|
||||
String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
IFileStorageType fst = fileStorageService.getFileStorageType(fileStorageType);
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
|
||||
resources.forEach(r -> {
|
||||
// 删除资源文件
|
||||
try {
|
||||
File file = new File(siteRoot + r.getPath());
|
||||
FileUtils.delete(file);
|
||||
final String fileNamePrefix = StringUtils.substringBeforeLast(file.getName(), ".");
|
||||
// 删除图片缩略图
|
||||
File[] others = file.getParentFile().listFiles(
|
||||
(dir, name) -> name.startsWith(fileNamePrefix)
|
||||
);
|
||||
if (Objects.nonNull(others)) {
|
||||
for (File f : others) {
|
||||
FileUtils.delete(f);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Delete resource file failed: " + r.getPath());
|
||||
}
|
||||
// 删除数据库记录
|
||||
// 删除文件
|
||||
deleteResource(r.getPath(), r.getResourceType(), fileStorageHelper);
|
||||
// 删除数据库记录
|
||||
this.removeById(r.getResourceId());
|
||||
});
|
||||
}
|
||||
@ -287,31 +315,15 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
@Override
|
||||
public void downloadResource(CmsResource resource, HttpServletResponse response) {
|
||||
CmsSite site = this.siteService.getSite(resource.getSiteId());
|
||||
IFileStorageType storagetType = this.getFileStorageType(resource.getStorageType());
|
||||
StorageReadArgsBuilder builder = StorageReadArgs.builder();
|
||||
FileStorageArgs fileStorageArgs = FileStorageArgsProperty.getValue(site.getConfigProps());
|
||||
if (fileStorageArgs != null) {
|
||||
builder.endpoint(fileStorageArgs.getEndpoint())
|
||||
.accessKey(fileStorageArgs.getAccessKey())
|
||||
.accessSecret(fileStorageArgs.getAccessSecret())
|
||||
.bucket(fileStorageArgs.getBucket())
|
||||
.path(resource.getPath());
|
||||
}
|
||||
InputStream is = storagetType.read(builder.build());
|
||||
try {
|
||||
IFileStorageType storagetType = fileStorageService.getFileStorageType(resource.getStorageType());
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(storagetType, site);
|
||||
try (InputStream is = fileStorageHelper.read(resource.getPath())) {
|
||||
IOUtils.copy(is, response.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IFileStorageType getFileStorageType(String type) {
|
||||
IFileStorageType fileStorageType = fileStorageTypes.get(IFileStorageType.BEAN_NAME_PREIFX + type);
|
||||
Assert.notNull(fileStorageType, () -> StorageErrorCode.UNSUPPORTED_STORAGE_TYPE.exception(type));
|
||||
return fileStorageType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析html中的图片标签,如果是远程地址图片则下载到资源库中并将图片标签的src替换为资源内部链接
|
||||
*
|
||||
@ -359,13 +371,83 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createThumbnailIfNotExists(InternalURL internalUrl, int width, int height) throws IOException {
|
||||
if (!InternalDataType_Resource.ID.equals(internalUrl.getType())
|
||||
|| !ResourceType_Image.isImage(internalUrl.getPath())) {
|
||||
return; // 非图片资源忽略
|
||||
}
|
||||
Long siteId = MapUtils.getLong(internalUrl.getParams(), InternalDataType_Resource.InternalUrl_Param_SiteId);
|
||||
CmsSite site = siteService.getSite(siteId);
|
||||
|
||||
String filePath = internalUrl.getPath();
|
||||
String thumbnailPath = ImageUtils.getThumbnailFileName(filePath, width, height);
|
||||
|
||||
String storageTypeId = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
IFileStorageType fst = fileStorageService.getFileStorageType(storageTypeId);
|
||||
FileStorageHelper storageHelper = FileStorageHelper.of(fst, site);
|
||||
if (storageHelper.exists(thumbnailPath)) {
|
||||
return;
|
||||
}
|
||||
CmsResource resource = this.getById(internalUrl.getId());
|
||||
if (Objects.nonNull(resource)) {
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
try (InputStream is = storageHelper.read(resource.getPath())) {
|
||||
String format = FileExUtils.getExtension(resource.getPath());
|
||||
ImageHelper.of(is).format(format).resize(width, height).to(os);
|
||||
}
|
||||
storageHelper.write(thumbnailPath, os.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dealDefaultThumbnail(CmsSite site, List<String> internalUrls, Consumer<List<String>> consumer) {
|
||||
if (internalUrls.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int w = ThumbnailWidthProperty.getValue(site.getConfigProps());
|
||||
int h = ThumbnailHeightProperty.getValue(site.getConfigProps());
|
||||
if (w > 0 && h > 0) {
|
||||
List<String> thumbnails = dealThumbnails(internalUrls, w, h);
|
||||
consumer.accept(thumbnails);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dealDefaultThumbnail(CmsSite site, String internalUrl, Consumer<String> consumer) {
|
||||
if (StringUtils.isEmpty(internalUrl)) {
|
||||
return;
|
||||
}
|
||||
dealDefaultThumbnail(site, List.of(internalUrl), thumbnails -> consumer.accept(thumbnails.get(0)));
|
||||
}
|
||||
|
||||
private List<String> dealThumbnails(List<String> internalUrls, int w, int h) {
|
||||
return internalUrls.stream().map(iurl -> {
|
||||
try {
|
||||
InternalURL internalURL = InternalUrlUtils.parseInternalUrl(iurl);
|
||||
if (Objects.nonNull(internalURL)) {
|
||||
// 先检查是否存在缩略图,如果不存在需要生成
|
||||
createThumbnailIfNotExists(internalURL, w, h);
|
||||
// 返回缩略图路径
|
||||
return ImageUtils.getThumbnailFileName(InternalUrlUtils.getActualPreviewUrl(internalURL), w, h);
|
||||
}
|
||||
// 非内部链接返回原路径
|
||||
return iurl;
|
||||
} catch (Exception e) {
|
||||
log.error("Generate thumbnail fail: " + iurl, e);
|
||||
return iurl;
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计资源引用
|
||||
*/
|
||||
public void statResourceUsage(Long siteId) throws InterruptedException {
|
||||
Map<Long, Long> quotedResources = new HashMap<>();
|
||||
for (IResourceStat resourceStat : this.resourceStats) {
|
||||
resourceStat.statQuotedResource(siteId, quotedResources);
|
||||
resourceStat.statQuotedResource(siteId, quotedResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.security.domain.LoginUser;
|
||||
import com.chestnut.common.storage.FileStorageService;
|
||||
import com.chestnut.common.storage.IFileStorageType;
|
||||
import com.chestnut.common.utils.*;
|
||||
import com.chestnut.common.utils.file.FileExUtils;
|
||||
import com.chestnut.contentcore.ContentCoreConsts;
|
||||
@ -27,7 +29,7 @@ import com.chestnut.contentcore.config.CMSConfig;
|
||||
import com.chestnut.contentcore.core.IProperty;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.domain.dto.SiteDTO;
|
||||
import com.chestnut.contentcore.domain.dto.SiteDefaultTemplateDTO;
|
||||
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
|
||||
@ -39,9 +41,11 @@ import com.chestnut.contentcore.mapper.CmsSiteMapper;
|
||||
import com.chestnut.contentcore.perms.SitePermissionType;
|
||||
import com.chestnut.contentcore.perms.SitePermissionType.SitePrivItem;
|
||||
import com.chestnut.contentcore.properties.EnableSiteDeleteBackupProperty;
|
||||
import com.chestnut.contentcore.properties.FileStorageTypeProperty;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.util.CmsPrivUtils;
|
||||
import com.chestnut.contentcore.util.ConfigPropertyUtils;
|
||||
import com.chestnut.contentcore.util.FileStorageHelper;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import com.chestnut.system.service.ISysPermissionService;
|
||||
@ -77,6 +81,10 @@ public class SiteServiceImpl extends ServiceImpl<CmsSiteMapper, CmsSite> impleme
|
||||
|
||||
private final SiteMonitoredCache siteCache;
|
||||
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
private final AsyncTaskManager asyncTaskManager;
|
||||
|
||||
@Override
|
||||
public CmsSite getSite(Long siteId) {
|
||||
CmsSite site = siteCache.getCache(siteId, () -> this.getById(siteId));
|
||||
@ -165,7 +173,7 @@ public class SiteServiceImpl extends ServiceImpl<CmsSiteMapper, CmsSite> impleme
|
||||
prop.getProps().entrySet().removeIf(e -> !publishPipeProps.containsKey(IPublishPipeProp.BEAN_PREFIX + e.getKey()));
|
||||
});
|
||||
Map<String, Map<String, Object>> publishPipeProps = dto.getPublishPipeDatas().stream()
|
||||
.collect(Collectors.toMap(PublishPipeProp::getPipeCode, PublishPipeProp::getProps));
|
||||
.collect(Collectors.toMap(PublishPipeProps::getPipeCode, PublishPipeProps::getProps));
|
||||
site.setPublishPipeProps(publishPipeProps);
|
||||
site.updateBy(dto.getOperator().getUsername());
|
||||
this.updateById(site);
|
||||
@ -226,8 +234,8 @@ public class SiteServiceImpl extends ServiceImpl<CmsSiteMapper, CmsSite> impleme
|
||||
@Override
|
||||
public void saveSiteDefaultTemplate(SiteDefaultTemplateDTO dto) {
|
||||
CmsSite site = this.getSite(dto.getSiteId());
|
||||
List<PublishPipeProp> publishPipeProps = dto.getPublishPipeProps();
|
||||
for (PublishPipeProp ppp : publishPipeProps) {
|
||||
List<PublishPipeProps> publishPipeProps = dto.getPublishPipeProps();
|
||||
for (PublishPipeProps ppp : publishPipeProps) {
|
||||
Map<String, Object> sitePublishPipeProps = site.getPublishPipeProps(ppp.getPipeCode());
|
||||
sitePublishPipeProps.putAll(ppp.getProps());
|
||||
}
|
||||
@ -245,6 +253,13 @@ public class SiteServiceImpl extends ServiceImpl<CmsSiteMapper, CmsSite> impleme
|
||||
site.updateBy(operator);
|
||||
this.updateById(site);
|
||||
this.clearCache(site.getSiteId());
|
||||
// 重新加载StorageClient
|
||||
asyncTaskManager.execute(() -> {
|
||||
String fileStorageType = FileStorageTypeProperty.getValue(site.getConfigProps());
|
||||
IFileStorageType fst = fileStorageService.getFileStorageType(fileStorageType);
|
||||
FileStorageHelper fileStorageHelper = FileStorageHelper.of(fst, site);
|
||||
fileStorageHelper.reloadClient();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -308,6 +308,9 @@ public class SiteThemeService {
|
||||
} finally {
|
||||
FileUtils.deleteDirectory(new File(destDir));
|
||||
this.setProgressInfo(100, "导入完成");
|
||||
if (log.isDebugEnabled() && StringUtils.isNotEmpty(this.getErrMessages())) {
|
||||
this.getErrMessages().forEach(log::debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,7 +65,7 @@ public class FileExtractorFunction extends AbstractFunc {
|
||||
@Override
|
||||
public Object exec0(Object... args) throws TemplateModelException {
|
||||
if (args.length < 1) {
|
||||
return StringUtils.EMPTY;
|
||||
return List.of();
|
||||
}
|
||||
List<String> suffixArray = List.of();
|
||||
if (args.length == 2 && Objects.nonNull(args[1])) {
|
||||
|
||||
@ -18,14 +18,10 @@ package com.chestnut.contentcore.template.func;
|
||||
import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.staticize.func.AbstractFunc;
|
||||
import com.chestnut.common.storage.local.LocalFileStorageType;
|
||||
import com.chestnut.common.utils.ObjectUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.common.utils.file.FileExUtils;
|
||||
import com.chestnut.common.utils.image.ImageHelper;
|
||||
import com.chestnut.contentcore.core.InternalURL;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
|
||||
import com.chestnut.contentcore.domain.CmsResource;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
@ -33,15 +29,11 @@ import com.chestnut.contentcore.util.SiteUtils;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.SimpleNumber;
|
||||
import freemarker.template.SimpleScalar;
|
||||
import freemarker.template.TemplateBooleanModel;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@ -67,8 +59,6 @@ public class ImageSizeFunction extends AbstractFunc {
|
||||
|
||||
private static final String ARG3_NAME = "{FREEMARKER.FUNC." + FUNC_NAME + ".Arg3.Name}";
|
||||
|
||||
private static final String ARG4_NAME = "{FREEMARKER.FUNC." + FUNC_NAME + ".Arg4.Name}";
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
@ -97,48 +87,27 @@ public class ImageSizeFunction extends AbstractFunc {
|
||||
if (!InternalUrlUtils.isInternalUrl(iurl)) {
|
||||
return iurl; // 非内部链接直接返回
|
||||
}
|
||||
boolean crop = false; // 是否在缩放后进行居中裁剪,默认:false
|
||||
if (args.length == 4) {
|
||||
crop = ((TemplateBooleanModel) args[3]).getAsBoolean();
|
||||
}
|
||||
TemplateContext context = FreeMarkerUtils.getTemplateContext(Environment.getCurrentEnvironment());
|
||||
InternalURL internalUrl = InternalUrlUtils.parseInternalUrl(iurl);
|
||||
if (Objects.isNull(internalUrl) || !InternalDataType_Resource.ID.equals(internalUrl.getType())) {
|
||||
return iurl;
|
||||
}
|
||||
String actualUrl = InternalUrlUtils.getActualUrl(internalUrl, context.getPublishPipeCode(),
|
||||
context.isPreview());
|
||||
String originalUrl = InternalUrlUtils.getActualUrl(internalUrl, context.getPublishPipeCode(), context.isPreview());
|
||||
String siteResourceRoot = SiteUtils.getSiteResourceRoot(siteService.getSite(Long.valueOf(internalUrl.getParams().get("sid"))));
|
||||
String destPath = StringUtils.substringBeforeLast(internalUrl.getPath(), ".") + "_" + width + "x" + height
|
||||
+ "." + StringUtils.substringAfterLast(internalUrl.getPath(), ".");
|
||||
String thumbnailUrl = StringUtils.substringBeforeLast(originalUrl, ".") + "_" + width + "x" + height + "."
|
||||
+ StringUtils.substringAfterLast(originalUrl, ".");
|
||||
if (Files.exists(Path.of(siteResourceRoot + destPath))) {
|
||||
return StringUtils.substringBeforeLast(actualUrl, ".") + "_" + width + "x" + height + "."
|
||||
+ StringUtils.substringAfterLast(actualUrl, ".");
|
||||
return thumbnailUrl;
|
||||
}
|
||||
try {
|
||||
CmsResource resource = this.resourceService.getById(internalUrl.getId());
|
||||
if (Objects.nonNull(resource) && LocalFileStorageType.TYPE.equals(resource.getStorageType())) {
|
||||
if (crop) {
|
||||
String ext = FileExUtils.getExtension(resource.getPath());
|
||||
File file = new File(siteResourceRoot + resource.getPath());
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
ImageHelper.of(is).format(ext).centerCropAndResize(width, height).to(os);
|
||||
}
|
||||
Files.write(file.toPath(), os.toByteArray());
|
||||
}
|
||||
} else {
|
||||
ImageHelper.of(new File(siteResourceRoot + resource.getPath()))
|
||||
.resize(width, height)
|
||||
.toFile(new File(siteResourceRoot + destPath));
|
||||
}
|
||||
actualUrl = StringUtils.substringBeforeLast(actualUrl, ".") + "_" + width + "x" + height + "."
|
||||
+ StringUtils.substringAfterLast(actualUrl, ".");
|
||||
}
|
||||
resourceService.createThumbnailIfNotExists(internalUrl, width, height);
|
||||
return thumbnailUrl;
|
||||
} catch (Exception e) {
|
||||
log.warn("Generate thumbnail failed: " + actualUrl, e);
|
||||
log.warn("Generate thumbnail failed: " + originalUrl, e);
|
||||
}
|
||||
return actualUrl;
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -146,8 +115,7 @@ public class ImageSizeFunction extends AbstractFunc {
|
||||
return List.of(
|
||||
new FuncArg(ARG1_NAME, FuncArgType.String, true, ARG1_DESC),
|
||||
new FuncArg(ARG2_NAME, FuncArgType.Int, true),
|
||||
new FuncArg(ARG3_NAME, FuncArgType.Int, true),
|
||||
new FuncArg(ARG4_NAME, FuncArgType.Boolean, false)
|
||||
new FuncArg(ARG3_NAME, FuncArgType.Int, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,17 +111,18 @@ public class CmsPageWidgetTag extends AbstractTag {
|
||||
if (!PageWidgetStatus.PUBLISHED.equals(pw.getState())) {
|
||||
return null;
|
||||
}
|
||||
String template = pw.getTemplate(context.getPublishPipeCode());
|
||||
CmsSite site = this.siteService.getSite(siteId);
|
||||
File templateFile = this.templateService.findTemplateFile(site, pw.getTemplate(), context.getPublishPipeCode());
|
||||
Assert.notNull(templateFile, () -> new IncludeTemplateNotFoundException(pw.getTemplate(), env));
|
||||
File templateFile = this.templateService.findTemplateFile(site, template, context.getPublishPipeCode());
|
||||
Assert.notNull(templateFile, () -> new IncludeTemplateNotFoundException(template, env));
|
||||
|
||||
boolean ssi = MapUtils.getBoolean(attrs, ATTR_SSI, EnableSSIProperty.getValue(site.getConfigProps()));
|
||||
String templateKey = SiteUtils.getTemplateKey(site, pw.getPublishPipeCode(), pw.getTemplate());
|
||||
String templateKey = SiteUtils.getTemplateKey(site, context.getPublishPipeCode(), template);
|
||||
if (context.isPreview()) {
|
||||
env.getOut().write(this.processTemplate(env, pw, templateKey));
|
||||
} else {
|
||||
String siteRoot = SiteUtils.getSiteRoot(site, context.getPublishPipeCode());
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(pw.getPublishPipeCode()));
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(context.getPublishPipeCode()));
|
||||
String staticFilePath = pw.getPath() + staticFileName;
|
||||
if (ssi) {
|
||||
// 读取页面部件静态化内容
|
||||
|
||||
@ -15,21 +15,17 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
|
||||
import com.chestnut.common.utils.NumberUtils;
|
||||
import com.chestnut.common.utils.SpringUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IProperty;
|
||||
import com.chestnut.contentcore.core.IProperty.UseType;
|
||||
import com.chestnut.contentcore.exception.ContentCoreErrorCode;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConfigPropertyUtils {
|
||||
|
||||
@ -120,9 +116,28 @@ public class ConfigPropertyUtils {
|
||||
String v = null;
|
||||
if (prop != null) {
|
||||
v = MapUtils.getString(firstProps, prop.getId());
|
||||
if (StringUtils.isEmpty(v)) {
|
||||
if (Objects.isNull(v) && Objects.nonNull(secondProps)) {
|
||||
v = MapUtils.getString(secondProps, prop.getId());
|
||||
}
|
||||
if (Objects.isNull(v)) {
|
||||
v = prop.defaultValue().toString();
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public static Object getValue(String propertyKey, Map<String, String> firstProps,
|
||||
Map<String, String> secondProps) {
|
||||
IProperty prop = getConfigProperty(propertyKey);
|
||||
Object v = null;
|
||||
if (prop != null) {
|
||||
v = MapUtils.getObject(firstProps, prop.getId());
|
||||
if (Objects.isNull(v) && Objects.nonNull(secondProps)) {
|
||||
v = MapUtils.getObject(secondProps, prop.getId());
|
||||
}
|
||||
if (Objects.isNull(v)) {
|
||||
v = prop.defaultValue();
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@ -151,12 +166,20 @@ public class ConfigPropertyUtils {
|
||||
int intV = 0;
|
||||
if (prop != null) {
|
||||
String v = MapUtils.getString(firstProps, prop.getId());
|
||||
if (Objects.isNull(v) && Objects.nonNull(secondProps)) {
|
||||
v = MapUtils.getString(secondProps, prop.getId());
|
||||
}
|
||||
if (NumberUtils.isCreatable(v)) {
|
||||
intV = NumberUtils.toInt(v);
|
||||
} else {
|
||||
v = MapUtils.getString(secondProps, prop.getId());
|
||||
if (NumberUtils.isCreatable(v)) {
|
||||
intV = NumberUtils.toInt(v);
|
||||
Object defaultV = prop.defaultValue();
|
||||
if (defaultV instanceof Integer defaultIntV) {
|
||||
intV = defaultIntV;
|
||||
} else {
|
||||
v = defaultV.toString();
|
||||
if (NumberUtils.isCreatable(v)) {
|
||||
intV = NumberUtils.toInt(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,19 +15,16 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.util;
|
||||
|
||||
import com.chestnut.common.storage.IFileStorageType;
|
||||
import com.chestnut.common.storage.StorageListArgs;
|
||||
import com.chestnut.common.storage.StorageReadArgs;
|
||||
import com.chestnut.common.storage.StorageWriteArgs;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.storage.*;
|
||||
import com.chestnut.common.storage.local.LocalFileStorageType;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.properties.FileStorageArgsProperty;
|
||||
import com.chestnut.contentcore.properties.FileStorageTypeProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -48,6 +45,8 @@ public class FileStorageHelper {
|
||||
|
||||
private String endpoint;
|
||||
|
||||
private String region;
|
||||
|
||||
private String pipeline;
|
||||
|
||||
@Getter
|
||||
@ -66,36 +65,87 @@ public class FileStorageHelper {
|
||||
helper.accessSecret = args.getAccessSecret();
|
||||
helper.bucket = args.getBucket();
|
||||
helper.endpoint = args.getEndpoint();
|
||||
helper.region = args.getRegion();
|
||||
helper.pipeline = args.getPipeline();
|
||||
helper.domain = args.getDomain();
|
||||
helper.validate();
|
||||
return helper;
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (LocalFileStorageType.TYPE.equals(this.fileStorage.getType())) {
|
||||
return; // 本地存储不需要校验
|
||||
}
|
||||
if (StringUtils.isEmpty(this.accessKey)) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("fileStorage.accessKey");
|
||||
}
|
||||
if (StringUtils.isEmpty(this.accessSecret)) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("fileStorage.accessSecret");
|
||||
}
|
||||
if (StringUtils.isEmpty(this.endpoint)) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("fileStorage.endpoint");
|
||||
}
|
||||
if (StringUtils.isEmpty(this.region)) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("fileStorage.region");
|
||||
}
|
||||
if (StringUtils.isEmpty(this.bucket)) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("fileStorage.bucket");
|
||||
}
|
||||
if (StringUtils.isEmpty(this.domain)) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("fileStorage.domain");
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadClient() {
|
||||
fileStorage.reloadClient(this.endpoint, this.region, this.accessKey, this.accessSecret);
|
||||
}
|
||||
|
||||
public boolean exists(String thumbnailPath) {
|
||||
StorageExistArgs args = StorageExistArgs.builder()
|
||||
.accessKey(this.accessKey)
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.path(thumbnailPath)
|
||||
.build();
|
||||
return fileStorage.exists(args);
|
||||
}
|
||||
|
||||
public InputStream read(String path) {
|
||||
StorageReadArgs storageReadArgs = StorageReadArgs.builder()
|
||||
.accessKey(this.accessKey)
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.path(path)
|
||||
.build();
|
||||
return fileStorage.read(storageReadArgs);
|
||||
}
|
||||
|
||||
public void write(String path, byte[] bytes) throws IOException {
|
||||
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) {
|
||||
write(path, is);
|
||||
public void write(String path, File file) throws IOException {
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
write(path, is, file.length());
|
||||
}
|
||||
}
|
||||
|
||||
public void write(String path, InputStream is) {
|
||||
public void write(String path, byte[] bytes) throws IOException {
|
||||
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) {
|
||||
write(path, is, bytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(String path, InputStream is, long length) {
|
||||
StorageWriteArgs args = StorageWriteArgs.builder()
|
||||
.accessKey(this.accessKey)
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.path(path)
|
||||
.inputStream(is)
|
||||
.length(length)
|
||||
.build();
|
||||
this.fileStorage.write(args);
|
||||
}
|
||||
@ -110,9 +160,46 @@ public class FileStorageHelper {
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.prefix(prefix)
|
||||
.maxKeys(count)
|
||||
.build();
|
||||
return this.fileStorage.list(storageListArgs);
|
||||
}
|
||||
|
||||
public void remove(String path) {
|
||||
StorageRemoveArgs args = StorageRemoveArgs.builder()
|
||||
.accessKey(this.accessKey)
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.path(path)
|
||||
.build();
|
||||
this.fileStorage.remove(args);
|
||||
}
|
||||
|
||||
public void removeByPrefix(String prefix) {
|
||||
StorageListArgs storageListArgs = StorageListArgs.builder()
|
||||
.accessKey(this.accessKey)
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.prefix(prefix)
|
||||
.maxKeys(100)
|
||||
.build();
|
||||
List<String> list = this.fileStorage.list(storageListArgs);
|
||||
for (String path : list) {
|
||||
StorageRemoveArgs args = StorageRemoveArgs.builder()
|
||||
.accessKey(this.accessKey)
|
||||
.accessSecret(this.accessSecret)
|
||||
.bucket(this.bucket)
|
||||
.endpoint(this.endpoint)
|
||||
.region(this.region)
|
||||
.path(path)
|
||||
.build();
|
||||
this.fileStorage.remove(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,6 +94,10 @@ public class InternalUrlUtils {
|
||||
return getActualUrl(iurl, null, true);
|
||||
}
|
||||
|
||||
public static String getActualPreviewUrl(InternalURL internalURL) {
|
||||
return getActualUrl(internalURL, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是内部资源URL
|
||||
*/
|
||||
|
||||
@ -17,6 +17,7 @@ package com.chestnut.contentcore.util;
|
||||
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.StaticizeConstants;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.utils.ReflectASMUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
@ -44,7 +45,7 @@ public class TemplateUtils {
|
||||
/**
|
||||
* 模板变量:请求参数
|
||||
*/
|
||||
public final static String TemplateVariable_Request = "Request";
|
||||
public final static String TemplateVariable_Request = StaticizeConstants.TemplateVariable_Request;
|
||||
|
||||
/**
|
||||
* 模板变量:<@cms_include>标签file属性请求参数,下个大版本移除
|
||||
@ -252,33 +253,6 @@ public class TemplateUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String appendTokenParameter(String url, Environment env) throws TemplateModelException {
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
return url;
|
||||
}
|
||||
String tokenName = FreeMarkerUtils.getStringVariable(env, TemplateUtils.TemplateVariable_TokenName);
|
||||
String token = FreeMarkerUtils.getStringVariable(env, TemplateUtils.TemplateVariable_Token);
|
||||
if (url.contains("?")) {
|
||||
return url + "&" + tokenName + "=" + token;
|
||||
}
|
||||
return url + "?" + token + "=" + token;
|
||||
}
|
||||
|
||||
public static String appendTokenParameter(String url) {
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
return url;
|
||||
}
|
||||
SaTokenConfig config = StpAdminUtil.getStpLogic().getConfigOrGlobal();
|
||||
String token = StpAdminUtil.getTokenValue();
|
||||
if (StringUtils.isNotEmpty(config.getTokenPrefix())) {
|
||||
token = config.getTokenPrefix() + " " + token;
|
||||
}
|
||||
if (url.contains("?")) {
|
||||
return url + "&" + config.getTokenName() + "=" + token;
|
||||
}
|
||||
return url + "?" + config.getTokenName() + "=" + token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 页面区块静态文件相对路径
|
||||
@ -293,4 +267,36 @@ public class TemplateUtils {
|
||||
includeTemplateName.length() - TemplateSuffix.getValue().length())
|
||||
+ "." + site.getStaticSuffix(publishPipeCode);
|
||||
}
|
||||
|
||||
public static String appendParam(String path, String name, String value) {
|
||||
if (path.contains("?")) {
|
||||
return path + "&" + name + "=" + value;
|
||||
}
|
||||
return path + "?" + name + "=" + value;
|
||||
}
|
||||
|
||||
public static String appendPageIndexParam(String path, String pageIndex) {
|
||||
return appendParam(path, StaticizeConstants.TemplateParam_PageIndex, pageIndex);
|
||||
}
|
||||
|
||||
public static String appendTokenParameter(String url, Environment env) throws TemplateModelException {
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
return url;
|
||||
}
|
||||
String tokenName = FreeMarkerUtils.getStringVariable(env, TemplateUtils.TemplateVariable_TokenName);
|
||||
String token = FreeMarkerUtils.getStringVariable(env, TemplateUtils.TemplateVariable_Token);
|
||||
return appendParam(url, tokenName, token);
|
||||
}
|
||||
|
||||
public static String appendTokenParameter(String url) {
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
return url;
|
||||
}
|
||||
SaTokenConfig config = StpAdminUtil.getStpLogic().getConfigOrGlobal();
|
||||
String token = StpAdminUtil.getTokenValue();
|
||||
if (StringUtils.isNotEmpty(config.getTokenPrefix())) {
|
||||
token = config.getTokenPrefix() + " " + token;
|
||||
}
|
||||
return appendParam(url, config.getTokenName(), token);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +88,7 @@ ERRCODE.CMS.CONTENTCORE.RESOURCE_ACCEPT_SIZE_LIMIT=上传文件大小超过限
|
||||
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_STORAGE=资源存储方式与当前站点配置不一致
|
||||
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_IS_EMPTY=被合并栏目不存在
|
||||
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_NOT_LEAF=被合并栏目不能包含子栏目
|
||||
ERRCODE.CMS.CONTENTCORE.DENY_LINK_TO_LINK_INTERNAL_DATA=不能链接到链接内容或栏目
|
||||
|
||||
# freemarker模板标签
|
||||
FREEMARKER.TAG.cms_site.NAME=站点列表标签
|
||||
@ -161,9 +162,8 @@ FREEMARKER.FUNC.imageSize.Arg1.Name=图片资源内部路径(iurl://)
|
||||
FREEMARKER.FUNC.imageSize.Arg1.Desc=仅支持处理内部资源图片(iurl://)
|
||||
FREEMARKER.FUNC.imageSize.Arg2.Name=宽度
|
||||
FREEMARKER.FUNC.imageSize.Arg3.Name=高度
|
||||
FREEMARKER.FUNC.imageSize.Arg4.Name=是否居中裁剪
|
||||
FREEMARKER.FUNC.internalUrl.DESC=将内部链接“iurl://”解析为正常http(s)访问地址,例如:`${internalUrl(content.redirectUrl)}`
|
||||
FREEMARKER.FUNC.internalUrl.Arg1.Name=內部鏈接(iurl://)
|
||||
FREEMARKER.FUNC.internalUrl.Arg1.Name=内部链接(iurl://)
|
||||
FREEMARKER.FUNC.siteUrl.DESC=获取站点指定发布通道访问链接,例如:`${siteUrl(Site.siteId, 'h5')}`
|
||||
FREEMARKER.FUNC.siteUrl.Arg1.Name=站点ID
|
||||
FREEMARKER.FUNC.siteUrl.Arg2.Name=发布通道编码
|
||||
@ -198,6 +198,7 @@ FREEMARKER.FUNC.fileExtractor.Arg2.Name=文件类型/后缀
|
||||
# 校验规则
|
||||
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=发布通道配置不能为空
|
||||
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=属性编码只能使用大小写字母、数字和下划线
|
||||
VALIDATOR.CMS.FILE_NAME.REGEXP_ERR=文件名不能包含斜杠或反斜杠
|
||||
|
||||
# 定时任务
|
||||
SCHEDULED_TASK.RecycleExpireJobHandler=回收站过期内容删除任务
|
||||
|
||||
@ -88,6 +88,7 @@ ERRCODE.CMS.CONTENTCORE.RESOURCE_ACCEPT_SIZE_LIMIT=Upload file size exceeded.
|
||||
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_STORAGE=The resource storage type is inconsistent with the site configuration.
|
||||
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_IS_EMPTY=Merge catalog not exists.
|
||||
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_NOT_LEAF=Cannot merge catalog which has children.
|
||||
ERRCODE.CMS.CONTENTCORE.DENY_LINK_TO_LINK_INTERNAL_DATA=Cannot link to link-catalog or link-content.
|
||||
|
||||
# 模板freemarker
|
||||
FREEMARKER.TAG.cms_site.NAME=Site list tag
|
||||
@ -161,7 +162,6 @@ FREEMARKER.FUNC.imageSize.Arg1.Name=Internal image resource link (iurl://)
|
||||
FREEMARKER.FUNC.imageSize.Arg1.Desc=Only supports internal resource images with prefix "iurl://".
|
||||
FREEMARKER.FUNC.imageSize.Arg2.Name=Width
|
||||
FREEMARKER.FUNC.imageSize.Arg3.Name=Height
|
||||
FREEMARKER.FUNC.imageSize.Arg4.Name=Center cutting
|
||||
FREEMARKER.FUNC.internalUrl.DESC=Use `${internalUrl(site.logo)}` in template to parse "iurl://xx" to http(s) link.
|
||||
FREEMARKER.FUNC.internalUrl.Arg1.Name=Internal resource url(iurl://)
|
||||
FREEMARKER.FUNC.siteUrl.DESC=Use `${siteUrl(Site.siteId,'h5')}` in template to get the site publishpipe url.
|
||||
@ -198,6 +198,7 @@ FREEMARKER.FUNC.fileExtractor.Arg2.Name=File type or suffix
|
||||
# 校验规则
|
||||
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=The site publish pipe props cannot be empty.
|
||||
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=The code can only use uppercase/lowercase letters, numbers and underscores.
|
||||
VALIDATOR.CMS.FILE_NAME.REGEXP_ERR=The file name cannot contain slashes or backslashes.
|
||||
|
||||
# 定时任务
|
||||
SCHEDULED_TASK.RecycleExpireJobHandler=Recycle Content Expired Task
|
||||
|
||||
@ -88,6 +88,7 @@ ERRCODE.CMS.CONTENTCORE.RESOURCE_ACCEPT_SIZE_LIMIT=上傳文件大小超過限
|
||||
ERRCODE.CMS.CONTENTCORE.UNSUPPORTED_RESOURCE_STORAGE=資源存儲方式與當前站點配置不一致
|
||||
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_IS_EMPTY=被合並欄目不存在
|
||||
ERRCODE.CMS.CONTENTCORE.MERGE_CATALOG_NOT_LEAF=被合並欄目不能包含子欄目
|
||||
ERRCODE.CMS.CONTENTCORE.DENY_LINK_TO_LINK_INTERNAL_DATA=不能链接到链接内容或栏目
|
||||
|
||||
# freemarker模板標籤
|
||||
FREEMARKER.TAG.cms_site.NAME=站點列表標籤
|
||||
@ -161,7 +162,6 @@ FREEMARKER.FUNC.imageSize.Arg1.Name=圖片資源內部路徑(iurl://)
|
||||
FREEMARKER.FUNC.imageSize.Arg1.Desc=僅支持處理內部資源圖片(iurl://)
|
||||
FREEMARKER.FUNC.imageSize.Arg2.Name=寬度
|
||||
FREEMARKER.FUNC.imageSize.Arg3.Name=高度
|
||||
FREEMARKER.FUNC.imageSize.Arg4.Name=是否居中裁剪
|
||||
FREEMARKER.FUNC.internalUrl.DESC=將內部連結“iurl://”解析為正常http(s)訪問地址,例如:`${internalUrl(content.redirectUrl)}`
|
||||
FREEMARKER.FUNC.internalUrl.Arg1.Name=內部鏈接(iurl://)
|
||||
FREEMARKER.FUNC.siteUrl.DESC=獲取站點指定發布通道訪問連結,例如:`${siteUrl(Site.siteId, 'h5')}`
|
||||
@ -198,6 +198,7 @@ FREEMARKER.FUNC.fileExtractor.Arg2.Name=文件類型/後綴
|
||||
# 校驗規則
|
||||
VALIDATOR.CMS.SITE.PUBLISH_PIPE_PROPS_EMPTY=發布通道配置不能為空
|
||||
VALIDATOR.CMS.SITE_PROPERTY.REGEXP_ERR=屬性編碼只能使用大小寫字母、數字和下劃線
|
||||
VALIDATOR.CMS.FILE_NAME.REGEXP_ERR=文件名不能包含斜杠或反斜杠
|
||||
|
||||
# 定時任務
|
||||
SCHEDULED_TASK.RecycleExpireJobHandler=資源回收筒過期內容刪除任務
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-customform</artifactId>
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(190785909@qq.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chestnut.customform.cache;
|
||||
|
||||
import com.chestnut.common.redis.IMonitoredCache;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import com.chestnut.system.SysConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* CustomFormCaptchaMonitoredCache
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Component(IMonitoredCache.BEAN_PREFIX + CustomFormCaptchaMonitoredCache.ID)
|
||||
@RequiredArgsConstructor
|
||||
public class CustomFormCaptchaMonitoredCache implements IMonitoredCache<String> {
|
||||
|
||||
public static final String ID = "CustomFormCaptcha";
|
||||
|
||||
public static final String CACHE_PREFIX = "cms:customform:captcha:";
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCacheName() {
|
||||
return "{MONITORED.CACHE.CUSTOM_FORM_CAPTCHA}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCacheKey() {
|
||||
return CACHE_PREFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCache(String cacheKey) {
|
||||
return redisCache.getCacheObject(cacheKey, String.class);
|
||||
}
|
||||
|
||||
public void deleteCache(String cacheKey) {
|
||||
this.redisCache.deleteObject(cacheKey);
|
||||
}
|
||||
|
||||
public void setCache(String cacheKey, String code, Integer captchaExpiration, TimeUnit timeUnit) {
|
||||
this.redisCache.setCacheObject(cacheKey, code, captchaExpiration, timeUnit);
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
@ -46,7 +47,6 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -96,12 +96,12 @@ public class CustomFormController extends BaseRestController {
|
||||
Assert.notNull(form, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("formId", formId));
|
||||
|
||||
CustomFormVO vo = CustomFormVO.from(form);
|
||||
List<Map<String, String>> templates = this.publishPipeService.getPublishPipes(form.getSiteId())
|
||||
.stream().map(pp -> Map.of(
|
||||
"name", pp.getName(),
|
||||
"code", pp.getCode(),
|
||||
"template", form.getTemplates().getOrDefault(pp.getCode(), "")
|
||||
)).toList();
|
||||
List<PublishPipeTemplate> templates = this.publishPipeService.getPublishPipes(form.getSiteId())
|
||||
.stream().map(pp -> new PublishPipeTemplate(
|
||||
pp.getName(),
|
||||
pp.getCode(),
|
||||
form.getTemplates().getOrDefault(pp.getCode(), "")
|
||||
)).toList();
|
||||
vo.setTemplates(templates);
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
@ -15,46 +15,34 @@
|
||||
*/
|
||||
package com.chestnut.customform.controller.front;
|
||||
|
||||
import com.chestnut.common.captcha.CaptchaType;
|
||||
import com.chestnut.common.config.CaptchaConfig;
|
||||
import com.chestnut.common.captcha.CaptchaData;
|
||||
import com.chestnut.common.captcha.CaptchaService;
|
||||
import com.chestnut.common.captcha.ICaptchaType;
|
||||
import com.chestnut.common.captcha.math.MathCaptchaType;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.exception.GlobalException;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.common.utils.*;
|
||||
import com.chestnut.customform.CmsCustomFormMetaModelType;
|
||||
import com.chestnut.customform.CustomFormConsts;
|
||||
import com.chestnut.customform.cache.CustomFormCaptchaMonitoredCache;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.customform.exception.CustomFormErrorCode;
|
||||
import com.chestnut.customform.fixed.config.CustomFormCaptchaExpireSeconds;
|
||||
import com.chestnut.customform.service.ICustomFormApiService;
|
||||
import com.chestnut.customform.service.ICustomFormService;
|
||||
import com.chestnut.member.security.StpMemberUtil;
|
||||
import com.chestnut.system.annotation.IgnoreDemoMode;
|
||||
import com.chestnut.system.config.properties.SysProperties;
|
||||
import com.chestnut.system.domain.vo.ImageCaptchaVO;
|
||||
import com.chestnut.system.exception.SysErrorCode;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.util.FastByteArrayOutputStream;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -73,14 +61,12 @@ public class CustomFormApiController extends BaseRestController {
|
||||
|
||||
private final ICustomFormApiService customFormApiService;
|
||||
|
||||
private final CustomFormCaptchaMonitoredCache captchaCache;
|
||||
|
||||
private final Map<String, Producer> captchaProducers;
|
||||
|
||||
private final SysProperties properties;
|
||||
|
||||
@GetMapping("/captchaImage")
|
||||
public R<?> getCaptchaImage(@RequestParam @LongId Long formId, HttpServletRequest request) {
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
@GetMapping("/captcha")
|
||||
public R<?> getCaptcha(@RequestParam @LongId Long formId, HttpServletRequest request) {
|
||||
CmsCustomForm form = this.customFormService.getById(formId);
|
||||
Assert.notNull(form, CustomFormErrorCode.FORM_NOT_FOUND::exception);
|
||||
// 是否需要验证码
|
||||
@ -93,40 +79,12 @@ public class CustomFormApiController extends BaseRestController {
|
||||
}
|
||||
String uuid = CustomFormConsts.tryToGetUUID(request);
|
||||
Assert.notEmpty(uuid, CustomFormErrorCode.MISSING_UUID::exception);
|
||||
// 保存验证码信息
|
||||
String verifyKey = CustomFormCaptchaMonitoredCache.CACHE_PREFIX + uuid;
|
||||
|
||||
String capStr;
|
||||
String code;
|
||||
BufferedImage image;
|
||||
|
||||
Producer captchaProducer = captchaProducers.get(CaptchaConfig.BEAN_PREFIX + this.properties.getCaptchaType());
|
||||
Assert.notNull(captchaProducer, () -> SysErrorCode.CAPTCHA_CONFIG_ERR.exception(this.properties.getCaptchaType()));
|
||||
// 生成验证码
|
||||
String captchaType = properties.getCaptchaType();
|
||||
if (CaptchaType.MATH.equals(captchaType)) {
|
||||
String capText = captchaProducer.createText();
|
||||
capStr = capText.substring(0, capText.lastIndexOf("@"));
|
||||
code = capText.substring(capText.lastIndexOf("@") + 1);
|
||||
image = captchaProducer.createImage(capStr);
|
||||
} else if (CaptchaType.CHAR.equals(captchaType)) {
|
||||
capStr = code = captchaProducer.createText();
|
||||
image = captchaProducer.createImage(capStr);
|
||||
} else {
|
||||
throw new GlobalException("Unknown captcha type: " + captchaType);
|
||||
}
|
||||
|
||||
Integer expireSeconds = CustomFormCaptchaExpireSeconds.getSeconds();
|
||||
captchaCache.setCache(verifyKey, code, expireSeconds, TimeUnit.SECONDS);
|
||||
|
||||
try(FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
|
||||
ImageIO.write(image, "jpg", os);
|
||||
ImageCaptchaVO vo = ImageCaptchaVO.builder().captchaEnabled(true).uuid(uuid)
|
||||
.img(Base64.getEncoder().encodeToString(os.toByteArray())).build();
|
||||
return R.ok(vo);
|
||||
} catch (IOException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
// TODO 目前固定使用数学计算验证码,待修改成自定义表单后台配置
|
||||
ICaptchaType captchaType = captchaService.getCaptchaType(MathCaptchaType.TYPE);
|
||||
CaptchaData captchaData = new CaptchaData(MathCaptchaType.TYPE);
|
||||
captchaData.setToken(uuid);
|
||||
Object captcha = captchaType.create(captchaData);
|
||||
return R.ok(captcha);
|
||||
}
|
||||
|
||||
@IgnoreDemoMode
|
||||
@ -165,16 +123,13 @@ public class CustomFormApiController extends BaseRestController {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
private void validateCaptcha(String code, String uuid) {
|
||||
Assert.notEmpty(code, () -> CommonErrorCode.INVALID_REQUEST_ARG.exception("captcha"));
|
||||
|
||||
String cacheKey = CustomFormCaptchaMonitoredCache.CACHE_PREFIX + Objects.requireNonNullElse(uuid, StringUtils.EMPTY);
|
||||
String cacheValue = captchaCache.getCache(cacheKey);
|
||||
// 过期判断
|
||||
Assert.notNull(cacheValue, CommonErrorCode.CAPTCHA_EXPIRED::exception);
|
||||
// 未过期判断是否与输入验证码一致
|
||||
Assert.isTrue(StringUtils.equals(code, cacheValue), CommonErrorCode.INVALID_CAPTCHA::exception);
|
||||
// 移除缓存
|
||||
captchaCache.deleteCache(cacheKey);
|
||||
private void validateCaptcha(String captchaJson, String uuid) {
|
||||
Assert.notEmpty(captchaJson, () -> CommonErrorCode.INVALID_REQUEST_ARG.exception("captcha"));
|
||||
CaptchaData captchaData = JacksonUtils.from(captchaJson, CaptchaData.class);
|
||||
captchaData.setToken(uuid);
|
||||
ICaptchaType captchaType = captchaService.getCaptchaType(MathCaptchaType.TYPE);
|
||||
if (!captchaType.isTokenValidated(captchaData)) {
|
||||
throw CommonErrorCode.INVALID_CAPTCHA.exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
package com.chestnut.customform.domain.dto;
|
||||
|
||||
import com.chestnut.common.security.domain.BaseDTO;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import com.chestnut.system.validator.Dict;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
@ -24,7 +25,6 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义表单编辑DTO
|
||||
@ -77,7 +77,7 @@ public class CustomFormEditDTO extends BaseDTO {
|
||||
/**
|
||||
* 模板配置
|
||||
*/
|
||||
private List<Map<String, String>> templates;
|
||||
private List<PublishPipeTemplate> templates;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
|
||||
@ -15,18 +15,13 @@
|
||||
*/
|
||||
package com.chestnut.customform.domain.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import com.chestnut.system.validator.Dict;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义表单详情VO
|
||||
@ -56,7 +51,7 @@ public class CustomFormVO {
|
||||
|
||||
private String ruleLimit;
|
||||
|
||||
private List<Map<String, String>> templates;
|
||||
private List<PublishPipeTemplate> templates;
|
||||
|
||||
public static CustomFormVO from(CmsCustomForm form) {
|
||||
CustomFormVO vo = new CustomFormVO();
|
||||
|
||||
@ -25,6 +25,7 @@ import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeTemplate;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.service.ITemplateService;
|
||||
@ -56,6 +57,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ -127,7 +129,7 @@ public class CustomFormServiceImpl extends ServiceImpl<CustomFormMapper, CmsCust
|
||||
form.setNeedLogin(dto.getNeedLogin());
|
||||
form.setRuleLimit(dto.getRuleLimit());
|
||||
form.setRemark(dto.getRemark());
|
||||
dto.getTemplates().forEach(item -> form.getTemplates().put(item.get("code"), item.get("template")));
|
||||
form.setTemplates(dto.getTemplates().stream().collect(Collectors.toMap(PublishPipeTemplate::code, PublishPipeTemplate::template)));
|
||||
this.updateById(form);
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-dynamic</artifactId>
|
||||
|
||||
@ -66,10 +66,11 @@ public class DynamicCoreHandler implements ICoreDataHandler {
|
||||
data.setSiteId(context.getSite().getSiteId());
|
||||
data.createBy(context.getOperator());
|
||||
Long count = dynamicPageService.lambdaQuery().eq(CmsDynamicPage::getPath, data.getPath()).count();
|
||||
if (count > 0) {
|
||||
if (count > 0 || dynamicPageService.isRequestMappingExists(data)) {
|
||||
data.setPath(data.getPath() + "_" + data.getPageId());
|
||||
}
|
||||
dynamicPageService.save(data);
|
||||
dynamicPageService.registerDynamicPageMapping(data.getCode(), data.getPath());
|
||||
} catch (Exception e) {
|
||||
AsyncTaskManager.addErrMessage("导入自定义动态模板页面失败:" + data.getName() + "[" + data.getCode() + "]");
|
||||
log.error("Import dynamic page failed: {}", data.getCode(), e);
|
||||
|
||||
@ -29,7 +29,7 @@ public interface IDynamicPageInitData {
|
||||
|
||||
String BEAN_PREFIX = "DynamicPageInitData.";
|
||||
|
||||
void initTemplateData(TemplateContext context, Map<String, String> parameters);
|
||||
void initTemplateData(TemplateContext context, String path, Map<String, String> parameters);
|
||||
|
||||
String getType();
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ public class MemberDynamicPageInitData implements IDynamicPageInitData {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initTemplateData(TemplateContext context, Map<String, String> parameters) {
|
||||
public void initTemplateData(TemplateContext context, String path, Map<String, String> parameters) {
|
||||
if (StpMemberUtil.isLogin()) {
|
||||
LoginUser loginUser = StpMemberUtil.getLoginUser();
|
||||
context.getVariables().put(CmsMemberConstants.TEMPLATE_VARIABLE_MEMBER, loginUser.getUser());
|
||||
|
||||
@ -16,7 +16,9 @@
|
||||
package com.chestnut.cms.dynamic.core.impl;
|
||||
|
||||
import com.chestnut.cms.dynamic.core.IDynamicPageInitData;
|
||||
import com.chestnut.common.staticize.StaticizeConstants;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.contentcore.util.TemplateUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -44,8 +46,12 @@ public class PaginationDynamicPageInitData implements IDynamicPageInitData {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initTemplateData(TemplateContext context, Map<String, String> parameters) {
|
||||
int pageIndex = MapUtils.getIntValue(parameters, "pi", 1);
|
||||
public void initTemplateData(TemplateContext context, String path, Map<String, String> parameters) {
|
||||
int pageIndex = MapUtils.getIntValue(parameters, StaticizeConstants.TemplateParam_PageIndex, 1);
|
||||
context.setPageIndex(pageIndex);
|
||||
String prefix = context.getVariables().get(context.isPreview() ? TemplateUtils.TemplateVariable_ApiPrefix
|
||||
: TemplateUtils.TemplateVariable_Prefix).toString();;
|
||||
context.setFirstFileName(prefix + path);
|
||||
context.setOtherFileName(prefix + TemplateUtils.appendPageIndexParam(path, TemplateContext.PlaceHolder_PageNo));
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,5 +31,11 @@ public interface IDynamicPageService extends IService<CmsDynamicPage> {
|
||||
|
||||
void deleteDynamicPage(List<Long> dynamicPageIds);
|
||||
|
||||
void generateDynamicPage(String uri, Long siteId, String publishPipeCode, Boolean preview, Map<String, String> parameters, HttpServletResponse response) throws IOException;
|
||||
boolean isRequestMappingExists(CmsDynamicPage dynamicPage);
|
||||
|
||||
void registerDynamicPageMapping(String code, String path);
|
||||
|
||||
void unregisterDynamicPageMapping(String code, String path);
|
||||
|
||||
void generateDynamicPage(String uri, Long siteId, String publishPipeCode, Boolean preview, Map<String, String> parameters, HttpServletResponse response) throws IOException;
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
dynamicPage.setPageId(IdUtils.getSnowflakeId());
|
||||
this.save(dynamicPage);
|
||||
|
||||
this.registerDynamicPageMapping(dynamicPage);
|
||||
this.registerDynamicPageMapping(dynamicPage.getCode(), dynamicPage.getPath());
|
||||
|
||||
dynamicPageHelper.updateCache(dynamicPage);
|
||||
}
|
||||
@ -91,13 +91,18 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
CmsDynamicPage dbDynamicPage = this.getById(dynamicPage.getPageId());
|
||||
Assert.notNull(dbDynamicPage, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("pageId", dynamicPage.getPageId()));
|
||||
|
||||
String oldPath = dbDynamicPage.getPath();
|
||||
String oldCode = dbDynamicPage.getCode();
|
||||
|
||||
dbDynamicPage.setName(dynamicPage.getName());
|
||||
dbDynamicPage.setCode(dynamicPage.getCode());
|
||||
dbDynamicPage.setDescription(dynamicPage.getDescription());
|
||||
dbDynamicPage.setInitDataTypes(dynamicPage.getInitDataTypes());
|
||||
dbDynamicPage.setTemplates(dynamicPage.getTemplates());
|
||||
this.updateById(dbDynamicPage);
|
||||
|
||||
if (!oldPath.equals(dynamicPage.getCode()) || !oldPath.equals(dynamicPage.getPath())) {
|
||||
unregisterDynamicPageMapping(oldCode, oldPath);
|
||||
}
|
||||
dynamicPageHelper.updateCache(dbDynamicPage);
|
||||
}
|
||||
|
||||
@ -107,8 +112,8 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
this.removeByIds(dynamicPages);
|
||||
|
||||
dynamicPages.forEach(dynamicPage -> {
|
||||
this.unregisterDynamicPageMapping(dynamicPage);
|
||||
dynamicPageHelper.clearCache(dynamicPage);
|
||||
this.unregisterDynamicPageMapping(dynamicPage.getCode(), dynamicPage.getPath());
|
||||
});
|
||||
}
|
||||
|
||||
@ -118,25 +123,34 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
.or().eq(CmsDynamicPage::getCode, dynamicPage.getCode()))
|
||||
.ne(IdUtils.validate(dynamicPage.getPageId()), CmsDynamicPage::getPageId, dynamicPage.getPageId())
|
||||
.count();
|
||||
Assert.isTrue(count == 0, () -> CommonErrorCode.DATA_CONFLICT.exception(dynamicPage.getPath() + "||" + dynamicPage.getPath()));
|
||||
Assert.isTrue(count == 0, () -> CommonErrorCode.DATA_CONFLICT.exception(dynamicPage.getPath()));
|
||||
// 校验路径是冲突
|
||||
if (!IdUtils.validate(dynamicPage.getPageId())) {
|
||||
for (RequestMappingHandlerMapping mapping : allRequestMapping) {
|
||||
Set<RequestMappingInfo> requestMappingInfos = mapping.getHandlerMethods().keySet();
|
||||
for (RequestMappingInfo requestMappingInfo : requestMappingInfos) {
|
||||
Assert.isFalse(requestMappingInfo.getPatternValues().contains(dynamicPage.getPath()),
|
||||
() -> new GlobalException("Conflict request handler mapping: " + dynamicPage.getPath()));
|
||||
}
|
||||
}
|
||||
Assert.isFalse(isRequestMappingExists(dynamicPage),
|
||||
() -> new GlobalException("Conflict request handler mapping: " + dynamicPage.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerDynamicPageMapping(CmsDynamicPage dynamicPage) {
|
||||
@Override
|
||||
public boolean isRequestMappingExists(CmsDynamicPage dynamicPage) {
|
||||
for (RequestMappingHandlerMapping mapping : allRequestMapping) {
|
||||
Set<RequestMappingInfo> requestMappingInfos = mapping.getHandlerMethods().keySet();
|
||||
for (RequestMappingInfo requestMappingInfo : requestMappingInfos) {
|
||||
if (requestMappingInfo.getPatternValues().contains(dynamicPage.getPath())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDynamicPageMapping(String code, String path) {
|
||||
try {
|
||||
RequestMappingInfo.Builder builder = RequestMappingInfo
|
||||
.paths(dynamicPage.getPath())
|
||||
.paths(path)
|
||||
.methods(RequestMethod.GET)
|
||||
.mappingName(dynamicPage.getCode());
|
||||
.mappingName(code);
|
||||
RequestMappingInfo mappingInfo = builder.options(this.dynamicPageRequestMapping.getBuilderConfiguration()).build();
|
||||
|
||||
DynamicPageFrontController handler = SpringUtils.getBean(DynamicPageFrontController.class);
|
||||
@ -148,12 +162,13 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterDynamicPageMapping(CmsDynamicPage dynamicPage) {
|
||||
@Override
|
||||
public void unregisterDynamicPageMapping(String code, String path) {
|
||||
try {
|
||||
RequestMappingInfo.Builder builder = RequestMappingInfo
|
||||
.paths(dynamicPage.getPath())
|
||||
.paths(path)
|
||||
.methods(RequestMethod.GET)
|
||||
.mappingName(dynamicPage.getCode());
|
||||
.mappingName(code);
|
||||
RequestMappingInfo mappingInfo = builder.options(this.dynamicPageRequestMapping.getBuilderConfiguration()).build();
|
||||
|
||||
this.dynamicPageRequestMapping.unregisterMapping(mappingInfo);
|
||||
@ -168,7 +183,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
this.list().forEach(dynamicPage -> {
|
||||
dynamicPageHelper.updateCache(dynamicPage);
|
||||
|
||||
this.registerDynamicPageMapping(dynamicPage);
|
||||
this.registerDynamicPageMapping(dynamicPage.getCode(), dynamicPage.getPath());
|
||||
});
|
||||
}
|
||||
|
||||
@ -206,7 +221,7 @@ public class DynamicPageServiceImpl extends ServiceImpl<CmsDynamicPageMapper, Cm
|
||||
for (String initDataType : dynamicPage.getInitDataTypes()) {
|
||||
IDynamicPageInitData initData = dynamicPageHelper.getDynamicPageInitData(initDataType);
|
||||
if (Objects.nonNull(initData)) {
|
||||
initData.initTemplateData(templateContext, parameters);
|
||||
initData.initTemplateData(templateContext, dynamicPage.getPath(), parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-exmodel</artifactId>
|
||||
|
||||
@ -23,6 +23,7 @@ import com.chestnut.common.log.enums.BusinessType;
|
||||
import com.chestnut.common.security.anno.Priv;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.security.web.PageRequest;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
@ -78,9 +79,12 @@ public class EXModelController extends BaseRestController {
|
||||
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@GetMapping("/options")
|
||||
public R<?> getModelOptions() {
|
||||
public R<?> getModelOptions(@RequestParam(required = false) Long siteId) {
|
||||
PageRequest pr = this.getPageRequest();
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
if (IdUtils.validate(siteId)) {
|
||||
site = this.siteService.getSite(siteId);
|
||||
}
|
||||
LambdaQueryWrapper<XModel> q = new LambdaQueryWrapper<XModel>()
|
||||
.eq(XModel::getOwnerType, CmsExtendMetaModelType.TYPE)
|
||||
.eq(XModel::getOwnerId, site.getSiteId());
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-image</artifactId>
|
||||
|
||||
@ -30,16 +30,11 @@ import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.contentcore.core.IContent;
|
||||
import com.chestnut.contentcore.core.IContentType;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp.PublishPipePropUseType;
|
||||
import com.chestnut.contentcore.domain.BCmsContent;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.*;
|
||||
import com.chestnut.contentcore.domain.pojo.PublishPipeProps;
|
||||
import com.chestnut.contentcore.domain.vo.ContentVO;
|
||||
import com.chestnut.contentcore.enums.ContentCopyType;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IContentService;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -57,6 +52,8 @@ public class ImageContentType implements IContentType {
|
||||
|
||||
private final static String NAME = "{CMS.CONTENTCORE.CONTENT_TYPE." + ID + "}";
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final IContentService contentService;
|
||||
|
||||
private final IImageService imageService;
|
||||
@ -65,6 +62,8 @@ public class ImageContentType implements IContentType {
|
||||
|
||||
private final IPublishPipeService publishPipeService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
@ -135,7 +134,7 @@ public class ImageContentType implements IContentType {
|
||||
});
|
||||
vo = ImageAlbumVO.newInstance(contentEntity, list);
|
||||
// 发布通道模板数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
PublishPipePropUseType.Content, contentEntity.getPublishPipeProps());
|
||||
vo.setPublishPipeProps(publishPipeProps);
|
||||
} else {
|
||||
@ -146,11 +145,17 @@ public class ImageContentType implements IContentType {
|
||||
// 发布通道初始数据
|
||||
vo.setPublishPipe(publishPipes.stream().map(CmsPublishPipe::getCode).toArray(String[]::new));
|
||||
// 发布通道模板数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
List<PublishPipeProps> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
PublishPipePropUseType.Content, null);
|
||||
vo.setPublishPipeProps(publishPipeProps);
|
||||
}
|
||||
vo.setCatalogName(catalog.getName());
|
||||
// 内容引导图缩略图处理
|
||||
CmsSite site = siteService.getSite(catalog.getSiteId());
|
||||
resourceService.dealDefaultThumbnail(site, vo.getImages(), thumbnails -> {
|
||||
vo.setImagesSrc(thumbnails);
|
||||
vo.setLogoSrc(thumbnails.get(0));
|
||||
});
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-link</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.5</version>
|
||||
<version>1.5.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-media</artifactId>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user